知识健康
知识库用久了会出三类问题:同一份内容存了好几遍(制度改版后旧版没删,同一份材料从网盘、飞书、手工上传各进来一次);两份文档说的是同一件事,细节却对不上(旧版写年假 15 天,新版写 10 天);更多的文档没人动过,内容却已经过时(VPN 地址早换了)。检索时副本互相挤占名额,回答引用的可能恰好是错的那一份。
知识健康在内容变化时、按复核周期、以及根据回答反馈,自动发现这些问题,交给该处理的人,由人决定怎么处理。系统不会自动修改或删除内容。
1. 发现什么
| 类型 | 含义 | 来源 |
|---|---|---|
duplicate 内容重复 | 两篇文档有逐字相同的段落 | 内容比对 |
divergent 内容有出入 | 两篇文档的段落几乎相同但不完全一样:其中一份可能过时,或两份说法矛盾 | 内容比对 |
stale 需要复核 | 超过知识库的复核周期,没有人确认或修改过 | 定期复核 |
disputed 回答被反馈有误 | 引用这篇文档的回答被标记为「没帮助」 | 回答反馈 |
1.1 内容比对:重复与有出入
两篇文档的段落在语义向量上几乎一致,就判定它们内容重叠。比对只用索引时已经存好的向量,不重新做 embedding,也不调用大模型,几乎没有额外成本。
- 取出发生变化的文档的每个文本分块,在同一知识库、其它文档的分块里找最相近的若干个(按余弦相似度,同一向量维度内比较);
- 相似度达到阈值(默认 0.95)的分块对,按对方文档归组;
- 对每个找到的对方文档,再从它那一侧反查一遍,合并两个方向的结果——无论哪篇文档触发,结论都一样;
- 每对文档生成一条问题记录。
光看向量分不清「一模一样」和「只改了一个数字」:用 bge-m3 实测,「年假 15 天」和「年假 10 天」两段话的相似度是 0.97,高于重复阈值。所以匹配上的段落还会逐字比对:
- 所有段落对都逐字相同(忽略空白和换行),是内容重复,提示 info,重叠比例 ≥ 50% 时为 warning;
- 有任何一对在共同的文字内部有改动,是内容有出入,一律为 warning。证据以第一处差异为中心截取,界面里会标出不同的字。
两篇文档切块位置不同时,同一段内容被切出的分块头尾会带着各自的上下文。这种头尾差异不算「有出入」:只有落在两段共同文字之间的改动才算。完全没有一段较长共同文字的两个段落(意思相同、换了说法)也算有出入。
不参与比对的内容:已停用的分块;摘要、OCR、图片描述等非正文分块;少于 40 个字符的段落(标题、页脚这类模板内容在不相关的文档之间也高度相似);自动生成的问题。
1.2 定期复核
大部分知识过时并不是因为有人写了冲突的新文档,而是现实变了、文档没人动。比对永远发现不了这种情况,所以知识库可以设一个复核周期(天,默认 0 表示不复核):一篇文档超过这么多天没人确认也没人修改,就生成一条「需要复核」交给它的负责人;超期再满一个周期时升为 warning。
复核计时从「最近一次有人经手」算起:有人点「确认仍然有效」,或者修改了内容。重新解析、换向量模型、移动都不算。从没人经手的文档从创建时算起。
后台每小时巡检一次,为到期的文档和复核周期已改变(缩短、延长、关闭)的文档安排检测。把某条「需要复核」忽略后,本周期内不再提示;下次有人经手、再到期时会重新考虑。
1.3 回答反馈
问答里,引用了知识库文档的回答下方有「有帮助 / 没帮助」。「没帮助」可以写一句哪里不对。
一篇文档自最近一次有人经手以来,引用它的回答收到的「没帮助」都会汇总成一条「回答被反馈有误」,交给文档负责人;达到 3 条时为 warning。负责人确认内容无误或修改后,计数重新开始,问题在随后的检测中关闭;之后再有人反馈,是新的一次(即使之前忽略过,也会重新打开)。
隐私上的约定:
- 问题记录对能读这个知识库的人可见,所以只展示反馈者写的意见和回答的开头;提问内容只有在反馈者勾选「附上我的问题」时才附上(默认不勾选)。不显示反馈者是谁;
- 反馈只到达本工作区的文档:回答引用了其它工作区共享来的知识库时,那边的负责人看不到这里的提问;
- 会话被删除后,其中的反馈不再计入;
- API Key 不能提交反馈。
1.4 一条问题记录里有什么
| 字段 | 含义 |
|---|---|
| 主体文档 / 相关文档 | 两篇文档的问题:主体是「更大比例被对方包含」的那一篇。只关于一篇文档的问题(复核、反馈)没有相关文档 |
重叠比例 overlap_ratio、相似度 score | 两篇文档的问题才有:主体的段落中有多少比例在对方找到了匹配;最相近一对段落的相似度 |
证据 evidence | 最多 3 对段落,各截取 300 字;有出入的段落对标 differs,并优先展示 |
extra | 检测器自己的信息:复核的 due_at(应复核日期);反馈的 count 与最近 3 条 reports |
处理人 assignee | 见第 2 节 |
关闭原因 resolution | distinct_scope、intentional(人工忽略时选的原因),cleared(内容改了,检测不再报告) |
1.5 暂不检测的内容
- 用大模型判断两篇文档是否真的矛盾、谁新谁旧(「内容有出入」只说明文字不同,由人判断);
- 跨知识库的比对;
- 链接失效。
FAQ 知识库不做内容比对:FAQ 在导入和编辑时已经按标准问去重(见 FAQ 能力);复核和回答反馈对 FAQ 同样适用。只开了关键词检索、没开向量检索的知识库没有向量可比,不做内容比对。
2. 谁来处理
每篇文档有一个负责人:默认是添加它的人,可以转交。负责人不是权限——谁能看、谁能改照旧——而是出了问题该找的人。每篇文档还记录最近一次经手:谁在什么时候确认过或修改过它。
每条问题都会派给一个人,规则按问题的性质定:
| 类型 | 派给 | 理由 |
|---|---|---|
| 内容重复 | 两篇中最近被经手的那篇的经手人 | 副本是刚写的人造成的,趁他还记得时处理最省事 |
| 内容有出入 | 两篇中最久没人经手的那篇的负责人 | 更可能已经过时;另一方在自己的页面上同样看得到 |
| 需要复核、回答被反馈有误 | 该文档的负责人 |
找人的顺序是负责人 → 最近经手人 → 知识库创建者,跳过已经离开工作区、被停用或暂停的人;选中的那篇文档找不到人时,改找另一篇文档的人。都找不到时显示「未指派」。
派发在每次检测时重新计算,所以转交负责人、有人修改文档后,未处理的问题会跟着换人。知识库编辑者也可以手动指派给某个成员,之后的检测不再改派;选「交还自动派发」则恢复自动规则。
转交负责人时,新负责人必须能编辑这个知识库(知识库创建者或管理员),否则派给他的问题他也处理不了。在线文档页面的负责人在页面上转交(当前负责人或页面管理员可以操作,新负责人须能编辑该页面),知识库里对应的条目自动跟随,不需要重新索引。
3. 什么时候检测
- 文档索引完成后约 30 秒:上传、重新解析、手工编辑、在线文档页面同步到知识库都会触发;同一篇文档 30 秒内的多次变化只检测一次,检测中又发生的变化会再安排一次;
- 转交负责人、确认仍然有效、提交或撤回「没帮助」之后;
- 每小时的复核巡检;
- 在健康页面点「重新检测」(知识库创建者或管理员,单次最多 5000 篇,分批错开执行)。
检测作为后台维护任务执行,失败按队列策略重试;没有 Redis 的单进程部署同样可用。文档被删除或移动到其它知识库,与它有关的问题记录随之删除。
4. 怎么处理
每条问题有三种状态:open 待处理、dismissed 已忽略、resolved 已解决(检测不再报告)。处理方式:
以一份取代另一份(重复、有出入):选择保留哪一份,另一份退出知识库。退出的方式取决于文档的来源:
- 上传或手工创建的文档:删除;
- 在线文档页面:不删除,而是排除出知识库,并在页面上显示「本页已被《X》取代」,读者仍然可以打开;有编辑权限的人点「重新参与知识库」即可撤销,标记随之清除;
- 数据源同步来的文档:不能在这里移除(下次同步会回来),需要到源头删除。
取代在线文档页面需要能编辑那个页面。写新版页面的人在自己页面的提示里就能点「用本页取代它」(需要能编辑两个页面);知识库编辑者在健康页面操作;
确认仍然有效(需要复核、回答被反馈有误):重新开始复核计时,问题在随后的检测中关闭。在线文档的写作者可以在页面上确认,不需要知识库权限;
修改内容:改掉后问题自动关闭,不需要手工处理;
忽略:必须选原因——「适用范围不同」(内容相近,但适用于不同地区、产品、年份)或「有意保留」(例如汇总页引用原文)。证据不变时一直保持忽略;相关内容变了会重新打开,因为你忽略的是当时那些内容。重新解析(分块 ID 变了、文字没变)不会重新打开;
重新打开:误点忽略可以改回待处理。
以上操作都会记入知识库的活动记录。
我的知识待办:派给你、还没处理的问题会在页面右上角出现一个待办按钮和数量(没有时不显示),点开是跨知识库的列表,「去处理」跳到对应知识库的健康页面。它是一个查询而不是消息推送:批量导入一次发现上百处重复,只会让数量变大,不会发来上百条通知。健康页面也可以只看「派给我的」。
在线文档页面会提示与本页有关的问题:只展示对方同样是你有权查看的页面的那些;对方是上传的文件、你看不到的页面或其它文档空间的页面时,只显示「另有 N 条」,不透露对方的标题和内容。
5. 配置
| 环境变量 | 默认值 | 说明 |
|---|---|---|
YUHENG_FINDINGS_ENABLED | true | 是否自动检测(包括复核巡检)。关闭后不再产生新的问题,已有记录仍可查看 |
YUHENG_FINDINGS_DUPLICATE_MIN_SCORE | 0.95 | 两个段落被视为内容重叠的余弦相似度,取值 0.5–1 |
阈值调低会报出更多只是话题相近的段落,调高则只剩几乎逐字相同的内容。取值超出 0.5–1 时服务拒绝启动,而不是悄悄按默认值运行——阈值写错(比如写成 95)不会有任何报错,只会让检测什么都报或什么都不报,很难察觉。
复核周期在知识库设置的「基本信息」里设置,单位是天,0 表示不复核,最大 3650。
内容比对依赖检索引擎能比较已存储的向量。内置的 PostgreSQL(pgvector)引擎支持;其它引擎若不支持,健康概览里会显示「不支持」。
6. API
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| GET | /api/v1/knowledge-bases/:id/findings | 问题列表。status=open(默认)/dismissed/resolved/all;type、knowledge_id 过滤;assignee=me 只看派给我的;page、page_size(最大 100) | 知识库读权限;API Key 需 retrieve |
| GET | /api/v1/knowledge-bases/:id/findings/summary | 概览:open_total、open_by_type、last_scan_at、enabled、supported | 同上 |
| PATCH | /api/v1/knowledge-bases/:id/findings/:finding_id | {"status":"dismissed","reason":"distinct_scope"} 忽略(reason 必填:distinct_scope/intentional),{"status":"open"} 重新打开 | 知识库创建者或管理员,且有编辑权限;API Key 需 ingest |
| PUT | /api/v1/knowledge-bases/:id/findings/:finding_id/assignee | {"assignee_id":"…"} 手动指派;空字符串交还自动派发 | 同上;需要登录用户 |
| POST | /api/v1/knowledge-bases/:id/findings/:finding_id/supersede | {"keep_knowledge_id":"…"} 保留一份、另一份退出知识库,返回 {"retired_knowledge_id","how"},how 为 deleted 或 excluded | 同上 |
| POST | /api/v1/knowledge-bases/:id/findings/scan | 重新检测整个知识库,返回 {"queued": n} | 知识库创建者或管理员,限所属工作区;API Key 需 manage_kbs |
| GET | /api/v1/findings/assigned | 当前工作区里派给我的问题(跨知识库,带 knowledge_base_name),参数同列表 | 工作区成员 |
| GET | /api/v1/findings/assigned/count | 派给我、未处理的数量 {"open_total": n} | 工作区成员 |
| GET | /api/v1/knowledge/:id/stewardship | 文档的负责人、最近经手、复核周期、review_due_at、overdue | 知识库读权限 |
| PUT | /api/v1/knowledge/:id/owner | {"owner_id":"…"} 转交负责人,空字符串表示不设;在线文档镜像返回 409 | 知识库创建者或管理员 |
| POST | /api/v1/knowledge/:id/review | 确认文档仍然有效 | 同上;需要登录用户 |
| GET | /api/v1/docs/pages/:pid/findings | 在线文档页面的问题:items 与 other_count | 页面读权限 |
| PUT | /api/v1/docs/pages/:pid/owner | 转交页面负责人 | 当前负责人或页面管理员 |
| POST | /api/v1/docs/pages/:pid/review | 确认页面仍然有效 | 页面写权限 |
| POST | /api/v1/docs/pages/:pid/supersede | {"finding_id":"…"} 用本页取代问题另一方的页面 | 两个页面的写权限 |
| GET | /api/v1/sessions/:id/feedback | 我对该会话各条回答的反馈 | 会话所有者 |
| PUT | /api/v1/sessions/:id/messages/:message_id/feedback | {"rating":"up"/"down"/"","comment":"…","share_question":false} | 会话所有者;需要登录用户 |
列表中的每一项:
{
"id": "…",
"knowledge_base_id": "…",
"type": "divergent",
"detector": "duplicate",
"severity": "warning",
"status": "open",
"score": 0.97,
"overlap_ratio": 0.5,
"subject": { "knowledge_id": "…", "title": "年假制度(2024)" },
"related": { "knowledge_id": "…", "title": "年假制度(2026)" },
"evidence": [
{
"subject_chunk_id": "…", "subject_excerpt": "员工每年享有15天带薪年假……",
"related_chunk_id": "…", "related_excerpt": "员工每年享有10天带薪年假……",
"score": 0.97, "differs": true
}
],
"assignee": { "id": "…", "username": "Alice", "active": true },
"assigned_manually": false,
"created_at": "…", "updated_at": "…",
"resolved_at": null, "resolved_by": null, "resolution": null
}resolved_by 为处理者的用户 ID,或 system(检测不再报告而自动解决)。
7. 局限
- 内容比对的依据是 embedding 模型眼中的「语义相同」,加上逐字比对。「内容有出入」只说明文字不同,不判断谁对谁错、是否真的矛盾;
- 差异恰好落在分块末尾、且另一侧还有后续内容时,可能被当作切块边界而判为「重复」;
- 每篇文档最多与 20 篇最相似的文档比较;
- 知识库换了 embedding 模型、旧文档尚未重新索引时,新旧维度的向量之间无法比较;
- 在线文档页面被排除出知识库再重新纳入时,页面上「确认仍然有效」的记录不会保留,复核计时回到页面最近一次修改的时间;
- 比对只看同一知识库内部。
检测框架是可扩展的:新的检测器可以通过扩展点接入,并声明自己的派发规则,无需修改核心代码,见 扩展点指南 第 9 节。