第 26 章 案例二 企业知识库问答 Agent

0 阅读7分钟

第 26 章 案例二:企业知识库问答 Agent

本章要解决的问题

企业文档中心几万份 PDF,怎么让 Agent 精准回答「我们公司的报销标准是什么」?

章节大纲

  • 26.1 文档处理与向量检索(Qwen + 内网知识库)
  • 26.2 RAG + 反思 + 评估自检
  • 26.3 MCP 集成企业系统
  • 26.4 评测与上线
  • 🛠 解决方案:知识库问答幻觉抑制方案 + 检索质量评估

26.1 文档处理与向量检索

26.1.1 场景与挑战

场景:企业内部文档中心,几万份 PDF(制度、流程、项目文档),员工随时提问。

三个挑战:

  1. 文档格式杂:PDF(含扫描件)、Word、Excel、PPT——要先解析。
  2. 数据敏感:内网数据不能出域 → 私有化部署或数据脱敏(第 2/22 章)。
  3. 问题多样:制度查询、流程咨询、项目检索——需要精准检索。

26.1.2 文档处理管线(第 8 章完整实践)

示例代码:以下代码演示核心结构,省略了异常处理、日志和完整 import。

import os
from typing import List, Dict

def parse_document(path: str) -> str:
    """解析各种格式 → 纯文本"""
    ext = os.path.splitext(path)[1].lower()
    if ext in (".pdf",):
        return parse_pdf(path)          # 文本型 PDF
    if ext in (".docx", ".doc"):
        return parse_word(path)
    if ext in (".xlsx", ".xls"):
        return parse_excel(path)        # 表格转文本
    if ext in (".png", ".jpg"):
        return ocr_image(path)          # 扫描件 → OCR
    return ""

def build_index(doc_dir: str):
    """全量建索引:解析 → 分块 → 向量化 → 存储"""
    chunks = []
    for root, _, files in os.walk(doc_dir):
        for fname in files:
            text = parse_document(os.path.join(root, fname))
            for chunk in split_into_chunks(text, size=600, overlap=80):
                chunks.append({
                    "text": chunk,
                    "metadata": {
                        "source": fname,
                        "dept": infer_dept(fname),   # 部门过滤
                        "type": infer_type(fname),   # 制度/流程/项目
                        "updated": file_mtime(fname),
                    },
                })
    vector_store.add_documents(chunks)   # Qwen embedding 向量化
    return len(chunks)

图 1:文档处理管线

图 1:文档处理管线

关键设计:

  • 解析要"识别失败":解析不了的文档(加密/乱码)要标记并告警,不能静默丢失。
  • 元数据三件套:source(出处可追溯)、dept(部门过滤)、type(类型过滤)——检索的"筛子"。
  • 扫描件走 OCR:PDF 分两种——文本型直接解析,扫描型先 OCR。

26.1.3 检索优化(第 8 章)

def retrieve_for_query(question, filters=None):
    # 混合检索:向量 + BM25 + RRF 融合
    results = hybrid_retrieve(question, filters=filters, top_k=20)
    # 重排:Cross-Encoder 精排 Top-20 → Top-3
    return rerank(question, results, top_k=3)

检索质量三步:混合检索(召回全)→ 重排(排序准)→ 过滤(范围对)。

26.2 RAG + 反思 + 评估自检

26.2.1 三件套组合:让回答"可信任"

知识库问答最大的风险是幻觉(编造制度条款)。组合三件套压制幻觉:

图 2:RAG+自检+反思

图 2:RAG+自检+反思

[RAG] 检索相关资料(提供事实)
  ↓
[自检] 规则校验(必含引用出处)+ 忠实度检查(第17章)
  ↓
[反思] 有引用但不确定 → 重查/重答(第13章)
  ↓
[兜底] 资料不足 → 明确说"未查到",不硬答

26.2.2 忠实度自检实现

def faithfulness_check(answer, sources):
    """检查回答是否忠实于资料:每个关键断言都要能在资料中找到依据"""
    resp = llm_judge(f"""检查回答中的每个断言是否都能在参考资料中找到依据。
回答:{answer}
资料:{sources}
只输出 JSON:{{"faithful": bool, "unsubstantiated": ["无依据的断言"]}}""")
    return resp

def knowledge_answer(question, filters=None):
    sources = retrieve_for_query(question, filters)
    if not sources:
        return "未在知识库中找到相关资料,建议联系行政部确认。", []
    answer = llm.chat(
        system="你是企业知识库助手。仅基于资料回答,并注明出处(文件名)。",
        user=f"【资料】{sources}\n【问题】{question}",
        temperature=0.2,          # 知识问答用低温度
    )
    # 忠实度自检,不通过 → 反思重答
    check = faithfulness_check(answer, sources)
    if not check["faithful"]:
        answer = llm.chat(
            system="基于资料重新回答,修正以下无依据的内容:"
                   f"{check['unsubstantiated']}",
            user=f"【资料】{sources}\n【问题】{question}",
            temperature=0.1,
        )
    return answer, sources

兜底原则:查不到就说查不到(附建议渠道)——比编造一个看似合理的答案好 100 倍(第 8 章"没有就说没有")。

26.2.3 出处可追溯

回答必须带出处(哪个文件、哪个版本),这是企业知识库的硬要求:

return f"{answer}\n\n【出处】{sources[0]['metadata']['source']}({sources[0]['metadata']['updated']})"

出处 = 可信任的最后一道证明——员工能自己打开原文件核对。

26.3 MCP 集成企业系统

26.3.1 知识库 Agent 要连什么

不只是问答,还要能联动企业系统:

图 3:知识库权限过滤

图 3:知识库权限过滤

系统用途接入方式
OA 系统查审批流程状态MCP Server
HR 系统查假期/薪资制度MCP Server
项目管理系统查项目文档MCP Server
内部 Wiki实时内容MCP Server

26.3.2 用 MCP 接入(第 7 章实践)

from fastmcp import FastMCP

mcp = FastMCP("oa-integration")

@mcp.tool()
def query_approval_status(approval_id: str) -> dict:
    """查询 OA 审批状态。用户询问审批进度/结果时使用。"""
    # 内部 OA API 封装
    return oa_api.get_approval(approval_id)

@mcp.tool()
def query_leave_policy(department: str) -> dict:
    """查询假期制度。用户询问年假/病假/调休时使用。"""
    return hr_api.get_leave_policy(department)

# 注册进 Agent:MCP 发现的工具 = 知识库 Agent 的动态工具集

价值:知识库 Agent 从"纯问答"升级为"问答 + 操作"——问完制度还能直接查自己的审批状态。

26.4 评测与上线

26.4.1 知识库专项评测(第 8/20 章)

EVAL_SET = [
    {"question": "报销标准是什么?", "gold_docs": ["报销制度-v3.pdf"],
     "check": "含金额上限且注明出处"},
    {"question": "年假怎么休?", "gold_docs": ["休假管理办法.pdf"],
     "check": "按制度回答"},
    {"question": "去年的团建费怎么报?", "gold_docs": [],   # 无此文档
     "check": "明确说未查到,不编造"},
    {"question": "扫描件里的制度也查得到吗?", "gold_docs": ["旧制度-扫描版.pdf"],
     "check": "OCR 后能检索"},
    ...
]
# 指标:检索命中率 / 忠实度 / 幻觉率 / 出处覆盖率

26.4.2 上线要点

事项说明
私有化部署内网数据不出域(第 2/22 章)
文档更新机制新文档入索引 + 旧版本标记(防止旧版误导)
权限过滤不同部门看到不同文档(RBAC,第 22 章)
监控检索命中率周报 + 幻觉率抽评(第 20 章)
反馈闭环员工点踩的答案回流评测集(第 18/20 章)

26.4.3 更新索引的工程细节

# 文档变更 → 增量更新(别全量重建,费时费钱)
def sync_index():
    changed = detect_changed_docs()          # 对比文件指纹
    for doc in changed:
        vector_store.delete(filter={"source": doc["name"]})  # 删旧
        index_document(doc)                                  # 加新
    # 版本冲突:同标题多版本 → 只保留最新(按 updated 排序)

图 4:文档增量同步

图 4:文档增量同步

🛠 解决方案:知识库问答幻觉抑制方案 + 检索质量评估

常见问题

  1. "答案编造制度条款":约束松 + 无出处。对策:仅基于资料 + 忠实度自检 + 强制带出处(26.2)。
  2. "检索不到扫描件内容":没 OCR。对策:扫描型 PDF 走 OCR(26.1.2)。
  3. "旧版制度误导用户":多版本并存无过滤。对策:版本标记 + 只保留最新(26.4.3)。
  4. "A 部门能查到 B 部门的机密":无权限过滤。对策:RBAC 元数据过滤(第 22 章)。
  5. "文档更新了但 Agent 答的还是旧的":索引未同步。对策:增量同步 + 文件指纹检测(26.4.3)。

解决方案速查表

现象根因解决方案
编造条款约束松仅基于资料 + 忠实度自检 + 出处
扫描件查不到无 OCROCR 管线
旧版误导多版本未过滤版本标记 + 保留最新
越权查看无权限过滤RBAC 过滤
答旧内容索引未同步增量同步

实战提示

  1. 出处是知识库的信任根基:回答必带文件名 + 版本,员工能自己核对。
  2. 幻觉抑制三件套:低温度(0.2)+ 忠实度自检 + 查不到就明说。
  3. 元数据过滤别省:部门/类型/版本过滤让检索精准十倍。
  4. 文档生命周期要管理:新增/更新/废弃都要反映到索引。
  5. 内网数据安全第一:私有化部署或脱敏,数据出域要评估(第 22 章)。