企业知识问答从 40% 到 85%:一条 RAG 落地的真实路径
字数 5.5k / 19 min 读完 / 阅读2022 年中,我们大前端基础部门有两类事情很烧人。一类是线上应用指标波动,凌晨被电话叫起来归因;另一类是每天在群里重复回答基建系统和组件的用法问题。那时的想法是让扁鹊做自动归因,再在钉钉和基础项目里接一个问答机器人,把答疑接住。
问答机器人最早打算用语义分词加定制场景规则来做。调研之后发现维护成本压不住:系统名、组件名、告警口径一直在变,每加一个场景就要补一批词典和规则,通用不了,也复用不了。所以前期只落地了扁鹊的自动归因,问答那半件事搁下了。
转折在 2022 年 11 月前后。GPT-3.5 出来,我用 LangChain 串了一个知识库召回的 Demo,把内部文档灌进去,用自然语言问,答案居然大致能用。这件事后来长成了公司内部的 AI 应用平台「海螺」。这篇文章只讲一件事:它的问答准确率怎么从最早约 40% 一步步抬到 85% 以上,以及我在这个过程里对 RAG 的几点认识。
先跑通最小闭环
Demo 有效之后,摆在面前有两条路。一条是直接按平台来设计:多知识库、多机器人、权限、渠道、运营后台一起上;另一条是只做一条链路,导入、召回、生成,三步跑通就上线。
我们选了后一条。当时最大的不确定性是企业知识问答这件事本身到底能不能缓解答疑,这个问题只有真实用户用起来才能回答,平台功能齐不齐排在它后面。
于是第一版就是 Milvus 存向量、GPT-3.5 生成、LangChain 把两头串起来,再抽象了知识库关联、Prompt 和开场白三个配置项,让别的团队也能配一个自己的机器人。早期权限、运营、评测都弱,发展期基本是在补课。但如果重来一次,我还是会这样选。一个能演示的 Demo 加一批真实用户的使用记录,比任何方案文档都管用。
顺便说一下 LangChain。它在早期帮我们很快把召回和模型调用串起来。后来平台要支持多机器人、多知识库、多入口和长期运营,我们才逐步把机器人配置、知识生命周期、检索编排和入口协议收敛到自己的应用层,核心链路不再强绑定一个框架。
上线之后,错答长什么样
第一版上线不久,BadCase 就开始堆。我把它们翻了一遍,大致有两类。
第一类是精确实体漏召回。用户问「某系统报某个错误码怎么处理」,向量检索会给出一堆语义相近但完全不是那篇的文档,回答看起来头头是道,实际没命中真正的内部文档。系统名、错误码、工单号这种东西,在向量空间里天然离得近,越是内部黑话越容易漂。比如用户问「扁鹊如何接入」,但扁鹊是前端监控系统,天启才是 SDK,用户真正想问的是「如何通过天启 SDK 接入扁鹊监控」。
第二类是知识本身有问题。切分把标题、前置条件和结论拆到了三个块里;文档半年没更新,回答的是过期流程;还有把手机号、邮箱原文吐出来的。这些错,模型和检索都没做错,是喂进去的东西就不对。
那段时间只能靠人工抽检,再逐一补充和完善知识文档,当时评估出来的结果是准确率在四成左右。
意识到这两类问题之后,做了一个后来看很关键的决定:把「答错了」拆开看它错在哪一段。错答在前台看起来都像胡编,实际可能出在改写、召回、排序、生成任何一环。不拆开,就只能等模型迭代。
把「答不准」拆成几段来治

查询侧先做改写。用户的问题往往很短,「发布失败了咋办」这种,本身信息不够。改写时补上提问人的部门和岗位,再对内部系统名做一层预设别名映射,把黑话和简称展开成知识库里的标准写法。这一步的难点在分寸:补太少,歧义消不掉;补太多,会把用户原意改掉。我们补的内容限定在部门、岗位和系统别名,别的不动。
召回侧上了 ES 和向量混合。ES 负责关键词和精确实体,向量负责同义和口语表达,两路各取一批候选再合并。有一个坑:两路的分值不在一个空间里,不能把 ES 的相关性分和向量距离直接排在一起比,合并时只做去重和候选归并,排序交给下一层。
权限也收在这一层。检索时就带上提问人的可见范围,两路召回各自按范围过滤,无权的知识根本不会进候选集。
排序侧加 Rerank。粗排要宽,尽量不漏;进 Prompt 的上下文要准,噪声会直接污染生成。Rerank 用交叉编码器把问题和每条候选拼在一起打分,只对几十条候选做精排,用额外的延迟和成本换最终上下文的质量。
生成侧改 Prompt。除了常规的角色和边界约束(只用检索内容回答,信息不足就说不知道),提示词里加了两样东西:一段思维链,让模型先判断召回的知识是否匹配问题,再决定怎么答;以及每条证据的匹配度分值,直接拼进提示词,让模型知道哪条证据更可信。模型拿到文本先做一次评估,然后才开始写。
最后是生成前的标注和生成后的表达。候选进 Prompt 之前,标记久未更新的内容,标记来源类型。证据不足的直接拒答,给几个引导问题让用户把问题问清楚。
回答旁边展示参考的知识来源,并给回答一个可信度评级。评级综合四个维度:来源权威度(正式制度文档高于部门沉淀)、内容新鲜度(最近更新还是放了一年多没人动)、证据匹配度(Rerank 给出的最高分和整体分布)、证据一致性(多条证据互相印证还是彼此矛盾)。四个维度合成一个档位展示给用户,让他知道什么时候可以直接用,什么时候该自己再去翻一下原文。
另一条并行的路是预设回答。对高频标准问,从导入的知识里生成预知问题,用户提问先做向量匹配,命中就直接回预设答案,不走生成,不让所有流量都过一遍模型。
整条链路的编排骨架大致是这样,省掉了错误处理和流式部分:
async function answer(question: string, user: UserContext): Promise<Reply> {
// 1. 查询改写:补部门、岗位、内部系统别名
const rewritten = rewrite(question, {
department: user.department,
role: user.role,
aliases: SYSTEM_ALIASES, // 内部简称 → 知识库里的标准名
});
// 2. 两路召回:关键词保精确实体,向量保同义表达
// 权限在这一步就收敛,无权知识不进候选集
const scope = aclScope(user);
const [byKeyword, byVector] = await Promise.all([
keywordSearch(rewritten, { topK: 10, scope }),
vectorSearch(embed(rewritten), { topK: 10, scope }),
]);
const candidates = mergeCandidates(byKeyword, byVector); // 去重;两路原始分不直接比
// 3. 精排:只对这批候选做 Cross-Encoder 打分
const ranked = await rerank(rewritten, candidates, { topN: 5 });
// 4. 标注:时效和来源类型,供提示词和前端展示
const evidence = ranked.map((c) => ({ ...c, stale: isStale(c.updatedAt) }));
// 5. 证据不足直接拒答,不让模型硬编
if (evidence.length === 0 || evidence[0].score < MATCH_THRESHOLD) {
return refuse(question, suggestQuestions(rewritten));
}
// 6. 提示词里带匹配度,让模型先评估再作答
const prompt = buildPrompt({ question: rewritten, evidence, requireCitation: true });
const text = await llm(prompt);
// 7. 评级:来源权威度 + 新鲜度 + 匹配度 + 证据一致性
return { text, citations: evidence, grade: grade(evidence) };
}
这几层各管一段:改写解决问法和知识表达不对齐,混合召回解决实体和语义的矛盾,Rerank 压候选噪声,引用和评级管信任。链路变长之后,延迟、成本和调参的复杂度都上去了,某一层收益不足时应该允许降级。
知识那一侧才是大头
检索调得再好,知识不对还是答不准。后来我们干脆把知识治理当成准确率的一部分来做。
导入时先脱敏,手机号、邮箱这类字段在进库前处理掉。之后有一步可选的格式化改写,由导入方按文档形态决定开不开:开了就在保留原文的前提下,另产出一份按文本模板整理过的版本,把标题、段落、列表、表格这些结构理顺,切分和召回走整理后的文本,引用和核对仍回到原文。PDF、Word 这类版式杂乱的文档开着有用;本身结构就规整的语雀文档和 QA 对没必要走这一步,多一道改写反而多一次出错的机会。
然后对每篇文章生成一份总结,作为 chunk 的元数据带着,减轻切分之后全局信息丢失的问题。总结能辅助召回,但摘要本身也可能漏事实,不能替代原文当证据。
切分方式做了好几种:定长切分、滑动窗口切分、按文档结构切分、按自定义分隔符切分,还有一种按语义边界的切分。做法是先按句或小段切成候选块,每块做向量,再算相邻两块的相似度:高于阈值就合成一块,掉下去就当作主题换了、在这里断开;同时设一个最大长度,避免一路合并把整章都揉进去。
chunk 太大噪声多、token 浪费,太小语义碎片化、回答不完整,所以要在切分时判断合并。哪种切法合适取决于文档形态,制度文档和接口文档就不该用同一套。预处理还包括清洗无意义符号、代码块单独处理,并给 chunk 挂上标题、分类、目录结构这些元数据。
同步靠每日定时任务。长时间没更新的知识打上标记,并在回答里展示出来,让用户自己感知时效风险。这里有两点当时没解决好:每日同步意味着最长可能有一天的延迟,紧急的权限撤回不够快,但也提供了手动处理的方式进行同步;「久未更新」也只是提示,它不等于「已失效」。
知识版本当时没做,这是我现在最想补的一环。文档更新后,旧 chunk 基本是覆盖写入,回答引用的是哪一版、评测跑的是哪一版知识,事后对不上。该做的是每次导入或同步打版本号,索引按版本切换,线上回答带上知识版本,评测集也钉在同一号上。没有这一层,修知识、改切分、换模型都分不清到底变好了还是碰巧碰上新文档。
导入渠道上,支持语雀文档直接导入,也支持本地 PDF、Word、Excel、JSON、Markdown 批量导入。语料形态除了整篇文档,还有 QA 对,QA 对直接对接前面说的预设回答逻辑。语料可以配权限,多个团队复用同一套知识库和机器人配置时靠这个隔离。

这一段做完,接入、清洗、可选的格式化改写、总结、切分、索引、同步、过期提示串成了一个知识生命周期,知识侧和回答侧解耦了。准确率的提升,我个人的判断是有一大半来自这一侧。
点赞点踩之后发生什么
RAG 上线后一定会错,问题是错了之后怎么办。回答旁边放一个点赞点踩按钮不难,难的是点了之后发生什么。
我们把反馈接到后台,每一条错答都由人来归类:知识缺失,改写把意思改跑了,召回没命中,排序把对的排到后面,上下文对的但生成偏了,或者证据不足却没有拒答。归到哪一类,就决定了修什么:改文档、改预设答案、改改写规则、改检索、改 Prompt,或者改拒答阈值。每一类都有明确的 Owner 去跟。
原则是:反馈只做运营输入,模型的输出和用户的反馈都不直接写回事实库。点赞点踩看起来是数据,但愿意点的人不是全体用户,有选择偏差;模型答对了的内容也不能反过来当知识。事实库只从原始文档和人工维护的 QA 对里来。
运营上,每周看使用数据,跟反馈,找用户一对一聊。用户的问题我们拆成三种:不会用、不敢信、答错了。不会用靠开场白和示例问题引导;不敢信靠引用和评级;答错了才走上面那条归因修复的链。混在一起看,会以为都是模型的问题,拆开看才知道大部分不是。
运营的分工也要提一下。反馈归类、改文档、补 QA 对、调预设答案这些动作,后台都开放给了各场景的 Owner,安全侧、HR 侧各自看自己机器人的错答,自己修。几十个场景的知识和业务规则,平台团队管不过来,Owner 也比我们更清楚哪条答案该是什么。我们这边看的是跨场景积累下来的反馈和数据:哪类错答在多个场景反复出现,是改写规则没覆盖到,还是某种切分方式对某类文档普遍不合适,再回到底层能力上改。场景里的问题由场景 Owner 修,平台改的是跨场景反复出现的底层问题。
这套闭环有个明显的缺口:修完一个 BadCase 之后,没有回归集能告诉我们有没有打坏别的场景。当时更多靠人工归类和阶段观察,很难说清一段时间的提升到底来自改写还是 Rerank。
怎么知道自己变好了
评估这件事我们做得晚,也做得不够好,但拆法是清楚的。
测试集的来源有两种:从用户历史提问里挑,请领域同学标出标准答案和对应的语料块;或者用大模型基于语料生成 QA 对,人工过一遍。每条样本至少包含问题、期望答案和相关的知识块。
评估分两侧看。检索侧看 Top-K 里有没有召回到正确的知识块、Top-K 的准确率、正确块排在什么位置。生成侧看事实一致性(回答是否依据召回的内容,有没有编)和答案相关性(是否在回答用户问的那个问题,和标准答案有多接近)。两侧分开看,因为修的动作不一样:检索侧的问题去动改写、召回、排序;生成侧的问题才动 Prompt 和模型。
我们做过阶段性的抽样评测,其中一轮在 1200 条样本的窗口上,解决率大约 84%。
没做好的是版本化。评测集没有跟知识版本一起编号,也没有做到每次改切分、改写、召回、Rerank、Prompt 或模型都跑同一套回归。
平台化:把重复的部分抽出来
问答链路稳定下来之后,别的团队开始来接,安全侧和 HR 行政侧先后配了自己的机器人。如果各团队各自接模型,知识导入、Prompt、权限、渠道每家都要建一套。到这时我们才开始做平台化的抽象:模型、知识库、Prompt、工具、卡片、工作流、渠道,抽成公共能力。机器人可以配名称、描述、权限、模型、Prompt、温度、历史对话轮数、开场白、引导问题。入口从独立页面扩到前端 SDK 悬浮窗、iframe 嵌入、钉钉群和 API,业务可以让人来问,也可以让系统来触发。
配置边界是个反复权衡的东西。Prompt 开放度太高,业务能快速试验,也可能把平台的安全约束覆盖掉;历史对话轮数开太大,token 和无关上下文一起涨,开太小又丢指代。最后的分工是:平台管「模型怎么回答」,接入方管「何时触发、带什么业务上下文、怎么展示」,我们提供基础模板配置,对于不了解的同学可直接使用我们模板进行配置微调,需要高级配置的同学也可以自定义所有配置,实现更加定制化的配置能力,在下游系统仍做最终的业务鉴权。业务规则、SOP 和验收都留在场景 Owner 那里,平台不承担复杂业务逻辑和保证正确性。
顺序上,先用最小配置验证几个团队能不能共用一套入口,确认共性之后再统一其余能力。反过来先建平台再找用户,很容易做出一堆没人用的配置项。
推广拉不动之后
有能力不等于有人用。我们做过全员推送、内网宣传、会议室投屏,每次都能拉出一个使用峰值,然后回落。用户回到自己的系统里找不到入口,或者不知道该问什么,留存就上不去。
后来把动作拆成几条线。入口上和高频团队合作,把机器人放进对方已经在用的阵地;场景上放弃自助开通碰运气,逐个找产品、技术、测试、数分的同学聊,先确认有没有稳定频次、可验证的结果和长期 Owner,再决定接不接。
周活从最初的不足 100 人做到最高峰 1200+。下图是 2024 年 1 月到 9 月的周活曲线:上半年缓慢抬升,年中有一次明显冲高,之后回落并稳定在更高的水位。峰值和推送、活动对得上;真正留下来的,是入口嵌进业务阵地之后那一段。

但通用问答的增长在某个阶段见了顶:用户不会提问,问题里上下文不全,外部 AI 工具也在分流。这时候我有了一个后来影响平台方向的判断:推广只能拉短期峰值,通用问答见顶之后,重心应该转向系统触发的任务。系统触发的好处是上下文由系统拼好,不用指望用户每次把背景说全;结果可以用处置结果来验收,不用猜用户满意不满意。代价是每个场景都要规则、工具、验收和 Owner,接入成本比通用 Chat 高得多。
渗透也不只靠一个入口堆人数。下图是 2024 年 3 月和 9 月各部门月渗透率对比:多数部门在抬,部分事业部和中心涨幅超过一倍;也有部门基本持平或回落。这和前面说的运营分工是同一件事:场景能不能留下来,最终看各业务自己有没有 Owner 在持续修。

后续的成熟期,我们和算法团队共建了知识库迁移、工作流编排、工具接入、问答自定义卡片和提示词模板,产品形态从问答机器人变成了「数字员工入口 + 数字员工平台」。
做成了什么,还欠着什么
这段路走下来,海螺从一条「导入、召回、生成」的问答链路,变成了内部能接多团队、多入口的 AI 应用平台。问答准确率从早期约 40% 做到 85% 以上,累计接了 50 多个问答场景和 10 多个提效场景,多入口聚合的日调用量到过 50 万以上,2023 和 2024 连续两年拿了公司优秀项目奖。下图是 2023 年度优秀项目的奖杯,2024 年 1 月颁奖,右边那座写的是「海螺智能分析平台」。

模型换代确实带来了提升,但准确率能抬上来,靠的是知识治理和 BadCase 闭环:切分、同步、过期、权限、拒答、引用,再加上每条错答归到具体环节、交给具体的人。点赞点踩只是入口,事实库始终只从原始文档和人工 QA 来。
欠着的东西前面都提到了,放在一起看就是同一件事:没有版本。知识没有版本,评测集没有版本,改切分、换模型、修文档之后分不清哪一步起了作用;每日同步让权限撤回最长滞后一天。如果今天重做,我会在跑通最小闭环之后先补这一层,再谈换更大的模型。
很多做法今天已经是 RAG 的常规配置。我们只是在 2023-2024 年那个时间点,业界都在探索和尝试,我们也是根据实际场景需求逐步迭代到了这个阶段。文中的判断都带着当时的条件,不一定适合别的团队,有不对的地方欢迎指出。