☰
AI工程化从零开始:构建稳定可评估的Agent工作流
2026/9/29 23:36:19 网站建设 项目流程

AI engineering from scratch 这串英文,最开始只是我 Git 仓库里的一个目录名。后来这个目录越写越大,成了我半年跌撞实践后的浓缩——从零开始,把一个只会生成文字的模型,变成一套能稳定产出结果的工程系统。这篇文章就是把整个过程里的设计取舍、代码细节、踩坑记录一次讲透。

适合哪些人看?已经会调大模型 API、写过几个 prompt,但还没把 AI 功能做出产品感的开发者;以及想系统理解 AI 工程化到底在做什么,准备上手 agent 或 AI 工作流的团队。如果你以为 from scratch 是要从训练模型开始,那会绕很远的路;我的实践结论恰好相反,真正的工程瓶颈不在模型,而在模型外面那套约束、评估和协作机制。

1. 从零开始做 AI 工程,先想清楚要解决什么问题

1.1 我踩过的“非工程化”弯路

第一次做 AI 需求时,我的方案很原始:写一大段 prompt 塞给模型,让它直接输出结果。demo 演示很惊艳,一到真实数据上就原形毕露。格式偶尔崩、内容偶尔偏、同一个问题问两次答案不一致,更别说超时和成本波动。当时我以为是 prompt 写得不够好,反复调词、加示例、改语气,折腾两周,效果依旧不可控。

后来我意识到,问题根本不在 prompt 写得好不好,而是我把“调用模型”当成了“做工程”。让模型稳定干活,需要的不是一段更长的咒语,而是一整套围绕模型建立起来的工程框架。这也是项目名叫 from scratch 的原因:不是从零训练模型,而是从零搭建一个能让模型稳定工作的系统。

1.2 我理解的 AI 工程四层结构

做了几个项目后,我把 AI 应用工程化拆成四层,后面所有实践都围绕这四层展开:

  1. 模型接入层:选哪个模型、怎么调用、超时重试、成本控制、版本切换。
  2. 上下文与提示词层:怎么组织 prompt、怎么管理上下文窗口、怎么给模型提供必要信息。
  3. 工具执行层:模型决定要做什么,系统真正去执行什么,包括函数调用、文件读写、命令执行、结果回传。
  4. 观测评估层:怎么判断模型干得好不好,怎么在它犯错时及时发现并修正。

用一个生活化类比:把大模型想象成一个能力很强但经验不足的实习生。你不可能只丢一句“把这事搞定”就期待高质量交付。你需要给他工作手册、明确交付格式、限定能动的工具、设置检查节点,还要有一套验收标准。AI 工程化做的就是这样的事。

这四层里面,很多人把注意力全放在第 2 层(prompt),忽略第 3 层和第 4 层。但真正让 AI 功能从“能用”变成“好用”的,恰好是工具执行和观测评估。后面我会用一个完整案例把这四层全部串起来。

1.3 工程目标与边界划定

做 from-scratch 项目,最重要的一件事是先划定边界。我做的是“面向代码生成与执行的 AI 工程工作流”,所以明确不碰模型训练、不碰大规模分布式推理,只聚焦“基于现成大模型,构建可复现、可评估、可维护的 AI 应用系统”。

把边界写清楚,能避免很多无效劳动。比如我不会花时间去微调模型来让输出格式变稳,而是用结构化约束和后处理校验解决;我不会追求让模型“一次答对”,而是设计一个“生成—执行—反馈—修复”的闭环,让系统自己迭代到正确结果。这个思路,也是我理解的 harness engineering 的核心。

2. 核心设计:prompt engineering 与 harness engineering

2.1 提示词工程真不是写作文

很多人把 prompt engineering 理解为“把话说漂亮”,实际不是。一段可维护、可复用的 prompt,应该像代码一样有结构。我自己常用的模板包含六个部分:

  • 角色定义:让模型明确自己是谁。
  • 任务目标:一句话说清要做什么。
  • 背景上下文:必要的信息输入。
  • 约束条件:明确禁止项和边界。
  • 输出格式:指定结构化格式,比如 JSON、代码块。
  • 示例样本:给一两个 few-shot 例子。

实际的模板例子长这样:

你是一名资深测试工程师。给定仓库结构、目标文件路径和需求说明,请完成以下任务: 1. 分析目标模块的输入输出和核心逻辑; 2. 设计覆盖正常路径、边界条件和异常路径的测试用例; 3. 输出可直接执行的 pytest 代码。 约束: - 只依赖 pytest,不引入额外测试框架; - 所有 mock 必须写在测试文件内; - 输出只需包含 python 代码块,不要任何解释性文字; - 代码必须能够在当前工作目录下直接运行。 目标文件:src/order.py 仓库结构:见上下文中的 JSON 列表

这个模板看起来简单,但每个字段都有存在的理由。角色和任务目标降低理解偏差;“输出只需包含 python 代码块”配合后续的解析逻辑,能减少格式解析的失败率;few-shot 示例我通常会附在模板下方,让模型知道“成品的具体长什么样”。

关于采样参数,也要根据任务调整。生成测试用例这类偏逻辑的任务,我会把 temperature 设在 0.2 以下,减少随机性;如果是创意文案类任务,才把 temperature 调高。很多人全程用默认参数,遇到输出不稳定就怪模型,其实参数也是工程的一部分。

2.2 harness engineering 到底是什么

harness engineering 这个词听起来很玄,其实可以直译为“约束工程”或“夹具工程”。它的思路是:不要指望模型自觉,而是在模型外面套一层限制装置,让它可以发挥能力,但无法跑偏。我在实际项目中把它拆成三层约束:

第一层是输出约束。模型输出必须通过 JSON schema 校验或者正则匹配,不符合就直接判定失败重来。第二层是行为约束。给模型配置工具白名单,它不能随意调用任何函数,只能使用我们注册过的、有权限边界的工具。第三层是流程约束。把任务拆成固定状态机,模型每一步做完后系统检查状态是否合法,再决定下一步给什么指令。

打个比方,这就像给赛车修赛道。赛车性能再强,没有护栏和赛道边界,它也只能跑成事故。模型是赛车,harness 就是护栏和赛道标识。你是在创造一个“它怎么跑都不会出大问题”的环境,而不是赌它每次都跑对。

我在 CodeBuddy 里落地这个理念时,会把每个工具函数写成一个带描述和参数 schema 的注册项,然后由执行器统一调度。模型不能直接执行代码,它只能“请求”执行,真正执行和结果校验都交给系统。这样一来,即使模型产生了不合理的请求,也会在执行层被拦截。

2.3 上下文管理与函数调用设计

上下文窗口是 AI 工程里最容易被忽略的瓶颈。很多人把一整个项目的文件内容全塞进 prompt,很快就撞上 token 上限,或者让模型被海量信息淹没。我的策略是三层管理:

  1. 滑动窗口:只保留最近 N 轮对话内容,更早的做截断。
  2. 摘要压缩:当上下文快满时,调用模型对中间过程做摘要,把压缩后的结果放回上下文。
  3. 检索增强:不让模型看整个仓库,而是先通过工具拿到目录结构,再按需读取目标文件。

函数调用(function calling)是工具执行层的核心。我通常这样定义一个工具:

{ "name": "list_files", "description": "列出指定目录下的文件和子目录", "parameters": { "type": "object", "properties": { "path": { "type": "string", "description": "要列出的目录路径" } }, "required": ["path"] } }

定义工具时,description 写清楚比参数设计更重要。模型靠描述来决定什么时候调用、传什么参数,如果描述含糊,它就会在错误的时候调用。参数类型和必填项也要严格,减少模型传错参数的概率。此外,每次工具调用都必须在系统侧记录日志,方便后续排查。

3. 实操:用 CodeBuddy 搭一个“自动生成测试用例”的 Agent 工作流

3.1 场景定义与工程规划

纸上谈兵够多了,我拿一个真实跑通过的项目做例子:做一个 AI Agent,输入一个仓库地址,自动分析核心模块并生成 pytest 测试用例,然后执行测试、收集结果、修复失败用例,直到通过或达到迭代上限。

这个场景非常适合展示 AI 工程化,因为它同时涉及代码理解、生成、执行、反馈闭环,几乎覆盖了前面讲的所有层。项目结构我按下面这样组织:

ai-test-agent/ ├── agent/ │ ├── planner.py # 任务拆解与规划 │ ├── executor.py # 工具调度与执行 │ ├── reviewer.py # 结果评审与决策 │ └── prompts.py # 所有提示词模板 ├── tools/ │ ├── file_tools.py # 文件读取与列表 │ ├── run_tests.py # 执行 pytest │ └── registry.py # 工具注册表 ├── core/ │ ├── context.py # 上下文管理 │ └── schema.py # JSON schema 校验 └── main.py # 工作流入口

这个结构我是刻意设计的。prompts 单独放,因为提示词会频繁改版;tools 单独放,因为工具是模型能力的边界;core 里放上下文管理和校验逻辑,这是 harness 的关键。一切按职责分开,后续换模型、加工具、调提示词都不会牵一发动全身。

3.2 从“手动调模型”到 Agent 模式

刚开始我是在 PyCharm 里手动写脚本调用模型,后来换了更高效的方式:直接使用 CodeBuddy 的 Agent 模式来搭建和调试这个工作流。CodeBuddy 这类支持 AI 编程和 agent 编排的工具,本身就能让你把提示词、工具函数、执行流程串起来,省掉很多基础设施代码。

我在 PyCharm 里也装了 AI 插件,但说实话,插件更适合写代码片段和解释报错;真正要跑一个完整的“生成—执行—反馈”循环,还是需要把流程写成脚本。实操中我的做法是:用 PyCharm 写 agent 代码,用 CodeBuddy 的 Agent 模式跑通端到端流程,两边互相配合。

工作流的状态机我设计成四态:

PLAN -> GENERATE -> EXECUTE -> REVIEW
  • PLAN:模型分析仓库结构,确定要测的目标函数。
  • GENERATE:基于目标文件生成 pytest 测试代码。
  • EXECUTE:系统实际运行 pytest,捕获结果。
  • REVIEW:模型看失败报告,判断是测试写错还是被测代码有 bug,决定是否修复重试。

每次 REVIEW 后如果判定需要重试,系统回到 GENERATE,但会带上上一次的执行结果。最多重试三次,超过就终止并把中间过程写入日志。

3.3 关键代码逻辑与反馈闭环

主循环核心逻辑并不复杂,重点是“执行结果必须原样回传给模型”,这个反馈闭环是整个 agent 能收敛的关键。

for attempt in range(MAX_ATTEMPTS): if state == "GENERATE": test_code = generate_test(target_info, previous_feedback) if not validate_code_shape(test_code): previous_feedback = "输出格式不合法,请只输出代码块" continue write_test_file(test_code) state = "EXECUTE" if state == "EXECUTE": result = run_pytest(test_file) state = "REVIEW" if state == "REVIEW": decision = review_result(result) if decision == "pass": break previous_feedback = build_feedback(result, decision) state = "GENERATE"

这里最容易犯错的地方,是把“模型认为的结果”当成“真实结果”。比如模型生成代码后自信地说“测试应该会通过”,可我们绝不能就此为止。真实行情是:必须让系统实际执行 pytest,拿到 stdout 和退出码,再把这些真实反馈塞给模型。没有执行层的反馈闭环,一切都是模型在自说自话。

另一个容易踩坑的点是 validate_code_shape。模型有时会在 python 代码块外夹带“以下是测试代码”这样的说明,如果直接写文件,pytest 会因 Markdown 标记报语法错。我的做法是用正则提取代码块内容,再做一次 Python 编译检查,编译通过才写入文件。

3.4 评估机制:怎么证明 Agent 靠谱

Agent 能跑通只是第一步,还得证明它稳定。我建了两层评估。

第一层是过程评估。每次运行都记录:生成轮数、pytest 通过率、代码覆盖率、失败原因分类。连续跑 30 次后汇总,如果通过率低于 90%,就去检查是哪类问题导致的。第二层是回归集评估。我准备了一个包含 5 个不同模块的测试仓库,每次改完提示词或工具代码,就全量跑一遍,防止“修好一个案例、弄坏另一个案例”。

评估还可以引入 LLM-as-judge,让另一个模型来评审判定结果质量。比如 reviewer 角色判断失败原因是“测试逻辑写错”还是“被测代码缺陷”,这个分类结果直接影响下一步动作。但要注意,模型做判断也不一定准,所以关键路径上的判定需要人工抽检。我的经验是:评估机制本身也要做回归和抽检,否则会变成“用模型相信模型”的循环。

4. 常见问题与排查技巧实录

4.1 输出格式飘忽不定

模型有时会输出 JSON 前后夹带解释文字、代码块标记不闭合、甚至把工具调用参数拼接错误。我实测下来最有效的三层方案是:在提示词里明确输出格式;在解析层做容错,能提取的尽量提取;在架构层用 schema 校验,校验不过就重试。三层都做了之后,格式问题基本从高频降为偶发。

有个小技巧:如果解析失败,不要直接把原始输出吞掉,要把“期望格式 vs 实际输出”的差异作为反馈回传给模型。比如“你上一个输出没有闭合代码块,请重新生成,这次只输出代码”。这种反馈往往比修改提示词更有效,因为它是在具体错误上下文里的即时纠正。

4.2 上下文窗口不够用

项目仓库一大,文件名列表就可能几百行,更别说读取文件内容。刚开始我直接把整个目录树塞进去,很快触发上下文上限,而且模型开始忽略后面的信息。后来我改成两阶段:先调用 list_files 拿到目录结构,由模型自己决定读哪些文件,并且限制每次只能读一个文件,避免一次读太多导致注意力分散。

对于必须要读的大文件,我会做分段读取加摘要压缩。先用滑动窗口读前 100 行,让模型生成摘要,再根据摘要决定是否继续读后面部分。虽然多了几次模型调用,但效果比一口气全塞进去好得多。

4.3 工具调用陷入死循环

模型有时会在同一类工具上调来调去,始终无法推进任务。比如反复 list_files 同一个目录,或者反复读取同一个文件。我的解决办法是加最大调用次数限制,默认 15 次,超过就自动终止;同时记录“相同参数调用次数”,如果同一个工具在同一个参数上出现了 3 次,就打断循环并把历史记录回传给模型,要求更换策略。

这个问题的根因往往不是模型傻,而是它缺少足够的信息来做决策。所以打断后要给的信息不是“你错了”,而是“你已经看过这些内容,当前真正缺失的是另一部分信息”。用有效反馈而不是简单报错,能大幅减少再次进入死循环的概率。

现象根因处理方案
输出夹带解释文字格式约束不足解析层提取 + schema 校验
同一个目录反复 list缺少信息做决策打断循环 + 回传历史记录
生成测试文件写错路径模型凭空猜测先 list_files 再让模型选真实路径
测试全红但不知原因反馈信息太粗把完整 pytest stdout 回传给模型
每次运行结果差异大采样参数过大调低 temperature,固定 prompt 顺序
token 成本飙升无条件重试限制最大轮次 + 中间结果缓存

4.4 成本与性能的权衡

AI 工程绕不开算账。一次完整的生成测试用例流程,我实测大约消耗 8 万到 15 万 token。如果每次代码变更都全量跑评估,成本很快失控。我的做法是:对所有不依赖模型输出的中间状态做缓存,比如目录树、摘要、历史工具调用记录,命中缓存就直接复用,只有真正变化的部分才重新调用模型。

另外一个性价比很高的技巧是模型分层。简单任务比如格式转换、摘要生成,用便宜的小模型;复杂任务比如生成测试用例、审查失败原因,才用更强的大模型。这个思路在工程界叫 prompt routing,做 AI 应用时值得优先考虑,能省掉一大部分成本。

5. 从单个 Agent 到多 Agent 协作

5.1 为什么需要多个角色分工

单个 Agent 能力再强,承载的任务一多就容易顾此失彼。我在实践中慢慢把单 Agent 拆成三个角色:planner 负责分析和拆解任务,executor 负责具体工具调用和代码生成,reviewer 负责检查执行结果并判断是否返工。这其实是参考了真实团队的协作方式,也是我理解的多 AI 协作的基本形态。

拆分的核心价值,不只是“多模型并行干活”,而是“每个模型的任务边界更清晰”。planner 只需要做决策,不需要生成完整代码;executor 只需要聚焦生成,不需要纠结策略;reviewer 只需要对照目标做判断,不需要自己动手修复。边界清晰之后,提示词可以更短,输出质量也更稳定,因为每个模型都在做自己最擅长的那部分。

5.2 多 Agent 协作的消息协议

多 Agent 协作最怕没有约定,各说各话。我定义了一套简单的消息协议,每个 agent 之间传递的都是结构化消息:

{ "task_id": "test-gen-001", "actor": "planner", "action": "plan_delivered", "payload": { "target": "src/order.py", "strategy": "功能测试+边界测试" }, "status": "done" }

这条消息其实就是“数据和状态流转”。planner 输出 plan,executor 拿到 plan 后干活并回传结果,reviewer 把审核结果再传回 executor。所有消息汇总统一日志,每次流程结束后都能回放“谁在什么时间做了什么判断”,排查问题的时候特别有用。

多 Agent 协作的正确姿势,是先把单 Agent 和工具流程跑通,再把流程拆成角色。一上来就铺六个 agent 互相聊天,大概率会得到一本谁也读不懂的流水账。我见过太多团队把多 Agent 做成了一台烧钱的大型复读机,问题不在于概念不好,而是还没有为拆分做好基础约束和消息设计。

5.3 robot engineering 给我带来的启发

做这些工作流时,我一直参考 robot engineering(机器人工程)的思路。机器人领域有一个常识:机械臂的每个动作都受限于关节角度、力矩范围和环境感知,系统必须为这些限制做规划。把 AI 模型当作机械臂,把 prompt 当作控制指令,把工具当作执行器,很多设计就豁然开朗了。

比如,机器人不会因为“意识”而突然偏离规划路径,是因为控制器实时采样并校正。我们的 AI 工作流也应该如此:每次模型输出,都要经过校验,发现偏差立即校正。这个类比提醒我,工程化的核心不是让系统看起来智能,而是让系统在每一个不智能的瞬间都有兜底方案。这套思路,就是 harness engineering 和 robot engineering 最相通的地方。

6. 写在最后

做完这个 from-scratch 项目,我最深的体会是:AI 工程化不是把 prompt 写得更长更花哨,而是给系统加上可观测、可回滚、可评估的机制。模型是一个高速引擎,我们要做的是为它安装仪表盘、方向盘和安全气囊。判断一个 AI 项目是不是工程化,最简单的标准就是:当模型输出错误时,你的系统是能自动发现、自动修正,还是只能在用户反馈后手足无措?

最后分享一个非常实用的小建议:任何一个 AI 工作流,都先做一个最小的完整闭环——一段输入、一次模型调用、一次工具执行、一个结果判断。跑通这个四步闭环,比设计任何复杂架构都重要。这半年实践下来,我所有稳定好用的功能,都是从这样的小闭环长出来的;所有翻车严重的功能,也都是因为跳过了这个小闭环直接铺了大摊子。如果你正打算开始一个 AI 工程,老实从闭环做起,先把反馈转起来,再去装饰你的控制面板。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询