企业专属GPT落地:RAG、语料治理与评测的工程实践
不少团队在内部搞“企业专属GPT”这件事上,第一版通常死得很安静。我见过一个很典型的场景:某公司花了两个月,把内部知识做成问答助手推给全员,上线第一周大家图新鲜,问了上百个问题;第三周访问量掉了一大半;一个月后基本没人打开。事后复盘,答案不是“模型不够强”,而是三个很朴素的原因——问“年假怎么算”它引用了一份已经作废的旧制度,问“某个项目现在的对接人是谁”它一本正经地编了一个名字,问“这份合同有没有风险条款”它给了三段听起来很专业但没有出处的分析。
这就是我想聊“如何构建企业专属GPT”的起点。这里的“专属”不是给模型换个名字、塞一段提示词那么简单,它意味着这个助手知道你们公司的事、用你们公司的说法、在你们公司的规则边界内说话,并且在不知道的时候敢于承认不知道。它要解决的问题是:把散落在共享盘、邮件、工单系统、会议纪要里的组织记忆,变成一个人人可问、答案可追溯的服务。适合看这篇的人大概是这几类:被老板点名要“搞个AI助手”的技术负责人、正在做RAG原型但效果不稳定的工程师、以及想评估这条路到底值不值得投入的产品同学。下面这份内容,是我自己从零到一带过几个内部助手项目之后,觉得真正会踩坑、真正需要提前想清楚的环节。
1. 拆开“专属”这两个字:企业掏钱到底在买什么
先把需求收敛,不然技术选型一定会飘。企业内部想让“专属GPT”干的活,粗分下来就三类,每一类背后的技术重点完全不同,混在一起做就会变成四不像。
1.1 信息不对称型:把“找不到”变成“问得到”
这是绝大多数项目的起点。规章制度、产品手册、历史项目文档、常见故障处理手册、售后FAQ,这些内容的共同点是:存在,但没人愿意翻。新人入职三个月还在群里问“报销流程在哪”,老员工也记不住两年前那份接口规范放在哪个目录。
这类需求的本质是检索问题,不是生成问题。模型在这里的角色是“把检索到的原文用人话复述一遍”,它自己不需要掌握任何业务知识。想清楚这一点很关键,因为它直接决定了你后面应该在切块、索引、召回上砸时间,而不是急着去微调模型。
我见过一个反例:某团队为了“让模型更懂公司业务”,拿几百份内部文档去做了微调,结果模型把文档里的某些具体数字记串了,回答时把A产品的保修期说成了B产品的。这种错误比“不知道”危险得多,因为它带着模型的自信。信息型需求走微调,收益极低、风险极高。
1.2 流程重复型:把“每次重写一遍”变成“套模板”
第二类是格式化输出:把会议录音整理成纪要、把工单内容改写成客户可读的回执、把零散的技术变更整理成上线说明、把一段需求描述扩写成测试用例。这类任务的特点是输入结构相对固定、输出格式有明确要求、对事实准确性的要求低于对格式和语气的准确性。
流程型需求才是微调真正的主场。因为你要的是“风格”和“格式”的稳定复现,而不是“事实”的注入。用少量高质量样本做一次轻量微调,能让输出稳定性提升一大截,尤其是当你的输出格式比较特殊(比如公司内部有一套固定的周报模板、有一套特殊的工单分类标签体系)时,靠提示词硬掰往往掰不干净,模型总会漏字段或者自创格式。
1.3 判断辅助型:把“人肉比对”变成“先给一版初筛”
第三类最诱人也最容易翻车:合同条款比对、报表异常解释、投标文件合规性初筛、代码变更的风险提示。这类需求的共同点是,答案不是“有或者没有”,而是一个需要专业判断的结论。
我的建议很明确:这类场景可以做,但产品定位必须是“初筛”而不是“结论”。界面上要明确写“以下为机器初筛结果,需人工复核”,输出必须带原文定位。技术上它依然是检索加生成,但提示词里要强制模型逐条列出依据,并且给出“未发现”和“无法判断”的区分。把这类功能包装成“AI帮你审合同”,短期演示很爽,长期一定会因为一次漏判而失去信任。
| 需求类型 | 典型场景 | 技术重心 | 微调必要性 | 主要风险 |
|---|---|---|---|---|
| 信息不对称型 | 制度问答、故障手册查询 | 切块质量、混合检索、重排 | 低 | 引用过期文档、张冠李戴 |
| 流程重复型 | 会议纪要、工单改写、周报生成 | 提示词模板、轻量微调 | 中到高 | 格式漂移、漏字段 |
| 判断辅助型 | 条款比对、异常解释 | 逐条溯源、拒答机制 | 低 | 过度自信、漏判 |
2. 语料治理:从共享盘里的Word到能检索的知识块
这一步是决定成败的分水岭,也是最枯燥、最容易被跳过的一步。我的经验是,一个企业问答助手最终效果的天花板,80%在语料治理阶段就已经定死了,后面调模型、调提示词都只是在这个天花板下面挪腾。
2.1 先做减法:哪些文档压根不该进库
新人上手第一反应是“全量导入”,把能爬到的文档一股脑塞进去。这个做法几乎必然失败,原因有两个:一是噪声会淹没信号,二是过期内容会污染答案。
导入前必须做一轮人工加规则的筛选。我在实际操作中会先划三条线:第一,只保留当前有效的版本,历史版本单独存一个归档库,默认不参与检索,只在用户明确问“以前是怎么规定的”时才放开;第二,剔除过程性文档,比如各种会议记录草稿、汇报PPT的中间版本,这类内容语义模糊、结论未定,检索出来只会误导;第三,剔除纯数据表格而无说明的附件,除非你专门为它做了结构化处理。
还有一个容易被忽略的点:权限。文档在导入前就要打上部门和密级标签,因为检索阶段必须做权限过滤。如果你打算上线后再补权限,那基本等于重做一遍索引。这件事一定要在第一天就定好字段。
2.2 切块不是按字数切那么简单
切块(chunking)直接决定检索粒度。切太大,一个块里混了三四个主题,向量表示被稀释,召回不准;切太小,一句话被切碎,语义不完整,模型拿到也没法用。
中文场景下我常用的起点是:正文类文档按 300 到 600 字一个块,重叠 10% 到 15%。重叠的作用是防止关键信息正好落在切口上被劈成两半。但这个数字只是起点,真正要按文档结构调:有明显标题层级的文档(制度、手册、产品说明书),优先按标题切,把标题路径作为元数据带上,比如“第三章 报销管理 > 3.2 差旅住宿标准”,检索命中后模型看到的上下文自带层级,回答准确率会明显提升。没有结构的文档(聊天记录、纯文本纪要)才退回按字数切。
表格是最麻烦的。我的处理方式是:小表格转成 Markdown 保留在块里,大表格单独拆成“一行一记录”的形式,把表头拼到每一行前面,比如“产品型号:X200 | 保修期:24个月 | 适用区域:境内”。这样每一行都能被独立检索到,而不是整张表作为一个巨块。
扫描件和图片必须走 OCR,这一步别省钱。OCR 质量差的语料进来,后面所有环节都在为它买单,检索出一堆乱码,模型只能瞎编。
2.3 元数据是检索质量的一半
很多人只关注文本内容,忽略元数据,这是很大的浪费。我建议每个块至少带这几类字段:
- 来源信息:文件名、章节路径、页码或段落号,用于生成引用
- 时间信息:文档生效日期、最后更新时间,用于排序和过期判断
- 归属信息:所属部门、适用人群、业务线
- 密级信息:公开、内部、受限,用于权限过滤
时间字段的价值特别大。同样一个问题检索到两份内容矛盾的文档,按更新时间降序排,把新的放前面,很多“答错”问题就这么解决了。我在一个项目里加了这个排序规则之后,制度类问题的准确率肉眼可见地上升,而成本只是加了一个字段。
3. 路线选型:检索增强、参数微调、提示词工程三者的真实边界
技术路线上最常见的误会,是把“微调”当成“灌输知识”的手段。这个误会代价很高,值得单独掰扯清楚。
3.1 检索增强解决“知道什么”,微调解决“怎么说”
大模型的知识存在参数里,但这份参数化记忆有三个毛病:不可靠(同一个事实可能在不同上下文下给出不同答案)、不可控(你没法精确指定它记住哪一句)、不可更新(改一条制度要重训一次,成本离谱)。所以凡是“会变的事实”,都不应该指望微调。
检索增强的逻辑正好相反:知识留在外部库里,每次提问现查现用。好处是更新即时(改文档就行)、可追溯(能给出处)、可控(不相关的可以不召回)。代价是每次请求多了一次检索开销,以及依赖检索质量。
那微调干什么用?我的经验是:微调主要改“行为”,不改“知识”。它能显著改善的是输出格式的稳定性、语气的一致性、特定任务的指令遵循度,比如“永远按‘结论—依据—建议’三段式回答”“永远输出 JSON 且字段名严格按给定 schema”“永远用客服口吻,不使用第一人称”。
3.2 什么时候值得上微调
判断标准很简单:如果你发现自己在提示词里反复加同一段约束,而且模型遵守率还是不稳定,那就是微调的信号。反过来说,如果提示词里塞到两三千字还能稳定遵守,就别折腾微调,维护成本不划算。
如果决定做,起步规模不用大。我参与过的几个项目,任务类微调的数据量都在几百到两千条这个区间,用的是 LoRA 这类低秩适配方式,只训练一小部分附加参数,不动机础权重,好处是显存占用低、训练快、可以给不同业务线各挂一个适配器,切换成本很低。
数据质量远比数量重要。一千条格式规范、答案确切的样本,效果远好过一万条从系统里导出的脏数据。我通常会这样构造:先用当前最好的提示词跑一遍真实问题,把输出人工改到满意,改完的问答对就是训练样本。这个过程顺带还能把提示词的短板暴露出来。
3.3 一张表把选型说清楚
| 你遇到的问题 | 优先方案 | 为什么不选另一个 |
|---|---|---|
| 模型不知道公司制度 | 检索增强 | 微调难以精确注入,且制度会变 |
| 模型答公司制度时引用旧版本 | 检索增强 + 时间元数据排序 | 微调无法解决版本时效 |
| 输出格式总是不对、漏字段 | 提示词 + 少量微调 | 纯提示词在复杂格式上遵守率不稳 |
| 语气太“AI”,不像公司客服 | 微调(风格样本) | 检索对此毫无帮助 |
| 回答太长、抓不住重点 | 提示词约束 + 输出结构模板 | 微调成本高于收益 |
| 专业术语老是说错 | 检索增强(术语库)+ 提示词 | 微调可以但更新麻烦 |
4. 检索链路:把“找得准”拆成四道工序
大部分“RAG 效果不好”的问题,追到根上都是检索没做好。模型拿到一堆不相关的片段,再强也只能编。这条链路我一般拆成四步:查询改写、混合召回、重排过滤、上下文拼装。每一步都有具体可调的旋钮。
4.1 查询改写:用户那句口语不是检索语句
真实用户的提问往往极短、指代多、带口语。“那个报销的怎么弄”“上次说的那个流程”“这个能不能过”——这类问题直接拿去做向量检索,命中率会惨不忍睹。
我常用的三个处理动作:
指代消解。多轮对话里把“它”“这个”“那个”还原成具体名词。这一步靠把最近两三轮对话一起喂给模型做改写,输出一个自包含的检索语句。成本很低,收益很明显。
关键词抽取。让模型从问题里抽出实体和术语,尤其是公司内部的黑话和简称。内部简称往往在嵌入模型那里表示很弱,但对关键词检索很有效。
多查询扩展。把一个问题改写成两到三个不同角度的检索语句,分别召回再合并。比如“出差住宿标准”可以扩展成“差旅费 住宿 上限”“员工出差 酒店 报销额度”。这会增加检索开销,但召回率提升通常值这个钱。
4.2 混合召回:向量和关键词谁也替代不了谁
只走向量检索是新手最常见的配置,问题在于向量对“精确字符串”不敏感。产品型号、合同编号、错误码、人名,这些内容在向量空间里经常糊成一团,反而是传统的关键词检索(BM25 这类基于词频的算法)一击即中。
所以生产环境我基本都用混合召回:向量检索取 Top 20 到 50,关键词检索取 Top 20 到 50,然后用倒数排名融合(RRF)把两路结果合并。RRF 的公式很朴素,就是每个文档的得分等于它在各路排名倒数之和,常数项一般取 60:
score(d) = Σ 1 / (k + rank_i(d)),k 通常取 60它的好处是不需要关心两路分数量纲不一致的问题,只看排名,实现简单且稳定。融合之后取前 20 到 30 条进入下一步。
4.3 重排与阈值:宁可说“不知道”,也别硬编
召回阶段追求的是“不漏”,重排阶段追求的是“准”。做法是把召回的候选片段和问题一起送进一个重排模型,逐条打相关性分数,按分数重排,然后卡一个阈值。
阈值这块我要多说两句,因为它是最容易被忽略、又最能提升体验的参数。相似度低于某个值的片段,直接丢弃,不要硬塞给生成模型。塞进去的后果是模型被迫从一堆不相关的内容里“找话说”,幻觉率飙升。
阈值定多少要实测。常见的余弦相似度区间在 0.35 到 0.45 之间作为起点,但不同嵌入模型的分布差异很大,必须在自己的评测集上跑一遍,看召回率和准确率的平衡点在哪。我的做法是画一条曲线:横轴是阈值,纵轴分别是“正确回答率”和“拒答率”,找一个正确率还不错、拒答率可接受的拐点。
还有一个技巧:如果一个片段都没过阈值,不要让模型自由发挥,直接返回兜底话术,并把问题记进“未命中日志”。这份日志是后续补语料的黄金材料,我每个项目都会盯着它看,看用户到底在问什么我们没覆盖到的东西。
4.4 上下文拼装:位置、去重、引用一个都不能少
拼上下文看起来只是把片段拼起来,其实有不少讲究。
数量:塞太多片段是有害的,模型注意力会被稀释,还容易把不同片段的信息混在一起。我一般控制在 3 到 6 个片段,每个 600 到 800 token,总上下文预算控制在模型窗口的 30% 到 50%,剩下的留给对话历史和输出。
顺序:把重排分数最高的放在最前面和最后面,中间放次相关的。这是利用了模型对首尾位置更敏感的特点,实测能提升一点引用准确率。
去重:相邻块因为有重叠,内容会重复,重复内容会让模型误以为某个信息特别重要。简单的做法是对文本做相似度去重,超过 0.9 的丢掉。
引用标注:在每个片段前面加编号和来源标记,比如[1] 来源:差旅管理制度 2024版 > 3.2,然后在提示词里要求模型引用编号。这一步是后面做可追溯的基础,千万别省。用户看到答案下面挂着“依据:[2]”,信任度是完全不一样的。
5. 提示词与输出护栏:让回答能追溯、能拒答
模型本身没有“我不知道”的自觉,它会顺着你的问题往下编。护栏的作用就是在提示词层面把它摁住。
5.1 系统提示的结构化写法
我习惯把系统提示分成五段,顺序固定:角色与边界、可用材料说明、回答规则、输出格式、兜底策略。分散着写效果很差,模型容易顾此失彼。
你是某公司的内部知识助手,服务对象是公司员工。 你只能依据【参考资料】中的内容回答,不得使用参考资料之外的知识。 如果参考资料不足以回答,必须明确说明"现有资料中未找到相关内容",并建议用户联系对应部门。 回答规则: 1. 每个结论后标注依据编号,格式为 [编号] 2. 涉及金额、期限、比例等数字,必须原样引用,不得换算或估算 3. 参考资料之间存在冲突时,优先采用发布时间较新的那份,并说明存在版本差异 4. 不得对制度条款做扩展解释,不得给出法律或合规建议 输出格式: - 先用一句话给出结论 - 再分条列出依据 - 最后列出引用的资料编号与标题 【参考资料】 {context}这段提示词里有几个点值得说。第一,“只能依据参考资料”这句话必须放在最前面,位置靠后会被稀释。第二,“不得换算或估算”这条是吃过亏之后加的——早期模型会把“24个月”自动说成“两年”,遇到按月份计算的场景就错了。第三,冲突优先取新版这条规则,配合前面的时间元数据排序,能挡掉很大一部分版本矛盾问题。
5.2 拒答不是失败,是功能
产品侧要把拒答当正例来设计,而不是当 bug。我在界面上会明确区分三种状态:找到答案、部分找到(提示可能不完整)、未找到(给出可联系的部门或可提交的反馈入口)。第三种状态配上“一键反馈”,用户点一下就能把问题提交给知识维护人,形成闭环。
数据上也要监控拒答率。拒答率突然升高,通常是索引出问题或者某批文档失效了;拒答率长期为零,反而要警惕,说明模型可能在无差别硬答,需要抽查。
5.3 输出内容的二次校验
生成完之后加一层轻量校验,成本不高但很有用。常见规则包括:答案里出现的数字是否能在引用片段中原样找到;引用的编号是否真实存在于本次上下文中;输出长度是否超限;是否包含不该出现的敏感字段。
这层校验不需要模型,正则加字符串匹配就能覆盖大部分场景。命中异常就降级返回“已找到相关资料,但自动校验未通过,请查看原文”,并把原文链接给出来。这种降级体验比给出一个错误答案要好得多。
6. 评测:没有评测集,效果好坏全靠玄学
这是我最想强调的一章。没有评测集的项目,迭代过程就是几个人凭感觉说“好像好了一点”,然后陷入无休止的调参。做评测集不难,难的是下决心开始做。
6.1 冷启动阶段怎么凑出第一批问题
不用等系统上线。项目一开始就可以去找一线同事要问题:客服要真实工单标题,人事要真实咨询记录,技术支持要真实提问。一个下午能收集到一两百条。我一般会要 150 到 300 条,覆盖不同业务线,然后人工标注每条的标准答案和应该引用的文档片段。
这份数据不需要完美,但必须真实。自己坐在工位上想出来的问题,和用户真问的问题,差距大到超出想象。真实问题往往短、口语、信息不全,这正是难的地方。
6.2 三个层次的指标,分开看
我不建议只盯一个“准确率”,它太粗了,出了问题找不到原因。分开看三个层次:
| 层次 | 指标 | 怎么算 | 参考目标 |
|---|---|---|---|
| 检索层 | Top-5 召回率 | 标准答案所在片段是否出现在前 5 条 | 85% 以上 |
| 生成层 | 忠实度 | 答案中的论断是否都能在引用片段中找到支撑 | 90% 以上 |
| 生成层 | 拒答正确率 | 无答案的问题是否被正确拒答 | 80% 以上 |
| 体验层 | 有用性评分 | 人工 1 到 5 分打分 | 平均 4 分以上 |
检索层和生成层要分开评,因为它们的优化手段完全不同。召回率低就去改切块和混合检索;召回率够但忠实度低,就去改提示词和重排阈值。混在一起调,等于蒙眼打靶。
忠实度这一项可以半自动化:让另一个模型逐句判断“这句话能否在给定片段里找到依据”,人工抽检 10% 校准。这样评测可以高频跑,不至于每改一版都靠人工。
6.3 把评测变成回归测试
评测集建好之后,最重要的事情是把它固化进流水线。每次改切块策略、换嵌入模型、调提示词、升模型版本,都跑一遍,输出对比报告。
这件事的价值在一次模型升级时体现得淋漓尽致。我们当时把一个底层模型换了新版本,主观感觉“回答更流畅了”,但回归测试一跑,制度类问题的忠实度掉了 6 个点——新模型更倾向于“总结归纳”,会把原文的限定条件吃掉。如果没有回归测试,这个问题可能要等用户投诉才会暴露。
提示:评测集要定期更新。业务变了、文档变了,老问题的标准答案也会变。我一般每个季度补 20 到 30 条新问题,同时把已经失效的老问题标注归档,避免评测结果失真。
7. 上线之后:成本、延迟、权限与知识漂移的真实账本
Demo 跑通和真正上线之间,还隔着一堆工程问题。这一章讲的就是那些演示时看不见、上线后天天找你的东西。
7.1 成本结构拆开看,钱花在哪
企业助手的成本主要三块:模型调用、检索服务、语料维护人力。前两块是显性的,第三块最容易被低估。
模型调用这块,优化空间主要在上下文长度。很多人习惯把 Top 10 全塞进去,实际有效信息可能只有 3 条。把重排阈值卡严、片段数压到 5 条以内,token 消耗能降一半以上,效果反而更好。另外一个省钱手段是缓存:高频问题(比如“年假怎么算”)在内部一定是重复被问的,把问题和答案缓存起来,命中直接返回,能省下相当可观的调用量。语义缓存要注意相似度阈值别设太松,否则会把不同问题误判成同一个。
检索服务的成本相对固定,主要看索引规模和查询并发。索引重建是重活,别放在业务高峰期做。
人力成本这块我得直说:语料维护是一个长期投入,不是一次性工程。制度更新、产品迭代、组织调整,都会让索引里的内容过期。如果没有明确的知识维护责任人,这个助手会在半年内自然衰亡。我建议在上线时就指定每个业务域的“知识 owner”,把文档更新和索引重建绑进他们的常规流程。
7.2 延迟:用户能忍多久
内部工具的可接受延迟比公有产品宽松,但超过 5 秒,使用意愿就会明显下滑。优化的几个动作按性价比排序:
第一,流式输出。哪怕实际总耗时没变,首字返回时间从 4 秒降到 1 秒,主观感受完全不同。这个改动通常只需要前端配合,成本最低。
第二,检索并行。向量检索和关键词检索同时发,别串行。多查询扩展的几个查询也并行发。
第三,把重排模型和生成模型的调用链路理顺,避免不必要的串行等待。
第四,缓存。刚才说过的高频问题缓存,对延迟的改善比成本更明显。
第五,如果 QPS 上来了,考虑把推理服务独立部署,做批处理调度,比逐条调用效率高得多。
7.3 权限过滤必须在检索阶段做
这一点是硬要求。检索结果返回前,必须按用户身份过滤掉无权访问的片段,而不是生成完了再脱敏。因为一旦无权限内容进入了模型上下文,它就可能以各种变形出现在输出里,事后过滤根本拦不住。
实现上,每个语料块带上部门标签和密级标签,检索时把用户身份转成过滤条件,在向量检索的元数据过滤里直接生效。同时要留审计日志:谁在什么时候问了什么、命中了哪些文档、返回了什么。这份日志在出问题时是唯一的排查依据。
7.4 知识漂移:索引不会自己保持新鲜
语料是会腐烂的。制度改版、产品下线、组织架构调整,索引里的内容就变成“历史遗迹”。用户问到一个已经失效的流程,模型照着旧文档答得头头是道,这是最伤信任的失败模式。
我的处理办法有三条。一是给每份文档设“有效期”字段,过期文档在检索时降权或直接排除,同时通知 owner 更新。二是定期跑一次“孤儿文档”检查,找出长时间没被任何查询命中的文档,人工判断是没人问还是检索不到。三是把用户反馈闭环接进来,点“这个回答不对”的问题自动进待处理队列。
这几件事听起来琐碎,但它们决定了这个助手在第三个月还有没有人用。技术方案能解决“能不能答”,运营机制才解决“答得对不对”。
最后分享一个我自己踩过的坑:早期我特别执着于把准确率从 78% 推到 85%,花了很多时间调重排和提示词。后来发现,用户真正抱怨的不是那 7 个百分点,而是“答错了还特别自信”。把拒答机制和引用展示做扎实之后,即使准确率只涨了 2 个点,满意度却上了一个台阶。做这类工具,让人知道什么时候该信它,比让它显得无所不知重要得多。