
1. 从零开始的AI工程到底是什么比调API多出来的那一层我做这个项目时起的名字是ai-engineering-from-scratch。身边不少做应用的朋友第一反应是现在大模型API这么方便不是调几个接口、写写提示词就完事了吗为什么还要强调from scratch我当时回答了一句话调API是消费工程是生产。如果你只是把别人的模型拿来用一下那叫集成但如果你要围绕AI能力搭出一套稳定、可控、可迭代的系统就得把模型能力、提示词、工具调用、测试评估全部纳入工程体系里这就是AI工程。我也看到最近行业里开始频繁提到一个词叫harness engineering。它说的不是马具而是AI工程里那层约束和编排框架。模型像一台性能强悍的引擎引擎不能光着放地上踩油门得有进气、排气、冷却、限速、传感器这些管线才能装进车身安全行驶。Harness在AI里的作用就是这个约束输入输出、管理工具权限、组织上下文、记录运行过程、定义退出条件。它才是让AI应用从demo能跑变成上线可靠的关键差一层。这个项目我规划了完整的学习和实战主线适合三类人刚入门、只想拼API但总被各种跑不通卡住的开发者已经在做LLM应用、但靠感觉调提示词和参数、遇到性能问题就头大的朋友对Agent和工作流有兴趣想弄明白它们背后工程结构的同学。这篇文章会把这条主线拆开给出来从亲手训练小模型理解能力边界到不训练也能落地的提示工程再到用Harness把Agent编排起来最后讲测试、评估和持续迭代。每一段我都尽量写清楚为什么这么做和我踩过的具体坑。2. 第一步亲手训练一个能推理的小模型2.1 学习路径先手撕Transformer再谈大模型很多人问我想看《Build a Large Language Model from Scratch》这本书是不是要先精通数学我的经验是不需要等到完全看懂数学才动手但要动手把模型写一遍只看代码和跑实验完全不是一回事。我从零开始构建推理模型的学习路径大概是先写一个字符级tokenizer把文本切成小单词/子词理解离散符号怎么映射成向量。自己实现embedding层别怕它就是一个查表操作。然后写单头注意力接着扩展到多头理解为什么需要三个矩阵Q/K/V。接上前馈网络、残差连接和层归一化这时候一个Transformer block就拼出来了。给模型加位置编码用softmax生成概率分布训练它预测下一个token。最后用微调的方式教它特定任务比如做简单算术题。这个过程最直观的作用是你会清楚模型的上限来自哪里。之前我总觉得提示词写得不好所以模型效果差训练过小模型以后才明白更多时候是模型在预训练阶段就没见过类似模式提示词再花里胡哨也救不回来。2.2 用PyTorch实现一个极简推理模型骨架为了不悬在概念上这里放一个极简可跑的骨架代码。它不是完整的GPT但保留了一个推理模型最核心的部件输入嵌入、一个TransformerBlock、输出投影、损失函数。我们用它训练一个能看图算结果的超小模型。import torch import torch.nn as nn import torch.nn.functional as F class TinyTransformer(nn.Module): def __init__(self, vocab_size, embed_size, num_heads, ff_size, num_blocks, max_len, dropout0.1): super().__init__() self.token_emb nn.Embedding(vocab_size, embed_size) self.pos_emb nn.Embedding(max_len, embed_size) self.blocks nn.ModuleList([ nn.TransformerEncoderLayer( d_modelembed_size, nheadnum_heads, dim_feedforwardff_size, dropoutdropout, batch_firstTrue, ) for _ in range(num_blocks) ]) self.ln_f nn.LayerNorm(embed_size) self.head nn.Linear(embed_size, vocab_size, biasFalse) def forward(self, x): # x: (batch, seq_len), 内容是token id positions torch.arange(x.size(1), devicex.device).unsqueeze(0) hidden self.token_emb(x) self.pos_emb(positions) for block in self.blocks: hidden block(hidden) logits self.head(self.ln_f(hidden)) return logits训练循环也很简单准备一批文本把token ids切成inputs和targetstargets就是输入向右错一位然后用交叉熵损失更新权重。我实际训练了一个10M参数级别的小模型在五千条计算题-答案格式的数据上跑了20个epochloss从10附近降到0.8已经能答对一部分两位数加法。重点不是模型多聪明而是通过这个实验你会把输入序列、概率分布、损失函数、参数更新这条链路里每一环都看得清清楚楚。2.3 训练中的三个关键细节数据错位、初始化、学习率调度训练过程里我踩过三个坑新手很容易忽略第一个坑数据错位。生成目标序列时一定要让第t步的输入是第t-1步预测的上下文但训练时我们用的是句子内真实错位。比如句子是235那输入是235的前4个token目标是235的后4个token。如果不做mask模型就能看到后面的答案训练loss很漂亮测试全废。第二个坑权重初始化。我一开始用默认初始化训练特别不稳定。后来读到小GPT的实现推荐用标准差为0.02的正态分布初始化嵌入并把残差层后的线性层scale到0.02/sqrt(num_blocks)这样深层网络能被训练得更稳定。这也是从零训练和直接调API的显著差异——你开始关心数值稳定性了。第三个坑学习率。固定学习率很难同时兼顾前期的快速下降和后期的收敛。我现在固定用warmup cosine调度器先让学习率线性上升到峰值比如3e-4再用余弦函数慢慢降到底。对10M级别的小模型这个策略普遍稳定。torch.optim.lr_scheduler里已经有现成的CosineAnnealingLR配合LinearLR拼起来就行。训练完这个能推理的小模型下一步我建议别急着部署。先把它放到一边切到预训练大模型做工程因为你已经明白了模型能力边界由预训练决定这件事后面设计提示词和Agent时会带来完全不同的思路。3. 第二步不训练也能工程落地的关键——提示工程与提示词设计3.1 Prompt Engineering不是写话术是结构化接口设计build a reasoning model from scratch这条热搜词背后很多人的误区是只有训练才能让模型会推理。但实际情况是工程落地阶段90%的能力来自怎么组织输入输出。提示词不是你跟模型闲聊的话术而是你给模型设计的API规格。举个例子你要让模型从一段客户反馈里提取需求。新手会写请提取客户说的问题模型可能会用半页纸的自由文本回复你。更合理的提示词是从给定反馈中提取结构化字段只返回JSON { category: 功能问题/性能问题/价格问题/其他, description: 不超过30字的问题描述, suggestion: 可选的改进建议或空字符串 } 用户反馈: 最近上传图片总是失败网页卡死了两次。为什么这就算结构化接口因为你在定义输入范围只要一段反馈别自己脑补输出格式JSON且字段有约束边界条件没有建议就给空字符串。模型对这种明确规格的响应质量通常远好于开放式提问。你也可以理解为提示词里的格式要求就是类型声明few-shot示例就是测试用例。别把它当文学创作而是当接口文档来写。3.2 AI编程提示词让模型帮你写代码的正确姿势AI编程提示词也是热搜词里的常客。我用AI辅助写代码已经一年多最大的体会是让它写代码前先把需求、边界、验收条件讲清楚比润色任何魔法咒语都重要。下面是我现在常用的编程提示词模板任务用Python写一个函数从URL列表批量下载文件。 环境Python 3.11可用requests、pathlib不要用selenium。 输入格式list[str]URL以http/https开头。 输出格式返回下载成功的文件路径list失败URL要写日志但不抛出异常。 边界并发数不超过3文件大小超过2GB跳过超时设为10秒。 验收请先给2个正常用例和1个异常用例再写代码。这个模板看起来麻烦但它把模型要做的工程决策都提前定了模型输出的代码基本能直接用。我实测下来先写测试用例再写代码这一条尤其有效。原因也好理解模型生成代码时如果先模拟了测试场景就会自己检查逻辑而不是只产出语法正确的垃圾。另外注意AI编程提示词和普通对话提示词是两种物种。对话提示词要的是开放生成编程提示词要的是封闭确认。如果你让它帮我写个爬虫而不给环境约束它默认给你装一堆库、开20个线程跑起来全是坑。所以我会把这类提示词当伪代码来写越靠近验收标准产出越可复用。3.3 提示词的可观测性把它当成需要版本控制的代码提示词工程最大的隐患是玄学化——感觉效果波动就觉得是模型抽风实际上问题往往出在提示词没被稳定管理。我自己的做法是每个任务建一个prompts/目录按任务名_版本号.py或.md保存。提示词文件里写清楚version,date,目的,依赖的模型,每次改动只改一个变量不连续改十句话。建一组固定输入样本跑完记录输出。这组样本相当于提示词的回归测试模型更新或提示词改动后跑一遍马上能看到差异。我现在甚至会为提示词写契约测试。比如上面提取客户反馈的任务我准备20条真实反馈、20条边界输入空文本、全大写、超长文本每次调整提示词后先跑这套样本通过率低于90%就不上线。这种做法上手以后你一定会发现很多看似模型不行的问题其实是提示词的版本管理落后导致的问题。4. 第三步从一个模型到AI Agent用Harness把能力编排起来4.1 Agent的协作模式工具、记忆、循环缺一不可做到这一步你已经拥有了会调用的模型和写好的提示词。但真实任务往往不是一个模型调用能搞定的。比如帮我分析项目代码并生成重构建议你需要让模型先读文件、再跑搜索、还可能执行测试。这时候就进入了AI Agent的领域而Agent的核心不仅是模型更是Harness。我把Agent的Harness拆成四块组成作用类比工具注册表明确模型可以调用哪些函数参数怎么校验给引擎加传感器和阀门状态管理保存对话历史、中间结果、文件路径油箱和油管控制循环决定继续思考还是停止执行发动机运行逻辑可观测性记录每次工具调用、耗时、token消耗仪表盘很多初学者只用模型提示词搭Agent模型说什么就听什么结果就是模型说我执行了测试但其实根本没跑或者模型在一个死循环里不断调用同一个工具烧掉几百次token。其实模型自己并不知道安全是什么安全是Harness定义的。我自己在设计Agent时控制循环会遵循最多尝试5次工具调用每次调用都要输出结构化结果如果连续3次相同状态就强制退出。这个规则不写进提示词而是写在Harness代码里保证模型再胡闹也跳不出约束。4.2 一个完整案例用CodeBuddy实现Harness Engineering在热搜词里有个具体例子是codebuddy实现harness engineering的完整案例。我实际用过CodeBuddy来做这类工作但说句实话核心不是这个工具本身而是它把模型写代码自动测试反馈修复的循环封装成了可配置的Harness。我做过一个最小可复制的类似流程任务解析先用一个模型读需求输出要修改的文件清单和验收标准。代码生成另一个模型根据任务解析结果生成候选补丁写到临时分支。自动测试Harness在隔离容器里运行测试命令比如pytest tests/收集测试输出。反馈循环如果测试失败把报错信息和相关代码片段喂回第二步的模型让它修改补丁重复最多3次。人工确认只有通过测试的补丁才被标记为建议合并同步到PR描述里。这个过程中模型其实只负责生成代码和修复错误而该跑哪些测试失败了几次就放弃哪些文件允许修改这些铁律全部在Harness里写死。我看到很多失败的工单问题都出在把控制权交给了模型而不是把模型塞进一个受控的循环里。用CodeBuddy或自己写一套Python流程实现都行我建议第一次别直接用太重的工作流引擎先用asyncio.Queue或简单的状态机初始状态ACT_TASK_PARSING收到解析结果后ACT_CODE_GENERATION测试通过则ACT_HUMAN_REVIEW连续失败3次进入ACT_QUARANTINE这个状态机跑起来你才会有模型在干活、Harness在管模型的真实体感。4.3 多AI协作与AI工作流把单一Agent扩展成生产线热搜词里还有多AI协作和AI工作流这两个词其实都和Harness直接相关。我之前把4.2的方案扩展成多角色协作常见的编排模式有链式Pipeline第一步模型负责解析需求第二步模型负责生成代码第三步模型负责写测试报告。适合流水线型任务。路由Router一个主模型先判断任务类型再把任务分派给不同的专用模型。比如代码问题给代码Agent文档问题给文档Agent。群聊GroupChat多个Agent聚在一个共享上下文里互相讨论、投票。适合创意类任务但token消耗高容易出现全员表扬的无效讨论。我自己最常用的是链式动作校验。每个Agent输出必须符合上一步定义的JSON schemaHarness负责校验和转发。下面是一个简化的工作流配置思路workflow: id: code_review_pipeline nodes: - id: parser model: claude-sonnet-4 prompt: prompts/parse_requirement.md output_schema: json_schemas/requirement.json - id: generator model: claude-sonnet-4 prompt: prompts/gen_patch.md inputs: [parser.output] output_schema: json_schemas/diff.json - id: tester model: gpt-4o prompt: prompts/run_tests.md inputs: [generator.output] tool_enabled: [shell] - id: reporter model: gpt-4o prompt: prompts/summarize.md inputs: [tester.output] max_retries: 3 timeout_seconds: 120这个YAML不是某个平台锁定的语法换到别的工具也大差不差。重点在于我把每个节点的输入输出契约都定义了当一个Agent输出非法JSON时Harness直接报错重试而不是把脏数据传给下游。我之前踩过一个坑下游Agent收到了一个字段名拼错的JSON它没有拒收反而脑补了一个字段继续处理结果整个流程的产出全错了。所以工作流里的节点之间一定要有强校验校验器就是Harness的核心代码。5. 第四步把AI工程做成可持续交付的系统5.1 AI测试开发不只是在用例里加断言热搜词里有一个是AI测试开发我最先想到的不是用AI去跑测试而是AI应用本身怎么被测试。AI应用和传统程序的最大区别是输入不可穷举输出没有唯一正确解。所以测试不能只对着函数返回断言而是要分层设计。我自己的测试分三层第一层单元测试。针对Harness里的纯逻辑比如工具参数校验、状态转移、超时判断、重试次数。这些和普通Java/Python测试没区别保证骨架是可靠的。第二层集成测试。把Agent放进一个沙盒环境用一组固定的任务用例跑通整个流程断言是否调用过某个工具返回的状态码对不对循环是否在预期次数内结束。这一层最常发现的问题是死循环和工具调用失败。第三层评估测试。这层最难也最关键。收集一批标注过的真实任务跑完Agent后用一组指标评估质量。比如一个代码审查Agent我会让两个人类工程师对输出打「采纳/部分采纳/不采纳」标签然后计算采纳率。不引入统计指标你永远只能说感觉效果变好了。我会把这三层测试合进CI流程每次修改提示词或Harness配置都自动跑。这是AI工程从灵光一现走向可信交付的分水岭。5.2 评估指标与LLM评判机制不当点赞机器要看统计信号搞完测试之后引入评估指标要克制。我最早做Agent评估时给自己列了一堆指标准确率、召回率、F1、BLEU、语义相似度……看是好看但大部分指标和业务目标脱节还让我花了大量时间调指标增长率而不是调系统。后来我只保留五个指标计算方法关注点任务完成率正常结束任务的占比是否容易被卡死在循环或解析错误工具成功率工具调用成功次数/总次数是否总给模型不存在的工具token效率完成一次任务平均消耗token是否上下文溢出导致成本飞涨输出格式率输出合法JSON/符合schema的比例提示词和校验器是否配合得当人工采纳率人工审核中接受Agent输出的比例质量的核心信号其他指标最终都该服务它评估模型本身也可以用另一个大模型当裁判即LLM-as-judge但有个重要经验不要用和被评估模型一致或同源的模型当裁判否则容易产生系统性偏好。我在项目里用不同模型互相评估时会额外用20条人工标注做个基准先校准裁判模型的打分偏差。5.3 上线监控与迭代闭环给Agent装上行车记录仪上线后最怕的不是模型能力差而是出了问题无法复现。模型行为是概率性的同样输入这次成功下次失败如果Harness没有记录中间过程排查基本靠猜。我目前给所有Agent都加了运行轨迹日志每次工具调用的输入输出截断保存、每次LlM调用的提示词和生成内容摘要、每个状态转换的时间戳、token消耗汇总。查询时用一条session_id就能看到完整一局游戏是怎么打的。上线后的迭代我也做成小步快跑模式每周只改一个变量要么调提示词要么调工具描述要么调Harness循环参数。改完必须跑一遍5.1里的三层测试然后把指标发到看板。我吃过不少连续改三处配置效果提升但不知道是谁的功劳的亏后来强制一次只动一处迭代效率反而更高。6. 我的踩坑清单与写给后来者的实践建议6.1 最容易翻车的五个坑回顾整个ai-engineering-from-scratch项目如果把战力按坑的痛感排个名盲目追求大模型任务边界没想清楚。小模型好Harness能解决的问题非要上大模型结果成本和延迟都失控。先定义任务再选模型。我见过一个团队用4.5级模型专门做从PDF里抽表格后来换成一个小模型加结构化提示词准确率只降1%成本降了90%。把提示词当一次性输入不做版本管理。提示词就是代码。没版本库、没回归测试模型一更新业务就崩你连是哪个提示词出的问题都不知道。Agent循环没有退出条件。设计Agent时总要给模型反复修正的机会但必须同时给Harness强行停止的权力。没有退出的Agent就像没有刹车的车跑得再快也不敢出门。只盯单点prompt不看整体工作流。很多失败出在节点之间的数据校验而不是某个节点的模型能力。我最后悔的一次是花两天调一个代码生成提示词最后发现是上游工具把文件路径传错了。评估靠感觉不靠统计。不管你是谁只要开始说我觉得效果好多了请立刻去建立一个评估集。没有统计信号的优化只是自嗨。6.2 想少走弯路请按这条路径走如果让我重新做一次这个项目我会用更务实的顺序先花一两周训练一个极小模型我选择10M参数目的不是做出产品而是理解底层的损失、注意力、采样再用预训练API做提示工程尽快把一个垂直任务做到可用但不完美。重点不是效果而是学会写结构化提示词、做版本管理然后才开始做Agent的Harness。一开始只允许模型调用2个工具比如search_code和run_test把循环、超时、重试、日志都封装好最后引入自动测试和评估把之前积累的任务用例沉淀成评估集并定时回放。这中间还有一个让我少走很多弯路的习惯所有实验脚本都保留运行日志所有提示词变更都留diff。有一次我发现新版工作流在极端输入下会绕过工具校验那就不是模型行为诡异而是Harness逻辑漏了边界。我自己最后把项目凝结成一句话AI工程不是让模型自己骑车而是给它造一台有刹车、有仪表盘、有维修手册的自行车。模型负责踩踏板你负责设计整台车。从一开始亲手训练小模型到后来给Agent配置Harness核心都是同一个道理——懂模型但不惯着模型用工程手段让能力输出变成稳定交付。如果你也想搞一套自己的ai-engineering-from-scratch建议从今天开始不要只想着调接口先找一份小数据把Transformer train起来。那一步跨出去后面所有环节的为什么都会自己浮出来。