
1. 从 Claude Code 的 settings.json 对不上 Codex CLI 的 config.toml 说起在 Claude Code 的 settings.json 里把ANTHROPIC_BASE_URL指向 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_intro再到 Codex CLI 的config.toml里配置model_provider最后把 Pi 的OPENAI_API_BASE也切到https://taotoken.net/api。你会发现三套 harness 都能跑通但 7 个模型的调用日志完全对不齐。Claude Code 输出的是input_tokens、output_tokensCodex CLI 可能写成prompt_tokens、completion_tokensPi 又可能把工具调用记为tool_calls或function_calls。一旦要把它们合并成同一张分析表字段名、时间戳格式、成功状态、重试次数、任务 ID 都开始互相打架。UC Berkeley 团队的 HarnessTax 研究正是围绕这个问题展开他们评估了 21 个模型-harness 组合覆盖 7 个模型和 3 个 harness——Claude Code、Codex CLI、Pi在 SWE-bench Lite 和 Terminal-Bench 2.0 上各选 30 个任务每个组合跑 3 次。研究想回答的是同一个模型换一个 harnesscoding agent 的表现和消耗会差多少。但如果你想在自己的环境里复现或做类似对比第一道坎不是模型能力而是日志口径。谁在消耗 Token是模型本身还是 harness 反复拼接上下文、调用工具、重试失败步骤如果日志字段不统一你根本算不清。本文不走新闻评论路线而是从接入和排障角度给出一套可跟做的日志拉齐方案用 TaoToken 的 Key 和 Base URL 把 7 个模型的调用日志统一采集产出 7 模型日志模板、字段映射表和合并分析脚本。TaoToken 在这里只提供 Key 和 Base URL不参与评测也不改变 harness 的行为。你需要自己准备模型列表、任务集和运行环境TaoToken 负责让调用入口保持一致。2. 准备 Key 与 Base URLTaoToken 只做接入不参与评测第一步不是改代码而是把入口统一。打开 TaoToken 官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_key注册并进入控制台。然后在 API Keys 页面创建一个专用 Key建议命名为harness-tax-log方便后续按项目隔离。创建入口在这里https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_keys。拿到 Key 后先确认两个核心参数Base URLhttps://taotoken.net/api这个地址不加 UTM直接用于工具配置API KeyYOUR_API_KEY替换成你刚创建的真实 Key建议把 Key 放进环境变量不要硬编码到脚本里。Linux 或 macOS 可以这样写export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell 可以用$env:TAOTOKEN_API_KEYYOUR_API_KEY $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api接下来规划 7 个模型的命名。不要直接在脚本里写死模型 ID而是维护一个models.yaml或models.json把模型 ID、显示名、所属供应商、备注写清楚。示例{ models: [ { id: YOUR_MODEL_1, name: model-1, provider: provider-a }, { id: YOUR_MODEL_2, name: model-2, provider: provider-b }, { id: YOUR_MODEL_3, name: model-3, provider: provider-c }, { id: YOUR_MODEL_4, name: model-4, provider: provider-d }, { id: YOUR_MODEL_5, name: model-5, provider: provider-e }, { id: YOUR_MODEL_6, name: model-6, provider: provider-f }, { id: YOUR_MODEL_7, name: model-7, provider: provider-g } ] }模型 ID 以你账号实际可用的列表为准。TaoToken 的作用是提供统一 Key 和 Base URL让你不用为每个 harness 维护一套供应商凭证。真正跑评测时每个组合仍然需要你自己控制温度、最大 Token、超时、重试策略等变量。3. Claude Codesettings.json 与 ANTHROPIC_* 的正确写法Claude Code 侧通常通过settings.json或环境变量接入。注意Claude Code 使用ANTHROPIC_*系列变量但不要把这一套直接套到 Codex CLI 上。两者配置体系不同混用会导致请求发不出去或模型找不到。一个可用的settings.json示例如下放在项目目录或用户配置目录中{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_MODEL_ID } }如果你更习惯用 shell 环境变量也可以这样启动export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_AUTH_TOKENYOUR_API_KEY export ANTHROPIC_MODELYOUR_MODEL_ID claude要让日志可合并建议在 Claude Code 外层包一层采集脚本。不要修改 Claude Code 内部逻辑而是记录以下信息运行开始时间、结束时间harness 名称固定写claude-code模型 ID从ANTHROPIC_MODEL读取benchmark 名称swe-bench-lite或terminal-bench-2.0task_id当前任务编号attempt第几次运行取 1、2、3原始输出文件路径解析出的 token 用量、工具调用次数、状态如果 Claude Code 的输出没有直接给出 token 用量可以从原始响应里解析或者在你的调用层记录请求前后的 usage 字段。关键是不要等到分析阶段才后悔字段缺失。4. Codex CLIconfig.toml 不要混用 ANTHROPIC_*Codex CLI 使用config.toml常见位置是~/.codex/config.toml。它的 provider 配置和 Claude Code 完全不同。这里要特别注意不要在 Codex 里设置ANTHROPIC_BASE_URL或ANTHROPIC_AUTH_TOKEN那套变量对 Codex 无效。Codex 应该用model_provider和model_providers来声明 TaoToken。一个可参考的配置如下model_provider taotoken model YOUR_MODEL_ID [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat启动前确保环境变量已设置export TAOTOKEN_API_KEYYOUR_API_KEY codexCodex CLI 的日志通常包含会话 ID、模型名、token 用量和工具调用信息。你要做的是把这些字段映射到统一模板。例如prompt_tokens→ 统一字段input_tokenscompletion_tokens→ 统一字段output_tokenstotal_tokens→ 统一字段total_tokensfunction_calls→ 统一字段tool_callselapsed_ms→ 统一字段latency_ms如果 Codex 的输出格式随版本变化建议保留原始 JSONL 日志再写一个轻量解析器。不要在解析失败时直接丢弃记录而是写入status: parse_error方便后续排查。5. Pi 侧OpenAI 兼容入口与字段差异Pi 作为第三个 harness接入方式取决于它支持哪种 API 协议。如果 Pi 走 OpenAI 兼容入口可以使用OPENAI_API_BASE和OPENAI_API_KEYexport OPENAI_API_BASEhttps://taotoken.net/api export OPENAI_API_KEYYOUR_API_KEY export OPENAI_MODELYOUR_MODEL_ID如果 Pi 支持自定义 provider 配置文件则把 Base URL 填为https://taotoken.net/apiKey 填YOUR_API_KEY模型填你的 7 个模型之一。Pi 的日志字段可能和 Claude Code、Codex CLI 都不同常见差异包括输入 token 可能叫input_tokens或prompt_tokens输出 token 可能叫output_tokens或completion_tokens工具调用可能叫tool_calls、function_calls或actions耗时可能叫latency_ms、duration_ms或elapsed_ms状态可能叫status、success、is_error不要试图让 Pi 去适配 Claude Code 的字段而是让三者都往同一个中间层靠。中间层只认统一字段原始日志原样保留。这样即使 Pi 后续升级你的合并脚本也不会立刻崩掉。6. 统一日志模板7 个模型、3 个 harness、21 个组合建议每个任务每次运行写一行 JSONL。JSONL 的好处是追加方便不会因为一次解析失败破坏整个文件。统一模板如下{ run_id: uuid-v4, timestamp: 2026-01-01T00:00:00Z, harness: claude-code, model_id: YOUR_MODEL_ID, model_name: model-1, benchmark: swe-bench-lite, task_id: task-001, attempt: 1, input_tokens: 0, output_tokens: 0, total_tokens: 0, latency_ms: 0, tool_calls: 0, status: success, error_type: null, raw_log_path: logs/claude-code/task-001-attempt-1.json, harness_version: unknown }字段含义run_id本次运行的唯一 ID方便和原始日志关联timestampISO 8601 格式统一用 UTCharness只允许claude-code、codex-cli、pi三个值model_id实际请求的模型 IDmodel_name分析时显示的短名benchmarkswe-bench-lite或terminal-bench-2.0task_id任务编号attempt1、2、3input_tokens、output_tokens、total_tokens整数latency_ms毫秒tool_calls工具调用次数statussuccess、fail、timeout、parse_errorerror_type失败时的错误类型raw_log_path原始日志文件路径harness_versionharness 版本便于后续对比每个组合按 7 个模型 × 3 个 harness × 2 个 benchmark × 30 个任务 × 3 次运行来组织目录。目录结构可以这样设计logs/ claude-code/ swe-bench-lite/ task-001/ attempt-1.json attempt-2.json attempt-3.json terminal-bench-2.0/ ... codex-cli/ ... pi/ ...统一日志文件可以放在logs/normalized/all.jsonl。每次运行结束后把解析后的记录追加进去。不要覆盖历史记录方便回溯。7. 字段映射表把 prompt_tokens、input_tokens、function_calls 拉到同一列字段映射是日志拉齐的核心。下面是一张可直接使用的映射表实际字段名请以你本地 harness 输出为准统一字段Claude Code 可能字段Codex CLI 可能字段Pi 可能字段说明input_tokensinput_tokens / prompt_tokensprompt_tokensinput_tokens输入 Tokenoutput_tokensoutput_tokens / completion_tokenscompletion_tokensoutput_tokens输出 Tokentotal_tokenstotal_tokenstotal_tokenstotal_tokens总 Tokentool_callstool_callsfunction_callstool_calls / actions工具调用次数latency_msduration_mselapsed_mslatency_ms请求耗时model_idmodelmodelmodel模型 IDstatusis_errorstatussuccess / is_error成功状态attemptattemptattemptattempt第几次运行映射时遵循三个原则统一字段名只用一种写法不要在不同脚本里混用。缺失字段填默认值但必须记录status: parse_error或error_type不要静默补 0。原始日志路径必须保留方便回到原始记录核对。如果你在 Claude Code 里看到input_tokens在 Codex 里看到prompt_tokens在 Pi 里又看到input_tokens不要手动改三次脚本。写一个normalize_record()函数按 harness 分支处理。下一节的合并脚本会给出示例。8. 合并分析脚本Python 读取 JSONL 并输出对比表下面是一个可运行的 Python 脚本读取三个 harness 的原始 JSONL做字段归一化再按 harness、模型、benchmark 聚合。它不依赖任何私有库只需要 Python 3.9 和 pandas。import json from pathlib import Path import pandas as pd def load_jsonl(path: str): records [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue try: records.append(json.loads(line)) except json.JSONDecodeError as exc: records.append({ status: parse_error, error_type: str(exc), raw_line: line[:500] }) return records def normalize_record(record: dict, harness: str) - dict: if harness claude-code: input_tokens record.get(input_tokens) or record.get(prompt_tokens) or 0 output_tokens record.get(output_tokens) or record.get(completion_tokens) or 0 tool_calls record.get(tool_calls) or 0 latency_ms record.get(latency_ms) or record.get(duration_ms) or 0 elif harness codex-cli: input_tokens record.get(prompt_tokens) or record.get(input_tokens) or 0 output_tokens record.get(completion_tokens) or record.get(output_tokens) or 0 tool_calls record.get(function_calls) or record.get(tool_calls) or 0 latency_ms record.get(elapsed_ms) or record.get(latency_ms) or 0 elif harness pi: input_tokens record.get(input_tokens) or record.get(prompt_tokens) or 0 output_tokens record.get(output_tokens) or record.get(completion_tokens) or 0 tool_calls record.get(tool_calls) or record.get(actions) or 0 latency_ms record.get(latency_ms) or record.get(duration_ms) or 0 else: input_tokens output_tokens tool_calls latency_ms 0 total_tokens record.get(total_tokens) or (input_tokens output_tokens) return { run_id: record.get(run_id), timestamp: record.get(timestamp), harness: harness, model_id: record.get(model_id) or record.get(model), model_name: record.get(model_name), benchmark: record.get(benchmark), task_id: record.get(task_id), attempt: record.get(attempt, 1), input_tokens: int(input_tokens), output_tokens: int(output_tokens), total_tokens: int(total_tokens), latency_ms: float(latency_ms), tool_calls: int(tool_calls), status: record.get(status, unknown), error_type: record.get(error_type), raw_log_path: record.get(raw_log_path), harness_version: record.get(harness_version, unknown), } def main(): harness_files { claude-code: logs/claude-code/normalized.jsonl, codex-cli: logs/codex-cli/normalized.jsonl, pi: logs/pi/normalized.jsonl, } all_records [] for harness, path in harness_files.items(): if not Path(path).exists(): print(fskip missing file: {path}) continue for raw in load_jsonl(path): all_records.append(normalize_record(raw, harness)) df pd.DataFrame(all_records) if df.empty: print(no records found) return summary ( df.groupby([harness, model_id, benchmark], dropnaFalse) .agg( runs(task_id, count), input_tokens(input_tokens, sum), output_tokens(output_tokens, sum), total_tokens(total_tokens, sum), avg_latency_ms(latency_ms, mean), tool_calls(tool_calls, sum), success_rate(status, lambda s: (s success).mean()), ) .reset_index() ) print(summary.to_string(indexFalse)) summary.to_csv(harness_tax_summary.csv, indexFalse) print(saved: harness_tax_summary.csv) if __name__ __main__: main()这个脚本只做三件事读日志、统一字段、输出聚合表。它不参与评测也不修改 harness 行为。你可以把harness_files换成自己的路径把model_id换成实际模型标识。如果某个 harness 的日志没有total_tokens脚本会用输入加输出兜底如果连输入输出都没有就记 0但保留status方便你回头查原始日志。9. CC Switch 三件套切换 harness 时不丢日志上下文如果你同时使用 Claude Code、Codex CLI 和 Pi手动改环境变量很容易切错。CC Switch 可以作为切换层但三件套要配齐供应商配置、模型配置、日志包装器。第一件套供应商配置。把 TaoToken 作为一个 providerBase URL 填https://taotoken.net/apiKey 填YOUR_API_KEY。示例结构如下具体字段名以你使用的 CC Switch 版本为准{ providers: [ { name: taotoken, baseUrl: https://taotoken.net/api, apiKey: YOUR_API_KEY, enabled: true } ] }第二件套模型配置。把 7 个模型写成 profile每个 profile 只改模型 IDBase URL 和 Key 保持 TaoToken 不变。这样切换模型时不会换错供应商。{ profiles: [ { name: model-1, provider: taotoken, model: YOUR_MODEL_1, harness: claude-code }, { name: model-2, provider: taotoken, model: YOUR_MODEL_2, harness: codex-cli } ] }第三件套日志包装器。无论 CC Switch 切到哪个 harness启动命令前都注入LOG_HARNESS和LOG_BENCHMARK环境变量export LOG_HARNESSclaude-code export LOG_BENCHMARKswe-bench-lite export LOG_TASK_IDtask-001 export LOG_ATTEMPT1然后由包装脚本读取这些变量写入统一 JSONL。这样即使你中途切换了 Claude Code、Codex CLI、Pi日志里仍然能分清每个组合。TaoToken 官网入口可以放在 CC Switch 配置说明旁边方便统一查 Key 和 Base URLhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_ccswitch。10. 常见报错与排障401、404、model not found、token 统计为 0接入阶段最常见的四类问题如下。第一类401 Unauthorized。原因通常是 Key 没传对。Claude Code 检查ANTHROPIC_AUTH_TOKENCodex CLI 检查TAOTOKEN_API_KEYPi 检查OPENAI_API_KEY。不要把 Claude Code 的变量复制到 Codex也不要反过来。排障时先打印环境变量确认没有空值再确认 Key 没有多余空格。第二类404 Not Found。原因通常是 Base URL 拼错。TaoToken 的 Base URL 是https://taotoken.net/api。不要在末尾多加/v1也不要把/api换成其他路径。不同 harness 对路径的处理不同如果 404先回到工具配置里只保留 Base URL让工具自己拼接。第三类model not found。原因通常是模型 ID 不在账号可用列表中或者大小写、连字符写错。建议维护一个models.json从实际可用列表里读取不要手写。每次运行前做一次模型 ID 校验失败直接标记status: model_not_found不要重试到超时。第四类token 统计为 0。原因可能是 harness 没有输出 usage或者你解析的字段不存在。解决办法是在调用层记录响应对象里的 usage或者使用工具自带的详细日志模式。如果实在拿不到不要伪造数据写入status: missing_usage并在分析时把该组合排除或单独说明。另外时间戳格式也要统一。建议全部转成 UTC ISO 8601例如2026-01-01T00:00:00Z。如果某个 harness 输出本地时间在归一化时转换不要留到合并后再猜。11. 复现 HarnessTax 的口径纪律每个组合 3 次、每个 benchmark 30 任务HarnessTax 的设计是 7 个模型、3 个 harness、21 个模型-harness 组合在 SWE-bench Lite 和 Terminal-Bench 2.0 各 30 个任务上各跑 3 次。复现时最容易出问题的地方不是模型而是口径。以下纪律建议写进你的运行脚本。第一固定变量。温度、最大输出 Token、超时时间、重试次数、并发数在整个实验期间保持一致。如果某个 harness 必须不同记录在harness_version或单独字段里不要静默改变。第二区分 attempt 和 retry。attempt 是实验设计里的第 1、2、3 次运行retry 是失败后的重试。不要把两者混为一谈。统一日志里attempt只记 1、2、3重试次数可以另加字段retry_count。第三原始日志和统一日志分开存。原始日志按 harness、benchmark、task、attempt 分目录保存统一日志只存归一化后的 JSONL。这样即使解析脚本有 bug原始数据还在。第四每次运行记录 run_id。run_id 可以用 UUID也可以用harness-model-benchmark-task-attempt拼接。关键是能唯一关联一次运行。第五不要用 TaoToken 的统计替代 harness 日志。TaoToken 只提供 Key 和 Base URL不参与评测。谁消耗了多少 Token应该以 harness 侧或调用层记录的 usage 为准。如果两边都有记录可以用来交叉校验但不要直接混用。第六分析前先做完整性检查。检查每个组合是否都有 30 个任务 × 3 次运行。缺失的组合要标记出来不要直接算平均。12. CTA从模型对话到 Coding Plan再到创建 Key 和 Claude Code 文档如果你已经准备好把 7 个模型的调用日志拉齐可以按下面的路径开始先到模型对话页确认模型可用性和基本响应https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_chat如果需要长期做 coding agent 对比查看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_plan创建专用 API Key用于日志采集项目https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_keysClaude Code 接入细节参考官方文档https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_doc更多接入说明和入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentharness_tax_cta配置时记住两个固定值Base URL 用https://taotoken.net/apiKey 用YOUR_API_KEY替换。Claude Code 走settings.json和ANTHROPIC_*Codex CLI 走config.toml和model_providersPi 走它支持的 OpenAI 兼容或自定义 provider 入口。三套日志最终落到同一张 JSONL 模板再用合并脚本输出harness_tax_summary.csv。这样你就能在不改变 HarnessTax 研究设计的前提下把 7 个模型经 3 个 harness 产生的调用记录拉到同一口径回答“谁在消耗 Token”这个问题。