1. 先理解“AI工程”到底在做什么
拿到“ai-engineering-from-scratch”这个题目,我第一反应是:现在市面上太多人把“调一个API、写一段提示词、跑通一个Demo”误当成AI工程了。从零开始做AI工程,首先要纠正的就是这个认知偏差。
我理解的AI工程,是一套把模型能力稳定落地的系统方法。它包含数据怎么准备、模型怎么选、提示词怎么管、Agent怎么编排、效果怎么评估、线上怎么监控、成本怎么控制。关键词是“稳定”,不是“跑通一次”。跑通一次只需运气,稳定输出需要工程。
这几年我带过不少从算法、后端、测试转过来的朋友,也带过完全零基础的新人。我发现起点差不多的两个人,半年后差距可以拉得非常大。差别不在谁更聪明,而是谁把AI当成“工程问题”来处理,而不是当成“魔法问题”来碰运气。一个把提示词当一次性试验品,一个把提示词当成需要版本管理、回归测试、线上监控的软件资产;一个让Agent自己瞎跑,一个从第一步就给Agent设计状态机和护栏。结果自然天差地别。
这篇文章,我想把从零开始做AI工程的那条主线完整讲一遍。适合两类人:一是刚入行、想系统建立AI工程能力的新人,二是已经做过一些AI项目、但总觉得效果不稳定、想补齐工程短板的后端或算法同学。我不讲天花乱坠的架构,只讲我实际落地验证过的思路、步骤和坑。
2. 第一步不是写Prompt,而是把最小闭环跑通
很多人犯的错是:项目一启动就扎进Prompt调参,调了三个小时看起来不错,然后发现数据格式不对、模型接口限流、输出解析不了,整体又要返工。正确做法是先跑通一个最小的完整链路,哪怕做得烂,也要先打通。
2.1 先定“输入什么、输出什么、由谁调用”
开始写任何代码之前,我会先画一个最简单的手绘链路,通常是这样的:
输入数据 → 模型调用 → 输出解析 → 结果校验 → 存储/返回
这个链路里的每一环都值得早期跑一遍。比如你要做一个文档摘要工具,输入是PDF或网页正文,输出是Markdown格式的摘要。那么先把“取出文本—送进模型—拿回结果—解析结果”跑通,哪怕摘要质量一般,链路通了才是地基。
我处理这个问题时会做一个很小的demo工程,目录大概是:
input/:放原始样本output/:放模型输出和解析结果main.py:主流程config.py:模型参数配置test_samples/:固定评测样本
这里有个细节:模型调用的输入输出要尽早“固定成数据结构”。不要直接用裸字符串到处传,否则后面做评估、做缓存、做重试时会非常痛苦。我会用TypedDict或者Pydantic定义输入输出结构,哪怕现在只有两三个字段,也把结构立住。
2.2 模型选型不要迷信“最强”,要迷信“最稳”
从零开始的新手常犯的第二个错是:直接上最强模型。模型强没错,但成本和延迟也高,而且很多场景用中等模型就够了。
我自己的选型顺序是:
- 先确定任务复杂度。是简单的抽取、分类,还是复杂的推理、多步决策?
- 准备20条代表性样本,跑两三个候选模型做对比。不看单条效果,看稳定性和错误类型。
- 再看延迟、成本、限流。很多场景下“稍微弱一点但快和便宜”的模型是更好的工程选择。
- 最后才锁默认模型,并把模型名写进配置,不要散落在代码里。
推荐一个稳妥的组合思路:核心推理用强模型,简单任务用弱模型,两者之间用路由规则连接。这个我在后面的Agent章节会再展开。
2.3 固定随机性:从“靠运气”到“靠参数”
第一次跑通链路后,很多人的代码有个通病:同样的输入,运行两次结果不一样。这在实验阶段没关系,但一旦进入工程阶段就麻烦,因为你会分不清“返回结果变了”到底是代码改动了,还是模型随机性导致的。
我现在写模型调用代码时,会注意这几个参数:
temperature:决定随机性。抽取、分类、格式化输出我一般设 0 到 0.3;创意写作才设到 0.7 以上。max_tokens/max_output_tokens:必须设上限,否则长输出会无限消耗预算。seed:部分模型支持,相同seed加上相同输入能得到更稳定的结果,在测试阶段很好用。output_format/response_format:支持JSON模式就尽早用,能省掉很多解析错误。
工程经验原语:先求稳,再求好。模型调用代码先固定temperature为低档,等业务效果不满意时再往高调,而不是反过来从高往下调。
3. 提示词工程:把玄学变成可管理的技术
我见过最多的高光与翻车都发生在提示词环节。一个优秀的提示词确实能让模型能力提升一个档次,但只靠“写得好”解决不了工程问题。提示词必须能版本管理、能测试、能回滚。
3.1 从散文到结构化:提示词的三段式骨架
早期我写提示词是写散文,效果随缘。后来总结经验,一套稳定提示词通常包含三个部分,缺一不可。
第一部分是角色与目标。告诉模型“你是谁、要完成什么”。不要只写“你是一个助手”,要写清楚任务的边界和服务对象。举例:我需要一个“能够从客服对话中抽取用户情绪标签并给出摘要的系统”,而不是“分析这段对话”。
第二部分是上下文与约束。把输入内容、格式要求、禁忌项都写清楚。例如对于抽取任务,写明“只输出JSON,不要输出任何解释性文字”。约束越明确,解析越省心。
第三部分是输入与示例。给模型提供结构化入口,并附上一两个“输入—输出对”作为示例。少样本示例的位置和数量本身就需要作为实验变量来对待,不是塞得越多越好。
结构化的好处是,改版时可以很快定位是角色定义的问题、约束的问题还是示例的问题。散文式提示词一改就是整段重写,没法精细化迭代。
3.2 提示词的版本控制怎么落地
我在实践里把提示词当作代码一样管理,配置仓库里存放的是一个“提示词模板”,不是最终拼好的字符串。模板使用变量占位,例如:
SYSTEM_PROMPT = """ 你是一个{role},你的任务是{task}。 约束条件: 1. {constraint_1} 2. {constraint_2} 输入内容: {input} 请直接输出结果,不要输出多余解释。 """每次修改提示词,我会附上变更说明,并在测试样本集上跑一遍回归。这里的核心动作是“改动前后必须可对比”。如果不做回归测试,你根本不知道一次Prompt改动是提升了还是回退了。
另外,我强烈建议把“提示词V1.0”这类命名方式彻底换成 Git 思路:每次改动留下diff记录,配合测试样本集。理由很简单:模型版本会升级、业务需求会变,但历史提示词为何那样写、当时规避了什么问题,必须保留可追溯记录。否则三个月后改回来又踩同一坑。
3.3 不知道怎么写?从“追查错误”反推提示词
经常有朋友问我:我不是很会写提示词,有什么套路?我的答案是:先不看提示词写得漂不漂亮,先看模型回答的错误类型。错误是格式问题,就在提示词里加格式约束;错误是逻辑问题,就加思维链或分步骤指令;错误是理解偏差,就增加示例。
用这种方法,提示词工程就不是玄学,而是一套以错误驱动迭代的工程方法。
4. Agent化:从单轮问答演进到多步自治系统
当链路跑通、提示词稳定之后,就会遇到更高阶的问题:单轮问答满足不了业务需求了。你需要让AI自主完成多步任务,比如查资料、算数据、调接口、综合判断。这就是Agent的用武之地。
4.1 最小闭环的ReAct模式
最简单的Agent骨架可以理解为一种循环:思考(Thought)→ 行动(Action)→ 观察(Observation)→ 再思考。
实现上,我先画出Agent的状态集合:
idle:空闲,等待任务thinking:模型正在规划下一步tool_calling:正在调用某个工具tool_result:拿到工具返回结果done:任务完成error:任务失败,需要人工介入
每个状态对应一个处理函数,状态之间通过事件驱动。很多Agent“跑飞”的根本原因,就是没有状态机概念,模型想干什么就干什么,最后逻辑乱成一团。
def agent_loop(task: str, max_iterations: int = 10): state = "idle" context = [] for _ in range(max_iterations): context.append({"role": "user", "content": format_task(state, task)}) response = call_model(context) action = parse_action(response) if action["type"] == "finish": return action["answer"] elif action["type"] == "tool": result = call_tool(action["tool_name"], action["arguments"]) context.append({"role": "tool", "content": result}) state = "tool_result" else: state = "error" raise AgentLoopError("max iterations exceeded")我设置max_iterations上限的初衷很简单:没有上限的循环,既烧钱又可能失控。同时要考虑工具调用频率限制,必要时在工具层做节流和超时。这个就是“护栏”的最基本形态。
4.2 多Agent协作:该分工时再分工
很多人一听“多Agent”就很兴奋,觉得让几个AI吵来吵去会碰撞出火花。我的经验是,多Agent协作的结构好坏,远远重要过数量多少。
最常用的结构是“规划者—执行者—审核者”三角色。规划者拆任务,执行者调用工具实现子任务,审核者检查最终结果质量。这个结构天然适合“写代码—跑测试—CodeReview”这类工作流。Harness Engineering这类概念实践,换个角度看其实也是在强调“用结构化流程约束AI逐环节干活”。
多Agent协作的三个工程要点:
- 信息传递必须有明确格式。不要Agent间传散文,要传结构化对象(任务ID、输入参数、预期输出、依赖关系)。
- 每个Agent只做一件事。不要一个Agent既规划又执行又审核,任务边界模糊是混乱之源。
- 失败要能向上抛。执行者失败时,规划者要能根据错误信息重新规划,而不是把错误堆在上下文里继续往下走。
上下文长度是Agent开发中最容易被低估的瓶颈。Agent越跑上下文越长,噪音越多,最终表现为“答非所问”。我的应对策略是:定期压缩历史,保留结论性摘要而不是完整过程;建立“记忆库”保存关键结论,下次直接从记忆库恢复现场。
4.3 工具调用是最需要打磨的接口
Agent再好,落地时总要落到工具上。我开发的经验是,工具设计必须做到:名称清晰、参数说明完整、返回结果结构化、超时可控。
一个反例是早期我给Agent写了个“获取天气”的工具,参数只写了城市,结果模型传了个“上海浦东新区”,工具解析不了;后来把参数改成“城市名(例如:北京、上海)”,错误率立刻下降。模型极度依赖工具描述里的“上下文提示”,所以工具描述要写得像给机器人看的产品说明书。
另一个重要机制是“工具结果的预清洗”。不要直接把工具原始结果全部塞给模型,先截断、改格式、剔除敏感字段。否则一个超长网页内容可能直接把上下文打爆。
5. 评估、测试与监控:AI工程的定盘星
不夸张地说,评估环节决定了一个AI项目能否长期跑下去。没有评估体系的AI工程,和开盲盒区别不大。
5.1 离线评估集怎么搭建
离线评估集是AI工程质量的生命线。我的建议是:这件事从项目第一天就开始攒,不要等项目做完了再补。
评估集结构可以包括:
| 样本类型 | 作用 | 占比建议 |
|---|---|---|
| 标准样本 | 覆盖业务主流程的典型用例 | 50%-60% |
| 边界样本 | 输入为空、超长、格式异常等 | 20% |
| 对抗样本 | 意图模糊、包含误导信息 | 10%-20% |
| 回归样本 | 历史上修复过的Bad Case | 作为回归补充 |
每天我会把线上反馈中暴露的问题加入评估集。加入的规则是:这条Case必须能复现,且单靠提示词修改无法快速解决时才进入评估集,避免评估集变成垃圾场。
5.2 评估指标怎么选
不同任务适合不同指标,我的选择矩阵大致如下:
- 分类/抽取任务:准确率、精确率、召回率、F1。
- 摘要/生成类任务:人工打分为主,辅助用ROUGE、BLEU做参考,但别迷信。
- 检索增强任务:召回率、命中率、排序指标。
- Agent任务:任务完成率、平均步数、工具调用成功率、人工审核通过率。
有一点要特别强调:AI测试开发不是“跑通就行”。我在线上监控里最看重的是“成功率变化趋势”和“成本曲线”,这两个指标比任何单条case的对话效果都更能反映健康度。建设一套AI系统的监控,初期只盯四个数字是有很强代表性的:
- 请求量
- 平均响应延迟
- 输出成功率(模型返回、解析通过的比例)
- 单位请求成本
这四个数字稳定了,再往上加业务指标(用户满意度、留存、转化)才靠谱。
5.3 回归测试:每次改动都要过一遍
有了评估集之后,我会把回归测试跑在每次改动前。做法不复杂:
- 先跑旧版本,记录结果。
- 再跑新版本,记录结果。
- 自动比对关键指标,低于阈值的直接拦截。
回归测试建议做成一条命令可以执行的任务,最好加入CI流程中。如果项目没有CI,至少在发布前手动跑完这一条命令并保存结果截图。这个东西是后期省时间最划算的投入。
6. 常见问题与排雷实录
最后分享一些实际踩过的坑,都是项目里反复出现的类型。
6.1 感觉模型效果“不稳定”怎么办
“不稳定”通常有三种来源:
- 模型版本变了。同一家API,上游模型悄悄升级后,行为出现变化。解决方法是锁定模型版本号,并在提示词变更时同步记录模型版本。
- 输入数据变了。线上数据分布与训练时差异大,导致表现下降。解决方法是定期抽检线上输入样本,更新评估集。
- 参数设置不当。temperature设高了,业务结果自然飘。解决方法是先固定低温和seed排查。
6.2 模型输出解析失败,是模型的问题还是我的问题
大多数情况是“你的问题”。检查顺序如下:
- 提示词里是否明确要求了输出格式?
- 是否启用了JSON模式等结构化输出支持?
- 输出中是否包含多余标记(例如代码块包裹)?
- 解析容错是否考虑了字段缺失?
- 有没有设置重试机制?
我自己的兜底方案是“两步解析法”:第一步尝试结构化输出解析;解析失败后,调用一个修复模型对原始输出做规范化再解析。代价稍高但成功率明显上升,适合重要链路使用。
6.3 成本居高不下,怎么控制
成本控制的核心是分级使用模型。简单任务用轻量模型,复杂任务才走强模型。在代码层面,我会埋设一些日志点,记录每个请求的模型名、token数和耗时,每周复盘一次。发现某个环节高频调用强模型后,我会尝试用弱模型先过滤,只把置信度低的样本升级给强模型,这是成本控制最有效的实践之一。
6.4 哪些知识是“从零开始”最该补的
如果你问我“从零开始做AI工程”最该补哪些基础知识,我的清单是:
- 大模型基础原理:至少知道token是什么、上下文窗口如何影响任务设计。
- Python数据操作:dict、list、JSON解析这类基本功必须熟练。
- API调用与异常处理:重试、超时、限流是每个AI工程人每天面对的事。
- 数据结构化设计:输入输出都要结构化,这是工程稳定性的根基。
- 基础测试理念:评估集、回归、人工审核,这些概念比具体框架更重要。
至于框架和工具,一个月换一个都很正常,但上面这些底层能力是长期有用的。
写在最后的一点个人体会
我至今还记得自己踩过最狠的一次坑:一个看起来完成度很高的RAG项目,上线三天后回答质量断崖式下跌,当时所有人都怀疑是提示词出了问题,查了很久才发现是上游文档库的版本更新导致部分文本变成乱码。那之后我把“输入数据变更”列进监控项,评估集里也专门加了“乱码输入”的对抗样本。
做AI工程时间越长,我越觉得它不像“写魔法”,更像“调理流水线”。流水线稳不稳,不取决于某一个环节多炫,而取决于每个环节有没有明确的输入输出、有没有质量检测、有没有异常处理。特征漂移、数据质量、成本核算、回归测试这些看起来不够“AI”的东西,反而决定了AI能不能真正为业务创造价值。
最后分享一个自己的小习惯:每当新需求过来,我会先问三个问题——输入稳定吗?输出可验证吗?失败可恢复吗?三个问题都能给出明确答案的项目,通常都做得比较稳。如果哪个答不上来,就先把这个洞补上,再谈效果优化。