ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零搭建企业级AI问答机器人:RAG、提示词工程与私有化部署全链路实践

从零搭建企业级AI问答机器人:RAG、提示词工程与私有化部署全链路实践 年后开工的第一周运维同事在群里丢了一句“有没有可能把过去三年散落的几十份项目文档做成一个能直接问问题的机器人”我看着收藏夹里那些PDF、Markdown、Wiki页面第一反应是“这不就是接个大模型嘛”。真正动手之后才发现从零开始把一个AI想法变成稳定运行的服务中间隔着的东西远比“调用模型”多得多。这就是我做ai-engineering-from-scratch的起点。这篇文章不是讲某个框架的API怎么用而是把一条完整的AI工程链路拆开讲从需求定义、提示词工程、模型选型、RAG落地到服务化部署和上线后的迭代。适合正在从0到1搭建LLM应用、脑子里有想法但不知道从哪下手、以及被“模型调通了但系统不稳定”折磨过的工程师。下面我按当时的推进顺序把几个关键环节逐个讲透。1. 先想清楚这个AI项目到底要解决哪类问题1.1 从问题出发而不是从模型出发我一开始犯的典型错误是先纠结选什么大模型ChatGPT还是开源模型7B还是13B。绕了两天发现方向反了。AI工程的起点永远是需求不是模型。于是我把运维同事的模糊诉求拆成了几个具体问题用户问什么在什么场景问期望的答案长什么样。我们的场景很简单内部人员查到某个项目的配置参数、历史决策、接口文档时不用翻几十个文件直接问机器人。答案是“一段带着出处的文字”就够了不需要生成图片、不需要多轮复杂对话。把这些写成用户故事之后技术选型才有判断标准。比如“答案必须能追溯到具体文档”这一条直接决定了后面必须做RAG而不是把模型调大比如“不导出公司文档”这一条直接排除了所有需要把数据传到公网模型API的路线。1.2 “该不该用大模型”的价值判断不是所有任务都值得让大模型上场。这个判断做错了后面全是成本黑洞。我这边的经验是简单把任务分成三类适合大模型的、适合传统规则的、适合两者结合的。比如“找出文档里出现过哪些项目代号”这种精确匹配用正则匹配比大模型快十倍、便宜十倍还不会幻觉“把用户口语化的问题转换成系统查询语句”这个就适合大模型“客户问某个接口的字段含义”最好先用检索把相关文档片段捞出来再交给大模型总结。我当时做了一个很土的判断表大概长这样任务类型例子推荐方案精确查询某个配置项是否存在正则/倒排索引语义理解把口语问题转成结构化查询大模型开放总结概括一篇文档的核心内容大模型知识问答文档里的某个细节是什么检索 大模型这个表帮我们砍掉了很多花哨的需求。工程里最贵的不是模型推理算力而是“做出来的功能没人用却还在烧钱维护”。1.3 定边界MVP可以先砍掉哪些功能聊需求的时候大家总会说“如果能顺便支持语音就好了”“最好能记住上次聊到哪”。这类需求听着合理但对验证核心价值毫无帮助。我的做法是给第一版划定极小的边界只处理文本类文档只支持单轮问答不做多轮记忆不做权限细分不做语音接口。核心交付只有一句话——“用户把问题贴进去5秒之内得到带来源的答案”。这条标准被我写在项目说明第一行所有功能设计都拿它来卡。砍功能不是偷懒是为了让反馈回路变短。先证明这个AI工具对用户有真实价值再去追加复杂能力路会稳很多。第一版上线之后确实有不少同事来用我们才有底气继续投入。2. 提示词工程从模糊需求到稳定输出的第一道坎2.1 提示词的第一性原理把上下文、指令、输出格式分开很多人写提示词习惯把所有内容揉在一起像是“你是一个机器人用户是员工请回答以下问题如果有资料就用资料回答……”这种写法能跑但极不稳定。我自己的经验是把提示词分成三块系统指令、动态上下文、输出协议。系统指令负责定义身份和行为边界固定不变动态上下文是每次请求时检索出来的文档片段输出协议明确要求返回的格式。这样分层之后调试成本大幅下降。比如同样的系统指令换了检索片段问题定位立刻清楚很多是上下文没召回到还是模型没理解还是格式解析挂了。我当时第一版提示词模板大致长这样system 你是一个企业内部文档问答助手。只依据提供的资料回答用户问题如果资料中没有答案请直接说“未在资料中找到相关信息”。不要编造事实。 user资料 - 《XX平台部署手册》第3节XX服务默认端口为8080需要在配置文件中指定。 - 《XX平台运维记录》2024年6月曾出现端口冲突临时调整到8081。 用户问题 XX服务默认端口是多少 输出格式 请用JSON返回字段 answer字符串简洁回答sources数组包含引用的资料标题。这看起来很简单但把“只依据资料回答”和“未找到请直说”写进系统指令比在代码里事后判断要省心得多。2.2 结构化输出与温度参数防止模型“自由发挥”问答工具追求的是确定性不是创意。所以在模型参数上我直接把温度压到了0.1到0.3之间同时开启稳定输出格式的相关配置要求模型返回JSON。低温不是万能药但能让输出更贴近资料原文减少即兴发挥。这里有个很细节的体会模型的“作文能力”在这种场景里不值钱“把一行配置参数原封不动找出来”才值钱。把任务限定成“从资料中摘取信息再做摘要”比让模型“自由回答”靠谱得多。同时要注意输出格式的校验。模型返回的JSON偶尔会缺字段、多字段、甚至直接一段Markdown。所以我在后处理里加了严格的JSON解析和字段校验。程序只要能解析失败就再走一次“重试生成”的逻辑连续失败就返回兜底话术。2.3 提示词版本管理像管理代码一样管理prompt提示词是会升级的而且改动的副作用比代码更隐蔽。我见过很多人直接在线上页面里改提示词改完就忘出了问题都不知道是哪个版本导致的。后来我把所有提示词模板放进Git仓库每条提示词对应一个版本号每一次修改都要跑一遍固定的回归问答集。这个习惯救了我好几次。有一次同事反馈“回答风格突然变啰嗦了”我一查才发现有人为了让回答更详细偷偷把系统指令加了一句“请多描述背景信息”结果所有回答都开始凑字数。加了版本管理和评审流程之后这类问题几分钟就能定位。提示词不是“随便写写”它和代码一样需要评审、测试、记录变更原因。2.4 从单轮问答到Agent工作流我们的项目一开始只是单轮问答后来有一个高频需求是“查一下XX服务的异常日志里有什么信息”这就涉及多步操作先检索日志文档的存放方式再调用查询工具再总结结论。天然就需要把工具调用串起来也就是现在常说的Agent。第一个Agent不能搞太复杂我建议先做“单Agent多个工具”的模式让模型自己决定是否调用某个工具工具列表通过函数描述方式暴露给模型。关键是定义清楚工具调用的输入输出协议把“调用工具”这个动作当作一种结构化输出。工程上常见的坑是模型反复调用同一个工具陷入死循环所以必须给最大迭代次数和超时时间。等单Agent稳定之后再接多Agent协作否则出了问题你很难判断是哪个环节在犯傻。3. 模型选型与私有化部署性能、成本、隐私的三角权衡3.1 先搞清楚你的约束条件很多选型教程上来就列排行榜但真实的选型首先看约束条件。我们这道题里最硬的约束有两个公司文档不能出内网没有GPU集群预算只有一台带24GB显存的单卡服务器。这两个条件直接锁死了方向必须私有化部署模型规模不能太大。如果你数据可以上云那闭源API会省很多事如果你有几十张A100那直接上大模型也不是问题。选型没有最优解只有约束条件下的可行解。建议先把数据敏感度、预算、并发量、时延要求这四项列清楚再去看模型榜单顺序别反。3.2 开源模型的量化取舍在我们的约束下7B级别模型成为首选。部署时通常会做量化量化位宽和显存换算大概是这样FP16权重占参数量乘以2字节所以7B模型约14GBINT8约7GBINT4约3.5到4GB。24GB显存跑一个INT8量化的7B模型很宽松还能留出空间给推理缓存。但量化不是免费的。我实际踩过的坑是INT4量化之后中文场景下偶尔会出现“话没说完就结束”的诡异情况而且对长文档里细节数字的复述能力会下降。如果是回答“端口是多少、版本号是多少”这类精确问题模型输出稍微一飘就完蛋。所以我的建议是尽量用INT8起步测试明显卡顿或显存不够再降到INT4不要一上来就为了省显存上极限量化。3.3 部署形态与并发量预估部署工具方面vLLM适合需要高吞吐的在线服务Ollama适合快速起步。我第一版先用Ollama跑通链路后面稳定性要求上来才迁移到vLLM。这个递进是必要的不要在一个还没调通RAG和提示词的项目里先花一星期搞推理框架调优。并发量预估可以算得很粗假设一次问答生成300个token单请求解码速度30 tokens/s那一个请求就要占将近10秒的推理资源。24GB显存跑7B模型同时处理两三个请求就已经很吃力了。所以MVP阶段我做了同步接口加短期缓存重复问题先命中缓存再走模型。用户感受到的响应速度一下子快了很多。在线服务一定还要考虑排队和超时不能让用户请求无限挂在那里。3.4 我的选型决策表最终我们的选择是7B量级开源模型INT8量化局域网私有化部署单独部署一个embedding模型用于文档向量化。这套方案谈不上性能极致但在“数据不出内网、预算有限、并发不高、响应可接受”的前提下是最稳的。我对选型的总结是先承认约束再在上面做权衡别拿生产环境当论文实验。大模型再强部署不了也是零。4. RAG落地把业务知识“接”进模型的关键细节4.1 为什么纯靠“喂上下文”不可持续最早的直觉是文档不长直接把原文塞进提示词让模型回答就好。一旦文档量涨到几十份、每份几十页这条路立刻断了。模型上下文窗口再大也扛不住每次把所有内容都带上——成本飙升、时延变长而且一大段无关文字会稀释模型对真正关键内容的注意力。RAG检索增强生成的核心思路很朴素不要把所有内容都告诉模型只把与当前问题最相关的片段送进去。工程上它其实就是两条链路离线把文档切成小块做向量化在线把用户问题变成向量去检索最相似的片段再拼进提示词。这个思路谁都能讲但每个细节都有坑。4.2 文本切块一个参数背后的工程取舍我是从chunk_size512个字符、overlap50开始调的。切块太小一个完整的信息可能被切断语义不完整切块太大向量检索的粒度变粗噪声也跟着增加。overlap的作用是兜底切分边界让跨块的上下文不至于彻底断掉。不同文档类型要做不同处理。代码片段和接口定义表适合小块因为引用关系明确长篇方案文档适合大块因为一个结论往往分散在前后几段。我最后是写了一个按文档类型选择chunk大小的配置表而不是全局统一。调参的经验法则是看检索结果里召回的片段“是不是正好覆盖了答案所在的段落”如果是说明chunk合理如果答案经常被拦腰截断说明chunk太小或overlap不够。4.3 embedding、召回与重排检索链路的三板斧向量召回负责从万级文档块里快速捞出TopK候选但只靠向量有局限。我们在实践里把召回数调成TopK20然后用一个更精确的重排模型交叉编码器对这20条重新打分只取前5条喂给大模型。原理很简单向量召回快但粗糙重排慢但更准确两者配合既控制算力又能提升精度。另一个提升召回质量的做法是混合检索。向量擅长语义相似BM25擅长精确关键词匹配。像“端口8080”“接口名XX”BM25命中往往比向量更准。把两种召回结果合并再去重召回覆盖率明显提升。这些小改动看着不起眼但在“用户问的就是文档里的原话”这一类场景里效果立竿见影。4.4 引用来源让AI说的话有据可查对内部工具来说答案里带引用来源是刚需。用户在配置错误排查时真正信的不是“机器人说”而是“文档第3节这么写”。我在提示词里要求模型在回答时把用到的资料标题放进sources字段展示的时候把引用附在答案下方。这还不够。模型偶尔会“脑补”一个不存在的文件名明明资料里没有它也能给你编一个出处。所以我后来加了一层后置校验模型返回的每个source标题必须真实出现在本次检索返回的chunk列表里否则剔除该引用并重写答案。这一步是纯工程问题但直接影响信誉。AI应用里用户信一次“它骗了我”的坏印象得用十次正确答案来弥补。5. 从原型脚本到稳定服务API化不只是包一层HTTP5.1 一个能跑的原型和能上线的服务之间原型脚本里print一个答案就完事但做成服务要面对的是鉴权、限流、日志、超时、优雅退出。我第一次把脚本用FastAPI包了一层觉得“这不就上线了吗”结果同事用浏览器多问几次进程直接卡死因为模型推理是同步阻塞的一个请求不结束其他请求全排队。后来我把服务拆成几块接收请求层、任务队列层、模型推理层。接收请求立刻返回一个任务标识推理放到后台队列里跑前端轮询或通过流式接口获取结果。不追求高并发的前提下这个方案比硬上复杂分布式架构实用得多。工程不是越复杂越好而是刚好能扛住当前压力并留一点余量。5.2 超时、重试与降级大模型服务的第一课大模型接口有几个老毛病推理慢、偶发超时、返回格式异常。我当时在调用层做了三层防护连接超时设为5秒读写超时根据生成长度动态计算超时后重试最多2次采用指数退避连续失败就返回降级文案比如“知识库暂时不可用请稍后再试”而不是让用户看到一片空白。这里有个非常容易被忽略的点重试要幂等。如果第一次请求已经让模型生成了结果但因为网络问题没能返回给前端重试时不要再让模型重新生成一次否则用户会看到两个不同的答案。我当时在请求层加了请求ID重试时带上同一个ID缓存命中就直接返回上次结果。这个细节做对了生产环境会少很多莫名其妙的问题。5.3 流式输出与用户体验模型生成300个字可能要好几秒如果让用户盯着一个转圈图标体感非常漫长。流式输出的核心是让客户端边生成边接收第一个字几十毫秒就出来后面的内容随推理逐渐展示。这项优化虽然不改变最终答案质量但用户满意度提升非常明显。流式实现的细节要注意健康检查和网关适配有些内网网关默认缓冲整个响应导致流式失效。我在本地联调正常一部署到内网环境就发现前端一次性拿到全文。排查到后来才发现是网关关闭了SSE的缓冲。这类坑几乎每个做AI应用的人都会遇到一次提前心里有数会省很多时间。5.4 最简单的FastAPI接入示例我不建议新手直接上复杂框架先跑通一条最简链路再逐步补工程能力。我当时的服务骨架大致长这样from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI() class Question(BaseModel): text: str app.post(/ask) def ask(q: Question): # 实际逻辑检索相关chunk - 组装prompt - 调用本地模型 # 示例返回真实项目请替换为完整链路 answer, sources run_rag_pipeline(q.text) return {answer: answer, sources: sources}这套代码只是骨架但它定义了一个清晰的边界外面是HTTP协议内部是检索和推理逻辑。后面加鉴权、加缓存、加流式都不需要动这条主线。先让链路能跑才有资格谈优化。6. 上线之后的真问题评测、监控与迭代闭环6.1 用“黄金问题集”代替拍脑袋判断上线前你觉得模型“还行”上线后用户一连串真实问题就暴露了短板。后来我养成了一个习惯从真实用户提问中收集100个问题覆盖配置查询、故障排查、概念解释、边界问题等类型每一条都要人工写好期望答案然后每次改动跑一遍全集记录通过率。这个“黄金问题集”就是AI项目的回归测试。有了它你就可以放心调提示词、换检索策略、换模型版本因为每次改动都能看到一个数值变化而不是凭感觉说“好像变好了”。我从最初的60%左右一直调到最后90%以上每一步都有据可依。没有测试集AI工程就是玄学。6.2 从badcase里找到优化方向我把失败的案例按原因分类基本上三类为主检索没召回、模型没理解、引用不对。每一类的优化路径完全不同。检索没召回就去调切块策略或混合检索模型没理解就去改提示词、增加few-shot示例引用不对就加强后置校验。分类的好处是不会一个问题调了一整天还摸不着头脑。排查badcase我有一套固定顺序先看日志里检索返回了哪些chunk再检查组装的最终prompt长什么样最后单独测试模型输出。大多数“模型很蠢”的情况查到最后都是检索环节就把正确答案漏掉了。链路长就是有这个麻烦所以必须把所有中间结果都记录成日志没有日志调试AI应用会非常痛苦。6.3 监控哪些指标才有价值系统指标我只看四类问答时延、失败率、GPU利用率、队列长度。业务指标我会记录用户的问题是哪些类型的、答案有没有被点踩、用户有没有追问“你确定吗”。AI应用和传统软件不一样只盯着系统可用性不够因为模型可能每次都答得很流畅但内容完全跑偏这比服务崩溃更隐蔽。我后来加了一个很笨但有效的机制每周抽10%的对话记录人工听一遍打分记录。模型答得流畅但内容不存在这种“高质量幻觉”只有人工抽查才能发现。把这些记录反馈回黄金问题集再推动下一轮优化。这套闭环跑起来之后这个项目才真正从“能跑”变成了“能用”。6.4 多AI协作的演进方向单问答机器人稳定之后我开始考虑更复杂的编排一个Agent负责检索一个Agent负责生成草稿还有一个Agent负责质检并附上修改建议。多Agent协作的价值在于职责分离每个模型只需做好一件小事出错也容易定位。但我的建议是不要一步到位。如果单Agent链路还没跑顺就上多Agent出了问题你根本不知道是检索Agent的问题、生成Agent的问题、还是编排逻辑的问题。先把一条链走到稳定再拆出子Agent每一步都要有评测数据支撑。工程上的稳妥永远比架构上的炫技重要。整个项目做下来我最深的体会是从零做AI工程真正花时间的地方不是“调用模型”而是让模型在真实场景里稳定达到预期。提示词、数据切块、服务治理、评测迭代这些才是决定项目成败的细节。如果你也在从0开始做类似的事情记住一点先让一条极简的链路跑起来再对着真实用户反馈一点点打磨。AI工程没有一蹴而就只有不断逼近稳定。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进