做了将近半年的基于大模型的智能问答系统,上线之后我最大的感受是:**决定这套系统好不好用的,往往不是大模型本身,而是知识库和检索链路。**模型答得再聪明,检索不到对的资料,它也只能一本正经地编。我们做的不是那种聊天玩具,而是一个企业内部的知识问答助手:员工上传制度文档、操作手册、项目总结,然后像跟人聊天一样提问,系统给出答案并标注出处。这套系统从需求梳理到上线迭代,我踩了很多坑,也总结出一些真正能落地的经验。这篇文章想把完整的实现过程、选型逻辑和实战心得写下来。如果你正准备做一个RAG形态的问答系统,或者已经在做了但效果不太理想,应该能从这里找到一些可以复用的思路。1. 为什么从零做一套企业级问答系统,而不是直接用现成产品1.1 最初的需求清单和隐含约束需求方最开始提得很简单:"用大模型做一个问答系统,让我们查资料不用翻文件夹。"但把这句话翻译成工程需求之后,就没那么简单了。我们实际拆出来的需求是这样的:文档问答:主要覆盖制度文件、技术手册、项目交接文档、会议纪要等非结构化资料;多轮对话:用户能接着上一轮问题追问,比如先问"报销周期多久",再问"那发票呢",系统要能理解"那"指的是上一轮报销流程里的发票要求;引用溯源:每个答复必须标出依据来自哪篇文档,方便用户点开原文核对,这也是内部信任感的关键;权限收敛:不同角色看到的知识范围不同,项目组资料不能泄露给无关部门,这个需求直接决定了检索阶段就要做权限过滤,而不是生成之后再做;数据安全约束:内部资料不允许直接传到外部服务,系统必须在可控环境内部署,或至少确保传输和存储过程可控。这里最容易被忽略的是权限约束。"权限"听起来像个管理功能,放在最后做也无所谓,但实际它影响了整个架构:如果你打算把问题和文档直接拼成提示词发给外部大模型接口,那就必须确认请求链路里是否有可能夹带越权内容。我们最终把权限过滤做在检索阶段,靠知识库元数据把用户和文档隔离,这样模型拿到的材料本身就是该用户有权限看的那一坨。1.2 现成产品差在哪我们也认真评估过"直接买"这个选项。通用型问答产品的问题在于它们不了解企业内部的文档体系和人员权限,而且很多产品只支持把文档往里一丢然后全网搜,缺乏精细的引用管理。商用RAG平台虽然省事,但当我们提出"文档要按部门隔离""引用格式要按我们内部模板输出""检索日志要留痕"这些要求时,平台的定制能力往往跟不上,或者费用直接翻了几倍。自己搭一套系统,核心成本不在写代码,而在于把文档处理、检索、模型编排、权限、评测这几条链路串起来并持续调优。但换来的是完全可控:想改提示词模板就改,想加一个重排阶段就加,出了badcase能顺着日志一路查到是切分问题还是检索问题。对一个要长期维护的内部系统来说,这个可控性值得投入。2. 架构定调:数据管道比模型更值得先设计2.1 四条核心链路,谁都绕不开很多关于大模型应用的分享会把重点放在"选哪个模型、怎么调提示词"上,但真正从零做一套可用系统的时候,我建议先把下面四条链路在纸上画清楚:离线知识库构建链路:文档上传 → 格式解析 → 内容清洗 → 结构切分 → 向量化 → 写入索引。这条链路是离线的,文档更新时触发,可以慢慢跑,但每一步的错误都会放大到线上。在线问答链路:用户问题 → 权限获取 → 意图识别(是否需要改写/是否需要检索) → 检索召回 → 融合排序 → 可选重排 → 组装上下文 → 大模型生成 → 引用标注 → 流式返回。这条链路是用户实际感受到的路径,每一跳的延迟和失败都会直接影响体验。管理运维链路:知识库的版本管理、文档上下线、权限配置、模型服务健康检查、日志追踪。这块经常被忽略,但实际运维中80%的问题排查都靠它。评测迭代链路:评测集维护 → 批量跑分 → badcase聚类 → 定位根因 → 修改策略 → 回归验证。没有这条链路,系统上线就是"感觉还行",改不改全靠运气。我把这四条链路列出来之后,团队里所有人都达成了共识:模型API只是在线链路里的一个组件,前面还有一大段检索逻辑决定了模型能看到什么材料。2.2 分层设计:小模型管理解,大模型管生成我们的架构明确做了三层分离:应用层:负责权限、会话、知识库管理、日志,是纯业务代码;检索层:负责QA对问题的理解、召回、排序、融合,这里用了开源向量索引和关键词索引,再加一个轻量级重排模型;生成层:负责最终答案的生成,这一层交给大模型API,并且做了主备切换和降级。一个小巧但重要的设计原则是:**能用规则或小模型解决的问题,绝不让大模型上。**比如是否需要对当前问题进行改写,我们先用规则判断当前问题是不是追问;比如判断检索材料里有没有包含答案,这本来可以靠大模型判断,但我们用召回分数和关键词覆盖做一个快速前置判断,只有拿不准的时候才让大模型多看一遍。这样做的原因是成本和延迟:企业内部问答的用户耐心很有限,如果每一次对话都走完整的重模型链,首字返回时间会慢到让人失去兴趣。3. 文档切分与向量化:问答效果的第一道分水岭3.1 切分策略,从固定窗口到结构切分我们第一批文档入库时,采用的是最省事的固定大小切块:每块500字,重叠100字。当时想着反正有向量检索兜底,怎么切都行。结果上线第一天就被用户问了一个"制度里规定的报销流程有几步",系统答得驴唇不对马嘴。我排查发现,文档里"第一步、第二步"这样的流程内容刚好被切块拦腰截断,第一块只给了"第一步、第二步"的标题,第二块才是内容,而向量检索把这两块都召回了,但模型拼上下文的时候顺序错乱,自然答不对。这次事故之后,我们把切分策略改成了结构感知切分,并且在线上对不同类型的文档配置不同参数:策略做法适用场景我们踩过的坑固定大小切分按字数切,带重叠窗口连续文本、对话记录结构信息被切断,流程类内容错乱结构切分按Markdown/Word标题层级划分,表格单独成块制度文档、技术手册同一章节过长时仍需要二次切分父块-子块检索命中子块,生成时携带父块上下文合同条款、规范条文上下文超长,需要控制携带范围结构切分的核心是让切出来的块尽量语义完整。比如一篇技术手册按"章节→小节→段落"的层级切,每个块自带标题路径元数据。这样检索到某一小节时,模型能看到它属于哪个章节,上下文信息充足得多。至于那些没有明显结构的长文,再退回到固定窗口切,但窗口大小按文档类型调整:操作流程类用较小的窗口(400字左右),背景介绍类用较大的窗口(800字左右)。3.2 Embedding模型选型关注的几个维度Embedding模型直接把文本映射成向量,它的质量决定了向量召回的天花板。我们不追最新榜单,只按实际场景评估。几个关键参考维度:中文语义效果:看它在行业术语上的表现,而不是通用句子相似度;最大输入长度:很多模型默认只支持512个token,如果你切出来的块超过这个长度就会被截断,信息直接丢尾巴;向量维度:维度越高理论上表达力越强,但索引内存和检索耗时也上升,内部系统数据量在几十万级时,中低维度完全够用;部署成本:Embedding模型通常不大,可以本地部署,不依赖外部API,这对内部数据安全很重要。我们当时把几个候选模型在内部文档测试集上各跑了一遍,做法很简单:挑50个真实问答对,分别看每个模型召回的块里有没有正确答案所在的原文块。结论是公开榜单上排名最高的模型在我们领域语料上未必最好,反而是对长文本支持好、对中文专有名词理解稳的模型更实用。3.3 容易被忽略的元数据设计文档入库时,我们会在索引里附上一批元数据字段:文档标题、章节路径、更新时间、文档归属部门、权限标签、文档类型。这些字段在检索阶段是过滤利器。举个例子:公司里同一份《差旅管理制度》每年更新一次,库里同时存在2023版和2024版。如果用户问的是"现在出差住宿标准是多少",检索阶段除了看语义相似度,还要按"更新时间"排序,优先让新版本的块排在前面,否则模型很可能引用了过期标准。再比如用户属于研发部门,那检索时就先用权限标签过滤掉财务部门的文档块,这样模型永远看不到不该看的内容。这里还有一个细节:**检索过滤必须在向量召回之前或和向量召回同时生效。**如果先全局召回再在代码里做权限过滤,很可能出现"已召回但被过滤掉"导致可用块不够的情况;更严重的是,如果日志记录了完整的候选块,那日志本身就泄漏了越权内容。我们把权限条件拼进了检索查询,让索引层面直接只返回有权限的块,干净利落。4. 检索链路:从纯向量到混合检索加重排4.1 纯向量检索翻车的真实场景第一版检索我们只用了向量相似度,也就是把用户问题和所有文档块都转成向量,算余弦相似度取TopK。内部文档测试时发现几个典型的翻车场景:精确数值条件:"入职满一年可以休几天年假",文档里写的是"累计工作满12个月",语义相近但向量召回也许能找到;可一旦问题变成"预算上限是5000还是3000",向量模型对数值的敏感度就很低,容易召回一堆提到"预算"但数值不对的块;缩写与同义词:研发文档里到处是"CI",业务人员问的是"持续集成",向量模型不一定能把缩写和全称关联起来;表格语义丢失:表格被切成块以后,向量检索对表格里的行列关系理解很差,经常答非所问。这些问题的共同点在于:向量检索擅长"语义相似",但不擅长"精确匹配"和"关系理解"。企业内部问答里,恰恰有大量精确匹配和关系型问题。4.2 关键词检索与向量检索的融合我们增加的第一个补丁是引入关键词检索。用经典的BM25算法,基于倒排索引做词频统计。它最大的优势是精确匹配能力强:"5000"就是"5000","CI"能先按字面命中文档里的"CI(持续集成)"再通过文本里的括号注释兜住缩写问题。混合检索的合并逻辑我们没有用复杂的机器学习排序,而是用了最朴素的RRF(Reciprocal Rank Fusion):每个检索通道各自给出TopK排名,然后按公式计算每个块的综合得分:score = Σ (1 / (k + rank_i))其中k是平滑常数,通常取60。这个公式的思想很简单:一个块如果在多个检索通道里都排在靠前位置,那它的综合分就高。我们试过加权分数融合和深度学习排序,最终还是RRF最稳、最好解释、也最容易调参。融合后的排序仍然不能完全信任,因为向量检索和关键词检索各自都有"看起来相关但实际不相关"的噪音。所以我们把召回数量调大(向量召回50个,关键词召回50个,融合后保留30个候选),交给下一步重排。4.3 重排模型值得多花的几十毫秒重排阶段我们用一个轻量级模型,对每个候选块重新计算与用户问题的相关度。它和Embedding模型最大区别是输入能同时看到问题和文档块,做的不是"压缩成向量再比相似度",而是类似文本对分类的任务,相关性判断更准确。虽然重排给在线链路增加了大约50到80毫秒的延迟,但这段时间花得非常值。重排后候选块的相关性明显更集中,大模型拿到的上下文噪音少了,幻觉率和答非所问的问题都下降了一个量级。如果检索召回Top1本身就是对的,重排只是锦上添花;但一旦Top1是错的,重排能把对的块排上来,这个收益立竿见影。5. 大模型接入的工程化细节:提示词、流式输出与降级策略5.1 把提示词当成可维护的模板工程很多文章把提示词写得像玄学,但实际工程里提示词就是一个需要版本管理的模板。我们最终的提示词结构是五段式:你是企业知识助手,严格基于提供的材料回答,不要编造事实。 【检索材料】 [相关材料1(编号[1])] [相关材料2(编号[2])] [对话历史] [最近几轮问答] 【用户问题】 [当前问题] 【输出要求】 1. 先用一句话给出结论,再给出依据; 2. 引用材料时使用[1][2]等编号标注; 3. 如果材料无法回答问题,明确回答“根据现有资料无法确认”,不要猜测; 4. 答案控制在300字以内。这里最关键的是第三点和第二点。企业内部问答最怕模型为了"显得有用"而编造,所以我们明确要求模型在材料不足时承认不知道。为了保证引用编号对得上,我们在代码里给每块材料编好号再拼进提示词,模型只需要在输出里引用编号,前端再做高亮,就完成了引用溯源。另外,提示词版本要和代码版本一样管理。改了一句提示词可能让全站效果变化,如果不可回滚,线上出问题会很被动。5.2 流式输出不是可选项问答系统最影响体验的指标是首字返回时间。如果用户提问后盯着转圈三四秒才看到第一个字,再准的答案也会被打低分。接入流式输出,把模型生成的token逐片返回,用户在1到2秒内就能看到文字开始滚动,体感会好非常多。流式输出用SSE协议做起来并不复杂,但有几个细节要处理:超时控制:大模型API偶发卡顿,我们设置了连接超时和首字超时两个阈值,超过就自动切换备用模型或降级;断线处理:用户网络闪断或服务端重启,前端要能自动重连并继续接收剩余内容,否则用户体验是"答到一半没了";后处理:流式输出过程中无法后置处理整个答案,所以引用标注的渲染要特殊处理,我们在流式文本里约定用特殊标记包裹引用编号,前端解析后显示为高亮角标。还有一个细节:**不要流式输出思维链。**内部问答只需要最终答案和引用,让用户看到大模型"推理过程"反而会影响信任感,而且思维链文本还会额外消耗token。5.3 超时、限流和降级的完整预案大模型接口再稳定也扛不住故障。我们设计了一套多级降级方案:主模型超时(等待回答超过设定阈值)→ 自动切换备用模型重试一次;备用模型也失败→ 返回引导话术"当前服务繁忙,请稍后重试",同时把问题记录到失败队列,方便事后补答;限流保护:按用户身份分配并发额度,普通用户每秒不超过特定请求数,防止个别用户刷屏拖垮服务;高频问题缓存:对于同一个权限组内的高频问题,把答案缓存起来,按文档更新事件自动失效。这个措施直接把大模型API费用砍掉了将近三分之一。缓存策略有个前提:答案要与用户权限绑定。两个不同权限的用户看到的知识范围不同,缓存key必须带上权限标识,否则就会出现越权缓存泄露。6. 多轮对话的上下文管理:别让对话历史拖垮效果6.1 上下文放多少?放哪几轮?第一版多轮对话,我们把最近十轮历史全部拼进提示词,结果模型反而开始"记性好、答得差":上下文太长把检索材料挤得只剩一小段,模型注意力分散,还容易把历史对话里的错误信息当成事实。后来我们改成了系统提示词 + 最近3轮对话 + 当前问题 + 检索材料的结构,结构顺序也有讲究:检索材料放最前面靠近系统指令,对话历史放中间,当前问题放最后。对话历史也不是每轮都要留。如果用户问了一个与历史完全无关的新问题,旧历史反而会成为误导。我们做了一个轻量的话题切换检测:计算当前问题与上一轮问题的语义相似度,如果低于阈值就把历史清空,当作全新会话处理。这个逻辑用一个小模型就够,但收益非常明显——很多"答非所问"其实不是模型问题,而是历史残留干扰。6.2 追问的指代消解与问题改写多轮对话最典型的场景是追问:"那它的预算是多少?"如果直接把这句话拿去检索,检索系统根本不知道"它"指什么。我们加了一层问题改写:当判断当前问题是追问时,先结合最近几轮历史把问题补全成完整问句,再用改写后的问句去检索。改写不一定要用大模型,我们用了一个轻量提示词让一个小模型来做,模板很简单:根据对话历史,把用户的追问改写成独立的完整问句,不要添加历史里不存在的信息。注意"不要添加历史里不存在的信息"是关键约束,否则改写很容易自己脑补出用户没提过的条件,把检索方向带偏。改写之后还有一个细节:**改写结果要同时用于检索和生成。**生成时我们不会拿长句原文提示模型,而是让模型看到"改写后的完整问题+原始追问",既保证检索精准,又保证回答符合用户原本的口语化表达。7. 上线前的评测与迭代:没有测试集别上线7.1 评测集怎么搭才靠谱我们刚开始犯的错是"自己拍脑袋写测试题",测出来效果很好,上线后被真实用户问得破绽百出。后来改成从业务侧收集问题样本:翻历史工单、看运营群里被问得最多的问题、从常见文档FAQ里提取,再让业务方补充它们最关心的场景。评测集规模不大,第一批只有80条左右,但覆盖了五种类型:类型示例问题模式通过标准事实查询型"公司的年假政策是什么"答案与原文一致并正确引用操作步骤型"怎么申请会议室"步骤完整、顺序正确对比分析型"A方案和B方案有什么区别"能分别说明两者要点数值条件型"超过5000元的采购需要什么审批"数值条件完全精确开放建议型"如何提升项目复盘质量"有依据、不编造每条评测样本都标注了参考答案和期望引用的源文档。人工标注80条很费时间,但这是评测的根基,后面自动化打分再快,也弥补不了测试集本身是"歪的"。7.2 自动化评测与badcase的归因路径评测脚本每天跑一遍,用一个大模型当"裁判模型"给系统回答打分,维度包括:相关度、正确性、引用一致性、是否拒绝回答。裁判模型本身也会偶然判错,所以我们只把自动化打分作为初筛,分数异常的样本再人工复核。真正让系统快速变好的,是badcase的归因流程。拿到一个错误回答,我们按下面的路径排查:查召回:把用户问题重复在离线检索流程里跑一遍,看正确文本所在的文档块是否在Top30候选中;查排序:如果正确的块在候选中但排得靠后,说明是排序问题,优先调重排或融合逻辑;查生成:如果正确的块排得靠前但答案还是错了,说明是模型生成问题,改提示词或补充示例;查切分:如果正确的块根本不存在或内容残缺,说明是离线切分问题,回炉改切分参数。这个归因路径的精髓是把错误责任隔离到具体环节,而不是笼统地说"系统效果不好"。我们每周都会把新badcase归类,再决定下周改哪一块:向量模型要不要换?切分参数要不要调?提示词要不要补充说明?按照这个闭环跑了两个月,评测集准确率从70%出头提升到了接近92%。8. 部署运行与成本控制的一笔账8.1 部署形态:数据安全与效果之间的平衡考虑到内部数据的敏感性,我们把部署形态定为:私有化部署检索链路和小模型,大模型API只接收脱敏后的必要上下文。具体来说,文档向量化和权限过滤全部在内部环境完成,只有最终拼好的提示词模板里的"检索材料、对话历史、当前问题"这些文本会传给大模型API。对话内容可能会被API服务用于改进模型——这是一个需要明示给用户的点,我们在系统使用说明里写清楚了这个边界。如果你所在团队的数据安全要求更高,也可以选择内部部署开源大模型,但推理资源成本会明显上升。我们的取舍是:内部部署小模型负责检索和改写,生成用商业化API保证效果和稳定性,中间再加一层服务端脱敏。这个组合在成本、效果、安全三方面取得了一个平衡。8.2 成本估算:别等月底账单吓一跳大模型API按token计费,所以估算成本不能只看"每问一次多少钱"。我们的成本模型是这样的:输入token= 系统提示词 + 检索材料拼接 + 3轮对话历史 + 当前问题,平均一次对话大约消耗多少token,根据统计取值;输出token= 大模型平均回答长度;月成本= 月问答次数 × (输入token数 × 输入单价 + 输出token数 × 输出单价) × 冗余系数。举个具体的简化案例。假设每次问答平均输入2000个token,输出500个token,输入单价0.001元/千token,输出单价0.002元/千token,那单次成本只有大约3厘钱。听起来很便宜,但如果是每天1万次问答,月成本就是10000 × 30 × 0.003 = 900元。再加上缓存命中率的折扣,总算下来不至于离谱。可一旦检索材料写得贪多,每次塞进一万个token,成本就会翻几倍,所以提示词里"检索材料数量上限"也是个成本控制参数。我把各量级成本做个表格,方便你建立量感:月问答量约输入token/次约输出token/次月成本量级(按上述单价推算)1千次2000500约9元1万次2000500约90元10万次2000500约900元10万次(材料扩到1万token)10000500约3000元降低成本的手段,除了高频答案缓存之外,还有三个:控制检索材料长度(我们限制最多拼接5个块,每块不超过800字);对话历史只留最近3轮;对超长文档先做一次粗筛再精读,而不是把所有候选块都堆给大模型。8.3 日常运维要盯的指标上线之后,每天要关心的核心指标包括:检索召回率、重排后Top1命中率、首字返回时间、回答失败率、引用被点击率、用户"不满意"反馈率。前几个是技术指标,后两个是体验指标。我们会在日志里给每一次问答生成一个全链路ID,从用户提问到最终展示,每一步的时间、候选块、改写结果、模型输出全都能串起来,debug的时候效率高很多。还有一条血泪教训:**检索日志一定要脱敏。**因为日志会记录完整的用户问题和候选文档块,如果不做权限过滤和访问控制,日志系统本身就会变成一个越权信息泄露点。我们最后把日志按权限分级,只有管理员能查看完整链路,普通运营只能看统计数字。这套系统在内部跑了大半年,说实话,它没有一开始想象中那么"智能",甚至用户夸它的时候,我心里清楚大部分功劳要记在"把文档切得好、把检索做得准"这两件事上。大模型只是最后那个把材料读出来、组织成通顺回答的朗读员。如果让我给后来者一个最实在的建议,那就是:先别急着换更大更强的模型,沉下心把知识库的切分、元数据、检索召回、评测集这四块打到80分,然后再回头看大模型,你会发现它忽然变聪明了。我踩过的那些坑,你大概率也会踩一遍,希望这篇分享能让你少绕几段路。
做了将近半年的基于大模型的智能问答系统,上线之后我最大的感受是:**决定这套系统好不好用的,往往不是大模型本身,而是知识库和检索链路。**模型答得再聪明,检索不到对的资料,它也只能一本正经地编。我们做的不是那种聊天玩具,而是一个企业内部的知识问答助手:员工上传制度文档、操作手册、项目总结,然后像跟人聊天一样提问,系统给出答案并标注出处。这套系统从需求梳理到上线迭代,我踩了很多坑,也总结出一些真正能落地的经验。这篇文章想把完整的实现过程、选型逻辑和实战心得写下来。如果你正准备做一个RAG形态的问答系统,或者已经在做了但效果不太理想,应该能从这里找到一些可以复用的思路。1. 为什么从零做一套企业级问答系统,而不是直接用现成产品1.1 最初的需求清单和隐含约束需求方最开始提得很简单:"用大模型做一个问答系统,让我们查资料不用翻文件夹。"但把这句话翻译成工程需求之后,就没那么简单了。我们实际拆出来的需求是这样的:文档问答:主要覆盖制度文件、技术手册、项目交接文档、会议纪要等非结构化资料;多轮对话:用户能接着上一轮问题追问,比如先问"报销周期多久",再问"那发票呢",系统要能理解"那"指的是上一轮报销流程里的发票要求;引用溯源:每个答复必须标出依据来自哪篇文档,方便用户点开原文核对,这也是内部信任感的关键;权限收敛:不同角色看到的知识范围不同,项目组资料不能泄露给无关部门,这个需求直接决定了检索阶段就要做权限过滤,而不是生成之后再做;数据安全约束:内部资料不允许直接传到外部服务,系统必须在可控环境内部署,或至少确保传输和存储过程可控。这里最容易被忽略的是权限约束。"权限"听起来像个管理功能,放在最后做也无所谓,但实际它影响了整个架构:如果你打算把问题和文档直接拼成提示词发给外部大模型接口,那就必须确认请求链路里是否有可能夹带越权内容。我们最终把权限过滤做在检索阶段,靠知识库元数据把用户和文档隔离,这样模型拿到的材料本身就是该用户有权限看的那一坨。1.2 现成产品差在哪我们也认真评估过"直接买"这个选项。通用型问答产品的问题在于它们不了解企业内部的文档体系和人员权限,而且很多产品只支持把文档往里一丢然后全网搜,缺乏精细的引用管理。商用RAG平台虽然省事,但当我们提出"文档要按部门隔离""引用格式要按我们内部模板输出""检索日志要留痕"这些要求时,平台的定制能力往往跟不上,或者费用直接翻了几倍。自己搭一套系统,核心成本不在写代码,而在于把文档处理、检索、模型编排、权限、评测这几条链路串起来并持续调优。但换来的是完全可控:想改提示词模板就改,想加一个重排阶段就加,出了badcase能顺着日志一路查到是切分问题还是检索问题。对一个要长期维护的内部系统来说,这个可控性值得投入。2. 架构定调:数据管道比模型更值得先设计2.1 四条核心链路,谁都绕不开很多关于大模型应用的分享会把重点放在"选哪个模型、怎么调提示词"上,但真正从零做一套可用系统的时候,我建议先把下面四条链路在纸上画清楚:离线知识库构建链路:文档上传 → 格式解析 → 内容清洗 → 结构切分 → 向量化 → 写入索引。这条链路是离线的,文档更新时触发,可以慢慢跑,但每一步的错误都会放大到线上。在线问答链路:用户问题 → 权限获取 → 意图识别(是否需要改写/是否需要检索) → 检索召回 → 融合排序 → 可选重排 → 组装上下文 → 大模型生成 → 引用标注 → 流式返回。这条链路是用户实际感受到的路径,每一跳的延迟和失败都会直接影响体验。管理运维链路:知识库的版本管理、文档上下线、权限配置、模型服务健康检查、日志追踪。这块经常被忽略,但实际运维中80%的问题排查都靠它。评测迭代链路:评测集维护 → 批量跑分 → badcase聚类 → 定位根因 → 修改策略 → 回归验证。没有这条链路,系统上线就是"感觉还行",改不改全靠运气。我把这四条链路列出来之后,团队里所有人都达成了共识:模型API只是在线链路里的一个组件,前面还有一大段检索逻辑决定了模型能看到什么材料。2.2 分层设计:小模型管理解,大模型管生成我们的架构明确做了三层分离:应用层:负责权限、会话、知识库管理、日志,是纯业务代码;检索层:负责QA对问题的理解、召回、排序、融合,这里用了开源向量索引和关键词索引,再加一个轻量级重排模型;生成层:负责最终答案的生成,这一层交给大模型API,并且做了主备切换和降级。一个小巧但重要的设计原则是:**能用规则或小模型解决的问题,绝不让大模型上。**比如是否需要对当前问题进行改写,我们先用规则判断当前问题是不是追问;比如判断检索材料里有没有包含答案,这本来可以靠大模型判断,但我们用召回分数和关键词覆盖做一个快速前置判断,只有拿不准的时候才让大模型多看一遍。这样做的原因是成本和延迟:企业内部问答的用户耐心很有限,如果每一次对话都走完整的重模型链,首字返回时间会慢到让人失去兴趣。3. 文档切分与向量化:问答效果的第一道分水岭3.1 切分策略,从固定窗口到结构切分我们第一批文档入库时,采用的是最省事的固定大小切块:每块500字,重叠100字。当时想着反正有向量检索兜底,怎么切都行。结果上线第一天就被用户问了一个"制度里规定的报销流程有几步",系统答得驴唇不对马嘴。我排查发现,文档里"第一步、第二步"这样的流程内容刚好被切块拦腰截断,第一块只给了"第一步、第二步"的标题,第二块才是内容,而向量检索把这两块都召回了,但模型拼上下文的时候顺序错乱,自然答不对。这次事故之后,我们把切分策略改成了结构感知切分,并且在线上对不同类型的文档配置不同参数:策略做法适用场景我们踩过的坑固定大小切分按字数切,带重叠窗口连续文本、对话记录结构信息被切断,流程类内容错乱结构切分按Markdown/Word标题层级划分,表格单独成块制度文档、技术手册同一章节过长时仍需要二次切分父块-子块检索命中子块,生成时携带父块上下文合同条款、规范条文上下文超长,需要控制携带范围结构切分的核心是让切出来的块尽量语义完整。比如一篇技术手册按"章节→小节→段落"的层级切,每个块自带标题路径元数据。这样检索到某一小节时,模型能看到它属于哪个章节,上下文信息充足得多。至于那些没有明显结构的长文,再退回到固定窗口切,但窗口大小按文档类型调整:操作流程类用较小的窗口(400字左右),背景介绍类用较大的窗口(800字左右)。3.2 Embedding模型选型关注的几个维度Embedding模型直接把文本映射成向量,它的质量决定了向量召回的天花板。我们不追最新榜单,只按实际场景评估。几个关键参考维度:中文语义效果:看它在行业术语上的表现,而不是通用句子相似度;最大输入长度:很多模型默认只支持512个token,如果你切出来的块超过这个长度就会被截断,信息直接丢尾巴;向量维度:维度越高理论上表达力越强,但索引内存和检索耗时也上升,内部系统数据量在几十万级时,中低维度完全够用;部署成本:Embedding模型通常不大,可以本地部署,不依赖外部API,这对内部数据安全很重要。我们当时把几个候选模型在内部文档测试集上各跑了一遍,做法很简单:挑50个真实问答对,分别看每个模型召回的块里有没有正确答案所在的原文块。结论是公开榜单上排名最高的模型在我们领域语料上未必最好,反而是对长文本支持好、对中文专有名词理解稳的模型更实用。3.3 容易被忽略的元数据设计文档入库时,我们会在索引里附上一批元数据字段:文档标题、章节路径、更新时间、文档归属部门、权限标签、文档类型。这些字段在检索阶段是过滤利器。举个例子:公司里同一份《差旅管理制度》每年更新一次,库里同时存在2023版和2024版。如果用户问的是"现在出差住宿标准是多少",检索阶段除了看语义相似度,还要按"更新时间"排序,优先让新版本的块排在前面,否则模型很可能引用了过期标准。再比如用户属于研发部门,那检索时就先用权限标签过滤掉财务部门的文档块,这样模型永远看不到不该看的内容。这里还有一个细节:**检索过滤必须在向量召回之前或和向量召回同时生效。**如果先全局召回再在代码里做权限过滤,很可能出现"已召回但被过滤掉"导致可用块不够的情况;更严重的是,如果日志记录了完整的候选块,那日志本身就泄漏了越权内容。我们把权限条件拼进了检索查询,让索引层面直接只返回有权限的块,干净利落。4. 检索链路:从纯向量到混合检索加重排4.1 纯向量检索翻车的真实场景第一版检索我们只用了向量相似度,也就是把用户问题和所有文档块都转成向量,算余弦相似度取TopK。内部文档测试时发现几个典型的翻车场景:精确数值条件:"入职满一年可以休几天年假",文档里写的是"累计工作满12个月",语义相近但向量召回也许能找到;可一旦问题变成"预算上限是5000还是3000",向量模型对数值的敏感度就很低,容易召回一堆提到"预算"但数值不对的块;缩写与同义词:研发文档里到处是"CI",业务人员问的是"持续集成",向量模型不一定能把缩写和全称关联起来;表格语义丢失:表格被切成块以后,向量检索对表格里的行列关系理解很差,经常答非所问。这些问题的共同点在于:向量检索擅长"语义相似",但不擅长"精确匹配"和"关系理解"。企业内部问答里,恰恰有大量精确匹配和关系型问题。4.2 关键词检索与向量检索的融合我们增加的第一个补丁是引入关键词检索。用经典的BM25算法,基于倒排索引做词频统计。它最大的优势是精确匹配能力强:"5000"就是"5000","CI"能先按字面命中文档里的"CI(持续集成)"再通过文本里的括号注释兜住缩写问题。混合检索的合并逻辑我们没有用复杂的机器学习排序,而是用了最朴素的RRF(Reciprocal Rank Fusion):每个检索通道各自给出TopK排名,然后按公式计算每个块的综合得分:score = Σ (1 / (k + rank_i))其中k是平滑常数,通常取60。这个公式的思想很简单:一个块如果在多个检索通道里都排在靠前位置,那它的综合分就高。我们试过加权分数融合和深度学习排序,最终还是RRF最稳、最好解释、也最容易调参。融合后的排序仍然不能完全信任,因为向量检索和关键词检索各自都有"看起来相关但实际不相关"的噪音。所以我们把召回数量调大(向量召回50个,关键词召回50个,融合后保留30个候选),交给下一步重排。4.3 重排模型值得多花的几十毫秒重排阶段我们用一个轻量级模型,对每个候选块重新计算与用户问题的相关度。它和Embedding模型最大区别是输入能同时看到问题和文档块,做的不是"压缩成向量再比相似度",而是类似文本对分类的任务,相关性判断更准确。虽然重排给在线链路增加了大约50到80毫秒的延迟,但这段时间花得非常值。重排后候选块的相关性明显更集中,大模型拿到的上下文噪音少了,幻觉率和答非所问的问题都下降了一个量级。如果检索召回Top1本身就是对的,重排只是锦上添花;但一旦Top1是错的,重排能把对的块排上来,这个收益立竿见影。5. 大模型接入的工程化细节:提示词、流式输出与降级策略5.1 把提示词当成可维护的模板工程很多文章把提示词写得像玄学,但实际工程里提示词就是一个需要版本管理的模板。我们最终的提示词结构是五段式:你是企业知识助手,严格基于提供的材料回答,不要编造事实。 【检索材料】 [相关材料1(编号[1])] [相关材料2(编号[2])] [对话历史] [最近几轮问答] 【用户问题】 [当前问题] 【输出要求】 1. 先用一句话给出结论,再给出依据; 2. 引用材料时使用[1][2]等编号标注; 3. 如果材料无法回答问题,明确回答“根据现有资料无法确认”,不要猜测; 4. 答案控制在300字以内。这里最关键的是第三点和第二点。企业内部问答最怕模型为了"显得有用"而编造,所以我们明确要求模型在材料不足时承认不知道。为了保证引用编号对得上,我们在代码里给每块材料编好号再拼进提示词,模型只需要在输出里引用编号,前端再做高亮,就完成了引用溯源。另外,提示词版本要和代码版本一样管理。改了一句提示词可能让全站效果变化,如果不可回滚,线上出问题会很被动。5.2 流式输出不是可选项问答系统最影响体验的指标是首字返回时间。如果用户提问后盯着转圈三四秒才看到第一个字,再准的答案也会被打低分。接入流式输出,把模型生成的token逐片返回,用户在1到2秒内就能看到文字开始滚动,体感会好非常多。流式输出用SSE协议做起来并不复杂,但有几个细节要处理:超时控制:大模型API偶发卡顿,我们设置了连接超时和首字超时两个阈值,超过就自动切换备用模型或降级;断线处理:用户网络闪断或服务端重启,前端要能自动重连并继续接收剩余内容,否则用户体验是"答到一半没了";后处理:流式输出过程中无法后置处理整个答案,所以引用标注的渲染要特殊处理,我们在流式文本里约定用特殊标记包裹引用编号,前端解析后显示为高亮角标。还有一个细节:**不要流式输出思维链。**内部问答只需要最终答案和引用,让用户看到大模型"推理过程"反而会影响信任感,而且思维链文本还会额外消耗token。5.3 超时、限流和降级的完整预案大模型接口再稳定也扛不住故障。我们设计了一套多级降级方案:主模型超时(等待回答超过设定阈值)→ 自动切换备用模型重试一次;备用模型也失败→ 返回引导话术"当前服务繁忙,请稍后重试",同时把问题记录到失败队列,方便事后补答;限流保护:按用户身份分配并发额度,普通用户每秒不超过特定请求数,防止个别用户刷屏拖垮服务;高频问题缓存:对于同一个权限组内的高频问题,把答案缓存起来,按文档更新事件自动失效。这个措施直接把大模型API费用砍掉了将近三分之一。缓存策略有个前提:答案要与用户权限绑定。两个不同权限的用户看到的知识范围不同,缓存key必须带上权限标识,否则就会出现越权缓存泄露。6. 多轮对话的上下文管理:别让对话历史拖垮效果6.1 上下文放多少?放哪几轮?第一版多轮对话,我们把最近十轮历史全部拼进提示词,结果模型反而开始"记性好、答得差":上下文太长把检索材料挤得只剩一小段,模型注意力分散,还容易把历史对话里的错误信息当成事实。后来我们改成了系统提示词 + 最近3轮对话 + 当前问题 + 检索材料的结构,结构顺序也有讲究:检索材料放最前面靠近系统指令,对话历史放中间,当前问题放最后。对话历史也不是每轮都要留。如果用户问了一个与历史完全无关的新问题,旧历史反而会成为误导。我们做了一个轻量的话题切换检测:计算当前问题与上一轮问题的语义相似度,如果低于阈值就把历史清空,当作全新会话处理。这个逻辑用一个小模型就够,但收益非常明显——很多"答非所问"其实不是模型问题,而是历史残留干扰。6.2 追问的指代消解与问题改写多轮对话最典型的场景是追问:"那它的预算是多少?"如果直接把这句话拿去检索,检索系统根本不知道"它"指什么。我们加了一层问题改写:当判断当前问题是追问时,先结合最近几轮历史把问题补全成完整问句,再用改写后的问句去检索。改写不一定要用大模型,我们用了一个轻量提示词让一个小模型来做,模板很简单:根据对话历史,把用户的追问改写成独立的完整问句,不要添加历史里不存在的信息。注意"不要添加历史里不存在的信息"是关键约束,否则改写很容易自己脑补出用户没提过的条件,把检索方向带偏。改写之后还有一个细节:**改写结果要同时用于检索和生成。**生成时我们不会拿长句原文提示模型,而是让模型看到"改写后的完整问题+原始追问",既保证检索精准,又保证回答符合用户原本的口语化表达。7. 上线前的评测与迭代:没有测试集别上线7.1 评测集怎么搭才靠谱我们刚开始犯的错是"自己拍脑袋写测试题",测出来效果很好,上线后被真实用户问得破绽百出。后来改成从业务侧收集问题样本:翻历史工单、看运营群里被问得最多的问题、从常见文档FAQ里提取,再让业务方补充它们最关心的场景。评测集规模不大,第一批只有80条左右,但覆盖了五种类型:类型示例问题模式通过标准事实查询型"公司的年假政策是什么"答案与原文一致并正确引用操作步骤型"怎么申请会议室"步骤完整、顺序正确对比分析型"A方案和B方案有什么区别"能分别说明两者要点数值条件型"超过5000元的采购需要什么审批"数值条件完全精确开放建议型"如何提升项目复盘质量"有依据、不编造每条评测样本都标注了参考答案和期望引用的源文档。人工标注80条很费时间,但这是评测的根基,后面自动化打分再快,也弥补不了测试集本身是"歪的"。7.2 自动化评测与badcase的归因路径评测脚本每天跑一遍,用一个大模型当"裁判模型"给系统回答打分,维度包括:相关度、正确性、引用一致性、是否拒绝回答。裁判模型本身也会偶然判错,所以我们只把自动化打分作为初筛,分数异常的样本再人工复核。真正让系统快速变好的,是badcase的归因流程。拿到一个错误回答,我们按下面的路径排查:查召回:把用户问题重复在离线检索流程里跑一遍,看正确文本所在的文档块是否在Top30候选中;查排序:如果正确的块在候选中但排得靠后,说明是排序问题,优先调重排或融合逻辑;查生成:如果正确的块排得靠前但答案还是错了,说明是模型生成问题,改提示词或补充示例;查切分:如果正确的块根本不存在或内容残缺,说明是离线切分问题,回炉改切分参数。这个归因路径的精髓是把错误责任隔离到具体环节,而不是笼统地说"系统效果不好"。我们每周都会把新badcase归类,再决定下周改哪一块:向量模型要不要换?切分参数要不要调?提示词要不要补充说明?按照这个闭环跑了两个月,评测集准确率从70%出头提升到了接近92%。8. 部署运行与成本控制的一笔账8.1 部署形态:数据安全与效果之间的平衡考虑到内部数据的敏感性,我们把部署形态定为:私有化部署检索链路和小模型,大模型API只接收脱敏后的必要上下文。具体来说,文档向量化和权限过滤全部在内部环境完成,只有最终拼好的提示词模板里的"检索材料、对话历史、当前问题"这些文本会传给大模型API。对话内容可能会被API服务用于改进模型——这是一个需要明示给用户的点,我们在系统使用说明里写清楚了这个边界。如果你所在团队的数据安全要求更高,也可以选择内部部署开源大模型,但推理资源成本会明显上升。我们的取舍是:内部部署小模型负责检索和改写,生成用商业化API保证效果和稳定性,中间再加一层服务端脱敏。这个组合在成本、效果、安全三方面取得了一个平衡。8.2 成本估算:别等月底账单吓一跳大模型API按token计费,所以估算成本不能只看"每问一次多少钱"。我们的成本模型是这样的:输入token= 系统提示词 + 检索材料拼接 + 3轮对话历史 + 当前问题,平均一次对话大约消耗多少token,根据统计取值;输出token= 大模型平均回答长度;月成本= 月问答次数 × (输入token数 × 输入单价 + 输出token数 × 输出单价) × 冗余系数。举个具体的简化案例。假设每次问答平均输入2000个token,输出500个token,输入单价0.001元/千token,输出单价0.002元/千token,那单次成本只有大约3厘钱。听起来很便宜,但如果是每天1万次问答,月成本就是10000 × 30 × 0.003 = 900元。再加上缓存命中率的折扣,总算下来不至于离谱。可一旦检索材料写得贪多,每次塞进一万个token,成本就会翻几倍,所以提示词里"检索材料数量上限"也是个成本控制参数。我把各量级成本做个表格,方便你建立量感:月问答量约输入token/次约输出token/次月成本量级(按上述单价推算)1千次2000500约9元1万次2000500约90元10万次2000500约900元10万次(材料扩到1万token)10000500约3000元降低成本的手段,除了高频答案缓存之外,还有三个:控制检索材料长度(我们限制最多拼接5个块,每块不超过800字);对话历史只留最近3轮;对超长文档先做一次粗筛再精读,而不是把所有候选块都堆给大模型。8.3 日常运维要盯的指标上线之后,每天要关心的核心指标包括:检索召回率、重排后Top1命中率、首字返回时间、回答失败率、引用被点击率、用户"不满意"反馈率。前几个是技术指标,后两个是体验指标。我们会在日志里给每一次问答生成一个全链路ID,从用户提问到最终展示,每一步的时间、候选块、改写结果、模型输出全都能串起来,debug的时候效率高很多。还有一条血泪教训:**检索日志一定要脱敏。**因为日志会记录完整的用户问题和候选文档块,如果不做权限过滤和访问控制,日志系统本身就会变成一个越权信息泄露点。我们最后把日志按权限分级,只有管理员能查看完整链路,普通运营只能看统计数字。这套系统在内部跑了大半年,说实话,它没有一开始想象中那么"智能",甚至用户夸它的时候,我心里清楚大部分功劳要记在"把文档切得好、把检索做得准"这两件事上。大模型只是最后那个把材料读出来、组织成通顺回答的朗读员。如果让我给后来者一个最实在的建议,那就是:先别急着换更大更强的模型,沉下心把知识库的切分、元数据、检索召回、评测集这四块打到80分,然后再回头看大模型,你会发现它忽然变聪明了。我踩过的那些坑,你大概率也会踩一遍,希望这篇分享能让你少绕几段路。