1. 从 Claude Code 的 Harness 设计说起:Agent 评测链路为什么总跑不稳
Claude Code 真正值得借鉴的地方,不是某一段 system prompt 写得多好,而是它把「模型决策」和「工程执行」拆成了两层:模型只负责决定下一步做什么,Harness 负责可靠地执行、约束和兜底。这个思路迁移到 Agent 评测场景,价值非常直接——你要测的不是模型单轮回答好不好,而是它在多轮工具调用、失败重试、权限边界下行为是否一致。
我见过太多团队做 Agent 评测,最后变成「手动点一遍看看效果」。问题出在三个地方:第一,评测入口不统一,今天用这个 Key,明天换那个通道,结果不可复现;第二,Prompt 和工具 Schema 散落在代码各处,改一处忘一处;第三,失败重试逻辑写在业务代码里,评测时根本触发不到。Claude Code 的 Harness 分层给了我们一个可抄的骨架:System Prompt 定行为准则,Tool Schema 约束参数空间,Input Validation 拒绝非法输入,Permission System 门控高风险操作,Runtime Safety 做沙盒和并发控制,Context Management 保证长对话约束不丢,System Reminders 持续矫正,Budget & Circuit Breakers 做绝对兜底。
把这八层映射到评测台,你会发现评测台本身就是一个 mini Harness。它需要统一 LLM 接入通道(否则换模型就换一套代码),需要可复现的 Prompt 与工具调用结构(否则每次跑的结果没法对比),需要三类验证动作:连通性验证、多轮工具调用验证、失败重试验证。而统一 Key/API 通道这件事,用 TaoToken 来做最省事——一个 Key 覆盖多家模型,评测代码不用为每个后端写适配层。
这篇就按「Harness 分层 → TaoToken 统一接入 → 可复制配置 → 三类验证 → 报错排查」的顺序走,每一步都给能直接跑的片段。适合正在搭 Agent 评测台、或者想把现有评测流程工程化的同学。
2. TaoToken 前置准备:统一 Key 与 API 通道怎么接
在动手写 Harness 之前,先把 LLM 接入层统一掉。这一步不做,后面每换一个模型就要改一遍评测代码,复现性直接归零。TaoToken 的作用就是提供一个统一的 API 通道,你用同一个 Base URL 和同一个 Key,就能在评测台里切换不同模型,而 Harness 代码完全不用动。
先明确三个核心信息,后面所有配置都围绕它们:
| 项目 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求走这个入口,不要加 UTM |
| API Key | 在控制台创建 | 形如sk-...,只显示一次,及时保存 |
| Model ID | 按需选择 | 评测时建议固定一个基线模型做对照 |
获取 Key 的路径:进入控制台,找到 API Keys 页面,创建一个新 Key。这里有个坑要提前说——Key 只在创建时完整显示一次,关掉页面就看不到了,所以创建后立刻复制到你的环境变量或密钥管理里。如果你用 Claude Code 这类工具,Key 的存放位置和普通脚本不一样,后面配置片段里会分别给。
为什么评测场景特别强调「统一通道」?因为 Agent 评测的核心诉求是「控制变量」。你要对比的是 Prompt 改动、工具 Schema 改动、重试策略改动带来的行为差异,而不是模型后端差异。如果每个模型走不同的 SDK、不同的鉴权方式、不同的超时默认值,那评测结果里混入的噪声根本没法排除。统一到 TaoToken 之后,Harness 里只需要维护一份 client 初始化代码,模型切换只是改一个字符串。
另外,评测台通常要跑批量用例,对并发和超时有要求。建议在接入层就设好两个参数:请求超时(比如 60 秒,工具调用链路长)和最大重试次数(比如 2 次,配合 Harness 自己的重试逻辑,别叠加太多)。这两个值写进配置,不要散落在业务代码里。
如果你还没创建 Key,可以先到控制台把 Key 建好,顺手把接入文档过一遍,确认 Base URL 和鉴权头的写法。文档里对 OpenAI 兼容格式和 Anthropic 格式都有说明,评测台用哪种取决于你选的模型和 SDK。
3. 可复制配置:Harness 的 settings 与评测用例结构
这一节给能直接抄的配置。分三块:TaoToken 接入配置、Harness 的 settings 片段、评测用例的 JSON 结构。
先说接入配置。如果你用 Claude Code 作为评测的交互入口,它的配置文件在用户目录下的.claude/settings.json(不同版本路径可能略有差异,以你本地为准)。核心是把 Base URL、Key、Model ID 三件套写全:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的Key", "ANTHROPIC_MODEL": "你的ModelID" } }注意这里三个字段缺一不可。只写 Base URL 不写 Key 会 401,只写 Key 不写 Model 会走默认模型导致评测基线漂移。如果你用的是 OpenAI 兼容的 SDK 写评测脚本,配置长这样:
import os from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key=os.environ["TAOTOKEN_API_KEY"], timeout=60.0, max_retries=2, ) MODEL = os.environ.get("EVAL_MODEL", "你的ModelID")把 Key 放环境变量,不要硬编码进仓库,这是评测台能长期跑的前提。
接下来是 Harness 的 settings 片段。参考 Claude Code 的分层,我们把评测台的约束写成一份 TOML,放在项目根目录harness.toml:
[llm] base_url = "https://taotoken.net/api" model = "你的ModelID" timeout_seconds = 60 max_retries = 2 [harness] # 对应 Layer 3: Input Validation reject_empty_prompt = true max_prompt_chars = 8000 # 对应 Layer 5: Runtime Safety max_tool_rounds = 8 tool_timeout_seconds = 30 allow_write_tools = false # 对应 Layer 8: Budget & Circuit Breakers max_total_tokens = 50000 max_wall_clock_seconds = 300 [context] # 对应 Layer 6: Context Management compact_threshold_tokens = 30000 keep_recent_tool_results = 3这份配置把「绝对限制」显式化了。评测时最怕的就是某个用例陷入无限工具调用循环,把额度烧光还跑不出结果。max_tool_rounds和max_wall_clock_seconds就是你的断路器。
最后是评测用例结构。每个用例是一个 JSON,包含输入、期望的工具调用序列、以及判定规则:
{ "case_id": "tool_chain_001", "description": "多轮工具调用:先查文件再改配置", "messages": [ {"role": "user", "content": "读取 config.json 并把 timeout 改成 30"} ], "tools": ["read_file", "write_file"], "expect": { "tool_sequence": ["read_file", "write_file"], "max_rounds": 4, "final_contains": "timeout" }, "retry_policy": { "on_tool_error": true, "max_retries": 2 } }这个结构的关键是expect.tool_sequence——它让你能断言 Agent 的行为顺序,而不只是看最终回答。行为一致性评测,顺序比内容更重要。retry_policy单独抽出来,是为了让失败重试验证可以独立触发,不用改业务代码。
把这三块配置放好,Harness 的骨架就立起来了。接下来是跑验证。
4. 三类验证动作:连通性、多轮工具调用、失败重试
配置写完不验证,等于没写。这一节给三类验证动作的具体做法和预期结果。
第一类,连通性验证。这是最基础的一步,但很多人跳过它直接跑复杂用例,结果报错时分不清是接入问题还是逻辑问题。连通性验证就一句话:发一个最小请求,确认能拿到回复。
resp = client.chat.completions.create( model=MODEL, messages=[{"role": "user", "content": "ping"}], max_tokens=16, ) print(resp.choices[0].message.content)预期结果是打印出一段短回复。如果这里就报 401,说明 Key 或 Base URL 有问题,先解决接入再往下走。如果报超时,检查网络和timeout_seconds设置。连通性过了,才说明通道是通的。
第二类,多轮工具调用验证。这是 Agent 评测的核心。你要验证的是:模型在收到工具结果后,能否正确地继续下一轮决策,而不是把工具结果当最终答案返回。
用一个最小工具集来测,比如只给read_file和write_file两个工具。跑上面那个tool_chain_001用例,观察 Harness 记录的调用序列。正确的行为是:第一轮模型返回tool_use(read_file),Harness 执行后把结果回灌,第二轮模型返回tool_use(write_file),第三轮模型返回最终文本。如果模型在第一轮工具结果后就返回文本,说明它没理解要继续调用工具,这时候要检查你的 Tool Schema 描述是否清晰,以及 system prompt 里有没有说明「需要多步完成」。
Harness 里记录序列的代码大概这样:
def run_case(case, client, tools): messages = case["messages"] called = [] for round_idx in range(case["expect"]["max_rounds"]): resp = client.chat.completions.create( model=MODEL, messages=messages, tools=tools, ) msg = resp.choices[0].message messages.append(msg) if not msg.tool_calls: break for call in msg.tool_calls: called.append(call.function.name) result = execute_tool(call) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result, }) return called跑完对比called和expect.tool_sequence,一致就通过。实测下来,这一步能抓出大部分 Prompt 和 Schema 的问题。
第三类,失败重试验证。故意让工具执行失败,看 Harness 和模型能否正确恢复。做法是把execute_tool改成第一次调用抛异常,第二次成功:
_call_count = {} def execute_tool_flaky(call): name = call.function.name _call_count[name] = _call_count.get(name, 0) + 1 if _call_count[name] == 1: raise RuntimeError("simulated tool failure") return real_execute(call)预期行为是:Harness 捕获异常,把错误信息作为 tool result 回灌给模型,模型决定重试同一个工具,第二次成功。如果模型直接放弃或编造结果,说明你的错误回灌格式有问题——错误信息要明确告诉模型「这次失败了,可以重试」,而不是一句模糊的 error。
这三类验证跑通,你的评测台就具备了基本的行为一致性检查能力。剩下的就是扩用例、加断言。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
评测台跑起来后,报错基本集中在这几类。逐个说清楚现象和排查路径。
401 Unauthorized。最常见,原因通常是 Key 没配对或没生效。检查三处:环境变量里 Key 是否完整(有没有多余空格或换行)、请求头里鉴权字段名是否正确(Anthropic 格式用x-api-key,OpenAI 兼容格式用Authorization: Bearer)、Base URL 是否写成了带路径的完整地址。如果 Key 刚创建,确认没有复制漏字符。还有一种情况是 Key 被禁用或额度耗尽,去控制台确认状态。
local proxy failed。这个报错通常出现在你本地配了某种转发但目标不可达时。排查顺序:先确认base_url是不是https://taotoken.net/api,有没有被本地环境变量覆盖成别的地址;再确认本机网络能正常访问该域名;最后检查是否有残留的代理环境变量(HTTP_PROXY/HTTPS_PROXY)指向了一个已经关掉的本地端口。把无关的代理变量清掉再试。
reading choices 相关报错,比如KeyError: 'choices'或reading 'choices' of undefined。这说明返回的响应结构和你代码里取字段的路径不匹配。常见原因是:你用了 OpenAI 兼容的取法(resp.choices[0]),但实际返回的是 Anthropic 格式(resp.content[0]),或者反过来。解决办法是打印完整响应体看一眼结构,再决定取哪个字段。评测台里建议封装一个extract_text(resp)函数,把格式差异吃掉,业务代码不直接碰原始响应。
OAuth 相关报错。如果你在 Claude Code 里看到 OAuth 登录失败或 token 过期,注意评测场景应该用 API Key 而不是 OAuth 流程。检查settings.json里是不是同时存在 OAuth 配置和 API Key 配置,两者冲突时以哪个为准取决于版本。最稳的做法是评测环境只用ANTHROPIC_AUTH_TOKEN,把 OAuth 相关字段清掉。如果必须用 OAuth,确认登录态没过期,重新走一次授权。
再补一个容易忽略的:模型返回了工具调用但 Harness 报「unknown tool」。这是 Tool Schema 注册名和模型返回的function.name不一致导致的。检查你传给 API 的 tools 列表里每个工具的name字段,和execute_tool里分发的 key 是否完全一致,大小写和连字符都算。
排查这类问题的通用思路是:先隔离接入层(连通性验证),再隔离工具层(单工具调用),最后看编排层(多轮序列)。一层一层往下,比盯着报错猜要快得多。
6. 把评测台跑成长期资产:CTA 与后续动作
Harness 搭好之后,最有价值的不是某一次评测结果,而是这套结构能持续复用。每次改 Prompt、换模型、加工具,都跑一遍三类验证,行为回归就能被自动抓到。这比人工点一遍可靠得多。
如果你要接着往下做,建议按这个顺序推进:先把连通性验证固化成 CI 里的一个 smoke test,每次提交都跑;再把多轮工具调用用例扩到覆盖你实际业务的核心链路;最后把失败重试和断路器阈值调成符合你额度预算的值。评测用例的 JSON 结构保持不变,只增用例不改框架,这样历史结果才能横向对比。
接入层这块,统一用 TaoToken 的 Key 和通道,模型切换只改一个 Model ID,Harness 代码零改动。需要创建 Key 或查看接入细节,走这两个入口:
- API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=harness_eval&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=harness_eval&utm_campaign=rewrite
如果你主要做长期编码类 Agent 的评测和迭代,Coding Plan 会更合适,额度模型和调用方式都按持续使用设计:
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=harness_eval&utm_campaign=rewrite
想先手动验证某个模型在工具调用上的表现,可以直接在模型对话里试:
- 模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=harness_eval&utm_campaign=rewrite
最后留一个实操建议:评测台的harness.toml和用例 JSON 一起进版本控制,Key 走环境变量。这样任何人拉下代码,配好 Key 就能复现你跑过的每一组结果。行为一致性这件事,靠的不是某次跑通,而是每次都能跑通。