Lab C · 企业知识助手
本章你会学到:怎么把「检索 + 生成」做成一个企业内部敢用的产品——权限在检索前生效、每条答案有引用、没有依据时敢说不知道。
一句话:知识助手最难的不是回答,而是「不该看的坚决看不到」和「不知道时敢说不知道」;这两件事都是工程问题,不是提示词问题。
一个现场片段
以下为合成场景,用于说明典型情境,不对应任何真实公司或内部文档。
一家 500 人的公司(合成),文档散在四个地方:Confluence 放产品文档,Notion 放团队笔记,飞书放制度与流程,共享盘里堆着历史资料。新员工问「报销流程是什么」「VPN 怎么连」「试用期年假怎么算」,平均要等 4 小时才有人回;HR 和 IT 每天重复回答同样的问题。
上个月出了一次事故:有人把一份还没公布的薪酬调整文档转到了全员群。安全团队听说要上 AI 助手,只问了两个问题:权限怎么控?答错了算谁的?
IT 负责人找到你:「我们不指望它什么都能答,但它不能看到不该看的东西,也不能编。答不上来就让员工去找人,别硬编一个答案。」
背景与用户故事
任务:做一个内部知识助手。员工用自然语言提问,系统给带引用的回答;无答案时明确说没找到;检索与生成都受用户权限约束。
- 新员工:入职第一周问题最多,不想反复打扰同事,希望自己查到。
- HR / IT / 财务:每天大量重复问答,希望把时间花在例外情况上。
- 知识管理员:想知道哪些问题总答不上来、哪些文档过期了,形成内容修复清单。
- 安全 / 合规:不能越权、要有审计、能说清数据流向。
明确不做:不做对外客服、不做跨租户查询、不回答个人薪酬与绩效类问题(直接引导到 HR)、不自动执行操作(改权限、发邮件、下单)。
成功指标
下面数字是教学示例,不是行业标准;用你自己的数据重设。
| 指标 | 定义与口径 | 目标(示例) | 为什么重要 |
|---|---|---|---|
| 有用率 | 点赞数 / 有反馈的回答数 | ≥ 70% | 用户是否真的愿意用 |
| 引用准确率 | 引用内容确实支持答案的比例 | ≥ 90% | 编引用的助手没有信任价值 |
| 引用可点率 | 引用链接能打开原文的比例 | ≥ 95% | 断链等于没有引用 |
| 无答案正确率 | 该拒答时正确拒答的比例 | ≥ 85% | 防止一本正经地编 |
| 越权泄漏事件 | 低权限用户拿到高权限内容 | 0 | 红线,一次就下线 |
| 自助解决率 | 用户未转人工即解决的比例 | ≥ 40% | 业务价值 |
| P95 时延 | 从提问到首字返回的耗时 | ≤ 6 秒 | 超过 6 秒用户就关了 |
| 单查询成本 | 检索 + 模型调用成本 | ≤ 0.02 美元 | 决定能否全员铺开 |
| 护栏:审计完整率 | 可追溯提问、检索、引用的比例 | 100% | 出问题能查 |
指标取舍:先保安全,再谈有用
知识助手的第一优先级不是「答得多」,而是「不越权、不乱编」。越权泄漏和编造答案是一票否决项,不能因为有用率高就放过。第二个取舍是「答对率 vs 覆盖率」:宁可多拒答,也不要为了显得聪明而猜。
数据与权限
数据从哪来
- 合成知识库:自己写 30-50 篇制度、流程、产品文档,每篇标注部门、owner、更新时间、权限组。这是最推荐的方式,可控、可公开、能覆盖边界情况。
- 公开文档:开源项目文档、公开的公司制度模板,确认许可证允许使用。
- 真实内部文档:只有在你获得明确授权、且环境合规时才用;脱敏、最小化、可撤销。
不要用:未脱敏的真实员工数据、薪酬与绩效信息、客户合同、任何受保密约束的材料。
权限模型
权限是这个 Lab 的核心。先定义清楚五件事:
| 要素 | 说明 | 常见错误 |
|---|---|---|
| 主体 | 用户、组、部门 | 用昵称/工号当唯一标识 |
| 资源 | 文档、文件夹、空间 | 只做文档级,忽略子文档继承 |
| 动作 | 读、搜索、导出 | 搜索能搜到标题也算泄漏 |
| ACL 来源 | SSO / IdP 的组关系 | 本地维护一份永久不变的权限表 |
| 同步 | 入职、调岗、离职 | 离职当天没回收权限 |
三条硬规则:
- 检索前过滤:先按权限缩小检索范围,再排序、再生成。不要「先检索全部、生成后再删」,那样敏感内容已经进了日志或模型上下文。
- chunk 继承文档 ACL:切片不能脱离文档权限单独存在;一个文档里如果有密级差异,按章节再拆权限。
- 权限变更要能撤销:用户被调岗或离职后,下一次提问就不能再检索到原部门文档。ACL 缓存要有 TTL,并且能强制刷新。
文档治理
- 每篇文档必须有 owner 和更新时间;超过 180 天未更新的回答要标「可能过时」;
- 重复、冲突、过期的文档要先合并或下架,否则助手会在两个矛盾答案之间随机选一个;
- 敏感文档(薪酬、并购、法务)默认不进索引,或只对特定组开放。
方案:检索 + 权限 + 引用 + 无答案 + 反馈
用户提问
│
├─ 身份与权限上下文(SSO 组 / 部门 / 租户)
│
├─ 查询改写(同义词、缩写、中英互换,可选)
│
├─ 混合检索:BM25 + 向量召回 top 50
│ └─ 权限过滤(在检索层执行)→ top 20
│
├─ 重排(rerank)→ top 5,带 doc_id / 页码 / 更新时间 / owner
│
├─ LLM 生成:只能基于给定上下文,逐句标注引用 [1][2]
│
├─ 校验器
│ ├─ 引用必须来自本轮检索结果
│ ├─ 关键结论必须有引用
│ └─ 无依据 / 检索分过低 → 拒答
│
└─ 输出:答案 + 引用列表(标题、更新时间、链接)+ 反馈按钮
│
└─ 反馈 → 内容修复 / 检索修复 / 提示词修复检索设计
- 切片:每片 400-800 token,重叠 10-15%,保留标题路径与页码;
- 元数据:
doc_id、title、url、owner、updated_at、acl_groups、department、doc_type; - 混合检索:关键词检索保证专有名词命中,向量检索保证语义召回,两路合并后重排;
- 查询改写:把「年假」扩展成「带薪年假 / 休假制度」,但改写不能改变权限范围。
引用规则
每条回答的每个关键结论都要有引用,格式统一:
员工入职满一年可享受 5 天带薪年假[1]。
[1] 《员工休假管理制度》第 3.2 节,HR 部,2026-03-01 更新
链接:公司 wiki 的 HR 空间 / 休假制度页后端必须校验:引用 doc_id 在本次检索结果里;引用的段落与答案语义一致(可用小模型或规则粗筛);链接可打开。引用缺失的答案不输出。
无答案处理
这是决定产品口碑的部分。三类情况必须拒答:
- 检索最高分低于阈值(知识库里根本没有);
- 重排后相关性不足(有相似文档但不回答问题);
- 答案没有引用,或引用不支持结论(生成不可靠)。
拒答不是一句「我不知道」,而是一个可执行的下一步:
我在知识库里没有找到可靠依据,所以不提供答案。
你可以:
1. 联系 HR 服务台(内线 8021);
2. 在「我要提问」里提交给 HR 团队,通常 1 个工作日内回复;
3. 查看《员工手册》第 4 章。据 Salesforce 公开披露,其内部知识助手 Customer Zero 把「答不上来」的比例从三成压到一成以下(公司披露口径)——这说明「无答案率」本身就是一个可以管理和优化的产品指标,而不是失败。
关键代码
权限过滤先于检索
def can_read(user: dict, doc_id: str) -> bool:
acl = DOC_ACL[doc_id] # 从源系统同步,不要手写死表
return bool(set(user["groups"]) & set(acl["groups"]))
def retrieve_allowed(query: str, user: dict, k: int = 20) -> list[dict]:
hits = hybrid_search(query, k=k * 5) # 先多召回,再过滤
allowed = [h for h in hits if can_read(user, h["doc_id"])]
return allowed[:k]真实系统里,DOC_ACL 应该来自 Confluence / Notion / 飞书等源系统的权限 API,而不是你本地维护的静态表;权限过滤最好下推到检索层(比如带 ACL 条件的向量库查询),避免把无权限内容读进应用内存。
无答案判定
ABSTAIN_SCORE = 0.35
def should_abstain(hits: list[dict], answer: dict) -> bool:
if not hits:
return True
if max(h["score"] for h in hits) < ABSTAIN_SCORE:
return True
citations = answer.get("citations") or []
if not citations:
return True
allowed_ids = {h["doc_id"] for h in hits}
if not {c["doc_id"] for c in citations} <= allowed_ids:
return True
return False生成调用(OpenAI 兼容)
import os
from openai import OpenAI
client = OpenAI(
base_url=os.environ.get("OPENAI_BASE_URL"),
api_key=os.environ.get("OPENAI_API_KEY"),
)
SYSTEM = """你是企业内部知识助手。只能依据提供的资料回答,遵守:
1. 每个关键结论后标注引用编号 [1][2];
2. 资料不足时明确说「没有找到可靠依据」,不要猜测;
3. 不回答与资料无关的问题;
4. 不输出资料中没有的链接、数字、政策条款。
"""
def answer(question: str, chunks: list[dict], user: dict) -> dict:
context = "\n\n".join(
f"[{i}] {c['title']}(更新于 {c['updated_at']})\n{c['text']}"
for i, c in enumerate(chunks, 1)
)
resp = client.chat.completions.create(
model=os.environ["LLM_MODEL"],
temperature=0,
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"资料:\n{context}\n\n问题:{question}"},
],
)
return {"text": resp.choices[0].message.content, "citations": chunks}评测
黄金集结构(60 条)
| 分层 | 条数 | 目的 |
|---|---|---|
| 可答:HR 制度 | 10 | 高频问题,考察引用 |
| 可答:IT 支持 | 10 | 步骤类问题,考察完整性 |
| 可答:产品文档 | 10 | 专业术语,考察检索 |
| 可答:流程 / 审批 | 10 | 多文档综合,考察引用覆盖 |
| 无答案(知识库没有) | 15 | 考察拒答 |
| 权限敏感(低权限用户提问) | 5 | 考察权限红线 |
评测动作
- 引用准确率:逐条核对引用是否支持答案,不要只看「有没有引用」;
- 无答案正确率:该拒答时是否拒答,不该拒答时是否误拒;
- 权限红队:用低权限账号问薪酬文档、董事会材料、裁员名单,确认系统拒答;再检查追问、改写、英文提问等绕过方式;
- 有用率:找 5-10 位真实同事试用一周,收集反馈;
- 回归:每次改提示词、检索参数、切片方式、文档索引,都重跑黄金集。
据 Salesforce 公开客户案例,其帮助站用 74 万+ 篇知识文章把 76% 的咨询挡在人工之前(公司披露口径)——这是产品成熟后的数字,你的 Lab 不该拿它当目标,但可以把它当成「知识库治理 + 检索 + 拒答」三件事都做好之后的参考方向。
上线检查
- 身份透传:SSO 登录、组关系、租户隔离,不能靠用户自报身份
- 权限在检索前过滤,chunk 继承文档 ACL,缓存有 TTL
- 离职 / 调岗权限回收链路已打通并演练
- 引用校验默认开启,引用必须来自本轮检索结果
- 无答案模板上线,包含联系人和提交入口
- 权限红队报告通过:越权 0,追问绕过 0
- 敏感文档(薪酬、并购、法务)默认不进索引
- 审计日志:提问、检索到的文档、引用、模型与提示词版本
- 内容 owner 与过期文档治理机制已就位
- 一键下线 / 回滚到纯搜索,已演练
常见误区
- 用提示词防越权:在 prompt 里写「不要泄露」不构成权限控制,检索阶段就必须过滤。
- 先检索再删:敏感内容已经进入日志、缓存或模型上下文,删结果没用。
- 没有无答案处理:模型一定会编,拒答逻辑是产品的一部分。
- 引用不校验:引用看起来有,但链接打不开、内容不相关,用户很快不信。
- 只测 happy path:不测权限、不测无答案、不测追问绕过,上线必出事。
- 知识库不治理:喂进去一堆过期和冲突文档,助手只会在错误答案里挑一个。
本章小结
- 权限是架构问题:检索前过滤、chunk 继承 ACL、变更可撤销。
- 引用是信任的基础:每条关键结论可点回原文,后端要校验。
- 无答案处理决定口碑:明确说没找到,并给出可执行的下一步。
- 反馈要能落到「内容 / 检索 / 提示词」三类修复,而不是只看点赞数。
- 知识库治理和模型同样重要:过期文档比模型幻觉更难被发现。
练习
- 画权限模型:为 500 人公司设计组、文档、ACL 的对应关系,写出「离职当天如何失效」。判断标准:能用表格说清任意两个用户能看到什么。
- 做一次权限红队:设计 10 个越权问题(含追问、改写、英文),跑一遍系统。判断标准:越权 0,且有审计记录。
- 写 20 条无答案测试:覆盖知识库没有、问题模糊、权限不足三类。判断标准:拒答率 ≥ 85%,且拒答文案给出可执行下一步。