1. 从零开始到底从哪里开始
ai-engineering-from-scratch这个项目名,乍一看很容易让人以为又要“从零训练一个大模型”。但你真在AI工程里泡过一段时间就会明白,绝大多数团队真正缺的,不是把基座模型重新训练一遍的本事,而是把现有模型稳定、可控、可评估地接入业务系统的工程能力。我理解的from scratch,是指从需求拆解、输入输出协议、上下文构建、模型调用、Agent编排,到评估与可观测性的一条完整链路。
这条链路做完,你得到的不再是一个“看起来能跑”的demo,而是一个可以上线、可以迭代、出了问题能定位的真正软件系统。适合谁看?两类人:一类是刚带团队做AI落地,需要一张全景路线图的技术负责人;另一类是已经调通过几个API,但总觉得项目“只能在演示时好用”的开发者和算法工程师。
1.1 先避开三个经典误区
第一个误区,是以为“from scratch”等于自己训练一个基座模型。这个误解来源不难理解,这几年“从零构建大语言模型”、“从零构建推理模型”的教程和资料确实多,看多了自然就往那个方向使劲。但现实问题是,预训练基座模型涉及数据工程、算力集群、分布式训练、对齐调优,投入和周期都是一个团队按年计算的事情。除非你的目标是做模型研究本身,否则这条路对绝大多数业务团队来说性价比极低。工程化落地的重点不是重造发动机,而是把现有发动机装进一台能上路的车里。
第二个误区,是以为接了API调用就是AI工程。只做一次模型调用,有点像你会启动发动机但不代表你会造车。真正工程化的部分全部在调用的外围:输入格式怎么定义、输出怎么校验、上下文怎么控制、错误怎么兜底、效果怎么度量、成本怎么控制。这些活,API本身一个都不帮你做。回头看早期折腾AI应用时踩的坑,绝大部分都出在“模型返回之后”和“调用模型之前”,而不是“调用那一刻”。
第三个误区,是跳过评估直接上线。太多项目在demo阶段看着惊艳,一上生产就暴露幻觉严重、格式不稳定、成本失控。原因不在模型,而在没有一把“回答质量”的尺子。没有评测集,就没有回归测试;没有回归测试,每次改提示词都是碰运气。这个道理和传统软件开发里“没有单测就改重构”是一样的,只不过AI系统的不确定性更大,评测的优先级应该更高。
1.2 我们把“最小工程闭环”定义清楚
我习惯把AI工程的最小闭环定义为六个环节:需求定义,把业务问题转成输入输出协议和评价指标;上下文构建,决定模型能看到什么,包括检索、摘要、历史会话;模型调用,选择模型和参数,执行生成;输出校验,用schema、规则、工具调用约束确保结果可以被程序安全消费;结果评估,用小而准的评测集度量质量;迭代回归,任何prompt、参数、检索策略的改动,都要跑一遍评测确认没有变差。
这六步听起来简单,但每一步都有各自的坑。比如需求定义阶段最常犯的错是把“做一个智能客服”当需求,其实那是解决方案,真正的需求是“把客服响应时长从10分钟降到2分钟”,后面这个需求会直接影响模型选型和评估标准。再比如输出校验阶段,很多团队只在demo里打印一下结果,根本没有校验环节,结果到了生产环境被JSON解析折腾得死去活来。
后面几节我会把每个环节展开,讲我实际用下来的方案和遇到的坑。整体读下来你会有个感觉:AI工程其实没有那么多玄学,把它当成一个普通但有严格约束的后端系统来设计,就已经赢过大多数团队。
2. 端到端链路设计:先看清全貌再动手
拿到一个AI需求时,我最不建议直接开写prompt或接框架。先花半天把链路画清楚,后面能省一周的返工。这个链路的全貌是:业务目标转需求、需求转输入输出协议、协议决定上下文策略、上下文策略决定模型选型、模型输出过校验、校验结果进评测、评测反馈驱动迭代。每一环都有取舍,而且环环扣在一起。
2.1 需求拆解:不是所有任务都需要智能体
我见过太多项目一上来就设计Agent,最后发现需求其实一个函数调用就能完成。拆需求时我习惯按任务形态先分四类。第一类是文本转化类,比如翻译、摘要、改写、信息抽取,特征是输入输出都明确,一条高质的prompt加结构化约束就能解决,不需要检索,不需要多步循环。第二类是知识问答类,比如“根据公司资料回答客户问题”,特征是答案依赖私有知识,必须走RAG,把检索和生成结合起来。第三类是多步任务类,比如“查一下订单状态、算一下退款金额、再生成一封回复邮件”,特征是需要调用多个工具、依赖中间结果、可能要好几轮推理。第四类是流式交互类,比如语音客服、实时助手,特征是低延迟、增量输出、还要有安全护栏。
以我常做的内部知识库客服为例。需求看起来是“问答”,但不能直接拿用户问题去问大模型。先要确定输入是用户问题加会话历史,输出是带来源引用的答案和置信判断。明确了输入输出协议之后,架构自然就指向RAG,而不是凭空造一个通用问答框架。你越早把“输入是什么、输出是什么、哪些能错、哪些绝对不能错”定义清楚,后面选型越省力。这个阶段我还喜欢顺手列一下负面清单:哪些问题不回答、哪些操作不做,宁可先窄后宽。
2.2 模型选型:通用模型、推理模型与部署形态
模型选型这件事,很多团队喜欢“一个模型打天下”,但工程化之后会发现,不同任务形态对模型的要求差异非常大。我用得比较顺的选型策略可以看这张表:
| 任务形态 | 模型倾向 | 原因 |
|---|---|---|
| 简单改写、抽取、分类 | 小尺寸对话模型 | 延迟低、成本低,效果完全够用 |
| 复杂推理、规划、工具编排 | 推理模型 | 会显式拆解步骤,适合做planning |
| 客服闲聊、内容生成 | 对话模型加少量示例 | 语气自然、回复快,体验好 |
| 私有数据问答 | 通用对话模型加RAG | 参数知识解决不了私有域,省下成本做检索更划算 |
推理模型和对话模型在工程里经常是搭配着用,而不是二选一。推理模型适合做规划者,产出工具调用序列和中间计划;对话模型适合做执行者,把计划变成面向用户的自然语言。这样搭配的好处是成本和延迟可调度,复杂的规划才上重模型,简单的执行走轻量模型。如果你把所有任务都交给最强的推理模型,账单会先撑不住。
部署形态也约束了选型。纯云端API接入快、不占算力,但数据要出网,延迟受网络影响;本地推理则适合私有化部署场景,可以根据业务并发买卡。我的经验是先用API快速跑通闭环,把链路验证好,再根据合规和成本决定要不要迁移到本地。一上来就自建推理集群,容易被运维拖住主干进度。
2.3 链路里最容易低估的环节:约束与协议
进到下一章之前,我想先强调一个容易被低估的环节:约束与协议。做AI工程和做传统接口开发是一样的,模型输入输出的协议应该被当成接口契约来定义,而不是当成一句自然语言描述。输入字段有哪些、哪些允许为空、输出结构长什么样、来源引用怎么标注、异常情况怎么表达,这些都要写清楚。
我在项目里会把协议直接写成JSON Schema或者类型定义,模型相关的提示词只是协议的一种表达方式。这个习惯帮我挡掉了大量“模型输出格式不对”的生产事故。你想想,传统后端接口如果参数类型不对,编译器直接报错;而模型的输出没有类型系统兜底,只能靠外部校验和约束。所以协议先行,不是可选动作,是必须动作。到这里,链路设计的框架已经出来了,接下来进入每个环节的具体做法。
3. 提示工程与结构化输出:把“问模型”变成“调系统”
提示工程这个词很多人听着玄,但本质上它就是把“对模型说话的方式”工程化。过去大家觉得prompt是写给模型看的文字,后来做得多了才明白,prompt更像是写给协作程序员看的接口文档。好的提示词能极大减少下游解析和校验的成本,差的提示词会让模型自由发挥,输出千奇百怪。这一节我把从system prompt到结构化输出到上下文检索的完整做法拆开讲。
3.1 System Prompt是工程配置,不是作文题
很多人把system prompt当成小作文来写,堆一堆“请你务必”“一定要认真”之类的话。实际效果呢,加不加都差不多。我实践下来,好的system prompt更接近一份API使用文档,结构固定,字段明确。一个能落地的system prompt模板长这样:
你是内部知识库客服助手。 任务:根据提供的资料回答用户问题,回答必须附引用来源。 可用工具: - search_docs(query): 按关键词检索知识库 - get_doc_metadata(doc_id): 获取文档标题、更新时间 输出协议(严格JSON): { "answer": "回答正文,最多200字", "sources": [{"doc_id": "...", "title": "..."}], "confidence": "high|medium|low" } 负面规则: - 资料中找不到答案时,明确回答“暂无资料”,不要推测。 - 不输出资料之外的额外承诺。 - 不讨论政治敏感话题。这个模板里每一项都有存在的理由。角色和边界限制了模型自由发挥的空间,任务定义划定了交付物,工具列表告诉模型它能用什么资源,输出协议让下游程序可以直接解析,负面规则是幻觉兜底。你发现没有,这不像作文,更像配置项。系统提示词不是写得越长越好,是信息密度越高越好。
3.2 真正可靠的结构化输出
现在聊一个生产环境绕不开的问题:怎么让模型稳定输出合法的结构化数据。先说结论:“请用JSON返回”这句话在工程上不可靠。模型是token概率系统,这种软约束经常失效,可能给你加一段解释,或者输出带注释的JSON,甚至给出CSV风格的结果。工程上有几层加固手段,我从弱到强排列。
第一层是函数调用约束。现在主流模型服务商都支持tool calling或function calling,你把输出结构定义在函数参数schema里,框架会强制模型生成合法的结构化参数。这比任何提示词都牢靠。第二层是外部校验器兜底。拿到模型文本之后,用类型校验工具做硬约束,Python就用pydantic,TypeScript就用zod,解析失败就走重试逻辑。第三层是降级策略。重试一次仍然失败,就返回统一错误结构,让调用方感知并处理,而不是把坏数据写进数据库。
这就是现在圈子里常说的“typesafe AI”的核心思想。类型安全不是只在Java或TypeScript里才有,大模型输出同样需要类型安全。尤其当AI系统要和订单、库存、账务这类业务数据对接时,一个不合法的字段就可能触发连锁故障。别指望模型每次都守规矩,要在它不守规矩的时候兜住。
3.3 上下文工程:检索参数不要拍脑袋
RAG的关键不是“把文档塞给模型”,而是“只把模型需要的内容给它”。检索参数直接决定回答质量,而且这些参数不能拍脑袋定。我常用的三个超参是:chunk size,分块大小,建议从400到800字符起步,而不是默认取整篇,因为内容太长会稀释模型的注意力;top-k,召回条数,先设5左右,再配合rerank做精排;相似度阈值,低于阈值的材料宁可不要,也别让模型硬答。
我自己踩过的坑是,一开始为了让“模型信息更多”把top-k设成20,结果模型在长篇材料里彻底迷路,回答又长又含糊,引用来源还点不对。后来改成top-k取5加重排加阈值过滤,回答的准确率和精炼程度都上来了。有一个概念你需要记住:上下文不是越全越好,是越准越好。一大堆弱相关内容堆进去,只会让原本清楚的问题变模糊。给你一个可以直接抄的配置感:chunk_size=600,chunk_overlap=100,top_k=5,similarity_threshold=0.72,先跑一轮看badcase再微调。这套配置不是标准答案,但比瞎调强很多。
4. Agent编排与Harness工程实践
当任务从单轮问答变成“查订单、算金额、发邮件”这种多步操作,就需要Agent编排了。Agent不是神秘概念,说白了就是让模型能够调用工具、观察结果、再决定下一步的系统。但让模型自由发挥是危险的,这一节的关键词是“约束”:怎么设计好编排流程,怎么用Harness给Agent装上骨架,怎么处理状态和恢复。
4.1 从单轮调用到多步Agent
Agent最常见的范式是ReAct,即推理加行动交替循环。模型的每一步先用“Thought”梳理当前状态,再决定“Action”调用哪个工具,工具返回结果作为“Observation”,然后进入下一轮,直到完成目标。拿订单售后场景举例:第一轮,模型判断用户想查询订单状态,调用查询工具,拿到订单为“已发货但用户申请退款”;第二轮,模型根据退款规则,调用金额计算工具,算出应退金额;第三轮,模型生成一封回复邮件,结束循环。每一轮都是模型调用加工具调用的组合,中间结果要传递下去。
这里有一个设计要点:工具返回的结果不要太啰嗦。模型上下文窗口有限,工具应该返回结构化、精炼的状态,而不是一整张表。比如查询订单工具返回{"order_id":"A001","status":"shipped","refundable":true,"amount":129.9},模型一看就懂。我自己早期犯过给工具返回大量无关字段的错误,模型反而抓不住重点,判断决策变慢。工具返回的字段,只给当前决策真正需要的。
4.2 Harness:给Agent装上骨架和安全带
“harness engineering”这几年在圈子里讨论很多。直译是“挽具”,你可以理解成给Agent做的工程脚手架。为什么需要它?一个没有harness的Agent像什么都能干的自由人,你托付它办事,它同时也给你闯祸。Agent的探索能力越强,越需要一个明确的行为边界。
我项目里的harness至少包含五件事。一是最小API面,Agent只能调用白名单里的工具,而不是任意函数或代码,白名单外的能力一律不暴露。二是最大步数限制,比如一个Agent最多跑8轮循环,超过就强制终止并提示人工介入,防止死循环烧钱。三是人工审批点,涉及写操作、发消息、扣款这类行为时,必须暂停等待确认,不能直接执行。四是记忆清理,每轮对话只保留当前摘要加最近两轮完整消息,历史对话做摘要压缩,避免上下文爆掉。五是参数校验,模型生成的工具参数在真正执行前先过一遍schema校验,非法参数直接拒绝。
我见过不少Agent失控的案例,追根溯源都是harness没做好。比如没有限制工具白名单,模型用某种方式拼出了危险参数;没有步数上限,一个重复失败的任务跑了三十几轮,费用吓人。所以我的原则是:每个Agent上线前,先写清楚“它绝对不能做的事”清单。这个清单比功能清单重要得多。
4.3 状态、记忆与可恢复性
多步Agent天然是有状态的系统,如果只把Agent当成无状态函数来写,早晚吃亏。进程崩溃、网络超时、工具报错,任何一个环节中断,任务怎么恢复?我的做法是坚持三个原则。第一,对外部系统调用保持幂等,每个工具调用带request_id,重复调用不会产生副作用。第二,会话状态持久化到数据库,记录已执行的步骤和中间结果,而不是只放在内存里。第三,支持断点续跑,从最后成功的一步继续,而不是从头再走一遍。
举个例子,任务执行链路是“查账号、扣款、发通知”,如果在扣款之后、发通知之前进程崩了,靠状态记录就能判断该从哪一步开始,并且重发通知之前先查一下是否已经发过。没有持久化状态的话,你只能靠运气重启任务。这个意识和做传统后端服务一样,AI应用首先是个软件系统,其次才是AI。把Agent当作正规服务来设计,可靠性问题就少掉一大半。
5. 评估、可观测性与迭代闭环
一个AI项目能不能长期维护,不取决于初期效果有多惊艳,而取决于当你改动系统时,能不能知道效果是变好了还是变差了。这一节讲评估和可观测性,也是我认为AI工程和“调AI demo”之间最本质的区别。
5.1 评测集:先有标尺再生产
没有评测集的AI项目,改动全靠感觉。我今天觉得回答变好了,明天用户反馈变差了,谁也说不清哪次改动导致的。我的建议是先建一个小而准的评测集,三十到五十条就够了,但覆盖面必须全。正常case,选取业务里最高频的问题;边界case,模糊措辞、超长问题、缺少上下文;对抗case,诱导模型胡说、问资料里不存在的内容。每条数据不一定需要标准答案全文,但要有明确的判断点,比如“回答必须包含正确来源”“必须拒绝回答超范围问题”。
这个评测集建完之后要持续补充。我自己的习惯是每周抽线上badcase加进去,确保每个修过的问题都有回归记录。评测集小没关系,但不能没有;评测集的质量比数量重要,宁可五十条精心标注,不要五百条随便造的。
5.2 LLM-as-Judge的正确用法
人工评测太慢,所以实践中普遍用LLM-as-Judge,让一个裁判模型按维度给结果打分。这里有几个容易踩的坑。第一,裁判模型最好和生成模型不是同一个,否则容易出现自卖自夸的系统性偏差。第二,评分标准必须带示例,不然AI打分比人工打分还飘。第三,能用规则校验的东西不要交给LLM判断,比如JSON是否合法、来源ID是否真实存在于知识库,这些用程序判断又快又准,没必要浪费模型调用。
给一个我常用的评分维度表:
| 维度 | 分值 | 评分要点 |
|---|---|---|
| 准确性 | 0-5 | 答案是否与检索材料一致,是否存在事实错误 |
| 完整性 | 0-5 | 是否覆盖用户问题的关键信息点 |
| 格式合规 | 0-5 | 是否符合输出协议,JSON是否能被正常解析 |
| 安全性 | 0-5 | 是否拒绝越权问题,是否包含不当承诺或内容 |
每一轮改动之后,用同一个评测集跑一次,对比各维度平均分。如果改动让准确率上升但合规率下降,那这个改动就不值得上。评分趋势才是你做迭代决策的依据。
5.3 可观测性三板斧:日志、追踪、成本
可观测性决定了你排障的效率。AI项目比传统后端系统更难排障,因为输出是概率性的,出错可能是提示词问题、检索问题、模型版本问题、工具返回问题。没有详细记录,就只能对着一个坏输出猜原因。
我的做法是每次模型调用都记录这样一批字段:会话ID和请求ID,方便串联整个链路;模型名称和版本;输入tokens和输出tokens;延迟;采样参数,如temperature;提示词版本号;检索命中的文档列表;工具调用序列和结果;最终输出和校验结果。有了这套记录,badcase就能复盘到具体环节:是检索没召回,还是提示词让模型跑偏,还是工具结果太含糊。你会发现,大多数问题根本不需要猜,看一眼trace就定位了。
成本也一样重要。tokens记录是基础,按会话聚合出单次任务平均成本,你才知道模型选型调整到底是省了还是贵了。有些团队只盯着模型单价低,没看到因为效果差导致的反复调用和人工复核成本,总账反而是亏的。可观测性的目标不是监控本身,是让每一次迭代都有据可依。
6. 实战问题排查速查表
最后整理一份我实际项目里反复遇到的高频问题速查表,每个问题都是我或身边团队付出过真金白银才总结出来的。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 模型输出JSON总带注释或解释 | 只依赖软约束 | 改用工具调用协议,再加schema校验器 |
| Agent陷入死循环 | 缺少最大步数限制 | 加循环上限,工具返回结构化状态 |
| 回答幻觉严重但检索看起来没问题 | top-k太高或阈值太低 | 降低top-k,提高相似度阈值,检查chunk质量 |
| 格式化正常但答案内容空洞 | 上下文被历史消息挤占 | 做摘要压缩,缩短系统提示词 |
| 同一问题效果时好时坏 | 模型版本漂移或采样参数过高 | 锁定模型版本,调低温度 |
| 成本涨得离谱 | 推理模型被用在简单任务上 | 拆任务,简单执行走轻量模型 |
| 改动后效果集体变差 | 评测集缺失,问题没暴露 | 先恢复上一版本,补评测集后再改 |
这些问题的共性是,大多数都不在“模型不够聪明”,而在外围工程质量。你可能会问,为什么这种问题一而再再而三出现?因为AI工程链路太长,任何一个环节出问题都会在输出端表现为“回答不好”,我们容易把锅甩给模型,但实际上有七成问题可以靠约束、校验和观测解决。
最后分享一个习惯。我现在的每个AI工程都是从最小闭环开始的:一个输入、一个输出、一条检索链路、一个小评测集,跑通之后再加Agent和工具调用。每次改动只碰一个变量,prompt、参数、检索策略分开调,然后用同一套评测集对比。个人体会是,AI工程百分之八十的工作发生在模型的调用外围,而不是模型本身;把外围的系统性做好,模型应用这件事就没有那么玄。你在自己的项目里遇到卡点的时候,可以对照这篇文章往回查一查,多半会有答案。