放弃微调?Auto-train Harness如何用外围优化全面提升Agent性能

放弃微调?Auto-train Harness如何用外围优化全面提升Agent性能 当你的 Agent 在测试集上表现不稳定时很多团队的第一反应是换底座模型比如从开源 7B 换成更大的 70B或者从 GPT-4o 换成“当前最强”的模型。结果常常是有提升但幅度不大而且换了供应商之后原来的工具调用方式可能又要重写一遍。另一个常见反应是微调模型。但微调成本高、数据难准备而且很多 Agent 场景里模型其实没有“知识缺陷”真正的问题出在它不知道该在什么时机调用工具、工具参数怎么填、调用失败后如何恢复、结果不满 意时应该继续检索还是直接作答。这些问题不发生在模型参数内部而发生在模型和外部环境之间的那一层代码与配置里。这一层现在被越来越多开发者称为 LLM Harness。最近在 Hacker News 上出现了一个很能概括这个方向的标题Auto-train the harness, not the LLM. cross-model, cross-benchmark gains。大意是自动优化外层 Harness而不是训练 LLM 本身并且把这种优化带来的收益迁移到多个模型和多个评测基准上。这个观点听起来有些反直觉但放在 Agent 应用大量落地的背景下它比“继续卷底座模型”更贴近工程现实。这篇文章不是为了介绍某个具体仓库而是想把这个思路拆开什么是 Harness、为什么要自动训练 Harness、怎么用最小工程验证它是否真的有效。1. 为什么说 Agent 的瓶颈可能不在模型而在 Harness先看一个高频场景。你给 LLM 接上了搜索工具和计算器业务流程大概是系统提示词告诉模型“不确定就搜索”工具描述里写清楚搜索接口能做什么模型返回一个结构化工具调用你的代码去执行函数再把结果拼回上下文让模型继续推理。听上去很顺实际跑起来却经常出现三类问题第一模型该搜索的时候不搜索不该搜索的时候反而搜索。这不是模型“笨”而是提示词里没有定义清楚判断条件工具描述也没有告诉模型什么样的问题属于“需要实时信息”。第二工具结果回填格式混乱。模型可能返回一个很长的网页摘要上下文被撑爆或者返回了 JSON 数组但你的解析器只处理了对象。这些格式问题本质上由 Harness 环节的人为规定造成。第三Agent 陷入死循环。模型反复调用同一个工具拿到相似结果然后继续调用。如果循环次数、停 止条件、结果去重没有兜底再强的模型也会在真实环境里打转。如果你把这些问题归因于“模型能力不够”就会走上换模型或微调的路。但稍微复盘一下就会承认模型大多是按照它看到的那套系统提示、工具定义、消息历史去决策的。模型像一个经验丰富但完全听信流程的员工真正决定工作质量的是他手里的操作手册和上级给的授权边界。这个操作手册和授权边界就是 Harness。Harness 不是某个大模型厂商提出的专有概念。只要你在做 Agent、写 RAG、做工具调用其实已经不知不觉写了大量 Harness 代码。问题在于过去我们把这些代码当成“调用 API 的临时胶水”没有意识到它们是可以系统设计的、拥有独立版本、需要评测和优化的核心部件。从很多公开讨论来看过去半年“Harness 工程”这个词出现的频率明显升高。围绕 DeepSeek、Codex 等模型的封装工具也开始出现。大家开始默认一个前提当模型能力差距缩小到一定程度Agent 效果的上限不取决于你选哪个底座模型而取决于你在这层外围工程上做得有多细。2. 什么是 LLM Harness模型、环境与任务之间的胶水层要理解 Auto-train Harness先得把 Harness 的边界画清楚。LLM Harness 可以理解为为了让 LLM 完成真实任务而在模型之外增加的提示模板、工具定义、循环控制、上下文管理、结果校验与安全边界的总称。它至少包含四块内容。第一块是认知接口。也就是模型“看到”什么。系统提示词、用户问题的组织方式、工具名称和描述、Few-shot 示例、输出格式约束都属于认知接口。模型不会直接看到你的内部工具它只能看到你给它描述过的工具。因此工具描述写得好不好直接决定模型会不会调用正确的工具。很多团队只把工具描述当作文档随手写一句“查询订单状态 API”模型在复杂语境里自然容易误用。第二块是执行循环。LLM 本身没有手它不能真正执行搜索、不能写数据库、不能发送 HTTP 请求。模型能做的只是输出一个结构化的工具调用请求。谁来执行这个请求谁来把执行结果放回消息历史谁来决定是继续让模型推理还是终止这些是 Harness 的运行时控制部分。ReAct 模式、Tool Calling 循环、LangChain Agent 里的执行器、各类 Agent 框架中的 Loop本质都属于这一层。第三块是稳定性机制。真实环境是不稳定的。工具可能超时第三方 API 可能返回脏数据模型可能连续多次输出无效 JSON甚至可能在一次回复中同时给出最终答案和工具调用。Harness 需要处理这些异常重试、截断、错误提示、回退策略、最大轮次限制。没有这层机制模型即使知道答案也可能会被一次工具异常带偏。第四块是安全和数据边界。一个 Agent 能访问哪些工具、每个工具允许哪些参数、调用前是否需要审批、日志里能不能出现敏感字段这些通常不会被模型自身感知而是由 Harness 在调用执行前强制执行。越是进入生产环境这部分的分量越重。把模型和外层 Harness 分开看会发现它们想解决的问题不同。层面主要问题改进方式成本类型LLM 参数知识、推理、指令遵循预训练、微调、RLHF算力和数据成本高Harness工具调用、流程控制、稳定性提示词优化、循环策略、代码逻辑开发和评测成本高评测体系改进是否真实存在基准集、交叉验证、回归测试评测基建成本Harness 经常被误认为只是“提示词工程”其实提示词只是认知接口的一部分。更重要的是Harness 是可以写代码、做控制流、跑自动化评测的系统。正因为它是系统所以可以在不改变模型权重的情况下被优化自动化的优化就变得有可能性。这就是 Auto-train Harness 的核心前提。3. Auto-train Harness 的思路调的不是模型参数而是决策策略“Auto-train”这个词听起来很大它未必意味着你要做 PyTorch 训练。在 Agent 场景里它更接近一种搜索或自动调优过程把 Harness 的关键变量从人工配置变成可枚举、可变异的参数空间然后通过自动评测寻找更优配置。传统手工调 Harness 是几天前最常见的做法。一个人对着系统提示词改三版把工具描述从“搜索网页”改成“当用户询问最新新闻时使用此工具获取搜索结果”再跑一遍评测集看准确率有没有涨。这种方式的问题在于评测集的噪声经常比提示词差异带来的收益还大人工很难判断一个改动是真正有效还是碰巧有效。自动优化至少能保证候选配置都跑在同一个评测集上打分一致最后用规则选出表现最好的配置而不是靠感觉。如果要自动训练的变量只有提示词那它其实还是提示词搜索。可以把优化空间扩大一些系统提示词与工具描述换措辞、换结构、增加边界条件说明。工具顺序与可见性模型不一定要看到全部工具按任务路由到不同工具子集可能更好。循环控制最大迭代次数、提前终止条件、连续相同工具结果时是否中断。输出解析与校验要求模型先输出 JSON 再输出解释或者反过来。模型路由策略简单任务用便宜小模型复杂任务才交给大模型。Few-shot 示例选择从已完成任务中挑相似样例放入上下文。这些变量共同决定了一个 Agent 的实际行为边界。而且它们大多不绑定某个具体模型。换句话说一套合理的 Harness 配置在换一个底座模型之后大概率仍然有效。这正是“cross-model”收益能成立的根本原因如果你的优化只针对某个模型的表达能力那它很难迁移如果你的优化改善了工具描述结构、循环控制这一层通用机制它就能在多个模型上同时带来增益。Cross-benchmark 的道理同样如此。不同基准会考察不同能力比如有些考察多跳推理有些考察工具调用准确性有些考察最终答案是否简洁。如果一个 Harness 变体只在某个基准上提升可能是过拟合到该基准的数据风格只有多个基准同时提升才能说明它优化的是比较通用的行为。3.1 Auto-train 与微调的区别很多人会问Auto-train Harness 是不是一种微调并不是。对比维度微调 LLMAuto-train Harness对象模型权重配置、提示词、循环策略成本GPU、训练框架、数据标注评测任务、API 调用、搜索算法迁移性一般只对当前模型有效跨模型迁移可能性更高迭代速度小时到天分钟到小时风险可能改变模型通用能力主要影响 Agent 行为边界这两者不互斥。如果场景高度垂直微调仍然有价值。但如果只是想让 Agent 更会调用工具、更少陷入循环、更稳定地输出结果先花时间优化 Harness 的性价比通常更高。Show HN 标题里“not the LLM”的重点应该理解为优化重心转移而不是否定所有的模型训练。4. Cross-Model、Cross-Benchmark 究竟该怎么验证很多团队做过类似的事给提示词加一句“请一步步思考”然后在自己的评测集上看到分数涨了就说 prompt engineering 有效。但如果把同一套提示词换一个模型效果可能反而下降。这说明这个改进只对单个模型有效属于某种模型偏好耦合不是真正的 Harness 改进。要验证一个 Auto-train Harness 方案是否真的带来了跨模型、跨基准增益需要一个相对严谨的评估矩阵。至少应该包含三个模型和两个以上评测基准。比如三个模型分别来自不同机构评测基准分别覆盖工具调用、多轮对话、信息检索或代码生成。优化的 Harness 在开发集上搜索时就应当在多个模型上同时评估选取平均表现更好的配置而不是只盯着主力模型。把数据分成开发集和测试集也很重要。开发集用来搜索和选择 Harness 配置测试集在最终配置选定后只跑一次。如果开发集和测试集来自同一个基准可能存在风格相似导致的过拟合所以更稳 妥的做法是额外准备一个 Held-out Benchmark也就是模型和 Harness 都没有见过的新评测基准。只有最终配置在 Held-out Benchmark 上也保持优势时才有底气说“提升是真的”。一句话跨模型验证是为了防止改进只对自己用的模型有效跨基准验证是为了防止改进只对某一个评测集有效。Show HN 标题中提到的“gains”如果缺少这种矩阵验证仍然只能视为一种初步观察。5. 最小实现一个可落地的 Harness 自动优化雏形下面用一个与具体厂商无关的 Python 示例来演示 Auto-train Harness 的完整链路。这里不依赖某一家模型 SDK而是把模型调用抽象成call_llm()。实际项目中根据你用的模型服务换成对应客户端即可。5.1 定义一份 Harness 配置先看 Harness 配置示例。它描述的是Agent 的系统提示词、可选工具列表、循环控制参数。学习这类思路时尽量把配置与代码分离因为 Harness 本身会被反复修改。{ harness_name: react_tool_agent_v1, system_prompt: You are a helpful research agent. If the question asks about real-time facts, call web_search before answering., tools: [ { name: web_search, description: Search the web for keywords and return relevant snippets., parameters: { type: object, properties: { keywords: { type: string, description: search keywords } }, required: [keywords] } }, { name: calculator, description: Evaluate a arithmetic expression., parameters: { type: object, properties: { expression: { type: string, description: A mathematical expression, for example 3 * (7 2) } }, required: [expression] } } ], loop: { max_rounds: 5, stop_when: no_tool_call } }这份配置的核心价值在于把 Harness 关心的东西集中到一个文件里。修改工具描述、调整系统提示词、限制最大轮次都通过配置完成不需要改 Agent 主逻辑。5.2 写一个最小评测循环下面代码的目标是给定一份 Harness 配置和一个模型在一个简单评测集上返回准确率和 token 成本。代码演示的是核心思路并不负责处理所有真实 SDK 差异。# harness/evaluator.py import json def to_openai_tool(tool_meta): return { type: function, function: { name: tool_meta[name], description: tool_meta[description], parameters: tool_meta.get( parameters, {type: object, properties: {}} ), }, } def execute_tool(name, args_json, example): 模拟执行工具。实际项目需要替换为真实搜索或计算逻辑。 args json.loads(args_json) if name calculator: expression args.get(expression, ) try: result eval(expression, {__builtins__: {}}, {}) except Exception: result calculation_error return fresult{result} if name web_search: # 在演示里我们用 example 中预置的 context 代替真实网页结果。 context example.get(context, ) return ffound:{context[:300]} return unknown_tool def judge_answer(answer, expected): 演示用判断真实场景建议引入独立的答案评估 prompt 或精确匹配。 return float(expected.strip().lower() in answer.lower()) def run_single(harness_cfg, model_name, example): messages [ {role: system, content: harness_cfg[system_prompt]}, {role: user, content: example[question]}, ] max_rounds harness_cfg[loop][max_rounds] tools [to_openai_tool(t) for t in harness_cfg[tools]] for _ in range(max_rounds): resp call_llm(model_namemodel_name, messagesmessages, toolstools) assistant resp.choices[0].message messages.append(assistant) tool_calls assistant.tool_calls or [] if not tool_calls: answer assistant.content or return judge_answer(answer, example[expected]), resp.usage.total_tokens for call in tool_calls: result execute_tool(call.function.name, call.function.arguments, example) messages.append( { role: tool, tool_call_id: call.id, content: result, } ) return 0.0, 0 def evaluate_candidate(harness_cfg, model_name, examples): total 0 correct 0 total_tokens 0 for example in examples: score, token_cost run_single(harness_cfg, model_name, example) correct score total_tokens token_cost total 1 return { accuracy: correct / max(total, 1), avg_cost: total_tokens / max(total, 1), }这里真正的核心不是代码本身而是它建立了一个评测入口同一份 Harness 配置同一个模型同一批例子跑出的分数才能互相比较。call_llm()在示例中没有定义因为它只是模型供应商 SDK 的一层薄封装。真实开发时用 OpenAI、Anthropic、本地部署的 DeepSeek、Qwen 等任意服务替换均可。5.3 实现自动搜索随机搜索已经能证明问题自动优化的算法可以从很简单的随机搜索开始。虽然听起来不高级但它能帮助你快速验证流水线是否通畅也能作为后续贝叶斯优化、进化算法的基线。# harness/optimizer.py import copy import random def random_search(base_config, eval_models, dev_set, rounds10): prompt_pool [ You are a helpful agent. Only call tools when necessary., You are a research agent. If you are not fully certain, search first., You are an assistant. Answer directly if you know; otherwise call tools., ] max_rounds_pool [3, 5, 8] best_cfg None best_score -1.0 for i in range(rounds): candidate copy.deepcopy(base_config) candidate[system_prompt] random.choice(prompt_pool) candidate[loop][max_rounds] random.choice(max_rounds_pool) # 调整工具展示顺序。顺序本身会影响模型先尝试哪个工具。 random.shuffle(candidate[tools]) # 在多个模型上同时评估避免只对单个模型有效。 acc_list [] for model_name in eval_models: metric evaluate_candidate(candidate, model_name, dev_set) acc_list.append(metric[accuracy]) avg_acc sum(acc_list) / len(acc_list) print(f[round {i}] avg_acc{avg_acc:.3f} acc_list{acc_list}) if avg_acc best_score: best_score avg_acc best_cfg candidate return best_cfg, best_score5.4 最终配置的跨基准验证选出最优配置后不能立刻上线还需要在 Held-out Benchmark 上验证。这段代码会把最终配置在先前没有优化过的评测集上运行一遍观察它是否还保持优势。def validate_on_heldout(final_cfg, eval_models, heldout_examples): print(--- held-out validation ---) for model_name in eval_models: metric evaluate_candidate(final_cfg, model_name, heldout_examples) print( f{model_name}: faccuracy{metric[accuracy]:.3f}, favg_cost{metric[avg_cost]:.2f} )到这里我们已经有了一条完整的 Auto-train Harness 雏形链路配置化 Harness、可重复评测、多模型打分、自动搜索、最后跨基准验证。算法可以从随机搜索变成更复杂的方法但这条链路本身不会变。6. 运行思路与效果验证怎样算一次优化真正成功运行上述脚本时你不需要一开始就用大规模评测集。可以先准备 50 到 100 条代表性样例再选择两到三个模型跑 10 到 20 轮搜索。重点不是跑出多高的分数而是确认优化链路能稳定运行。预期你会看到类似下面的输出示意。这里的数字只是为了展示格式不是真实评测结果。[round 0] avg_acc0.451 acc_list[0.47, 0.44, 0.45] [round 1] avg_acc0.438 acc_list[0.45, 0.43, 0.44] [round 2] avg_acc0.472 acc_list[0.50, 0.46, 0.46] [round 3] avg_acc0.466 acc_list[0.51, 0.43, 0.46] ... best_score0.491 acc_list[0.52, 0.48, 0.48]判断成功的标准不是只挑 acc_list 中某个模型最高的那轮而是看平均分是否稳定高于基线。如果候选配置在模型 A 上大幅提升在模型 B 上明显下降那这种方案很可能是在迎合模型 A 的表达偏好并不算跨模型收益。当最终配置进入 Held-out Benchmark 时你还需要关注当时在开发集上的优势是否还在。如果开发集提升明显Held-out 却完全无效说明优化过程过拟合了开发集的题目风格。运行失败时第一步不要急着看模型 API 报错先检查评测输入流水线样例有没有正常加载、工具执行函数是否可用、call_llm()返回格式是不是符合预期。很多“模型表现差”的结论最后都来自评测脚本 bug而不是模型问题。建立一条干净可复现的评测基线比任何优化算法都重要。7. Harness 自动优化的常见问题与排查思路问题现象可能原因排查方式解决方案每次搜索分数波动很大评测样例太少模型采样有随机性增加样例量或同题多次采样取平均固定温度或用多次运行中位数代替单次分数模型 A 提升但模型 B 下降优化到了模型 A 的特殊表达偏好查看候选配置在模型 A/B 上的详细 log将优化目标改成多模型平均分或加入约束惩罚最大下降幅度开发集涨了Held-out Benchmark 不涨Harness 过拟合了开发集风格检查两项评测集分布差异增加不同任务类型的评测集减少开发集与测试集重叠Token 成本明显上升循环次数变多上下文里重复内容增加打印每轮消息数量和 token 用量增加提前终止条件对相似工具结果做去重Harness 优化后输出不如手写版本自动搜索没有覆盖关键变量检查候选配置的差异程度扩大参数空间允许对工具描述做改写上线后效果与评测不一致线上环境分布和评测集不同记录线上输入与工具调用日志持续回流线上样本定期增量评测自动搜索最大的坑并不是算法选得不好而是评测集与真实任务不匹配。如果你拿一组答案很简短的单轮问答去做 Harness 搜索选出来的配置可能特别喜欢“直接回答”而不是“调用工具”。一旦上线面对需要多轮工具调用的真实问题效果自然下降。因此开发集需要覆盖足够多的真实形态至少包含需要调用工具、不需要调用工具、工具调用失败后重试等典型分支。常见问题里还有一个隐蔽点模型服务版本的波动。同一个模型名称背后供应商可能悄然更新版本。今天跑出 0.50 分明天同样是 0.46不一定是你的 Harness 变差而是模型行为漂移。团队内部要把模型版本固定为可见字段才能区分变化来自模型还是来自 Harness。8. 工程化落地建议把 Harness 当成独立项目来管理Harness 工程化可以在团队里落地为几个清晰动作。第一个动作是配置与代码分离。不要把系统提示词硬编码在 agent 源码里尤其是不要散落在多个 service 文件中。统一维护一份 Harness 配置包含提示词、工具描述、路由规则和循环参数。这样后续优化可以在配置层面做实验而不用频繁提交业务代码。第二个动作是建立评估矩阵。至少要有一个脚本可以输入候选配置、指定模型列表和评测集名称输出性能与成本表格。没有这个矩阵任何关于“Harness 是否改进”的讨论都容易沦为空谈。矩阵里建议同时记录模型名称与版本、评测集版本、Harness 配置 hash、运行时间和总 token 成本。第三个动作是搜索与人工 review 结合。自动优化可以发现人想不到的提示词写法但它发现问题的方式是分数驱动。分数高不代表安全不代表符合产品规则。上线前必须人工 review 自动选出的配置确认工具权限边界没有被扩大确认模型在敏感问题上仍能正确拒绝。第四个动作是严格控制工具权限。无论 Auto-train Harness 实验结果多好生产环境的工具调用都必须遵守最小权限原则。搜索工具只能访问白名单地址数据库工具只能执行只读查询或经过审批的写操作涉及用户隐私的字段在日志中脱敏。Harness 优化能做的是让模型更准确地调用工具但无法替代权限系统本身。第五个动作是回归测试。当你的核心模型从 GPT 换成 DeepSeek、Qwen 或其它模型时所有经过优化的 Harness 配置都要重新跑一遍小规模回归测试。跨模型迁移是一种期望但每种模型对提示词的敏感点不同不能默认迁移一定成立。9. 结语先把评估矩阵搭起来再谈是否 Auto-trainAuto-train the harness, not the LLM真正想表达的是Harness 不再应该被当作模型调用之外的附属品而应该被当作一个可以被设计、评测与搜索优化的系统。模型负责输出智能Harness 负责把智能安全稳定地转化为行动。如果你现在正在做 Agent 开发可以暂时不引入复杂的自动优化算法先做三件事把 Harness 配置化建立一份包含多个模型的小评测集跑出一个稳定的 baseline。当你发现手工调整提示词已经难以稳定提升时再把随机搜索或更高级的优化算法接到这条链路上。到那时你会更理解“Auto-train Harness”的价值它不承诺某个魔法提示词它只提供一种系统化的方式让你在模型不变的前提下找到更好的外围策略。建议先收藏这篇文章并在下一次 Agent 调试时尝试把问题归因从“模型不行”改写成“Harness 哪里还不够稳”你会发现调试思路开阔很多。