本章要解决的问题
企业文档中心几万份 PDF,怎么让 Agent 精准回答「我们公司的报销标准是什么」?
章节大纲
- 26.1 文档处理与向量检索(Qwen + 内网知识库)
- 26.2 RAG + 反思 + 评估自检
- 26.3 MCP 集成企业系统
- 26.4 评测与上线
- 🛠 解决方案:知识库问答幻觉抑制方案 + 检索质量评估
26.1 文档处理与向量检索
26.1.1 场景与挑战
场景:企业内部文档中心,几万份 PDF(制度、流程、项目文档),员工随时提问。
三个挑战:
- 文档格式杂:PDF(含扫描件)、Word、Excel、PPT——要先解析。
- 数据敏感:内网数据不能出域 → 私有化部署或数据脱敏(第 2/22 章)。
- 问题多样:制度查询、流程咨询、项目检索——需要精准检索。
26.1.2 文档处理管线(第 8 章完整实践)
示例代码:以下代码演示核心结构,省略了异常处理、日志和完整 import。
<span>import</span> os
<span>from</span> typing <span>import</span> <span>List</span>, <span>Dict</span>
<span>def</span> <span>parse_document</span>(<span>path: <span>str</span></span>) -> <span>str</span>:
<span>"""解析各种格式 → 纯文本"""</span>
ext = os.path.splitext(path)[<span>1</span>].lower()
<span>if</span> ext <span>in</span> (<span>".pdf"</span>,):
<span>return</span> parse_pdf(path) <span># 文本型 PDF</span>
<span>if</span> ext <span>in</span> (<span>".docx"</span>, <span>".doc"</span>):
<span>return</span> parse_word(path)
<span>if</span> ext <span>in</span> (<span>".xlsx"</span>, <span>".xls"</span>):
<span>return</span> parse_excel(path) <span># 表格转文本</span>
<span>if</span> ext <span>in</span> (<span>".png"</span>, <span>".jpg"</span>):
<span>return</span> ocr_image(path) <span># 扫描件 → OCR</span>
<span>return</span> <span>""</span>
<span>def</span> <span>build_index</span>(<span>doc_dir: <span>str</span></span>):
<span>"""全量建索引:解析 → 分块 → 向量化 → 存储"""</span>
chunks = []
<span>for</span> root, _, files <span>in</span> os.walk(doc_dir):
<span>for</span> fname <span>in</span> files:
text = parse_document(os.path.join(root, fname))
<span>for</span> chunk <span>in</span> split_into_chunks(text, size=<span>600</span>, overlap=<span>80</span>):
chunks.append({
<span>"text"</span>: chunk,
<span>"metadata"</span>: {
<span>"source"</span>: fname,
<span>"dept"</span>: infer_dept(fname), <span># 部门过滤</span>
<span>"type"</span>: infer_type(fname), <span># 制度/流程/项目</span>
<span>"updated"</span>: file_mtime(fname),
},
})
vector_store.add_documents(chunks) <span># Qwen embedding 向量化</span>
<span>return</span> <span>len</span>(chunks)
图 1:文档处理管线
关键设计:
- 解析要"识别失败":解析不了的文档(加密/乱码)要标记并告警,不能静默丢失。
- 元数据三件套:
source(出处可追溯)、dept(部门过滤)、type(类型过滤)——检索的"筛子"。 - 扫描件走 OCR:PDF 分两种——文本型直接解析,扫描型先 OCR。
26.1.3 检索优化(第 8 章)
<span>def</span> <span>retrieve_for_query</span>(<span>question, filters=<span>None</span></span>):
<span># 混合检索:向量 + BM25 + RRF 融合</span>
results = hybrid_retrieve(question, filters=filters, top_k=<span>20</span>)
<span># 重排:Cross-Encoder 精排 Top-20 → Top-3</span>
<span>return</span> rerank(question, results, top_k=<span>3</span>)
检索质量三步:混合检索(召回全)→ 重排(排序准)→ 过滤(范围对)。
26.2 RAG + 反思 + 评估自检
26.2.1 三件套组合:让回答"可信任"
知识库问答最大的风险是幻觉(编造制度条款)。组合三件套压制幻觉:
图 2:RAG+自检+反思
<span>[RAG]</span> 检索相关资料(提供事实)
↓
<span>[自检]</span> 规则校验(必含引用出处)+ 忠实度检查(第<span>17</span>章)
↓
<span>[反思]</span> 有引用但不确定 → 重查/重答(第<span>13</span>章)
↓
<span>[兜底]</span> 资料不足 → 明确说"未查到",不硬答
26.2.2 忠实度自检实现
<span>def</span> <span>faithfulness_check</span>(<span>answer, sources</span>):
<span>"""检查回答是否忠实于资料:每个关键断言都要能在资料中找到依据"""</span>
resp = llm_judge(<span>f"""检查回答中的每个断言是否都能在参考资料中找到依据。
回答:<span>{answer}</span>
资料:<span>{sources}</span>
只输出 JSON:{{"faithful": bool, "unsubstantiated": ["无依据的断言"]}}"""</span>)
<span>return</span> resp
<span>def</span> <span>knowledge_answer</span>(<span>question, filters=<span>None</span></span>):
sources = retrieve_for_query(question, filters)
<span>if</span> <span>not</span> sources:
<span>return</span> <span>"未在知识库中找到相关资料,建议联系行政部确认。"</span>, []
answer = llm.chat(
system=<span>"你是企业知识库助手。仅基于资料回答,并注明出处(文件名)。"</span>,
user=<span>f"【资料】<span>{sources}</span>\n【问题】<span>{question}</span>"</span>,
temperature=<span>0.2</span>, <span># 知识问答用低温度</span>
)
<span># 忠实度自检,不通过 → 反思重答</span>
check = faithfulness_check(answer, sources)
<span>if</span> <span>not</span> check[<span>"faithful"</span>]:
answer = llm.chat(
system=<span>"基于资料重新回答,修正以下无依据的内容:"</span>
<span>f"<span>{check[<span>'unsubstantiated'</span>]}</span>"</span>,
user=<span>f"【资料】<span>{sources}</span>\n【问题】<span>{question}</span>"</span>,
temperature=<span>0.1</span>,
)
<span>return</span> answer, sources
兜底原则:查不到就说查不到(附建议渠道)——比编造一个看似合理的答案好 100 倍(第 8 章"没有就说没有")。
26.2.3 出处可追溯
回答必须带出处(哪个文件、哪个版本),这是企业知识库的硬要求:
<span>return</span> <span>f"<span>{answer}</span>\n\n【出处】<span>{sources[<span>0</span>][<span>'metadata'</span>][<span>'source'</span>]}</span>(<span>{sources[<span>0</span>][<span>'metadata'</span>][<span>'updated'</span>]}</span>)"</span>
出处 = 可信任的最后一道证明——员工能自己打开原文件核对。
26.3 MCP 集成企业系统
26.3.1 知识库 Agent 要连什么
不只是问答,还要能联动企业系统:
图 3:知识库权限过滤
| 系统 | 用途 | 接入方式 |
|---|---|---|
| OA 系统 | 查审批流程状态 | MCP Server |
| HR 系统 | 查假期/薪资制度 | MCP Server |
| 项目管理系统 | 查项目文档 | MCP Server |
| 内部 Wiki | 实时内容 | MCP Server |
26.3.2 用 MCP 接入(第 7 章实践)
<span>from</span> fastmcp <span>import</span> FastMCP
mcp = FastMCP(<span>"oa-integration"</span>)
<span>@mcp.tool()</span>
<span>def</span> <span>query_approval_status</span>(<span>approval_id: <span>str</span></span>) -> <span>dict</span>:
<span>"""查询 OA 审批状态。用户询问审批进度/结果时使用。"""</span>
<span># 内部 OA API 封装</span>
<span>return</span> oa_api.get_approval(approval_id)
<span>@mcp.tool()</span>
<span>def</span> <span>query_leave_policy</span>(<span>department: <span>str</span></span>) -> <span>dict</span>:
<span>"""查询假期制度。用户询问年假/病假/调休时使用。"""</span>
<span>return</span> hr_api.get_leave_policy(department)
<span># 注册进 Agent:MCP 发现的工具 = 知识库 Agent 的动态工具集</span>
价值:知识库 Agent 从"纯问答"升级为"问答 + 操作"——问完制度还能直接查自己的审批状态。
26.4 评测与上线
26.4.1 知识库专项评测(第 8/20 章)
EVAL_SET = [
{<span>"question"</span>: <span>"报销标准是什么?"</span>, <span>"gold_docs"</span>: [<span>"报销制度-v3.pdf"</span>],
<span>"check"</span>: <span>"含金额上限且注明出处"</span>},
{<span>"question"</span>: <span>"年假怎么休?"</span>, <span>"gold_docs"</span>: [<span>"休假管理办法.pdf"</span>],
<span>"check"</span>: <span>"按制度回答"</span>},
{<span>"question"</span>: <span>"去年的团建费怎么报?"</span>, <span>"gold_docs"</span>: [], <span># 无此文档</span>
<span>"check"</span>: <span>"明确说未查到,不编造"</span>},
{<span>"question"</span>: <span>"扫描件里的制度也查得到吗?"</span>, <span>"gold_docs"</span>: [<span>"旧制度-扫描版.pdf"</span>],
<span>"check"</span>: <span>"OCR 后能检索"</span>},
...
]
<span># 指标:检索命中率 / 忠实度 / 幻觉率 / 出处覆盖率</span>
26.4.2 上线要点
| 事项 | 说明 |
|---|---|
| **私有化部署** | 内网数据不出域(第 2/22 章) |
| **文档更新机制** | 新文档入索引 + 旧版本标记(防止旧版误导) |
| **权限过滤** | 不同部门看到不同文档(RBAC,第 22 章) |
| **监控** | 检索命中率周报 + 幻觉率抽评(第 20 章) |
| **反馈闭环** | 员工点踩的答案回流评测集(第 18/20 章) |
26.4.3 更新索引的工程细节
<span># 文档变更 → 增量更新(别全量重建,费时费钱)</span>
<span>def</span> <span>sync_index</span>():
changed = detect_changed_docs() <span># 对比文件指纹</span>
<span>for</span> doc <span>in</span> changed:
vector_store.delete(<span>filter</span>={<span>"source"</span>: doc[<span>"name"</span>]}) <span># 删旧</span>
index_document(doc) <span># 加新</span>
<span># 版本冲突:同标题多版本 → 只保留最新(按 updated 排序)</span>
图 4:文档增量同步
🛠 解决方案:知识库问答幻觉抑制方案 + 检索质量评估
常见问题
- "答案编造制度条款":约束松 + 无出处。对策:仅基于资料 + 忠实度自检 + 强制带出处(26.2)。
- "检索不到扫描件内容":没 OCR。对策:扫描型 PDF 走 OCR(26.1.2)。
- "旧版制度误导用户":多版本并存无过滤。对策:版本标记 + 只保留最新(26.4.3)。
- "A 部门能查到 B 部门的机密":无权限过滤。对策:RBAC 元数据过滤(第 22 章)。
- "文档更新了但 Agent 答的还是旧的":索引未同步。对策:增量同步 + 文件指纹检测(26.4.3)。
解决方案速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 编造条款 | 约束松 | 仅基于资料 + 忠实度自检 + 出处 |
| 扫描件查不到 | 无 OCR | OCR 管线 |
| 旧版误导 | 多版本未过滤 | 版本标记 + 保留最新 |
| 越权查看 | 无权限过滤 | RBAC 过滤 |
| 答旧内容 | 索引未同步 | 增量同步 |
实战提示
- 出处是知识库的信任根基:回答必带文件名 + 版本,员工能自己核对。
- 幻觉抑制三件套:低温度(0.2)+ 忠实度自检 + 查不到就明说。
- 元数据过滤别省:部门/类型/版本过滤让检索精准十倍。
- 文档生命周期要管理:新增/更新/废弃都要反映到索引。
- 内网数据安全第一:私有化部署或脱敏,数据出域要评估(第 22 章)。
聚焦企业内网知识库的幻觉、权限与索引同步痛点,给出可落地的 RAG 自检、出处追溯和 MCP 集成方案,适合内部制度问答与流程查询团队参考。