从零搭建AI工程:模型服务、RAG、Agent与评测全流程实战
很多人一听到AI工程和from scratch脑子里蹦出来的第一个画面就是从头训练一个大模型。我之前也是这样想的直到真正上手做了一个完整的项目才发现从零开始这句话最容易被误解。它不是让你从随机权重和几TB语料起步而是让你从一个空仓库、一条命令行、一段业务需求出发把模型选型、数据管线、推理部署、RAG检索、Agent编排、质量评测这一整套东西老老实实搭起来。这篇文章我把完整过程拆给你看包括每一步的取舍理由和踩坑记录适合那些准备自己动手搭AI系统但还在纠结到底该从哪开始的人。1. 结论先行真正需要从零搭建的是什么1.1 先砍掉一个执念你不需要从权重开始训我见过不少人在项目启动会上拍板我们要自己训练一个大模型理由是这样才有壁垒。这种想法很容易把项目拖入泥潭。自己从零预训练一个可用的大模型意味着你要解决数据清洗、集群调度、分布式训练稳定性和动辄几十万算力账单的问题等你把模型跑起来窗口机会已经过去了业务部门的耐心也耗完了。我在这个项目里做的第一件事就是把from scratch重新定义从空代码仓库开始围绕已有的大模型能力搭建一整套工程体系。模型本身用开源权重作为底座有需要再微调工程侧的工作才是我真正的从零搭建——推理服务、数据切分、向量检索、工具调用、评测回归这些才决定了你的系统能不能稳定跑在生产环境里。想清楚这层区分之后思路一下子就通了。你不是在和OpenAI打模型能力之战而是在和如何把模型能力变成可靠产品这件事较劲。这个目标更现实也更值钱。1.2 用一个决策矩阵约束你的技术选型动手选型前我习惯先把需求拆成三个问题输出什么、给谁用、出错代价有多高。这三个问题的答案能帮你少走很多弯路。比如做内部知识库问答回答质量要求中等、出错代价有限那开源模型加RAG就够了如果是面向客户的实时Agent要考虑延迟和幻觉风险那可能得闭源API兜底、再加一层人工审批。下面是这个项目实际的选型对照表你可以直接参考方案启动成本数据控制力迭代速度适用场景完全自训极高最强最慢特定领域数据极其密集且算力充裕开源权重微调中高强中需要私有化部署、领域术语要求高闭源API调用低弱最快快速验证、通用场景、成本可控我最终选了第二行开源权重起步部分环节接闭源API做对照。这样既能把数据留在内网又能在我控制范围里摸清模型的行为边界。选型不是站队是在成本、控制和速度之间找出你当前最需要的那个平衡点。这一步想清楚后面的每个操作都有依据。2. 把模型变成服务最小可用闭环的搭建顺序2.1 部署引擎的选择与第一次启动模型权重拿到手之后你会发现它只是一堆参数文件离能用的服务还差着十万八千里。第一步是把权重加载到一个推理引擎里我这边用的是vLLM原因很朴素它的continuous batching能明显提高吞吐在同样的显卡上跑更多的并发请求这直接关系到单用户的推理成本。启动命令比想象中简单但有几个参数需要提前想清楚否则后面会反复调python -m vllm.entrypoints.openai.api_server \ --model /data/models/your-base-model \ --served-model-name your-model \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.90 \ --max-model-len 8192max-model-len这个参数容易被忽略。设太大显存直接被上下文占满设太小用户的超长文档根本没法处理。起步阶段我建议按业务场景里最长的单次输入估算一般4096到8192足够。启动之后用一行curl验证接口连通性和首token延迟确认无误后再接入业务层。2.2 服务端的封装不要只做一个裸转发vLLM默认暴露的是OpenAI兼容接口能直接curl调通但如果直接把这个端口丢给业务系统你会很快遇到几个问题客户端没有参数校验、错误信息不友好、超时控制缺失。我的做法是在前面加一层FastAPI封装把对外API和内部推理服务剥离开。封装层至少要做到三件事第一对输入做schema校验缺字段或类型错误直接返回400而不是等到模型报错第二统一管理超时和重试LLM接口是概率性服务偶发超时正常重试两次能显著提升成功率第三把流式输出单独做一条路径。对话类产品对首token延迟非常敏感非流式要等全量生成完才返回体验太差。核心逻辑大概这样组织from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): messages: list[dict] temperature: float 0.2 max_tokens: int 2048 class ChatResponse(BaseModel): content: str finish_reason: str这层封装花不了多少时间但它决定了你后面接RAG、接Agent时是优雅地扩展还是到处打补丁。我吃过亏先用裸接口跑demo业务接入时被迫改服务端签名连带前端一起返工。工程上的克制一开始就能省下后面好几天的返工成本。2.3 实测中的延迟与成本数字比感觉可靠服务跑起来之后别急着优化prompt先做一轮基准测试。我用200条真实业务请求统计了并发16和并发32两种情况下的首token延迟、单条总时延和吞吐。结果给了我两个意外一是并发翻倍后吞吐并没有翻倍说明显存带宽先到了瓶颈二是单条总时延波动非常大最长和最短差了好几倍原因主要在输入序列长度差异导致的计算量不均。这个测试环节看起来不性感但它给你的容量规划提供了硬数据需要多少显卡、QPS能支撑到多少、延迟能不能满足SLA。在这个阶段把时间和精力花在量化系统边界上比花在调prompt让他更聪明上划算得多。系统连稳定运行都做不到模型再聪明也没有用。3. RAG永远比你以为的复杂检索管线的真实工作量3.1 切分是第一个大坑没有万能窗口很多人提到RAG第一反应是把文档丢给embedding模型再塞进向量库。我最早也这么干过用了固定512字符的滑窗切割结果检索出来的片段经常内容断在半句话上答案七零八落。问题根源在于纯固定窗口切分完全无视了文档自身的结构。后来我改成按文档结构优先切分先看有没有markdown标题、表格、list结构按照章、节、段落来定切分边界遇到超长段落再二次切分并保留前后重叠区。这才是工程上稳妥的做法。切分不是洗数据是在为后续检索建立合理的语义单位——保证每个片段尽量是一个完整的论点。切分粒度也需要调试。颗粒太细上下文信息不完整颗粒太粗检索命中后塞进prompt会超出模型上下文限制还容易稀释关键信息。我现在的策略是默认按语义段落切段落超过800字就二次切切完后保留一段50字的上下文重叠。3.2 Embedding与向量库混合检索比单一向量稳得多模型选型上不要只看embedding模型的公开榜单分数一定要在你自己的领域数据上做验证。通用场景选一个对中英文双语支持好的开源embedding模型配合normalize_embeddingsTrue把向量归一化后面算余弦相似度会更稳定。向量库我用的是Qdrant部署轻、过滤条件好用也支持payload字段存储元数据。但真正让我涨经验的是向量相似度没你想的那么万能。它擅长语义相近但在关键词精确匹配、编号、型号这类场景上经常翻车。用户问ABC-123怎么配置embedding检索可能把ABC-124也拉进来。所以我在管线里加了一层BM25关键词检索和向量检索的结果做RRF融合效果立竿见影。工程上的可靠答案往往是多路召回互相补位而不是迷信某一种算法。3.3 用你自己的评测集验证检索效果构建检索管线之后需要一个量化的信号告诉你改动是否有效。我从业务反馈里挑了100个典型问题人工标注了每个问题对应的标准文档片段构成一个微型评测集。然后算三个指标hit_rate、recallk和MRR。一段参考计算逻辑def compute_recall_at_k(retrieved: list[str], gold: list[str], k: int) - float: hits [doc for doc in retrieved[:k] if doc in gold] return len(hits) / len(gold) if gold else 0.0这组数字非常有用。版本A的recall5是0.62加入BM25混合召回后涨到0.78我才放心继续往下做。没有这个评测集你可能被一两次手动的看起来变好了欺骗。RAG的优化必须建立在可重复的指标上否则后面每改一次切分逻辑都像在赌运气。4. Agent化改造从能答问题到会完成任务4.1 工具调用的核心给模型一份清晰的函数清单我基于大型语言模型做Agent时最大的认知变化是Agent不是更聪明的对话机器人而是一个会查工具清单、会填参数、会看结果的执行系统。第一步是定义好工具集。给模型的工具描述越准确调用成功率越高。描述里要写清楚这个工具做什么、每个参数的类型和含义、什么情况下不该用这个工具。工具定义示例{ name: create_work_order, description: 在工单系统创建一条维修工单, parameters: { device_id: {type: string, description: 设备唯一编号}, issue_description: {type: string, description: 故障描述}, priority: {type: string, enum: [low, medium, high]} } }模型拿到这份清单后输出格式化的工具调用请求你的代码负责解析、执行、把结果返回给模型。这里必须强调所有实际操作步骤一定要有完整的执行日志和审计Agent架构再花哨也不能绕过真实的权限校验和数据合规逻辑。工具的每一次执行都要有对应的业务追踪记录。4.2 手写循环还是上框架我的选型思路市面上有LangGraph、LlamaIndex等Agent编排框架开箱就能用。但我在这个项目里没用完整的Agent框架而是手写了一个约100行的循环原因是业务只有六七个工具流程以任务规划-逐步调用-结果汇总为主手写循环的调试难度远低于框架的抽象层。核心循环是这样的def agent_loop(query: str, max_steps: int 5): messages [{role: system, content: SYSTEM_PROMPT}] for step in range(max_steps): messages.append({role: user, content: query}) response chat(messages, toolsTOOLS) tool_calls parse_tool_calls(response) if not tool_calls: return response.content for call in tool_calls: result execute_tool(call) messages.append({ role: tool, tool_call_id: call.id, content: format_result(result) })这个循环简单但有效。关键约束有两条一是max_steps必须设上限防止模型陷入死循环二是每条工具返回结果要以结构化文本拼装避免大段原始输出污染上下文。Agent框架的编排能力当然更强但当你的工具列表只有个位数、流程基本固定时自己握紧控制权远比引入复杂抽象划算。等你手上有几十个工具、需要考虑并行规划时再考虑框架也不迟。4.3 可控性的底线不能做的事要前置拦截Agent化之后系统开始拥有行动力这带来一个工程层面的问题能做和该做是两回事。我在实践里加了三条防火墙第一高危工具只读化处理或需要人工二次确认第二模型输出中的工具调用参数必须做schema校验非法参数直接拒绝执行并提示模型重新规划第三所有Agent决策路径存trace方便事后回溯。这一点想明白很关键。Agent容易出事的场景往往不是模型能力不够而是边界不清。与其让模型在提示词里自觉遵守规则不如在工程层面把禁忌操作直接挡在门外。系统给人可控感的基础不是你有多强的模型而是你的代码在多深的层次上设了护栏。5. 评测和回归是你最该提前做的地基5.1 评测集从客服聊天记录里挖出来的真实问题很多项目做到中途会陷入一个困境每次改模型、改提示词效果到底是变好了还是变差了全靠主观体感。这种状态不可持续。我在项目进行到第三周时停下来从客服聊天记录和用户反馈里收集了300个真实问题按难度分三层基础事实题、复杂推理题、需要多步工具调用的任务题。每道题都标注了标准答案或至少一个可接受的答案范围。这一步极其繁琐但是整个项目里回报率最高的投入。测评集是你在后续迭代中最可靠的事实来源。没有它你评估模型A和模型B时只能靠一种感觉这个好一点的模糊体验这种感觉在心情不同的时候可能是反的。评测集把感觉变成了分数让迭代方向有了客观依据。5.2 LLM-as-judge的坑别让裁判自己带节奏对很多开放式问题标准答案没法做精确匹配我用了LLM-as-judge也就是让一个更强的语言模型担任裁判对比模型输出和标准答案的语义一致性。这个方法有效但要小心两个问题一是评委的位置偏差标准答案放在前还是后会影响打分所以同一批评测要固定顺序或交换顺序跑两遍取平均二是评委不能和被测模型是同一个否则会有自卖自夸倾向。这里还遇到过一个很有意思的现象温度参数设为0的时候同一个prompt多次调用结果也会有小幅差异。原因是并行计算时的浮点求和顺序不同会让结果在极小概率上出现波动。所以评测时每条测试用例至少要跑两到三次取多数票或平均值而不是只跑一次就记录分数。评测要有效第一步就是承认模型输出天然有波动这件事然后用过程设计把它压平。5.3 让评测跟着代码走回归测试平台化评测集建好之后如果只是手动跑跑价值会大打折扣。我把评测脚本接进了集成测试体系每次修改提示词、切分策略、Agent工具定义时都自动跑一遍评测集然后把指标对比上一次的基线。这相当于给整个系统装了一块仪表盘。这个做法带来的最大影响是你终于敢做实验了。以前改一个参数担心隐性问题导致线上出事故现在可以先跑评测分数不降再上线。RAG检索指标、工具调用成功率、最终答案质量三类指标分别看。评测跑完如果RAG recall掉了但最终回答分数没变说明当前瓶颈不在这如果答案分数掉了就去检查召回内容变化。一套完整的评测体系让优化过程从碰运气变成了可定位的系统工程。6. 落地以来踩过的坑和我的最终取舍6.1 上下文窗口不是越大越好我一度觉得只要把max-model-len调大就能把更多文档塞给模型直接理解。实践下来发现完全不是这样。上下文超过一定长度后模型对中段信息的关注度明显下降你塞得越多关键信息反而越容易被稀释。工程上正确的做法恰恰是把信息筛选好再送进去让上下文里每一段都有明确的用途。把RAG做好本质上就是给模型做信息减负。6.2 温度参数是幻觉的隐形开关很多看起来莫名其妙的幻觉根源不在模型而在温度。我们把温度从0.7降到0.1之后编造过多次的历史记录条目显著减少。原因很简单温度高意味着采样更多样也就更容易跳出高概率合理路径去生成那些听起来合理但其实是编的内容。对事实问答、信息查询这类场景温度设低是减少幻觉最便宜的手段只有创意生成场景才值得把温度调高。这个参数改动简单但很多人忽略了。6.3 Prompt叠太多的恶性循环项目中期我犯过一个典型错误为了提升某个指标不断往系统prompt里追加规则从你是客服助手一直写到请记住以下二十条注意事项。结果效果不但没变好基础能力反而下降了。后来我意识到提示词不是规则堆砌它要帮助模型理解目标而不是把模型变成规则的傀儡。每加一条规则之前先问自己这条规则能不能通过工具逻辑、工程校验或评测集来解决能在工程层解决的问题就不要压在提示词层。这个思路后来成了我自己的一个准则提示词只负责定义角色、输出格式、明确边界其余能算的、能拦截的、能校验的统统让代码来做。提示词减到原来的三分之一之后各项分数反而稳中有升。6.4 最后分享一个体验上的细节整个项目做下来我自己最大的体会是AI工程的核心竞争力不在于谁调的模型更聪明而在于谁的管线更可靠、谁的反馈更快速。当你把评测、监控、权限和重试这些工程地基打好之后上面长什么模型都是时间问题。而如果地基松动换再强的模型也救不回来。这个项目走到今天我依然在调整RAG切分、Agent工具描述和评测集扩充这些看似琐碎的东西但这些琐碎的东西组合起来就是系统的真正护城河。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →