揭秘Harness Engineering:不改模型也能让LLM编码能力大增?
2026/9/11 6:09:31 网站建设 项目流程

过去两年里,AI 编程类工具最大的认知误区之一,就是很多人把“编程效果”等同于“模型本身”。模型换得更强、参数更大,似乎就代表代码写得更好。但真实工程里,你会遇到一个非常反直觉的现象:同一个模型,在 A 团队的 Agent 里表现得像高级工程师,在 B 团队的 Agent 里却连“给函数加个参数”都会改错文件。

这篇文章想聊的,正是这类现象背后被反复提及、却又很少被讲透的概念——harness engineering。它对应的场景可以浓缩成一个英文标题:We improved 15 LLMs at coding in one afternoon. Only the harness changed。

翻译过来就是:一个下午改进 15 个 LLM 的编码能力,而且只改了 harness,模型一个都没换。

这篇文章不是要论证“某个工具比某个模型强”,而是想拆开“harness”这个词,讲清楚它到底由哪些工程组件构成,为什么它能在不碰模型参数的情况下显著改变编码质量,以及一个普通开发者如何为自己的项目搭建最小编码 harness。如果你正在做 AI Agent、AI 编程插件、代码评审机器人,或者只是想把 LLM 接入自己的研发流程,这篇文章会给你一个可以直接落地的框架。

1. 一个下午、15 个 LLM,变的是什么

先回到这个标题本身。它之所以有传播力,是因为它挑战了大家默认的因果链:编码能力 = 模型能力。如果这个等式成立,那么不改模型,编码只能原地踏步。但“only the harness changed”恰恰说明,模型能力只是结果的一部分,甚至只是一小部分。

从工程视角来看,模型在编码任务里真正消费的输入不是“整个代码仓库”,而是你喂给它的上下文。它真正执行的输出也不是“直接改好的代码”,而是它说出来的、被你的程序解析并落到磁盘上的文本。那么,模型最终能不能写对代码,很大程度上取决于三个变量:

  • 模型看到了什么(上下文范围、文件内容、相关依赖、历史修改记录)。
  • 模型被要求怎么行动(输出 JSON 调用工具,还是直接输出补丁,还是自由对话)。
  • 模型行动后得到了什么反馈(编译错误、测试输出、lint 结果,还是什么都没有)。

这三个变量,都是 harness 的职责。

所以“一个下午改进 15 个 LLM”并不是魔法。更合理的解释是:这套 harness 重新设计了上下文收集、工具调用、错误反馈和重试策略,而这些策略对 15 个不同的模型都有效。模型变了,但 harness 的“环境红利”还在。反过来说,如果你把同一个模型接到 15 套粗糙的 harness 里,效果波动也会非常大。

理解这件事,对技术选型有直接影响。团队在为 AI 编程工具做模型选型时,常常只比较“模型榜单”和“跑分”,却忽略了同样的模型在自己的执行框架里能发挥几分。很多团队在纠结“要不要换更强的模型”之前,其实应该先问一句:我们的执行框架,是不是已经拖累了当前模型?

2. 什么是 LLM Harness:基础概念与核心原理

“harness”这个词,直译是“挽具”或“线束”,在软件领域早期更多出现在测试领域,比如“test harness”指一套准备数据、执行测试、收集结果的脚手架。到了 LLM 场景,harness 的含义被扩展了:它是模型之外的全部执行与交互系统。

换一种更直白的说法:harness 是模型的“外部大脑”。模型负责根据当前 token 序列生成下一个 token,但“当前 token 序列”从哪里来、生成一段文本后接下来干什么、失败后怎么重来,这些全部由 harness 决定。

要理解 harness 和 prompt engineering 的区别,可以用一个类比。提示工程像是“说服一个人”:你尽量把话讲清楚,把背景交代完整,让对方一步到位。Harness 工程则像是“给一个人搭一套工作台”:工具有序摆放,图纸摊开在合适位置,旁边有测量仪器,做完一步马上有质检报告。前者依赖一次对话的质量,后者依赖整套工作流程的可靠性。

在编码场景里,harness 至少包含以下层次:

  • 上下文管理层:决定哪些文件进 prompt、哪些不进,文件怎么摘要,代码怎么切片。
  • 工具调用层:定义模型能调用的函数,解析模型输出的结构化指令,执行后把结果写回对话。
  • 迭代决策层:第一次结果不满意怎么办,测试失败怎么办,是重试、改方案,还是放弃。
  • 执行与回滚层:修改文件前是否做快照,能否一键回退,代码合入前是否自动跑校验。
  • 观测与日志层:每一轮模型输入、工具输出、中间结果是否被完整记录。

这五个层次协同工作,才能让一个基础模型稳定地完成“读代码 -> 改代码 -> 验证 -> 修错”的循环。很多人把 coding agent 理解为“一个很会写代码的模型”,但真实架构里,模型只是循环里的一个环节。

社区里经常提到的“DeepSeek harness”“codex harness”这类词,其实也证明了同一趋势:开发者开始围绕具体的编码 Agent 构建外挂的执行层。模型负责内容生成,开源社区负责把上下文、工具、验收、重试这些环节做厚。这个分工以后会越来越清晰。

3. Harness Engineering 的五个关键维度

理解了 harness 的概念,接下来要看它在编码场景中到底强在哪。以下五个维度是最值得投入精力的地方,也是“只改 harness”就能看到明显变化的主要原因。

3.1 上下文压缩与检索

编码任务和普通问答任务最大的不同,是代码仓库往往有大量上下文,但模型窗口有限。把一个十万行仓库全部塞进 prompt 既不现实,也浪费成本。

较好的做法是做分层压缩:

  • 先扫描仓库结构,生成文件树和模块说明。
  • 根据任务关键词,定位可能相关的文件和函数。
  • 对单个文件,截取关键类和函数,而不是整文件导入。
  • 对历史信息,只保留最近几轮修改的 diff。

这套逻辑和 RAG 非常像,但它检索的不是泛泛的“知识”,而是“与本次修改直接相关的代码范围”。一个编码 harness 的上下文质量,可以直接决定模型会不会改错文件、会不会重复实现已有函数、会不会把依赖关系搞混。

3.2 工具调用协议

模型不直接操作终端和文件。它通过结构化输出,把“执行哪个函数、传什么参数”表达出来。Harness 需要解析这种表达,并安全地执行。

目前比较常见的协议是 function calling。模型输出一个 JSON,里面包含函数名和参数,harness 验证后调用对应的工具。这个环节的难点在于容错:模型可能输出非法 JSON、可能编造不存在的函数、可能传错参数类型。一套成熟的 harness,必须能在这些错误发生时自动降级,而不是让整个任务崩溃。

3.3 多轮反馈机制

模型第一次生成的代码很少能直接通过全部测试。更常见的路径是:生成代码 -> 编译失败 -> 看到错误信息 -> 修改代码 -> 再跑测试 -> 还有一部分不通过 -> 再改。

这个“反馈回路”是否顺畅,是编码 harness 最核心的价值。许多模型单轮表现一般,但只要能拿到准确的错误日志,第二轮、第三轮的修复准确率会迅速上升。反之,如果 harness 只会把代码写进文件,然后就“等用户自己看”,模型的纠错能力就完全发挥不出来。

3.4 状态管理与回滚

编码任务是有风险的。Agent 可能在改一个 bug 时,顺手把另外两个函数改坏了。如果 harness 不管理代码状态,开发者的工作区会快速失控。

合理的状态管理至少包括:修改前对目标文件做快照,运行验收命令,失败时恢复快照。这样即使模型连续多轮修改,也能随时回到稳定版本。

3.5 观测与可调试性

最后一个是容易被忽略的维度。如果 harness 的每一步都不可见,那么模型表现变差时,你根本无法定位是上下文问题、工具调用问题、还是模型本身问题。

好的 harness 会记录:每一轮的完整 prompt、模型输出、工具执行结果、代码 diff、测试日志。这样你才能把一个失败的 case 回放,并判断“换 harness 组件”还是“换模型”。

4. 环境准备与前置条件

在开始构建自己的 coding harness 之前,先明确运行环境。以下是一个通用环境清单,版本信息请以你实际使用的官方文档为准,这里只展示通用思路。

  • 操作系统:Linux / macOS 均可,Windows 建议配合 WSL 或 Docker 环境。
  • 编程语言:推荐 Python 3.10 及以上,社区工具链完善。
  • LLM API:选择一个支持 function calling 或结构化输出的模型服务。
  • 代码仓库:建议准备一个小的测试项目,最好包含多个文件、若干测试用例。
  • 沙箱环境:如果允许 Agent 执行任意命令,建议使用 Docker 或受限容器隔离。

如果你不确定从哪里开始,可以先用一个很小的任务验证整套链路:比如“在一个 Python 项目里新增一个函数,并补充测试”。这个任务足够简单,但能完整暴露 harness 的上下文、工具调用、反馈三个核心环节。

5. 最小可用 Coding Harness:完整示例

下面给出一个极简但可运行的 coding harness 示例。它主要演示四个部分:上下文压缩、工具调用协议、测试反馈回路、主循环入口。代码风格以“通用实现思路”为主,具体方法名和参数以你使用的模型 SDK 为准。

5.1 第一步:上下文收集与代码摘要

我们先实现一个函数,它接收一个任务描述,扫描仓库结构,输出一个精简上下文。

# file_path: harness/context.py import os from pathlib import Path def create_repo_context(repo_path: str, max_files: int = 10) -> str: """扫描仓库,生成一个可放入 prompt 的精简上下文。""" repo = Path(repo_path) tree_lines = [] file_contents = [] for i, path in enumerate(repo.rglob("*")): if i >= max_files: break if path.is_file() and not path.name.startswith("."): tree_lines.append(f"- {path.relative_to(repo)}") if path.suffix in {".py", ".js", ".ts", ".java", ".go", ".rs", ".md"}: content = path.read_text(encoding="utf-8", errors="ignore") if len(content) > 800: content = content[:800] + "\n# ... (truncated)" file_contents.append(f"### {path.relative_to(repo)}\n```\n{content}\n```") header = "## 仓库文件结构\n" + "\n".join(tree_lines) body = "\n\n".join(file_contents) return f"{header}\n\n## 关键文件内容\n{body}"

这个函数会把仓库中最多 10 个文件生成摘要。真实项目中,你通常需要结合 grep 工具、语义检索或 AST 分析来精准定位文件,这里的最小实现只用于跑通链路。

5.2 第二步:模型调用与工具执行

我们定义一个简单的工具集。为了让示例清晰,只提供两个工具:读取文件内容和执行指定命令。

# file_path: harness/tools.py import subprocess from pathlib import Path def read_file(path: str) -> str: """读取文本文件内容,返回给模型继续分析。""" p = Path(path) if not p.exists(): return f"错误:文件不存在 {path}" return p.read_text(encoding="utf-8", errors="ignore") def run_command(command: str, cwd: str) -> str: """在指定目录下执行命令,返回标准输出和错误信息。""" try: result = subprocess.run( command, shell=True, cwd=cwd, capture_output=True, text=True, timeout=30, ) output = result.stdout[-2000:] + "\n" + result.stderr[-2000:] return output.strip() or "命令执行完成,无输出。" except Exception as e: return f"命令执行异常:{e}"

这里的工具函数直接执行 shell 命令,存在安全风险。生产环境请将命令限制在白名单中,并在沙箱容器内运行。

5.3 第三步:测试反馈回路

模型写完代码后,harness 需要自动运行测试,并把结果返回给模型。这部分逻辑可以封装成一个反馈函数。

# file_path: harness/feedback.py import subprocess def run_tests(repo_path: str, test_command: str = "python -m pytest --tb=short") -> str: """运行测试,返回适合模型阅读的紧凑反馈。""" result = subprocess.run( test_command.split(), cwd=repo_path, capture_output=True, text=True, timeout=60, ) tail = (result.stdout + result.stderr)[-2000:] if result.returncode == 0: return f"测试全部通过。\n{tail}" else: return f"测试未通过,返回码 {result.returncode},末尾输出如下:\n{tail}"

这个反馈回路是“一个下午改进 15 个 LLM”的关键。模型第一次写代码可能出错,但一旦把错误信息喂回去,模型会自动调用修复流程。如果你的 harness 少了这一环,模型能力一定会被严重低估。

5.4 第四步:主循环

最后把各模块拼起来,写入 main.py。

# file_path: harness/main.py from context import create_repo_context from tools import read_file, run_command from feedback import run_tests TOOLS = { "read_file": lambda args: read_file(args["path"]), "run_command": lambda args: run_command(args["command"], args["cwd"]), } def main(): repo_path = "demo_project" task = "在 calculator.py 中新增一个 multiply 函数,并在 test_calculator.py 中补充测试。" context = create_repo_context(repo_path) # 构建初始消息,把工具说明注入 system prompt system_prompt = f"""你是一个编码 Agent。你可以使用以下工具: - read_file: 读取文件内容,参数为 {{"path": "文件路径"}} - run_command: 运行命令,参数为 {{"command": "命令", "cwd": "工作目录"}} 请先阅读文件,然后修改代码。修改完成后,运行测试。最终输出不超过 2000 字。 {context}""" messages = [{"role": "system", "content": system_prompt}] for step in range(5): # 这一行需要替换成你真实使用的模型 SDK 调用 response = call_llm(messages, tools=TOOLS.keys()) if response.get("tool_calls"): for call in response["tool_calls"]: tool_name = call["name"] args = call["arguments"] result = TOOLS[tool_name](args) messages.append({"role": "tool", "name": tool_name, "content": result}) messages.append({"role": "assistant", "content": response["text"]}) else: # 模型认为任务已完成,我们跑一遍测试验证 test_feedback = run_tests(repo_path) messages.append({"role": "user", "content": f"请检查测试结果:{test_feedback}"}) if "测试全部通过" in test_feedback: print("任务完成,全部测试通过。") break if step == 4: print("达到最大迭代次数,测试仍未通过。") break # 保存对话记录,方便排查 import json with open("trace.json", "w", encoding="utf-8") as f: json.dump(messages, f, ensure_ascii=False, indent=2) if __name__ == "__main__": main()

上面的call_llm函数是示意占位,你需要替换为真实 SDK。例如 OpenAI 风格是openai.ChatCompletion.create,也可以使用其他兼容服务。关键是保持“模型输出工具调用 -> harness 执行 -> 结果回填 -> 再交给模型”这个循环。

6. 运行结果与效果验证

代码写完后,怎么验证 harness 确实有效?这里需要一套可持续复用的评估方法,而不是拍脑袋看一两个案例。

建议维护一份固定任务集,至少包含 10 个不同类型的编码任务:

  • 新增一个简单函数。
  • 修改函数签名并同步所有调用点。
  • 修复一个单元测试失败。
  • 重构一个重复代码块。
  • 给模块补充参数校验。
  • 在两个文件之间新增依赖。
  • 处理一个错误日志,定位崩溃原因。
  • 迁移某个 API 的调用方式。

评估时记录三个指标:

  • 任务通过率:最终测试是否全部通过。
  • 迭代轮数:模型从开始到完成经过多少轮工具调用。
  • 修改正确性:是否产生预期外的改动,是否破坏了其他测试。

运行方式可以用一个简单的 Bash 命令:

python -m harness.main --repo demo_project --task "新增 multiply 函数并补充测试"

对比“只换 harness”的效果时,可以在同一模型、同一任务集下,分别跑旧版配置和新版配置,记录通过率。预期结果是:新版 harness 的通过率明显更高,且迭代轮数更少。

如果效果不明显,优先检查三个位置:

  • 测试反馈是否真的被截取并回填给了模型,模型是否看到了失败信息。
  • 上下文是否覆盖了所有需要修改的文件,是不是只给了部分代码。
  • 工具调用失败时,错误信息是否返回给了模型,让模型自行修正。

7. 常见问题与排查思路

问题现象可能原因排查方式解决方案
模型反复调用同一个工具,始终不写代码上下文里缺少明确的终止条件查看对话历史,确认是否只有工具结果,没有要求模型输出总结在 system prompt 中强调“分析完成后必须输出最终代码”,或限制单轮工具调用次数
修改后测试全部通过,但代码风格破坏缺少 lint 和格式化反馈检查测试阶段是否只跑了 pytest,没有跑 ruff 或 prettier在验收命令中加入格式检查,把 lint 失败信息一并回填给模型
指令任务没理解,反复改错文件上下文没有给出准确的文件路径检查模型收到的文件列表,是否缺少完整路径在上下文中明确标注“你只能修改以下文件”
工具调用返回非法 JSON模型结构化输出不稳定查看原始响应,是否被截断或包含额外文本启用 JSON 模式或 function calling 严格模式;解析失败时,将原始输出作为错误信息重新提问
上下文膨胀,token 消耗过高每轮都重复全量仓库摘要对比首轮和后几轮的 prompt 大小对上下文做缓存,把不变的部分只发送一次,后续轮次只拼接增量
生产环境中 Agent 执行了危险命令没有限制命令白名单检查工具层是否有 shell=True 的任意命令接口使用白名单命令,启用沙箱容器,禁止网络请求和文件系统越权

这里真正容易踩坑的地方是“测试通过但改动错误”。很多初次搭建 harness 的开发者,只关注测试是否绿,却忽略了模型可能删掉了不该删的注释,或者改了无关模块的排版。解决方案是为每个任务记录 diff,并要求模型在提交前输出变更清单。

8. 最佳实践与工程建议

把 harness 从“能跑”做到“生产可用”,需要补充以下几层工程能力。

8.1 可观测性优先

每轮对话、每次工具调用、每次文件 diff,都应该以结构化日志落盘。这样可以复现失败案例,也可以用于后续评估。没有日志的 Agent 是不适合上生产的。

8.2 控制工具权限

编码 Agent 的工具不能等价于“本机终端”。应尽量拆分成只读工具和写工具:只读工具负责搜索、查看、分析;写工具负责修改文件、执行测试。对写工具设置独立审批或确认流程。命令执行要避免直接shell=True,尤其是当模型生成的命令可能包含拼接参数时。

8.3 用快照做回滚

在 Agent 开始修改前,对目标分支打快照。一旦连续迭代失败,可以快速恢复到初始状态。Git 的stash或独立分支都可以实现这一点。不要等代码被改乱再后悔。

8.4 成本控制

模型调用费用和 token 消耗是真实限制。可以启动上下文缓存、控制最大迭代次数、限制单轮工具结果长度,并对长文件的读取做截断。任务完成后要日志记录总 token 消耗,便于后续成本评估。

8.5 建立稳定评估集

团队如果要持续迭代 harness,就必须形成一套固定任务集。任务集应该覆盖多语言、多文件、多种 bug 类型。评估任务集不能频繁修改,否则你无法判断效果变化来自 harness 调整还是任务难度变化。

8.6 共享配置,而不是共享 prompt

个人开发者的 prompt 经验,难以在团队中直接复用。更有效的方式是把 harness 拆成配置文件:上下文策略、工具地址、验收命令、最大迭代轮数都做成可配置项。这样团队可以共享一套工程基础设施,而不是各自维护个性化提示词。

9. 总结与后续学习方向

回到标题里那 15 个 LLM。“一个下午改进 15 个模型”听起来像玄学,但如果把“模型”重新理解为“模型 + 外部系统”,这个结果就变得很自然。外部系统决定了模型看到什么、能做什么、怎么纠错,它也直接决定了最终编码质量。

这篇文章真正想表达的是:在编码任务里,模型是必要条件,但不是充分条件。如果你手头的 AI 编程工具效果不佳,先别急着换模型。先把上下文压缩、工具调用、反馈回路、快照回滚、日志观测这五件事做扎实。很多时候,你能从“工具不太好用”变成“模型看起来很聪明”,靠的就是 harness 层面的优化。

你可以在自己的项目里做一个对比实验:同一批编码任务,先让模型“裸奔”,再把它接进上面这套最小 harness,你会发现通过率和稳定性有明显区别。做完这个实验,也就理解了当前社区里“harness engineering”被反复提及的原因。

下一步可以继续研究的方向包括:更精确的代码检索、基于 AST 的上下文切片、多 Agent 协作框架、自动生成测试用例、以及针对特定语言和框架的校验工具。这些都是 harness 工程的延伸。编码 Agent 的未来,大概率不是“哪个模型更强”,而是“谁的系统能把模型能力包装得更好、更稳、更可控”。

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

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

立即咨询