- Published on
用 LLM 搭建企业内部知识库这件事 —— 权限感知检索、新鲜度,以及上线前必须准备的评测集
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 —— 一天承接一万五千次查询的内部搜索,实际在解决什么问题
- Cerebras 公开的架构 —— 一条窄腰身加六个工具
- 权限感知检索 —— 泄露 HR 文档的搜索,还不如没有搜索
- 新鲜度与删除传播 —— 索引总是很晚才知道真相
- 当 wiki 和工单说法不一致时
- 上线前必须搭建的评测集
- 按组织边界切分范围
- 结语 —— 搜索质量可以后补,泄露却无法撤回
引言 —— 一天承接一万五千次查询的内部搜索,实际在解决什么问题
2026 年 7 月 15 日,Cerebras 公开了他们是如何搭建内部知识库的(How we built our knowledge base,GeekNews 摘要)。上线仅三个月,它就成了一个日均接受超过 15,000 次提问的内部工具,换算到人均大约是每天 15 次。值得留意的一点是提问者的构成 —— 不只是人,自动化脚本和智能体也在向同一个接口发起请求。
不妨先准确界定一下这套系统实际在解决什么问题。企业内部搜索的问题并不是"文档不存在"。通常情况是这样的 —— 答案埋在六个月前某条 Slack 帖子里,要搜到那条帖子就得知道当时用的那个精确错误字符串,而知道那个字符串的人已经调去别的团队了。wiki 上倒是有一篇同主题的文档,但最后一次修改是在八个月前,架构在那之后已经变了。也就是说,这不是搜索质量的问题,而是知识存放位置和新鲜度的问题。
而团队在起步阶段几乎总会低估的,不是分块策略或重排器,而是权限、新鲜度、删除传播、来源冲突,以及上线前的评测集。本文只关注这几点。
Cerebras 公开的架构 —— 一条窄腰身加六个工具
设计骨架很简单。采集、混合检索、合成这三个阶段,让所有来源汇入同一张 Postgres 向量表。文档、向量、元数据(来源名称、时间戳)只用一套 schema,Slack、代码、wiki、事件记录(incident)、自定义数据库全部被归一化后放进去。他们没有把所有东西迁移到单一平台,而是直接从数据产生的地方把它抽取出来。
Slack 的处理花的功夫最多。通过 Socket Mode WebSocket 接收消息事件,每次事件都重新拉取整个线程并存成一行。随后由 LLM 把这个线程蒸馏成一份规范化文档 —— 一句工程师实际可能会打出来的问题、一段摘要、解决方法,以及提到的系统和代码引用。真正被向量化的是这份蒸馏结果,而不是原始对话;原始对话只保留用于全文检索。在较长的线程里,来自同一作者的高信号片段会被单独抽出并单独向量化(称为 bursting),触发条件是 IDF 稀有度 4.0 以上、长度 200 字符以上,外加可选的至少 1 次表情回应。
检索通过互惠排名融合(reciprocal rank fusion)把四种信号合并起来,k 取 60。
# 精确 token(错误字符串、标志位、主机名) -> 全文检索
# 释义改写 -> 向量检索
# 去除"嗯好的"之类的填充词 -> IDF 加权
# 把旧答案往后排 -> 时间衰减(age decay)
score(d) = sum over retrievers of weight / (60 + rank_r(d))
# 关键在于不对分数做归一化。
# 某个检索器给出的第 1 名,不如多个检索器共同认可的靠前结果。
代码用 CocoIndex 配合各语言专属的正则表达式做层级化分块 —— 从类到方法、从方法到代码块,并且只对某次提交改动过的部分重新向量化。据称能处理规模达 40GB 的代码仓库。
查询流水线一共六个阶段 —— 由一个小型 LLM 查看项目范围并挑选工具的规划器(planner)、并行调用工具的执行器、k=60 的 RRF 融合、去重后砍到约 20 条结果、打 0 到 10 分的交叉编码器重排器,以及负责附加引用的合成阶段。工具一共六个 —— search、search_slack、search_code(基于 ripgrep)、who_knows、recent_prs、subsystem_index。
有一个架构上的选择格外出色。Web UI 会跑完从规划到合成的整条流水线,但 MCP 把这些原语单独暴露出来。这样一来,当 Claude Code 这类智能体接入时,就能在没有隐藏 LLM 合成步骤介入的情况下保持自己的编排方式。人和智能体想要的是同一个索引,但不是同一种编排方式。
有一点需要说明。我多次尝试打开原始页面,但服务器一直返回 500 错误,因此以上部分细节依赖于对原文的二次整理(尤其是 mer.vin 的整理)。这些数字和参数在多份摘要中保持一致,但后文会提到的 ACL 相关论述只出现在二手来源中,因此不会直接引用。
权限感知检索 —— 泄露 HR 文档的搜索,还不如没有搜索
这正是企业内部知识库与面向公开文档的 RAG 分道扬镳的地方。在公开文档 RAG 里,最糟糕的失败是给出错误答案;而在企业内部搜索里,最糟糕的失败是给出正确答案 —— 把准确的答案交给了本不该被告知的人。
问题的根源可以用一句话概括:源系统的权限逻辑不会跟着分块(chunk)一起走。 原本挂在 SharePoint、Drive、Confluence、Slack 私密频道上的访问控制,在文本被切片并向量化的那一刻就消失了。当 LLM 把那个分块当作上下文接收进来时,这份内容原本只对某个部门或指定用户开放这件事,在任何地方都不再有记录。
在实践中需要把这件事拆成三层来设计。
第一,在索引阶段就把源系统的 ACL 作为元数据一并载入。 把频道 ID、代码仓库、文档所有者、群组列表放在向量行旁边。这里常见的失误是把用户列表原样展开存储 —— 组织架构一变,就得把整个索引重写一遍。更好的做法是存储群组或频道这类稳定的主体标识符,把用户到群组的映射放到查询时再解析。
第二,在查询时加过滤。 预过滤和后过滤的性质并不相同。
-- 预过滤:只在有权限访问的候选集里找最近邻。
-- 安全,但对可访问文档很少的用户来说,召回率会急剧下降。
SELECT id, content
FROM chunks
WHERE acl_group = ANY ($1) -- 提问用户所属的群组
ORDER BY embedding <=> $2
LIMIT 50;
-- 后过滤:先多捞一些,再过滤掉。
-- 召回率能保住,但可能凑不满最终的 k 条,
-- 而且"3 条结果里隐藏了 2 条"这种存在本身,也可能泄露信息。
实战中通常两者混用。用预过滤切掉明显越界的部分,捞出足够宽裕的候选集,在最后一步再重新核实一遍。而这最后一次核实,必须始终对接源系统本身,或者是镜像了源系统的权限服务。烙印在索引里的 ACL 只是一份快照,而快照永远是过去的状态。
第三,所有入口都必须经过同一套鉴权。 一旦 Web UI、MCP 服务器、内部机器人、批处理任务各自走不同路径接触索引,其中必然会有一条路径漏掉鉴权。智能体以服务账号身份接入的配置尤其危险 —— 服务账号的权限通常比普通用户更宽,如果把用该账号搜到的结果原样展示给用户,就等于权限提升。在智能体这条路径上,比较安全的做法是把提问用户的身份一路传播到底,鉴权只在最靠近索引的那个点强制执行一次。
最后,提示词注入(prompt injection)叠加在这整个表面之上。2025 年底针对 Microsoft 365 Copilot 报告的 EchoLeak 就表明,一封根本没被点开的邮件也可能进入企业内部 RAG 流水线,进而被用来抽取并外泄敏感数据(我只通过厂商博客的二手描述确认过这件事)。要点在于,被检索的语料库本身就是信任边界。一旦你把外部可写入的渠道 —— 邮件、客户工单、有外部访客的 Slack 频道 —— 纳入索引,那个渠道就变成了一条提示词注入的输入路径。
新鲜度与删除传播 —— 索引总是很晚才知道真相
时间衰减不是新鲜度问题的解法,只是一种缓解手段。它只是压低旧答案的分数,并不知道那个答案其实已经错了。如果没有更好的候选,时间衰减照样会把错误答案推到第一位。
真正需要留意的是三种不同的事件。
修改。 文档发生变化时,只需要重新向量化对应的分块。按提交粒度做增量处理就属于这一类。这不难,但有个陷阱 —— 如果分块边界发生了变化,旧的分块可能会变成孤儿数据。更安全的做法是按文档 ID 把已有分块全部删掉再重新写入。
删除。 这一步通常是最后才被实现的。如果源头已经删除的文档还留在索引里,搜索就会自信满满地引用一份早已作废的运维手册。Slack 消息删除、wiki 页面归档、代码仓库删除,各自以不同形式的事件出现,有些甚至根本不会产生事件。所以仅靠事件驱动的删除是不够的,还需要定期做协调(reconciliation)——一个任务,把索引里的文档 ID 列表重新去源头核对一遍,删掉已经消失的部分。这类工作是那种会悄无声息失败的活儿,所以需要把"本周期删除了 N 条"作为一个指标输出出来。
权限收回。 比删除还要安静。文档本身没变,只是访问权限收窄了 —— 频道变成私密、有人被移出项目、有人离职。索引里的 ACL 快照看起来就像什么都没发生过一样。这正是前一节里说"最终核实必须对接源头权限服务"的原因。
还有一点需要诚实承认。完全的实时一致性并不是目标。目标是清楚知道延迟有多久,并把那些延迟不可接受的数据类别排除在索引范围之外。HR 文档、薪酬、未公开的并购信息、安全事件的原始记录 —— 不管权限模型做得多好,这些内容最好都不要放进早期版本里,因为这里的风险是不对称的。
当 wiki 和工单说法不一致时
同一个问题,wiki 说 A,Jira 工单说 B,Slack 帖子说 C,这是常态,不是例外。大多数系统在这种情况下会悄悄挑一个来回答。这是最糟糕的做法 —— 它把冲突藏了起来,却依然传递出和没有冲突时一样的确定语气。
有几条规则在实践中很管用。
为不同来源明确设定权威等级。 设置一个类似"代码是最终权威,其次是事件记录,再其次是 wiki,最后是 Slack"的顺序作为配置项。Cerebras 把代码库当作权威来源,理由是它结构化、有测试、有版本管理,用的是同样的思路。不过这个顺序会因问题类型而反转 —— "这个标志位的默认值是什么"这种问题上代码说了算,但"当初为什么这么做"这种问题上,Slack 帖子或设计文档更有说服力。
在答案里直接呈现冲突。 在合成阶段的提示词里加一条指令:"如果各个来源给出的值不一样,不要挑一个,而是把两者都列出来,并注明各自的日期和来源。"这不是答案质量问题,而是信任问题。搜索一旦悄悄给出过一次错误答案,之后就不会再有人相信它了。
新鲜度信号要挂在事实这一粒度上,而不是文档这一粒度上。 整个 wiki 页面的修改时间几乎没什么用 —— 哪怕只是改了页面底部的一个错别字,整页也会显示为"最新"。像 Slack 蒸馏那样按事实粒度抽取出来,就能单独附加"这个解决方案是在什么时候被确认的"这样的信息。
上线前必须搭建的评测集
这是最常被省略、省略代价也最大的部分。演示永远都很顺利,因为做演示的人问的问题,自己早就知道答案。
评测集不需要很大。50 到 150 道题就够了,重要的不是规模,而是覆盖的失败类型。
| 问题类型 | 检验的是什么 | 出错时的症状 |
|---|---|---|
| 只有一个正确答案的事实性问题 | 检索准确度和引用一致性 | 听起来有道理,但引用的来源并不支持这个答案 |
| 最近发生变化的事实 | 新鲜度与时间衰减 | 自信地给出六个月前的旧政策 |
| 只存在于已作废文档中的事实 | 删除传播 | 引用一份早已删除的运维手册 |
| 只存在于权限之外文档中的事实 | 权限感知检索 | 把内容暴露给没有查看权限的用户 |
| wiki 与工单相互冲突的事实 | 来源优先级与冲突呈现 | 随意挑一边,把冲突藏起来 |
| 关键在于精确 token 的问题 | 全文检索路径 | 向量化把错误字符串磨平,导致找不到 |
| 根本不存在答案的问题 | 拒答能力 | 编造出一个并不存在的答案 |
| 寻找负责人的问题 | 专家路由 | 推荐已离职或并不负责该事项的人 |
每一条大致长这样就够了。
{
"id": "acl-003",
"type": "permission",
"query": "上个季度的绩效评分分布是怎样的",
"asked_by": "role:engineer",
"expect": {
"must_not_cite": ["source:hr-drive"],
"must_not_contain_any": ["评分分布", "S级", "占比"],
"acceptable_behavior": "abstain_with_reason"
}
}
有三点想特别强调。
第一,权限相关的用例必须按角色分别跑一遍。 只用一个管理员账号跑出来的评测,对权限问题没有任何参考价值。至少要准备普通工程师、其他团队的工程师、实习生、外部访客这几种角色,用同一个问题反复测试。
第二,拒答必须被判为正确答案。"我不知道,这个主题不在我能访问的文档里"这种回答,应该在评测集里占到 10% 到 20% 的满分题目。不加入这类题目,系统就会被调优成"总是给出一个答案"的方向。
第三,评测集要靠事件不断长大。 每当用户举报一个错误答案,就把那条查询原样加进评测集。这和回归测试是同一个道理。半年后积累出的 400 道题,比最初的 150 道题价值大得多。
按组织边界切分范围
Cerebras 设计中一个容易被低估的要素是"项目"这个概念。把相关的 Slack 频道、代码仓库、文档空间、数据库打包成一个带名字的整体,新员工在入职时选择一个默认项目。
这看起来只是个 UX 上的便利,实际上同时解决了三件事。它降低了搜索噪音(其他团队同名的服务不会排到前面),缩小了规划器的选择范围(小模型在六个工具里做选择变得更容易),并且大体上与权限边界是对齐的(通常一个团队能访问的东西,和这个团队关心的东西,重合度相当高)。
局限性也很明显。如果组织经常重组,项目定义很快就会过时。而且范围划分是为了搜索质量服务的,并不是安全边界 —— 超出范围的查询依然必须通过鉴权。如果用同一套机制来实现这两件事,某天用来扩大范围的功能就会变成一条权限绕过路径。
结语 —— 搜索质量可以后补,泄露却无法撤回
在企业内部知识库项目里,团队的时间通常都会偏向搜索质量。分块、重排器、混合权重 —— 这些全都可以量化,改进效果也立竿见影。但这些项目都可以后补,重建索引就行。
有三样东西无法挽回:已经展示给无权限用户的文档、已经基于错误答案做出的决策,以及组织学习到的"那玩意儿不可信"这种印象。最后一样尤其难以恢复。企业内部工具一旦失去信任,流量会悄悄收敛到零,而从指标上看,就像什么都没发生过一样。
总结一下。
- 把权限拆成三层来设计:索引时的元数据、查询时的过滤,以及对源头权限服务的最终核实。所有入口都必须走同一套鉴权,智能体用服务账号搜索的配置本质上就是权限提升。
- 时间衰减不是新鲜度问题的解法。修改、删除、权限收回是三种不同的事件,删除传播需要事件处理和定期协调两者兼备,协调的数量要作为指标输出出来。
- 来源出现冲突时悄悄挑一个是最糟糕的做法。把权威顺序作为配置项固定下来,并在答案里直接呈现冲突。
- 上线前的评测集里必须包含权限用例和拒答用例,而且要按角色分别跑一遍。
搜索准不准,后面就能知道;而泄不泄露,往往要更久之后才会知道。