基于 RAG 的员工手册智能问答系统实战(Python + ChromaDB + 通义千问)
一、引言:为什么要做 RAG?
只用大模型(LLM)回答企业私有问题有两个致命问题:
-
知识滞后:大模型训练数据有截止日期,公司最新的员工手册它根本没见过。
-
幻觉:让模型"编"一份公司的请假制度,它会一本正经地胡说八道。
RAG(Retrieval-Augmented Generation,检索增强生成) 是目前最主流的解法:
先把企业文档切分、向量化存进向量数据库;用户提问时,先检索最相关的片段,再把这些片段作为"上下文"塞给大模型,让它只基于真实文档作答。
本文用一个完整可运行的例子,带你从零实现一个「员工手册智能问答机器人」:喂给它一份 Word 版员工手册,问"员工有哪些权利",它能精准从手册里找答案。
二、整体架构
整个系统是一个标准的 6 步 RAG 流水线:
Word文档 ↓ ① 读取 (python-docx) 纯文本 ↓ ② 分片 (按长度切 chunk) 文本片段列表 ↓ ③ Embedding (Qwen 文本向量模型) 向量矩阵 ↓ ④ 入库 (ChromaDB 持久化) 向量数据库 ↓ ⑤ 检索 (用户问题向量 → 相似度 TopK) 相关片段 ↓ ⑥ 组装 Prompt + 调用大模型 (Qwen-Plus) 最终答案
三、环境准备
pip install python-docx chromadb openai
-
python-docx:读取.docx员工手册 -
chromadb:轻量级本地向量数据库(无需起服务,开箱即用) -
openai:兼容 OpenAI 接口调用通义千问(阿里云百炼 / MaaS 私有实例)
向量模型与对话模型均使用阿里云百炼的兼容模式端点(
compatible-mode/v1),只需把base_url和api_key换成你自己的即可。
四、代码逐段拆解
1. 读取 Word 文档
import os from docx import Document def load_file(file_path): document = Document(file_path) all_text = [] for para in document.paragraphs: all_text.append(para.text) return "\n".join(all_text)
用 python-docx 把每一段(paragraph)的文本抽出来,拼成一个大字符串。paragraphs 只覆盖正文段落,如果手册里还有表格内容,需要额外遍历 document.tables,本文示例只处理正文。
2. 文本分片(Chunking)
def split_text(all_text): chunks = [] for i in range(0, len(all_text), 200): chunks.append(all_text[i:i + 200]) return chunks
RAG 的核心细节之一:不能把整本文档一次性向量化。太长会超出模型 token 限制,且检索粒度太粗。
这里用最朴素的方式:每隔 200 个字符切一刀。生产环境更推荐用 递归字符切分(按段落/句子边界) 或 语义切分,效果会更好。
3. 文本向量化(Embedding)
from openai import OpenAI
def text_embedding(chunks):
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://ws-6nx5tbd10wyo5nw2.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
)
if isinstance(chunks, str):
chunks = [chunks]
batch_size = 20
embeddings = []
for i in range(0, len(chunks), batch_size):
batch_chunks = chunks[i:i + batch_size]
completion = client.embeddings.create(
model="qwen3.7-text-embedding",
input=batch_chunks,
dimensions=256
)
batch_embeddings = [item.embedding for item in completion.data]
embeddings.extend(batch_embeddings)
return embeddings
要点:
-
Embedding 模型:使用
qwen3.7-text-embedding,把文本变成一串浮点数向量。 -
dimensions=256:指定输出向量维度,维度越低存储和检索越快(精度略有取舍)。 -
批量调用:每 20 条一批,避免单次请求过大。
-
该函数既用于"入库时给文档片段向量化",也用于"查询时给用户问题向量化",两端必须用同一个 Embedding 模型,否则向量空间不一致,检索会失效。
4. 存入向量数据库(ChromaDB)
import chromadb
def save_to_vector_db(chunks):
embeddings = text_embedding(chunks)
client = chromadb.PersistentClient() # 数据落盘到本地 ./chroma 目录
collection = client.get_or_create_collection(
name="employee_manual_collection",
embedding_function=None # 我们已自己算好向量
)
ids = [f"chunk_{i}" for i in range(len(chunks))]
collection.add(
ids=ids,
embeddings=embeddings,
documents=chunks,
)
-
PersistentClient():持久化客户端,重启后数据还在。 -
embedding_function=None:因为我们手写好了embeddings,告诉 Chroma 不要再自己算。 -
每条记录由
ids(唯一标识)、embeddings(向量)、documents(原文)三部分组成。
5. 检索(Query → 相似片段)
def query_by_chroma(user_query_text): user_query_embeddings = text_embedding(user_query_text) client = chromadb.PersistentClient() collection = client.get_collection( name="employee_manual_collection", embedding_function=None ) results = collection.query( query_embeddings=user_query_embeddings, n_results=5, include=["metadatas", "documents", "distances"], ) return results
把用户问题向量化后,在集合里做余弦相似度检索,取最相关的 n_results=5 条。返回的 documents[0] 就是我们要找的"参考片段"。
6. 组装 Prompt + 调用大模型生成答案
def generate_answer(query_results, user_query_text):
retrieval_chunks = query_results["documents"][0]
content = "\n".join([f"--: {chunk}" for chunk in retrieval_chunks])
prompt = f"""
## 角色设定
你是一个专业的人力资源专家,
## 任务描述
根据用户的问题和公司的制度内容,回答问题
## 输入数据
用户的问题:{user_query_text}
公司制度内容:{content}
## 约束
1:严格基于公司的制度内容回答问题,不要胡编乱造
2:如果公司的制度内容没有相关信息,请明确说明"暂无此知识"
3:回答简洁明了,条理清晰
"""
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://ws-6nx5tbd10wyo5nw2.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
)
completion = client.chat.completions.create(
model="qwen-plus",
messages=[{"role": "user", "content": prompt}],
temperature=0.75,
)
return completion.choices[0].message.content
这是 RAG 的"生成"一环。关键点:
-
Prompt 工程:明确告诉模型"你是 HR 专家 + 只基于给定制度内容作答 + 没有就直说暂无此知识"。
-
约束 2 极其重要:它克制了模型的幻觉,让回答可追溯、可信。
-
temperature=0.75:适当保留一点表达灵活性,若追求绝对严谨可降到 0.2 左右。
7. 主流程串联
if __name__ == '__main__':
user_query_text = "员工有哪些权利"
results = query_by_chroma(user_query_text)
final_answer = generate_answer(results, user_query_text)
print(f"大模型生成的最终答案:\n{final_answer}")
首次运行请先取消注释 __main__ 里的入库步骤(load_file → split_text → save_to_vector_db),把员工手册灌进库;之后查询直接走检索 + 生成即可。
五、运行效果
问:员工有哪些权利
系统会:① 把问题向量化 → ② 从 employee_manual_collection 捞出最相关的 5 段手册内容 → ③ 连同问题拼进 Prompt → ④ qwen-plus 基于真实片段作答。
由于 Prompt 里强制"基于制度、无则说暂无",答案可溯源、不乱编。
六、生产化进阶建议
| 维度 | 当前朴素实现 | 生产建议 |
|---|---|---|
| 分片 | 固定 200 字符 | 递归字符切分 / 语义切分,保留段落边界 |
| 表格 | 未处理 | 遍历 document.tables 一并向量化 |
| 检索 | 单次向量检索 | 混合检索(向量 + 关键词 BM25) |
| 重排 | 无 | 加 Cross-Encoder 重排提升 TopK 精度 |
| 上下文 | 直接拼接 | 加引用来源、显示出处章节 |
| 并发 | 单进程 | 入库/查询异步化,批量提升吞吐 |
七、小结
本文用不到 150 行代码,跑通了一个企业级 RAG 问答原型:
-
文档读取:
python-docx -
向量化:通义千问
qwen3.7-text-embedding -
向量库:
ChromaDB本地持久化 -
生成:
qwen-plus+ 强约束 Prompt
RAG 的精髓就一句话:让大模型"知之为知之,不知为不知",答案永远有出处。把这个骨架换成你的合同、FAQ、产品文档,就是一个专属知识库问答机器人。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)