Claude Opus 4.8 在几项任务里输给了一套价格低得多的模型组合,这不是段子。最近社区流传的对比测试中,有人把昂贵模型的裸调用和一套经过设计的 Harness 工程放到同一批任务上,结果五项任务全部落败,而模型 API 单价差距被提到 57.1 倍。很多人看到这个结果会先质疑模型能力,但真正值得关注的不是“哪个模型更强”,而是 Harness 这个词反复出现背后的工程含义。
模型竞赛远未结束,但决定开发者日常效率的不再只是模型参数,而是围绕模型搭建的执行框架。接下来的内容会从一个价格对比事件出发,讲清楚 Harness 是什么、为什么它能让便宜模型赢、怎么搭一个最小 Harness、怎么评估它,以及落地时有哪些坑。适合正在做大模型 API 接入、智能编码助手、自动化代码修复、AI Agent 工具链的同学参考。
1. 先看“贵 57.1 倍却全输”的对比到底说明什么
1.1 对比中的价格差是怎么算出来的
先还原一下这个 57.1 倍是怎么来的。假设对比中的两个模型按 Token 计费,某个模型输出价格是每百万 Token 60 美元,另一个模型是每百万 Token 1.05 美元,两者价格比大概是 57.1 倍。也就是说,在输入输出 Token 数量不变的前提下,调用一次昂贵模型的费用约是便宜模型的 57 倍。
这里要区分“单价”和“总成本”。单价只反映每次 Token 消耗的价格,不反映整个任务最终花了多少。一个任务如果使用昂贵模型一次就通过,但便宜模型要重试 10 次才通过,最终总成本可能反而会拉平甚至反转。下面给一个成本估算模板:
| 成本项 | 计算公式 | 示例 |
|---|---|---|
| 单次调用成本 | 输入 Token 数 × 输入单价 + 输出 Token 数 × 输出单价 | 10万 × 0.001 + 1万 × 0.06 = 160 美元 |
| 任务总 Token | 单次调用 Token × 调用次数 | 11万 × 3 次 = 33万 |
| 任务总成本 | 各次调用成本累加 | 160 × 3 = 480 美元 |
| 单位通过成本 | 任务总成本 ÷ 通过任务数 | 480 ÷ 5 = 96 美元/个 |
现实中的价格结构还会包含上下文缓存、批量折扣、限流、重试、输出长度约束等。只看单价判断“哪个模型更划算”,会漏掉大头。
1.2 五项全输里有几项是模型输的,有几项是流程输的
被称为“五项全输”的测试,通常包含代码修复、测试生成、重构、文档补全、复杂推理这类任务。一个容易被忽略的细节是:对比双方并不是“模型 A 对模型 B”,而是“模型 A 的裸调用”对“模型 B + Harness 工程”。
裸调用是什么?就是一个请求发过去,模型返回一段代码或文字,过程结束。模型写完代码后没有编译,没有执行测试,没有把报错信息传回给模型,不会根据编译器输出自动修订。这种模式下,只要模型第一次生成的内容有语法错误或逻辑错误,任务就算失败。
Harness 则不同。它会调用代码解释器、编译器、测试命令,把退出码和 stderr 组装成反馈,再发回给模型,让模型看到具体的报错位置和异常信息,然后重新生成新版本。这个过程会重复多轮,直到测试通过或达到最大重试次数。
所以“五项全输”的真实含义是:单次生成的模型能力,输给了“生成 + 执行 + 反馈 + 修正”的完整闭环。并不能推导出“模型 A 不如模型 B”的结论,只能说明在相同的任务验收标准下,有执行反馈链路的方案完成率更高。
1.3 这个结果能被迁移到普通项目里吗
不能直接迁移成“某个模型不如另一个模型”这种结论,因为对比存在明显干扰项:
- 任务集是否足够多。五个任务在统计上没有说服力。
- 提示词是否对齐。双方是否用了相同的任务描述、输出约束和验收脚本。
- 执行环境是否一致。模型生成代码后的运行环境、依赖版本、沙箱限制是否相同。
- 随机性是否覆盖。大模型采样存在随机性,单次运行不能代表平均表现。
但有一个结论可以被迁移到普通项目:Harness 能显著改变同一模型的任务完成率。哪怕只做“执行工具 + 错误反馈 + 有限重试”这一件事,也能让原本“看起来不太聪明”的模型在结构化任务上变得可用。对一个团队来说,与其换更贵的模型,不如先检查自己的调用流程里有没有反馈闭环。
2. Harness 到底指什么:从“模型调用”到“模型工程”
2.1 一个类比:没有 Harness 的模型调用像裸引擎启动
Harness 在英文里是“线束、背带、控制装置”的意思。在 AI 工程语境里,它指围绕大模型 API 构建的一层可编排工程层,负责输入组装、工具执行、结果校验、错误反馈、重试节流、成本统计和日志审计。
通俗地讲,模型是发动机,Harness 是整车。发动机马力再大,没有变速箱、刹车、仪表盘和方向盘,也没法安全地开到目的地。模型本身能生成一段很优秀的代码,但如果没人负责编译它、运行它、把报错告诉它、控制它的重试次数,这段代码就只是一段没有验证过的文本。
技术上更准确的定义是:Harness 是一段程序,它调度模型和其他外部工具,让模型在循环中不断根据真实执行结果调整输出,直到满足验收条件或者终止条件。它不改变模型的内部权重,也不对模型做微调,它改变的是模型“被使用的方式”。
2.2 Harness、Agent、Workflow 是同一个东西吗
很容易混在一起,它们在真实系统里也经常重叠,但不完全等价。
| 术语 | 定位 | 核心机制 | 典型场景 |
|---|---|---|---|
| Harness | 控制层 / 运行环境 | 工具执行、错误反馈、重试、成本与日志控制 | 模型输出代码后自动跑测试并修正 |
| Agent | 决策主体 | 自主规划、工具选择、多步决策 | 让模型自己决定下一步调用哪个 API |
| Workflow | 预设流程 | 固定步骤、条件分支、人工审核点 | 将任务拆成“先生成、再检查、后发布” |
| RAG | 知识检索 | 向量检索、上下文拼装 | 给模型补充私有文档 |
| SDK / 客户端 | 接口封装 | HTTP 请求、重试、流式解析 | 只负责调用 API,不做业务决策 |
| Agent Framework | Agent 的开发框架 | 编排、记忆、插件、事件 | 快速搭建带工具的 Agent 应用 |
Harness 更偏“怎么安全可控地跑模型生成的东西”,Agent 更偏“让模型自己决定做什么”,Workflow 更偏“把步骤固定成流水线”。真实项目经常同时用到三者:外层是 Workflow,中间层是 Agent 决策,底层每个工具调用又被 Harness 控制。
2.3 Harness 工程需要哪些核心模块
一个能投入实际项目的 Harness,一般包含下面这些模块:
- 上下文组装器:把系统提示词、任务描述、历史结果、工具输出组合成一次模型请求的输入。它的职责是保证模型能看到“它上次犯的错”。
- 工具执行器:负责调用外部工具,比如执行 Python 代码、运行 pytest、执行 Shell 命令、查询数据库。关键点是超时、工作目录隔离、输出截断。
- 反馈循环:把工具执行结果转成紧凑的错误反馈,回填给模型。好的反馈会让模型知道“哪里挂了、挂在哪一行、期望是什么”。
- 终止条件判断:通过、达到最大重试次数、超过成本预算、超过时间预算,哪个先到都算结束。
- 评测器:把模型最终输出和验收脚本对比,输出是否通过的判断。
- 成本控制器:统计 Token、调用次数、耗时,触发限额后停止。
- 审计日志:记录每一轮请求、响应、工具输出、重试原因,供后续排查和复盘。
所谓“Harness 工程”,本质是把一次“输入文本,输出文本”的 API 调用,改造成一个可观测、可控制、可评测的软件系统。
3. 一个最小可运行的 Harness 示例:让模型跑完代码并自己改错
3.1 示例目标与前置要求
下面的示例不依赖具体厂商 SDK,使用 OpenAI 兼容的 Chat Completions 接口,通过 HTTP 请求调用模型。目标是让模型生成一个 Python 函数,然后 Harness 自动执行它。如果执行失败,把报错信息回填给模型,让模型重写,最多重试两次。
前置要求:
- Python 3.10 以上。
- 任一 OpenAI 兼容接口的 API Key 和 Base URL。
- 本地可以执行 Python 3 和 pytest。
如果当前项目用的是具体厂商 SDK,可以把示例中的call_model函数替换成对应 SDK 的调用,整体流程不变。
3.2 项目结构
mini_harness/ ├── harness.py # 核心循环:组装上下文、调用模型、反馈重试 ├── tools.py # 工具执行器:运行模型生成的代码并返回结果 ├── main.py # 读取任务,启动 harness,输出结果 └── tasks.json # 待执行任务列表这个结构足够小,但已经能体现 Harness 的核心分层:模型调用、工具执行、循环控制、任务入口分开。
3.3 核心代码:工具执行与反馈循环
先写工具执行器tools.py。它负责把模型生成的代码写入临时文件,用子进程执行,并返回退出码、stdout、stderr。
import subprocess import tempfile from pathlib import Path def run_python_code(code: str, timeout: int = 10) -> dict: """ 将模型生成的代码写入临时文件并执行。 返回结果统一为 dict,方便后续拼接错误反馈。 """ with tempfile.TemporaryDirectory() as tmp_dir: tmp_file = Path(tmp_dir) / "generated.py" tmp_file.write_text(code, encoding="utf-8") try: result = subprocess.run( ["python", str(tmp_file)], capture_output=True, text=True, timeout=timeout, cwd=tmp_dir, ) return { "ok": result.returncode == 0, "exit_code": result.returncode, "stdout": result.stdout[-2000:], "stderr": result.stderr[-2000:], } except subprocess.TimeoutExpired: return { "ok": False, "exit_code": -1, "stdout": "", "stderr": f"execution timeout after {timeout}s", }实现要点有三个。第一,代码写入临时目录而不是当前目录,避免模型生成的临时文件污染项目。第二,使用timeout参数限制执行时间,防止死循环把 Harness 卡死。第三,输出统一用 dict 结构化返回,便于后续拼接错误反馈。
然后是核心循环harness.py,它负责调用模型并根据工具执行结果决定是否重试。
import json import requests class MiniHarness: def __init__(self, base_url: str, api_key: str, model: str, max_retries: int = 2, temperature: float = 0.2): self.base_url = base_url.rstrip("/") self.api_key = api_key self.model = model self.max_retries = max_retries self.temperature = temperature def call_model(self, messages: list) -> str: """调用 OpenAI 兼容接口,返回模型回答的文本内容。""" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } payload = { "model": self.model, "messages": messages, "temperature": self.temperature, } resp = requests.post( f"{self.base_url}/chat/completions", headers=headers, json=payload, timeout=60, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] def solve(self, task: str, run_tool) -> dict: """ Harness 主循环: 1. 组装初始任务消息 2. 调用模型生成代码 3. 执行工具 4. 如果失败,把错误反馈追加到上下文,重试 """ messages = [ { "role": "system", "content": ( "你是一个 Python 程序员。请只输出可以直接保存为 .py " "文件的代码,不要输出 Markdown 围栏,不要解释。" ), }, {"role": "user", "content": task}, ] attempts = [] for attempt in range(self.max_retries + 1): answer = self.call_model(messages) result = run_tool(answer) record = { "attempt": attempt + 1, "code": answer, "result": result, } attempts.append(record) if result["ok"]: return {"ok": True, "attempts": attempts} # 将错误反馈作为 assistant 之后的附加消息回填 messages.append({"role": "assistant", "content": answer}) feedback = ( f"代码执行失败。exit_code={result['exit_code']}\n" f"stderr:\n{result['stderr']}\n" f"stdout:\n{result['stdout']}\n" "请修复代码,只输出完整的 .py 文件内容。" ) messages.append({"role": "user", "content": feedback}) return {"ok": False, "attempts": attempts}关键点在反馈循环里:每次失败后,把stderr和stdout结构化地拼进消息历史,模型才能知道自己错在哪里。如果只是简单地把“运行失败”四个字返回给模型,模型很难定位问题。
再写main.py作为入口,读取任务并逐个执行。
import json import os from pathlib import Path from harness import MiniHarness from tools import run_python_code def main(): base_url = os.getenv("LLM_BASE_URL", "https://api.example.com/v1") api_key = os.getenv("LLM_API_KEY", "") model = os.getenv("LLM_MODEL", "some-model") harness = MiniHarness( base_url=base_url, api_key=api_key, model=model, max_retries=2, temperature=0.2, ) tasks = json.loads(Path("tasks.json").read_text(encoding="utf-8"))["tasks"] for task in tasks: print(f"\n===== Task: {task['name']} =====") result = harness.solve(task["prompt"], run_python_code) for record in result["attempts"]: status = "PASS" if record["result"]["ok"] else "FAIL" print(f" attempt {record['attempt']}: {status}") print(f" final ok: {result['ok']}") if __name__ == "__main__": main()tasks.json内容示例:
{ "tasks": [ { "name": "reverse_string", "prompt": "编写一个 Python 函数 reverse_string(s: str) -> str,返回字符串反转结果。不要调用库函数实现反转。" }, { "name": "compute_fibonacci", "prompt": "编写一个 Python 函数 fib(n: int) -> int,返回第 n 个斐波那契数,要求 n 从 0 开始。" } ] }这个示例没有做代码静态检查,也没有删除临时目录外的文件,只是让读者理解 Harness 的最小闭环长什么样。
3.4 关键参数速查
MiniHarness 里已经暴露了几个关键参数,它们也是真实 Harness 最常需要调整的地方。
| 参数 | 默认值 | 调大的影响 | 调小的影响 | 建议 |
|---|---|---|---|---|
| max_retries | 2 | 提高通过率,但成本和耗时同步增加 | 成本更低,但复杂任务容易失败 | 简单任务 1~2,复杂任务 3~5,必须配成本上限 |
| temperature | 0.2 | 输出更多样,容易探索不同思路 | 输出更稳定,适合代码生成 | 代码生成建议 0~0.3 |
| tool_timeout | 10 秒 | 能容纳耗时更长的测试 | 快速失败,节省时间 | 按任务复杂度设 10~120 秒 |
| 上下文截断长度 | 2000 字符 | 保留更多错误细节 | 减少 token 消耗 | 先截断 stderr 和 stdout,避免无限增长 |
3.5 运行与验证
配置环境变量后,运行:
export LLM_BASE_URL="https://api.example.com/v1" export LLM_API_KEY="your_api_key" export LLM_MODEL="your_model" python main.py预期输出大致是这样:
===== Task: reverse_string ===== attempt 1: PASS final ok: True ===== Task: compute_fibonacci ===== attempt 1: FAIL attempt 2: PASS final ok: True第一个任务一次通过,第二个任务第一次生成失败,Harness 把报错反馈给模型后第二次通过。这是最典型的 Harness 工作轨迹。
验证时不要只看“最终是否通过”,还要关注三点:
- 失败时反馈是否包含真实报错。如果 stderr 为空,说明模型没有触发到真正的问题。
- 重试是否收敛。如果连续多次失败且报错不同,可能是提示词或任务描述有问题。
- 上下文是否增长过快。如果每一轮都保留完整 stdout,几次重试后 token 消耗会快速放大。
4. 想证明 Harness 有效:设计一对可对比的实验
4.1 固定任务集,别拿一两个问题下结论
评估 Harness 有没有用,不能靠一两个例子感觉。建议准备一个固定的任务集,至少 5 到 10 个任务,每个任务都有可自动判定的验收脚本。
任务集设计建议覆盖四类难度:
| 难度 | 任务示例 | 典型特征 | 通过标准 |
|---|---|---|---|
| 单步实现 | 反转字符串、求最大公约数 | 一个函数、一个返回 | 单测通过 |
| 多步实现 | 读取 JSON 文件并统计字段 | 文件 IO + 数据处理 | 输出文件内容符合预期 |
| 工具调用 | 给定一个 API 地址,编写请求代码 | 需要处理网络和异常 | 返回结果正确 |
| 调试修复 | 给一段有 bug 的代码,要求修复 | 需要阅读上下文和定位 | 修复后测试通过 |
任务越多,结论越接近真实水平。五个任务只能说明方向,不能说明比例。
4.2 两个对照组:裸 API 与 Harness
要证明 Harness 的价值,至少跑两组:
- 对照组:裸 API,只调用一次模型,不执行代码、不反馈、不重试。模型输出什么就评什么。
- 实验组:Harness,同一模型、同一提示词,但执行代码,失败后反馈重试,最大重试次数固定为 2。
两组使用同一份任务集、同一个温度参数、同一个验收脚本。温度建议都设置为 0.2,降低随机性影响。每次实验至少跑 3 轮,取平均值。
为了对比,需要让裸 API 组也产出“模型认为的代码”,然后再用验收脚本去评。不要凭肉眼判断代码像不像对的。
4.3 要盯的指标不只“通过率”
通过率是最直观的指标,但不足以判断是否能投入生产。完整记录以下数据:
- 通过率:任务通过数 ÷ 任务总数。
- 平均尝试次数:总尝试次数 ÷ 任务总数。
- Token 消耗:输入 Token、输出 Token、总 Token。
- API 成本:每次调用的真实价格累加。
- 平均耗时:从任务开始到结束的时间。
- 人工修正量:Harness 输出没有通过时,人工需要改多少行。
- 日志完整性:出错时能否从日志还原完整链路。
一个对比结果模板:
| 指标 | 裸 API | Harness | 说明 |
|---|---|---|---|
| 通过率 | 40% | 80% | Harness 提升明显 |
| 平均尝试次数 | 1 | 2.1 | Harness 通过重试换通过率 |
| 总 Token | 100万 | 220万 | Token 消耗增加 |
| API 成本 | 200 元 | 260 元 | 成本增加,但幅度低于 57 倍 |
| 单位通过成本 | 200 ÷ 4 = 50 元 | 260 ÷ 8 = 32.5 元 | Harness 的单位通过成本反而更低 |
“价格贵 57.1 倍”这个事件的核心就在这里:即使 Harness 让便宜模型多消耗了 Token,只要任务完成率提升,单位通过成本仍然远低于昂贵模型。
4.4 从结果反推 Harness 该调哪里
实验结果不会天然达到预期,常见情况有三个:
- 通过率低但重试次数很高:说明模型生成的代码经常有小错误,Harness 能救回来,可以保留当前重试策略。
- 通过率低且每次失败报错相同:说明反馈信息不够,需要把更完整的 stderr、测试输出或输入数据上下流传给模型。
- 重试次数很少但 Token 消耗很大:说明上下文增长过快,需要截断历史输出或对历史做摘要。
5. 落地 Harness 时最常踩的五个坑
5.1 只封提示词,没建反馈闭环
现象:模型一遍遍地给出同样的错误代码,或者最终结果永远是第一次生成的内容。
原因:Harness 只是把用户的提示词包装了一下,并没有把工具执行结果返回给模型。模型看不到“事实”,只能继续猜。
检查方式:打印每轮重试时 messages 的最后一条内容,看里面是否包含 stderr 或测试失败输出。
解决方法:在每次工具执行失败后,把退出码、stderr、stdout 和测试断言信息格式化成一条独立的 user 消息,追加到消息历史。
预防建议:在 Harness 设计阶段就把“反馈回填”当作必需模块,不要认为模型一次生成就能通过。
5.2 工具执行没有超时和权限边界
现象:模型生成的代码里出现死循环、sleep(100)、删除文件、读取敏感路径等操作。
原因:Harness 直接在宿主机上执行模型输出,没有做沙箱隔离和超时控制。模型本身可能没有恶意,但因为训练数据或提示词影响,生成了危险代码。
检查方式:查看任务执行记录的耗时和 Shell 指令,是否出现过超过预期时间的进程。
解决方法:所有工具执行加timeout参数;优先在 Docker 容器或临时虚拟机里运行;限制工作目录;使用白名单允许特定命令。
预防建议:把“不可信输出不能在宿主环境直接执行”作为默认安全红线。
5.3 对重试次数和温度不设上限
现象:一个简单任务花掉大量 Token,原因是无限重试;或者同一任务多次运行结果差异很大。
原因:Harness 的终止条件只有“通过”这一条,没有成本上限和时间上限。温度设置过高,导致每次生成都不一样。
检查方式:查看文章开头提到的单任务成本核算,看一个失败任务的 Token 消耗是不是正常任务的几十倍。
解决方法:同时设置max_retries、单任务 Token 上限、总成本上限、总耗时上限;温度代码生成场景控制在 0 到 0.3。
预防建议:在 Harness 入口加一个统一的预算拦截器,任何条件先触发都强制结束。
5.4 没有日志,输赢全靠感觉
现象:任务失败后不知道哪一轮失败、为什么失败、模型最后看到的是什么。
原因:Harness 没有记录消息历史、工具输出和重试原因。出现问题只能重新跑一遍,代价高且无法复现。
检查方式:看生产环境是否能把每次任务从输入到输出的完整轨迹还原出来。
解决方法:每轮调用写一条结构化日志,包含任务 ID、attempt 序号、模型输入摘要、模型输出摘要、工具退出码、工具 stderr、耗时、Token 数。
预防建议:日志字段固定成 JSON,纳入统一日志平台,方便按任务 ID 检索。
5.5 忽略上下文窗口硬边界
现象:任务越来越复杂时,模型开始遗忘早期约束,比如忘记“只输出代码”或忽略某个输入字段。
原因:每次重试都把全部历史塞进上下文,早期工具输出越长,越容易挤占窗口并干扰注意力。
检查方式:统计重试 n 轮后一次请求的输入 Token 数量,看是否接近模型上下文上限。
解决方法:对工具输出做摘要;只保留最近两轮完整代码和当前失败信息;早期历史压缩成“你已经试过哪几种方案,分别失败了”这样的摘要。
预防建议:在 Harness 里加一个上下文压缩模块,当输入 Token 超过阈值时自动触发。
6. 从能跑到生产可用:Harness 的工程化差异
6.1 学习环境:本地脚本快速验证
学习阶段,本文第 3 节的 MiniHarness 已经够用。你只需要一个 API Key、几个任务、一个能执行代码的本地环境。这个阶段的重点不是做高并发,而是理解反馈循环的逻辑:
- 模型生成代码。
- 工具执行代码。
- 失败信息回填。
- 模型基于反馈重写。
- 通过或达到终止条件。
在这个阶段,不要一上来就搭 Kubernetes、消息队列、分布式追踪。先用小任务验证 Harness 是否能提升通过率,再决定投入多少工程资源。
6.2 生产环境:并发、限流、权限、监控、回滚
生产环境的 Harness 是服务,不是脚本。只把 MiniHarness 包成一个 Web API 还不能上线,需要补的工程能力包括:
- 并发控制:多个任务同时运行时,限制模型 API 并发数,避免触发限流。
- 队列与超时:任务排队执行,超过队列等待时间的任务直接失败。
- 密钥管理:API Key 不能硬编码在环境变量里,要放到密钥管理系统。
- 沙箱执行:模型生成的代码必须隔离运行,不信任任何输出。
- 预算控制:每个租户、每个任务、每个模型分别设置成本上限。
- 审计日志:谁提交了什么任务、模型看到了什么、工具执行了什么,都要留痕。
- 失败恢复:任务执行到一半进程崩溃,需要能从最近一次检查点恢复或安全终止。
- 多模型路由:根据任务类型选择不同模型,比如简单任务走便宜模型,复杂任务走高性能模型。
6.3 学习环境与生产环境的差异表
| 维度 | 学习环境 | 生产环境 |
|---|---|---|
| 执行环境 | 本地 Python 进程 | 容器、沙箱、隔离工作目录 |
| 并发 | 单任务串行 | 多任务队列 + 限流 |
| 密钥管理 | 环境变量 | 密钥管理系统 |
| 日志 | 控制台打印 | 结构化日志 + 链路追踪 |
| 成本控制 | 手动观察 | 预算 + 告警 + 强制终止 |
| 模型服务 | 单一 API | 多模型路由 + 降级容灾 |
| 安全 | 信任本地输入 | 默认不可信,全部校验 |
| 回滚 | 重新运行脚本 | 版本化配置 + 蓝绿发布 |
生产环境里,Harness 更像一个独立的执行平台,而不是某个功能里的工具类。模型厂商、执行环境、任务系统、评测系统都要解耦。
7. 实用落地方案:Harness 工程启动和评估清单
7.1 从一个可验收的小任务开始
建议按下面的顺序启动第一个 Harness 项目:
- 选一个验收标准清晰的任务,比如“修复某个单元测试”。
- 只写验收脚本,先不写 Harness。先确认“通过”是什么。
- 用裸 API 跑一遍,记录通过率和成本。
- 加上“工具执行 + 错误反馈 + 重试”的最小 Harness。
- 再跑一遍同一任务集,对比通过率、Token、耗时和单位通过成本。
- 如果通过率提升且单位通过成本下降,再扩展任务集。
- 如果提升不明显,优先检查反馈信息是否完整,再检查模型和提示词。
判断 Harness 是否值得继续投入,标准很简单:同一模型,Harness 方案和裸调用相比,是否在可接受的成本增幅内换来了通过率提升。如果答案是“没有”,说明问题不在流程,而在模型能力或任务定义。
社区里讨论较多的 deepseek harness、codex harness,以及一些开源项目的 Harness 插件,其实都是这个方向的具体实现。评估时可以安装试跑,但要以自己任务集上的通过率和成本为准,不要只凭别人的对比图决定选型。
7.2 Harness 工程落地检查清单
下面的清单可以作为代码评审和上线前的检查项:
- 是否每个工具调用都有超时限制。
- 是否每条失败信息都能回填给模型,而不是只记录在日志里。
- 是否设置了最大重试次数、单任务 Token 上限和总成本上限。
- 是否记录了完整对话历史、工具输出和重试原因。
- 模型生成的内容是否在隔离环境执行。
- 是否对 stderr、stdout 做截断,防止上下文无限膨胀。
- 是否有明确终止条件:通过、超时、超预算、重试耗尽。
- 连续失败时是否有告警。
- 是否区分了模型输入和工具输出,避免模型被自己的旧输出干扰。
- 是否能把一次任务的完整链路从日志里还原出来。
7.3 后续扩展方向
Harness 工程的扩展空间很大,常见方向包括:
- 评测集建设:把线上失败案例沉淀成回归评测集,每次改 Harness 都跑一遍。
- 多模型路由:根据任务难度和成本预算选用不同模型,简单任务用便宜模型。
- 反馈模板库:把常见编译错误、测试断言、网络错误整理成标准反馈模板。
- 上下文压缩策略:对历史工具输出做摘要,让模型保留关键信息。
- 沙箱安全加固:在容器内限制 CPU、内存、网络、文件系统。
- 离线评估:把 Harness 跑过的轨迹保存起来,做离线批量评估,避免每次评估都消耗 API 费用。
- Harness 平台化:给团队提供统一的任务提交、运行、评估、报告界面。
对一个长期使用 AI 编码助手的团队来说,Harness 不是一次性脚本,而是会随任务复杂度、模型版本、成本预算持续演进的公共基础设施。
8. 回到开头那场对比:真正的启示是什么
多花 57.1 倍的钱买模型,未必能换来 57.1 倍的完成率提升。事件真正的启示是:当模型能力差距没有达到“一个能写对、一个完全不能写”的程度时,流程设计往往比模型选择更影响结果。
一个能执行代码、能把报错回填给模型、能控制重试成本和审计日志的 Harness,可以让一个便宜的模型在结构化任务上达到接近昂贵模型的表现。反过来,如果一个昂贵模型只被当成“问答盒子”来调用,没有执行反馈闭环,它的很多能力在生产环境里其实没有被发挥出来。
对大多数团队来说,最值得做的一步不是研究最新最强模型,而是给当前正在使用的模型搭一个带反馈闭环的最小 Harness。取 5 个有验收脚本的任务,先裸调用跑一遍,再 Harness 跑一遍,把通过率、Token 消耗、单位通过成本和人工修正量记录下来。这个实验的成本很低,但能清晰告诉你:你缺的到底是模型,还是流程。