1. 从 Vibe Coding 到生产可用:一个 Coding Agent 调优项目的真实起点
Vibe Coding 这个词这两年被聊得很多,大意就是“凭感觉写代码”——你给 AI 一个模糊的意图,它帮你把代码补全、把功能搭起来,你只需要在关键节点上做判断。听起来很爽,但真把它放到生产环境里,问题就来了:模型生成的代码能跑,但不一定稳;能过编译,但不一定过测试;能过测试,但不一定符合团队的工程规范。这中间的落差,就是所谓的“最后一公里”。
我这次参与的项目,核心目标就一件事:把一个基于华为内部工程体系搭建的 Coding Agent,从“演示能跑”调到“生产敢用”。关键词里出现的 Harness、Agent Harness、Harness 工程,其实指的就是这套东西——它不是模型本身,而是包裹在模型外面的那层“驾驭系统”。你可以把它理解成马具:模型是马,跑得快不快看马,但跑得稳不稳、听不听指挥、会不会跑偏,全看这套马具怎么设计。
为什么这件事值得单独拿出来讲?因为大多数人调 Agent 的注意力都放在提示词上,觉得提示词写好了,Agent 就聪明了。实际做下来你会发现,提示词只占效果影响的两三成,剩下七八成全在 Harness 层:工具怎么定义、上下文怎么裁剪、失败怎么回退、多轮怎么收敛、结果怎么校验。这些东西不解决,你提示词写出花来,Agent 在生产环境里照样翻车。
这篇文章适合三类人看:一是正在做 Coding Agent 落地、被效果问题折磨的工程师;二是想了解 Agent Harness 工程到底在做什么的技术负责人;三是对 Vibe Coding 感兴趣、想知道“感觉写代码”和“生产级代码”之间差在哪的开发者。我会把整个调优过程拆开讲,包括我们踩过的坑、试过的方案、最后留下来的那套配置,尽量做到你读完能直接抄作业。
2. 整体设计思路:为什么 Harness 层才是效果调优的主战场
2.1 先搞清楚 Coding Agent 的能力边界在哪
动手调优之前,我们花了大概一周时间做基线测试,目的不是看 Agent 有多强,而是看它有多弱。具体做法是拿一批真实的历史需求单,让 Agent 从零生成代码,然后统计几个指标:一次通过率、编译通过率、单测通过率、人工 review 打回率。结果挺有意思——一次通过率只有三成出头,但编译通过率能到七成,单测通过率五成左右,人工打回率接近六成。
这组数据说明什么?说明模型本身“会写代码”,但它不知道“写完要自检”。它生成完就交差了,不会主动去跑编译、跑测试、看 lint。这就是 Harness 要补的第一课:把“生成”和“验证”串成一个闭环,让 Agent 自己走完整个流程,而不是生成完就停。
提示:做基线测试时一定要用真实需求,不要用 LeetCode 那种题。真实需求的模糊性、上下文依赖、工程约束,才是暴露问题的关键。
2.2 Harness 层的四个核心模块
我们把 Harness 拆成四块,每一块都对应一类效果问题:
| 模块 | 解决的问题 | 关键设计点 |
|---|---|---|
| 工具层 | Agent 能做什么 | 工具粒度、参数校验、返回格式 |
| 上下文层 | Agent 看到什么 | 检索策略、裁剪规则、优先级 |
| 控制层 | Agent 怎么走流程 | 状态机、回退策略、终止条件 |
| 校验层 | Agent 做得对不对 | 编译、测试、lint、规范检查 |
这四块里,工具层和上下文层决定上限,控制层和校验层决定下限。生产环境里,下限比上限重要得多——你可以接受 Agent 偶尔写不出好代码,但不能接受它写出坏代码还蒙混过关。
2.3 为什么不用“一把梭”的大提示词方案
一开始我们也试过把所有的规则、约束、示例全塞进一个超长提示词里,指望模型自己理解。实测下来问题很明显:提示词超过一定长度后,模型对后半部分的注意力会衰减,规则写了等于没写;而且每次需求变化都要改提示词,维护成本极高,改一处可能影响另一处,根本没法做版本管理。
后来改成“提示词只放角色和总原则,具体规则下沉到 Harness 层”,效果立刻不一样了。比如“生成代码后必须跑单测”这条,写在提示词里模型经常忘,但做成 Harness 的一个强制步骤,模型想跳都跳不过去。这就是把“软约束”变成“硬约束”的价值。
3. 核心细节解析:工具层与上下文层的调优实操
3.1 工具定义:粒度比数量重要
工具层最容易犯的错是“工具越多越好”。我们一开始给 Agent 配了二十多个工具,从读文件、写文件、跑命令到查文档、搜代码,结果 Agent 经常选错工具,或者在一个简单任务上反复横跳。后来砍到八个核心工具,效果反而提升了。
砍的原则是:一个工具只做一件事,且这件事的边界要清晰。比如“读文件”和“搜代码”必须分开,不能让一个工具既读又搜,否则 Agent 不知道该用哪个。再比如“跑命令”这个工具,我们把它拆成了“跑编译”“跑测试”“跑 lint”三个,虽然底层都是执行 shell,但拆开之后 Agent 的选择准确率明显提高。
工具的参数校验也很关键。我们给每个工具加了严格的参数 schema,比如文件路径必须是相对路径、命令必须是白名单内的,这样即使 Agent 生成错误的调用,也会在工具层被拦下来,而不是执行到一半才报错。
3.2 上下文裁剪:让 Agent 看到该看的,而不是全部
上下文层是效果差异最大的地方。同样的模型,上下文给得好不好,效果能差一倍。我们的做法是分三级检索:
- 第一级是需求相关的文件,直接全文注入,这是必须看到的
- 第二级是依赖相关的文件,只注入函数签名和关键注释,不注入实现
- 第三级是参考实现,只注入最相似的代码片段,控制在 200 行以内
这样做的理由是,模型的上下文窗口是有限的,你塞得越多,它越容易迷失。我们实测发现,当上下文超过窗口的 60% 时,模型对关键信息的召回率会明显下降。所以宁可少给,也要给准。
注意:上下文裁剪不是简单的截断,而是要有优先级。我们给每个文件打了相关性分数,按分数排序后从高到低注入,超出预算就丢弃低分文件。
3.3 一个具体的裁剪案例
有个需求是“给订单模块加一个超时自动取消的功能”。如果直接把整个订单模块的代码全注入,大概有三千多行,模型生成出来的代码虽然能跑,但经常忽略已有的状态机约束。后来我们改成只注入状态机定义、订单实体、以及一个类似的超时处理实现,总共不到四百行,模型生成的一次通过率从三成提到了六成多。
这个案例说明,上下文的质量比数量重要得多。你要让 Agent 看到“约束”和“范例”,而不是让它自己去几千行代码里找。
4. 控制层与校验层:让 Agent 自己走完闭环
4.1 状态机设计:把“生成”变成“生成-验证-修复”的循环
控制层的核心是一个状态机,定义了 Agent 在每个阶段该做什么、什么条件下进入下一阶段、什么条件下回退。我们的状态机大概长这样:
- 理解需求,输出实现计划
- 按计划生成代码
- 跑编译,失败则回到步骤 2 修复
- 跑单测,失败则回到步骤 2 修复
- 跑 lint,失败则回到步骤 2 修复
- 输出最终结果
每个步骤都有最大重试次数,超过就终止并报错。这个设计的关键在于,Agent 不再是“生成完就交差”,而是必须走完整个验证流程。实测下来,加了状态机之后,人工打回率从六成降到了三成左右。
4.2 回退策略:不是所有失败都要重来
回退策略是控制层里最容易被忽略的部分。一开始我们的做法是“任何一步失败就全部重来”,结果 Agent 经常在一个小错误上反复重试,浪费大量 token。后来改成分级回退:
- 编译错误:只回退到生成步骤,保留已有的上下文
- 单测失败:回退到生成步骤,但注入失败的单测信息
- lint 失败:只回退到修复步骤,不重新生成
这样改完之后,平均修复轮次从 3.2 降到了 1.8,token 消耗也降了四成。
4.3 校验层的三道关卡
校验层我们设了三道关卡,每道关卡都有明确的通过标准:
| 关卡 | 检查内容 | 通过标准 |
|---|---|---|
| 编译 | 语法、类型、依赖 | 零错误 |
| 单测 | 功能正确性 | 新增代码覆盖率不低于 70% |
| 规范 | 命名、注释、复杂度 | lint 零警告 |
这三道关卡里,单测覆盖率是最难达标的。我们的做法是让 Agent 自己生成单测,然后人工 review 单测质量。如果单测本身写得不对,那覆盖率再高也没意义。
提示:不要迷信覆盖率数字,要看单测是否覆盖了边界条件和异常路径。我们后来加了一条规则,要求 Agent 必须为每个新增的 public 方法生成至少一个异常路径的单测。
5. 常见问题与排查技巧实录
5.1 Agent 反复在同一个错误上打转怎么办
这是最常见的问题。Agent 生成代码、编译失败、修复、又失败,来回好几次。排查下来通常是两个原因:一是上下文里缺少关键信息,Agent 不知道正确的写法是什么;二是回退策略太粗,每次都全部重来,导致 Agent 丢失了之前的修复成果。
解决办法是给回退加上“记忆”:每次修复的尝试都记录下来,下次回退时把之前的失败原因一起注入,让 Agent 知道“这条路走过了,别再走”。我们加了这个之后,死循环的情况基本消失了。
5.2 生成的代码能跑但不符合团队规范怎么办
这个问题要靠校验层解决。我们的做法是把团队的规范写成 lint 规则,让 Agent 在提交前必须过 lint。但 lint 只能查格式,查不了设计。所以后来又加了一层“设计 review”,用另一个 Agent 来检查生成代码的设计是否合理,比如是否引入了不必要的依赖、是否破坏了已有的抽象。
5.3 上下文太长导致效果下降怎么办
前面提过,上下文超过窗口 60% 效果就会下降。我们的做法是动态调整:如果检索到的上下文超过预算,就按相关性分数排序,只保留高分部分。同时给 Agent 一个“主动检索”的工具,让它需要更多信息时自己去查,而不是一次性全塞给它。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 反复修复同一错误 | 回退策略太粗 | 检查是否丢失修复记忆 | 加入失败原因注入 |
| 代码能跑但不规范 | 校验层缺失 | 检查 lint 和设计 review | 补充校验关卡 |
| 效果随上下文增长下降 | 上下文超预算 | 检查注入量占比 | 动态裁剪,按分数排序 |
| 工具选择错误 | 工具粒度过粗 | 检查工具定义 | 拆分工具,明确边界 |
| 生成结果不稳定 | 提示词过长 | 检查提示词长度 | 规则下沉到 Harness |
6. 调优后的效果与可复用的配置建议
6.1 调优前后的数据对比
调优大概持续了六周,前后数据对比还是挺明显的:
| 指标 | 调优前 | 调优后 |
|---|---|---|
| 一次通过率 | 32% | 61% |
| 编译通过率 | 71% | 94% |
| 单测通过率 | 53% | 82% |
| 人工打回率 | 58% | 27% |
| 平均修复轮次 | 3.2 | 1.8 |
这些数字不是终点,但至少说明 Harness 层的调优是有效的。而且这些改动都是工程层面的,不依赖模型升级,换一个模型也能复用。
6.2 可以直接抄的配置清单
如果你也在做类似的 Coding Agent 调优,下面这套配置可以直接拿去用:
- 工具层:控制在 8 到 10 个核心工具,每个工具只做一件事,参数加严格 schema
- 上下文层:分三级检索,按相关性排序,注入量控制在窗口的 50% 以内
- 控制层:用状态机定义流程,分级回退,加入失败记忆
- 校验层:编译、单测、lint 三道关卡,单测要求覆盖异常路径
6.3 一个容易被忽略的细节
最后分享一个我们踩过的坑:Agent 的“终止条件”一定要设清楚。一开始我们没设终止条件,Agent 有时候会一直修复下去,直到 token 耗尽。后来加了最大轮次限制和“无进展检测”——如果连续两轮修复没有改善,就强制终止并报错。这个细节看起来小,但在生产环境里能省下大量成本。
提示:无进展检测的判定标准要明确,比如编译错误数没有减少、单测通过数没有增加,都算无进展。
这套东西调下来,我最大的体会是:Coding Agent 的效果调优,本质上不是调模型,而是调工程。模型的能力是给定的,你能控制的是它看到什么、做什么、怎么验证。把这三件事做好,效果自然就上来了。Vibe Coding 的最后一公里,走的其实就是这段工程路。