返回学习路线/阶段 04 · 连接你的知识与记忆
LESSON 12 / 18

项目:有据可查的知识助手

将文档检索、证据引用和无答案回退组合成一个小产品。

60 分钟 · 含动手练习进阶项目实战知识助手

学完这一课,你将能够

  • 返回可追溯的原文证据
  • 对未知问题明确弃答
  • 验证检索命中与答案引用

项目目标

做一个回答内部制度问题的知识助手。 有资料时返回原文证据和来源,没有资料时明确说明知识库无法回答。 基础版使用离线词项检索与摘录式回答,便于逐步检查数据流。

建议先完成 RAG 与记忆课程,预留约 1 小时。 这个项目不需要先部署向量数据库,也不需要模型 API。

交付标准

  • 每份资料保留来源、版本和片段 ID。
  • 查询返回相关证据以及可核查的引用。
  • 资料不支持的问题明确弃答。
  • 评估检索命中与答案引用,不只看文字流畅度。
  • 至少准备 10 条评估样本,其中包含未知问题和相似但不相干的问题。

先约束知识范围

本例知识库只有两份制度,不能回答制度之外的问题。 日期、金额等具体数字必须从资料原文获取。 资料过期或互相冲突时,应显示限制,不能让模型自行决定哪份更权威。

一份真实项目还应写清楚资料归属和访问范围。 下面的示例数据是虚构教学数据,不代表任何公司的真实制度。

完整离线基础版

保存为 knowledge_assistant.py 并运行。 为了控制范围,查询使用明确的关键词匹配;这会有漏召回,需要在报告中说明。

DOCS = [
    {
        "id": "expense-v2-1", "source": "示例报销制度", "version": "v2",
        "keywords": ["报销", "发票"],
        "text": "报销须在消费后 30 天内提交,并附有效发票。",
    },
    {
        "id": "leave-v1-1", "source": "示例请假制度", "version": "v1",
        "keywords": ["年假", "请假"],
        "text": "年假申请须提前 3 个工作日提交。",
    },
]

def answer(question):
    if not isinstance(question, str) or not question.strip():
        return {"status": "invalid_input", "answer": "请提供问题。", "citations": []}
    ranked = []
    for doc in DOCS:
        score = sum(keyword in question for keyword in doc["keywords"])
        if score:
            ranked.append((score, doc))
    if not ranked:
        return {
            "status": "abstained",
            "answer": "现有知识库未找到相关依据,请补充资料或咨询负责人。",
            "citations": [],
        }
    ranked.sort(key=lambda pair: (-pair[0], pair[1]["id"]))
    evidence = [doc for _, doc in ranked[:2]]
    return {
        "status": "evidence_found",
        "answer": "\n".join(f'[{doc["id"]}] {doc["text"]}' for doc in evidence),
        "citations": [
            {"id": doc["id"], "source": doc["source"], "version": doc["version"]}
            for doc in evidence
        ],
    }

cases = [
    ("报销需要发票吗?", "expense-v2-1"),
    ("年假要提前多久申请?", "leave-v1-1"),
    ("每月餐补多少钱?", None),
]
for question, expected_id in cases:
    result = answer(question)
    ids = {citation["id"] for citation in result["citations"]}
    passed = expected_id in ids if expected_id else result["status"] == "abstained"
    print(question, "PASS" if passed else "FAIL", result)

三条示例应通过,但这只是起点。 基础版返回的是证据摘录,所以状态叫 evidence_found,没有声称已完整回答所有问题。 例如“报销能买电脑吗?”会检索到相关制度,却不包含购买资格答案。 这类问题应加入评估集,暴露“相关证据不等于充分证据”的局限。

接入生成步骤时的要求

让模型只依据检索证据回答,并输出答案、引用 ID 和是否证据不足。 程序验证引用 ID 确实来自本次检索集合。 引用合法仍不代表引用支持结论,还需要检查具体主张与原文的对应关系。

最简单的改进是按句保留证据,避免一段答案只在末尾放一个模糊来源。 对于资料没有覆盖的部分,允许部分回答并明确指出缺口。 不能因为用户要求“必须回答”就编造不存在的政策。

评估表应该记录什么

问题期望证据是否可答实际行为
报销多久内提交expense-v2-1是填写结果
发票抬头是什么无充分证据否检查是否乱猜
明天会下雨吗无否检查是否弃答

检索命中率检查目标片段是否出现。 答案支持率检查生成的每个关键事实是否有依据。 未知问题弃答率单独计算,不能用总体通过率掩盖错误自信。

最终验收

提交运行说明、虚构或有权使用的资料、10 条以上评估样本与失败分析。 至少记录一个检索失败和一个“证据相关但不充分”的例子。 如果加入模型,报告应分别记录检索结果和最终答案,方便定位问题。

延伸阅读

下一模块学习把多步任务组织成可恢复、可检查的工作流。

让这一课,真正成为你的收获

完成练习后标记完成,也可以随时回来复习。

笔记与进度保存在当前浏览器,无需登录

AgentStudy · Learn by building.以理解为起点,以作品为答案