跑 Hermes Agent 十九小时,TaoToken Key 的日志该记哪些字段?
2026/9/18 3:15:56 网站建设 项目流程

1. 先统一 TaoToken 入口,再设计 Hermes Agent 日志字段

TaoToken 用户如果正在跑 Hermes Agent,第一步是到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_intro 拿 Key,并把客户端 Base URL 设为 https://taotoken.net/api。最近 Hermes Agent 做多子代理大规模重构、Elvis Saravia 点评自进化技能与工程复利的讨论,把很多团队的注意力拉回一个更硬核的问题:长时间运行的 Agent,日志字段应该怎样设计,才能把成本、失败和复现路径讲清楚。如果没有统一模型出口,日志里会出现多个供应商、多个 Key 别名、多个 Base URL,最后排查时只能靠记忆。本文不写热点评论,直接给一套可以跟做的方案:先把 Hermes Agent 以及同机 Claude Code、Codex、CC Switch 的模型入口改到 TaoToken,再给日志字段清单、JSONL 样例、jq 解析命令和排障顺序。

这里要区分“模型请求日志”和“Agent 运行日志”。模型请求日志通常由 SDK 或网关侧产生,能看到 status_code、usage、request_id;Agent 运行日志由 Hermes Agent 自身产生,能看到子代理、工具调用、技能加载、文件变更、测试结果。两者不打通,就会出现一种尴尬局面:你知道某次调用花了 token,但不知道属于哪个子代理;你知道某个子代理失败了,但不知道它当时用的哪个模型、哪个 Base URL、哪个 Key 别名。所以第一原则是:日志字段必须能把 run、trace、span、subagent、request_id、tool_call_id 串起来。

在配置层,先把 Base URL 固定为:

https://taotoken.net/api

这个地址不加 UTM,UTM 只用于官网引导链接。Key 用占位符YOUR_API_KEY,不要写进代码仓库。拿到 Key 的入口仍然建议走官网:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_setup 。

1.1 Claude Code、Codex、CC Switch 不要混用变量

同一台开发机上经常同时装 Claude Code、Codex 和 CC Switch。它们读取的配置不同,混用会出现“明明 Key 没问题,但工具报 401”的情况。Claude Code 走 Anthropic 兼容配置时,用settings.jsonANTHROPIC_*;Codex 走config.toml,不要把它接成ANTHROPIC_*。CC Switch 则把它当成三件套:供应商名称、Base URL、API Key。

Claude Code 的settings.json可以这样写:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "YOUR_MODEL_NAME" } }

Codex 的config.toml用独立供应商名和独立环境变量:

model = "YOUR_MODEL_NAME" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"

然后在本地 shell 中导出:

export TAOTOKEN_API_KEY="YOUR_API_KEY"

CC Switch 三件套按下面填:

供应商名称: TaoToken Base URL: https://taotoken.net/api API Key: YOUR_API_KEY 模型: 在模型对话页选择可用模型后填入

如果 Hermes Agent 当前版本支持 OpenAI 兼容 provider,可以给它单独一组环境变量。不同版本的变量名可能不同,以它实际读取的 provider 配置为准。常见兼容写法是:

export TAOTOKEN_API_KEY="YOUR_API_KEY" export OPENAI_API_KEY="$TAOTOKEN_API_KEY" export OPENAI_BASE_URL="https://taotoken.net/api" export OPENAI_API_BASE="https://taotoken.net/api"

这里再次强调:Claude Code 的ANTHROPIC_*不要抄到 Codex;Codex 的env_key也不要硬塞给 Claude Code。日志字段里应该记录api_key_aliaskey_env_name,不要记录完整 Key。这样后面看到 401、429、404 时,可以快速判断是 Key 来源问题、Base URL 问题,还是模型名问题。

2. Hermes Agent 长时间多子代理运行,日志至少要覆盖六组字段

如果只记print("start")print("done"),长时间运行结束后只能知道“跑完了”或“没跑完”。真正可复现的日志,需要覆盖 trace、LLM、工具、子代理、成本资源、安全复现六组。下面这份清单可以直接作为 Hermes Agent 的 JSONL schema 草案。

2.1 trace 与运行上下文字段

这些字段负责把一次 run 串起来。建议每条事件都带:

字段说明
tsISO8601 时间戳,建议 UTC
run_id一次完整运行 ID
trace_id分布式追踪 ID
span_id当前事件 span
parent_span_id父 span,用于还原调用树
event_typerun_startllm_requestllm_responsetool_calltool_resultsubagent_spawnsubagent_joinerrorrun_end
leveldebuginfowarnerrorfatal
service固定为hermes-agent
versionAgent 版本
host机器名
pid进程 ID
thread线程或协程标识
session_id会话 ID
task_id任务 ID
subtask_id子任务 ID
agent_id主 Agent ID
subagent_id子代理 ID,主 Agent 可为空或root
parent_subagent_id父级子代理 ID
decomposition_depth任务拆解深度
concurrency_slot并发槽位编号

长时间运行时,concurrency_slot很关键。子代理数量一多,失败往往不是模型本身,而是并发抢占、队列等待、锁等待。日志里没有槽位编号,就很难判断某个时间段是否发生过拥堵。

2.2 LLM 请求与响应字段

TaoToken Key 的日志最值得记的字段集中在这里。建议每次模型调用记录:

字段说明
providertaotoken
base_url_hosttaotoken.net,不要记完整带 Key 的 URL
endpoint/api
model实际请求模型名
request_id供应商或网关返回的请求 ID
stream是否流式
temperature采样温度
max_tokens最大输出 token
usage.prompt_tokens输入 token
usage.completion_tokens输出 token
usage.total_tokens总 token
usage.cache_read_tokens缓存读取 token,如果有
usage.cache_write_tokens缓存写入 token,如果有
cost_estimate估算成本
currency币种
latency_ms总耗时
ttft_ms首 token 时间
retry_count重试次数
status_codeHTTP 状态码
finish_reason结束原因
error_type错误类型
error_message_digest错误信息摘要,不要记敏感原文
rate_limit_remaining剩余限流额度,如果有
rate_limit_reset限流重置时间,如果有

特别注意request_id。当用户反馈“某次请求异常”时,没有request_id就只能靠时间猜。base_url_hostendpoint用于确认请求确实走了https://taotoken.net/api,而不是被旧环境变量带到了其他地址。

2.3 工具调用字段

Hermes Agent 这类工具型 Agent,失败经常发生在工具层,而不是模型层。建议记录:

字段说明
tool_name工具名或命令类型
tool_call_id工具调用 ID
tool_args_digest参数摘要
tool_result_digest结果摘要
tool_duration_ms工具耗时
exit_code退出码
stdout_bytes标准输出字节数
stderr_bytes标准错误字节数
files_read读取文件列表
files_written写入文件列表
patch_size变更行数或字节数
diff_hashdiff 摘要
command_digest命令摘要
cwd工作目录
sandbox_id沙箱 ID
permission_decision允许、拒绝或需审批
approval_id审批 ID

不要记录完整命令里的密钥、令牌、密码。command_digesttool_args_digest用哈希即可。需要复现时,结合本地受控的审计存储再查原文。所有解析命令都由读者在自己的本地日志上执行,不要直连生产库,也不要让 Agent 绕过沙箱。

2.4 子代理与技能字段

Elvis Saravia 的点评里提到自进化技能和工程复利,这对日志字段有直接启发:如果技能会进化,就必须记录技能版本、来源和变更。建议字段:

字段说明
subagent_id子代理 ID
role子代理角色
skill_id技能 ID
skill_version技能版本
skill_sourcebuiltinmanualself_evolved
self_evolved是否自进化产生
skill_patch_id技能补丁 ID
step_index当前步数
max_steps最大步数
stop_reason停止原因
budget_tokenstoken 预算
budget_time_ms时间预算
used_tokens已用 token
used_time_ms已用时间
conflict_count冲突次数
merge_status合并状态
review_status审查状态

没有skill_version,你无法回答“这次成功是因为技能升级,还是因为模型变了”。没有stop_reason,你无法区分是任务完成、预算耗尽、还是子代理主动放弃。

2.5 成本与资源字段

长时间多子代理运行,成本会分散在大量请求里。建议记录:

字段说明
token_budget总 token 预算
token_used已用 token
cost_budget成本预算
cost_used已用成本
cpu_msCPU 时间
mem_peak_mb内存峰值
disk_write_bytes磁盘写入
queue_wait_ms排队等待
lock_wait_ms锁等待

这些字段不一定每条事件都有,但至少要在run_endsubagent_join和周期 checkpoint 中出现。否则只能事后估算。

2.6 安全、脱敏与复现字段

日志不能成为新的泄露面。建议:

字段说明
secret_redaction是否已脱敏
pii_redaction是否已处理个人信息
prompt_hash提示词摘要
response_hash响应摘要
tool_args_redacted工具参数是否脱敏
api_key_aliasKey 别名
key_env_name环境变量名
base_url_hashBase URL 摘要
tls_verify是否校验 TLS
git_commit_before运行前 commit
git_commit_after运行后 commit
branch分支
worktreeworktree 路径
repo_url_hash仓库地址摘要
test_suite测试集
test_status测试状态
test_duration_ms测试耗时
coverage_delta覆盖率变化
lint_statuslint 状态

其中git_commit_beforegit_commit_after是复现的底线。没有它们,你只能知道“代码变小了”,但无法回到运行前状态重放。

3. 一份可直接落地的 JSONL 日志样例

下面给出一组事件样例。实际写入时,每行一个 JSON 对象,文件后缀建议用.jsonl。不要把完整 Key、完整提示词、完整响应直接写进去。

运行开始:

{"ts":"2025-01-01T00:00:00.000Z","event_type":"run_start","level":"info","run_id":"run-001","trace_id":"trace-001","span_id":"span-root","parent_span_id":null,"service":"hermes-agent","version":"x.y.z","host":"dev-01","pid":12345,"session_id":"sess-001","task_id":"task-001","agent_id":"root","subagent_id":null,"parent_subagent_id":null,"decomposition_depth":0,"concurrency_slot":1,"repo_url_hash":"sha256:repo","git_commit_before":"abc123","branch":"refactor/log","worktree":"/workspace/repo"}

LLM 响应:

{"ts":"2025-01-01T00:00:01.200Z","event_type":"llm_response","level":"info","run_id":"run-001","trace_id":"trace-001","span_id":"span-llm-01","parent_span_id":"span-root","subagent_id":"sub-07","model":"YOUR_MODEL_NAME","provider":"taotoken","base_url_host":"taotoken.net","endpoint":"/api","api_key_alias":"taotoken-main","key_env_name":"TAOTOKEN_API_KEY","request_id":"req_xxx","stream":true,"temperature":0.2,"max_tokens":8192,"usage":{"prompt_tokens":1200,"completion_tokens":340,"total_tokens":1540,"cache_read_tokens":0,"cache_write_tokens":0},"cost_estimate":0.0123,"currency":"USD","latency_ms":2350,"ttft_ms":410,"retry_count":0,"status_code":200,"finish_reason":"stop"}

工具结果:

{"ts":"2025-01-01T00:00:03.550Z","event_type":"tool_result","level":"info","run_id":"run-001","trace_id":"trace-001","span_id":"span-tool-01","parent_span_id":"span-llm-01","subagent_id":"sub-07","tool_name":"shell","tool_call_id":"call_xxx","tool_args_digest":"sha256:args","tool_result_digest":"sha256:result","tool_duration_ms":812,"exit_code":0,"stdout_bytes":2048,"stderr_bytes":0,"files_read":["src/a.py"],"files_written":["src/a.py"],"patch_size":128,"diff_hash":"sha256:diff","command_digest":"sha256:cmd","cwd":"/workspace/repo","sandbox_id":"sbx-01","permission_decision":"allow","approval_id":null}

子代理启动:

{"ts":"2025-01-01T00:00:04.000Z","event_type":"subagent_spawn","level":"info","run_id":"run-001","trace_id":"trace-001","span_id":"span-sub-07","parent_span_id":"span-root","agent_id":"root","subagent_id":"sub-07","parent_subagent_id":"sub-01","role":"python-refactor","skill_id":"skill.python.refactor","skill_version":"2025.09.1","skill_source":"self_evolved","self_evolved":true,"skill_patch_id":"patch-001","step_index":3,"max_steps":25,"budget_tokens":50000,"budget_time_ms":600000,"used_tokens":1540,"used_time_ms":2350}

错误事件:

{"ts":"2025-01-01T00:00:05.000Z","event_type":"error","level":"error","run_id":"run-001","trace_id":"trace-001","span_id":"span-llm-02","subagent_id":"sub-03","request_id":"req_err","status_code":429,"error_type":"rate_limit","error_message_digest":"sha256:err","retry_count":2,"rate_limit_remaining":0,"rate_limit_reset":"2025-01-01T00:05:00Z","model":"YOUR_MODEL_NAME","base_url_host":"taotoken.net","endpoint":"/api"}

运行结束:

{"ts":"2025-01-01T01:00:00.000Z","event_type":"run_end","level":"info","run_id":"run-001","trace_id":"trace-001","status":"ok","stop_reason":"completed","total_tokens":123456,"total_cost_estimate":1.234,"total_duration_ms":3600000,"git_commit_after":"def456","test_status":"pass","lint_status":"pass","coverage_delta":0.5,"merge_conflicts":0}

如果 Hermes Agent 使用 Python logging,可以加一个 JSON formatter,把上述字段转成 JSONL。核心思路不是抄某个库,而是固定字段名。字段名一旦稳定,后面的 jq、Python、BI 都能复用。

4. 解析命令:用 jq 把日志拆成成本、错误、子代理和工具链

以下命令都在本地日志文件hermes-run.jsonl上执行。先确认文件存在:

ls -lh hermes-run.jsonl

按模型统计调用次数、总 token、平均延迟和错误数:

jq -s ' map(select(.event_type=="llm_response")) | group_by(.model) | map({ model: .[0].model, calls: length, total_tokens: (map(.usage.total_tokens // 0) | add), avg_latency_ms: ((map(.latency_ms // 0) | add) / length), errors: (map(select((.status_code // 0) >= 400)) | length) }) | sort_by(-.total_tokens) ' hermes-run.jsonl

计算 LLM 响应延迟 P95:

jq -s ' map(select(.event_type=="llm_response") | .latency_ms // 0) | sort | .[(length * 0.95 | floor)] ' hermes-run.jsonl

按子代理聚合 token、成本、错误和工具调用:

jq -s ' map(select(.subagent_id != null)) | group_by(.subagent_id) | map({ subagent_id: .[0].subagent_id, events: length, tokens: (map(.usage.total_tokens // 0) | add), cost: (map(.cost_estimate // 0) | add), errors: (map(select(.level=="error")) | length), tool_calls: (map(select(.event_type=="tool_result")) | length) }) | sort_by(-.tokens) ' hermes-run.jsonl

查看错误排行:

jq -r ' select(.level=="error") | [.ts, .run_id, .subagent_id, .error_type, .status_code, .request_id, .base_url_host, .endpoint] | @tsv ' hermes-run.jsonl | sort | uniq -c | sort -nr | head -30

按工具统计调用次数、平均耗时、错误数和写入文件数:

jq -r ' select(.event_type=="tool_result") | [.tool_name, (.exit_code // 0), (.tool_duration_ms // 0), ((.files_written // []) | length)] | @tsv ' hermes-run.jsonl | awk '{c[$1]++; d[$1]+=$3; if($2!=0) e[$1]++; w[$1]+=$4} END {for (k in c) printf "%s\tcalls=%d\tavg_ms=%.1f\terrors=%d\tfiles_written=%d\n", k, c[k], d[k]/c[k], e[k]+0, w[k]}'

追踪某个 request_id 或 tool_call_id:

REQ="req_xxx" jq -r --arg req "$REQ" ' select(.request_id==$req or .tool_call_id==$req) | [.ts, .event_type, .subagent_id, .tool_name, .status_code, .latency_ms] | @tsv ' hermes-run.jsonl

按时间窗口切片:

START="2025-01-01T00:00:00Z" END="2025-01-02T00:00:00Z" jq -r --arg start "$START" --arg end "$END" ' select(.ts >= $start and .ts <= $end) | [.ts, .event_type, .level, .subagent_id, .model, .status_code] | @tsv ' hermes-run.jsonl

按天和模型汇总成本:

jq -r ' select(.event_type=="llm_response" and (.cost_estimate // null) != null) | [.ts[0:10], .model, .cost_estimate] | @tsv ' hermes-run.jsonl | awk '{d[$1]+=$3; m[$2]+=$3} END{for(k in d) print "DAY",k,d[k]; for(k in m) print "MODEL",k,m[k]}'

如果 jq 不够,可以用一个小的 Python 脚本聚合子代理:

#!/usr/bin/env python3 import json import sys from collections import defaultdict def iter_jsonl(path): with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: yield json.loads(line) except json.JSONDecodeError: continue if __name__ == "__main__": path = sys.argv[1] if len(sys.argv) > 1 else "hermes-run.jsonl" sub = defaultdict(lambda: {"tokens": 0, "cost": 0.0, "errors": 0, "tools": 0}) for ev in iter_jsonl(path): sid = ev.get("subagent_id") or "root" usage = ev.get("usage") or {} sub[sid]["tokens"] += int(usage.get("total_tokens") or 0) sub[sid]["cost"] += float(ev.get("cost_estimate") or 0) if ev.get("level") == "error": sub[sid]["errors"] += 1 if ev.get("event_type") == "tool_result": sub[sid]["tools"] += 1 for sid, row in sorted(sub.items(), key=lambda kv: kv[1]["tokens"], reverse=True): print(sid, row)

这些命令只读本地 JSONL,不需要连数据库,也不需要让 Agent 访问生产环境。

5. 接入 TaoToken 后的排障顺序:先看 Key 和 Base URL,再看重试和限流,最后看子代理扇出

当 Hermes Agent 长时间运行失败时,不要一上来就怀疑模型。按日志字段排障,顺序通常是:

  1. base_url_hostendpoint。确认是taotoken.net/api,而不是旧的环境变量残留。
  2. api_key_aliaskey_env_name。确认请求用的到底是不是YOUR_API_KEY对应的环境变量。
  3. status_code。401、403 先查 Key 和权限;404 查模型名和 endpoint;429 查限流和并发;5xx 查服务端状态并保留request_id
  4. retry_count。重试很多次但最后成功,不代表没问题,可能正在放大成本。
  5. ttft_mslatency_ms。首 token 慢和整体慢要分开处理,前者影响交互,后者影响吞吐。
  6. rate_limit_remainingrate_limit_reset。如果频繁触顶,就要降低子代理并发或调整调度。
  7. subagent_idparent_subagent_iddecomposition_depth。定位是根代理失败,还是某个深层子代理失败。
  8. tool_nameexit_codestderr_bytes。很多“模型失败”其实是工具执行失败。
  9. merge_statusconflict_count。多子代理写同一仓库时,冲突会显著拖慢运行。
  10. token_usedcost_usedbudget_tokens。确认是预算耗尽,还是预算足够但效率低。
  11. skill_versionskill_sourceself_evolved。判断是否是技能升级后行为变化。
  12. secret_redactionpii_redaction。确认日志没有把敏感信息写进去。

如果你需要重新检查 Key 和模型,可以从官网入口进入:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_troubleshoot 。常见操作是:先在模型对话页确认模型可用,再创建或复制 API Key,然后把 Base URL 固定为https://taotoken.net/api

排障时不要贴完整 Key。只贴api_key_aliaskey_env_namestatus_coderequest_idbase_url_hostendpoint和错误摘要。这样既能定位问题,又不会扩大泄露面。

6. 最小可用字段集和扩展字段集:先跑起来,再逐层加字段

不是所有团队一开始都能打全字段。建议分两层。

最小可用字段集至少包含:

ts event_type level run_id trace_id span_id parent_span_id subagent_id parent_subagent_id model provider base_url_host endpoint api_key_alias key_env_name request_id status_code latency_ms ttft_ms retry_count usage.prompt_tokens usage.completion_tokens usage.total_tokens cost_estimate tool_name tool_call_id exit_code files_written patch_size skill_id skill_version stop_reason git_commit_before git_commit_after

扩展字段集再加:

concurrency_slot decomposition_depth queue_wait_ms lock_wait_ms rate_limit_remaining rate_limit_reset cache_read_tokens cache_write_tokens budget_tokens budget_time_ms used_tokens used_time_ms conflict_count merge_status review_status permission_decision approval_id sandbox_id diff_hash command_digest prompt_hash response_hash secret_redaction pii_redaction test_suite test_status coverage_delta lint_status

这份字段表的价值在于“工程复利”:同样的日志 schema,可以用于不同 harness、不同模型、不同子代理规模。Elvis Saravia 提到不同 harness 未必适用同一套方法,这个提醒同样适用于日志。字段要能回答“这次运行的成本、失败、技能版本、代码变更分别是什么”,而不是只回答“跑了多久”。

如果你想比较“更多子代理”和“更少子代理”哪个更省,也不能靠感觉。应该用subagent_id聚合 token、成本、错误、工具调用和合并冲突,再看单位有效 diff 的成本。日志字段足够细,才能把“规模”和“效率”分开看。

7. 文末 CTA:模型对话、Coding Plan、创建 Key、Claude Code 文档

如果你准备把这套日志字段落到 Hermes Agent,建议按下面顺序操作:

  1. 先在模型对话里确认目标模型可用:
    https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_chat

  2. 如果长时间运行和子代理并发较多,先看 Coding Plan 是否匹配:
    https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_plan

  3. 创建或复制 API Key,用YOUR_API_KEY占位,不要硬编码:
    https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_keys

  4. 如果你同时使用 Claude Code,按文档配置ANTHROPIC_*settings.json
    https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=hermes_log_fields_claude_code

最后再回到日志侧:把base_url_hostendpointapi_key_aliasrequest_idsubagent_idtool_call_idusage.total_tokenscost_estimateskill_versiongit_commit_beforegit_commit_after这些字段稳定写入 JSONL。这样下一次长时间多子代理运行结束后,你不需要翻聊天记录,也不需要猜哪个 Key 在哪个工具里生效。直接跑 jq,先把成本、错误、子代理和工具链拆开,再决定是限流、降并发、换模型,还是回滚技能版本。

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

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

立即咨询