资讯动态

Multi-Agent 日志串不起来?TaoToken 这样让 Codex 查 trace_id

发布时间:2026/9/16 20:44:14 来源:尧图企业网站定制
TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_end解决模型通道的 Key 问题但更头疼的是多智能体场景用户退款三天没到账你翻遍日志不知是接待 Agent 没传给退款 Agent还是退款 Agent 调支付工具传错参数两小时定位不到断链。根子在日志没有统一 schema、请求没有全局 trace_id。要根治一边按后文规范补结构化日志一边让 Codex 按 trace_id、span_id、session_id 查断链Codex 的 Base URL 同样走 TaoToken 提供的 https://taotoken.net/api。多智能体系统的问题排查慢就慢在「日志之间没有关系」没有父子 ID没有角色字段没有工具调用编号搜索只能模糊匹配。下面先把字段规范和链路扩展讲透再直接落到 Codex 配置和一次真实复盘。1. 先复现「日志串不起来」的现场print 与模糊搜索的极限1.1 多智能体的动态链路微服务那套日志规则兜不住多智能体系统的主流程看起来简单用户请求进入主 Agent 拆任务要调物流、支付工具就调工具要把子任务交给退款、查询 Agent 就发协作消息协作 Agent 执行完再回传最后由主 Agent 汇总。问题是这条链路不是代码里写死的而是大模型每次按上下文现决定的这轮请求调工具下轮请求可能改派 Agent同一用户会话里两个不相关的子任务还会异步交错。微服务那套「服务名 接口名 trace_id」的日志模板套到这里缺了三块第一日志里没有 Agent 角色和执行状态第二没有工具调用 ID调了哪个工具、传了什么参数查不到第三普通链路追踪的生命周期只有几秒多智能体一个任务可能跨越好几个用户请求。日志一旦散落在不同 Agent 的进程里搜索就只能靠模糊匹配漏检率自然高。1.2 一次「看起来全对」的搜索为什么定位不到断链先看一个典型现场。某个多智能体客服系统里退款 Agent 处理用户请求时进程里留下了下面三行日志2025-06-11 10:01:23 INFO 收到退款请求订单号 ord_88231 2025-06-11 10:01:25 ERROR 调用支付接口失败user_id 格式错误 2025-06-11 10:01:26 INFO 返回给用户退款成功同一进程里按时间排序这三行日志出现在相邻位置肉眼很容易误以为是一次完整请求收到请求、调用失败、返回成功。实际上这三行分别属于两个不同的用户请求第一行是 A 用户的退款单第二行是 B 用户的退款单第三行是 A 用户的一次查询响应。没有 trace_id 和 session_id日志系统只能按时间戳排序展示同一次请求的日志被其他请求的日志切得支离破碎。你花两小时翻日志大部分时间不是在找问题而是在排除「这些日志到底是不是同一次请求」。2. 结构化日志五类字段让每条日志自带定位坐标2.1 五类字段的分工要让 Codex 这类工具能直接读日志定位日志得先变成「一行 JSON 一条记录」的结构化格式并且字段分五类字段类别作用必填字段示例全局基础字段串联一次请求的完整链路trace_id、span_id、parent_span_id、session_id、timestamp、service_name、env、levelAgent 专属字段定位是哪个 Agent、哪个阶段出问题agent_id、agent_role、agent_version、agent_state、input_token_count、output_token_count、total_cost交互字段还原 Agent 间消息传递sender_type、sender_id、receiver_type、receiver_id、message_type、content_digest、message_status工具调用字段定位是哪次工具调用失败tool_name、tool_version、tool_call_id、tool_input_params、tool_output_digest、tool_duration、tool_error_code业务扩展字段按业务维度过滤order_id、ticket_id 等按场景自定义trace_id 是整个链路唯一的根标识16 位十六进制字符串span_id 是当前这一跳的标识parent_span_id 指向父级有了它才能把散落的日志按树形结构归位。session_id 解决长会话问题同一个用户跨多次请求的 trace 通过 session_id 关联起来。这三者是 Codex 查日志时优先读取的坐标。agent_id 和 tool_call_id 则是定位断链的二级坐标出问题时先看是「哪个 Agent 的哪一跳」出了错再看「调了哪个工具的哪个参数」不对。2.2 一条符合规范的 JSON 日志长什么样字段命名统一用蛇形命名时间戳统一毫秒级 Unix 时间戳枚举值全部大写、下划线分隔敏感信息脱敏大模型输入输出只存摘要。下面这条是退款场景里接待 Agent 向退款 Agent 发协作消息时的日志{timestamp: 1717234567890, level: info, trace_id: 9f2c7d1e4a8b3f05, span_id: 3a1b9c2d7e4f8016, parent_span_id: c8d2e6f4a1b93075, session_id: sess_8d21f7aa, service_name: agent-platform, env: prod, agent_id: agent_reception_07, agent_role: reception, agent_state: executing, input_token_count: 1200, output_token_count: 300, sender_type: agent, sender_id: agent_reception_07, receiver_type: agent, receiver_id: agent_refund_03, message_type: AGENT_COMMUNICATION, content_digest: sha256:9b2f..., message_status: sent}这条日志既包含当前 Agent 的执行信息也包含「发给谁、发了什么类型的消息、消息摘要是什么」。后续任何一步出错都可以先按 trace_id 搜出整条链路再按 span_id 和 parent_span_id 把顺序排出来最后看 tool_error_code 和 message_status 找到断点。Codex 拿到这样的 JSON能直接按字段做结构化分析而不是在一堆自由文本里做语义猜测。3. 链路追踪三件套Span 类型、上下文传播、动态采样3.1 五种 Span 类型与两次关键传播给 OpenTelemetry 做多智能体扩展核心是定义五种 Span 类型让每一种动作都有专属的追踪节点Span 类型触发时机排查作用ROOT用户请求进入接入层一次服务的完整边界AGENT_EXECUTEAgent 开始处理任务定位是哪个 Agent 耗时、出错TOOL_CALLAgent 调用工具定位工具名、入参、错误码AGENT_COMMAgent 向其他 Agent 发消息还原协作消息走向MEMORY_OPAgent 读写记忆存储发现上下文丢失或记忆污染上下文传播是链路不散的关键。Agent 间发消息在消息头里带 traceparent 字段遵循 W3C Trace Context 规范这样接收方 Agent 能提取出同一个 trace_id把新创建的 span 挂到父 span 下面。工具调用则在参数里加一个 trace_context 字段工具执行完把耗时、错误码写回 span。记忆存储写入时同时关联 trace_id 和 session_id这样即使几个小时后另一个 Agent 读记忆也能把这次操作归到原链路里。3.2 动态采样错误全量留普通日志按权重折减日志量过大时不能全量采否则存储成本压不住。动态采样率可以按这样一个思路设计采样率 基础采样率 × 错误率权重 × 成本权重 × Agent 优先级权重 × 用户优先级权重。错误率越高权重越大当某 Agent 最近十分钟内错误率达到阈值时权重上限可以拉到十倍也就是从 10% 的采样率直接升到 100% 全采。成本高的请求、核心 Agent主 Agent、支付相关 Agent、VIP 用户也都给更高权重。这里有一条不可妥协的规则不管采样率怎么调error 级别日志永远 100% 全量保留因为排障主要靠的就是错误日志。链路完整性也可以用「实际采集 span 数 / 理论应有 span 数」来评估低于 80% 就说明上下文传播断了需要检查消息头是否被过滤、工具参数里的 trace_context 是否被丢弃。4. 落地代码BaseAgent 基类与 tool_call 装饰器4.1 先把日志格式化成 JSON并把 trace_id 注入进去在代码层面做两件基础的事日志 JSON 化以及从当前 OpenTelemetry span 自动取出 trace_id、span_id 写入日志。下面这个自定义 Formatter 在每次写日志时自动带上链路坐标import json, time, logging from contextvars import ContextVar from opentelemetry import trace session_id_var: ContextVar[str] ContextVar(session_id, default) agent_id_var: ContextVar[str] ContextVar(agent_id, default) class TraceJsonFormatter(logging.Formatter): def format(self, record: logging.LogRecord) - str: span trace.get_current_span() span_ctx span.get_span_context() payload { timestamp: int(time.time() * 1000), level: record.levelname.lower(), message: record.getMessage(), trace_id: format(span_ctx.trace_id, 016x) if span_ctx.trace_flags else , span_id: format(span_ctx.span_id, 016x) if span_ctx.trace_flags else , session_id: session_id_var.get(), agent_id: agent_id_var.get(), } return json.dumps(payload, ensure_asciiFalse)注意这里通过 ContextVar 存放当前请求的 session_id 和 agent_id日志输出时自动带上业务代码不用每行手动传。基类 BaseAgent 在执行入口创建一个 AGENT_EXECUTE span并解析上一跳传来的 trace 上下文这样不管 Agent 被谁调用链路都能接上class BaseAgent: def run(self, user_input: dict, carrier: dict | None None) - dict: ctx extract_trace_context(carrier) if carrier else None with tracer.start_as_current_span( namef{self.role}.execute, contextctx, attributes{agent.id: self.agent_id, agent.role: self.role}, ): return self._execute(user_input)4.2 工具调用装饰器把每次调用变成独立 span工具调用类的问题最容易用 span 暴露传参错了、工具报错了、结果被 Agent 吞了这三种情况的特征完全不同。用一个装饰器包住所有工具函数每次调用自动创建 TOOL_CALL span记录工具名、入参摘要、耗时、错误码def tool_call(tool_name: str): def deco(fn): def wrapper(*args, **kwargs): with trace.get_tracer(__name__).start_as_current_span( nameftool.{tool_name}, attributes{ tool.name: tool_name, tool.input: str({k: mask(v) for k, v in kwargs.items()})[:200], }, ) as span: try: result fn(*args, **kwargs) span.set_attribute(tool.output_digest, sha256(str(result)[:200])) return result except Exception as exc: span.record_exception(exc) span.set_attribute(tool.error_code, getattr(exc, code, -1)) raise return wrapper return deco每次工具调用都会在链路里留下一个子 span入参先脱敏再记录输出只存摘要。这样排查「到底是工具返回错误、还是 Agent 把错误吞了」时从 TOOL_CALL span 的 error_code 就能直接判断而不用靠猜。到这里日志规范和链路追踪的代码基础就齐了。接下来是文章里最关键的一跃已经散落的存量日志怎么快速查出 trace_id 并定位断链答案是让 Codex 来读。5. 让 Codex 查 trace_idCodex 的 TaoToken 接入与排障工作流5.1 创建 Key先到 TaoToken 官网注册并建一个要让 Codex 跑起来需要一个可用的 API Key 和模型通道。这里推荐从 TaoToken 创建因为它提供统一兼容通道一个 Key 可以复用多种模型模型 ID 以 TaoToken 模型广场当时列表为准不要盲写网上流传的旧 ID。注册、创建 Key、看用量都在官网完成打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end → 注册登录 → 控制台 API Keys 创建 YOUR_API_KEY → 记下目标模型 ID你打算用哪个模型来跑 Codex就去模型广场确认那个 ID。5.2 修改 ~/.codex/config.tomlBase URL 指向 TaoTokenCodex 的接入不靠 ANTHROPIC_* 环境变量而是改 Codex 自己的配置文件。编辑~/.codex/config.tomlmodel 你的模型ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY保存后在终端导出 Keyexport TAOTOKEN_API_KEYYOUR_API_KEY这里有两个容易踩的坑一是 Base URL 是 https://taotoken.net/api末尾不加 /v1也不要顺手带上网页的 UTM 参数二是模型 ID 别在网上复制一段过期的在 TaoToken 模型广场确认当前可用的 ID 再填。配置保存后先随便问 Codex 一个问题确认能正常返回再进入排障工作流。5.3 把日志片段和 trace_id 交给 Codex 的具体问法Codex 排障的正确姿势是把结构化日志片段直接贴给它而不是丢一句「帮我看看日志哪里错了」。推荐的提问框架是先给出本次请求的坐标trace_id9f2c7d1e4a8b3f05、session_idsess_8d21f7aa。把按 trace_id 聚合后的日志片段或日志文件路径给 Codex并说明字段含义span_id是当前跳parent_span_id是父跳tool_error_code非 0 表示工具调用失败message_statusfailed表示 Agent 间消息没送达。让 Codex 按parent_span_id画出调用树标出哪些 span 有父无子、哪些 TOOL_CALL 的 error_code 非 0。示例问法这是同一次退款请求的完整日志 JSONtrace_id 是 9f2c7d1e4a8b3f05。 请按 parent_span_id 还原调用链找出哪个 AGENT_EXECUTE 或 TOOL_CALL span 异常中断 并对比 AGENT_COMM 的 receiver_id 和后续 span 的 agent_id看消息是否真的送到。Codex 会从 JSON 里提取 span 关系、按时间戳和父子 ID 还原出「接待 Agent → 退款 Agent → 支付工具」的完整链路标出断点。如果日志量太大可以让它在本地日志目录里执行grep先过滤出该 trace_id 的所有行再分析。注意 Codex 只读取你给出的日志文件和片段它生成的 SQL 脚本由你在本地数据库执行再把结果贴回对话不要让它直连生产库做诊断执行。6. 一次退款断链复盘从 trace_id 到错误参数只花几分钟6.1 断链链路还原回到开头的退款问题。有了结构化日志后客服拿到用户会话 IDsess_8d21f7aa在日志系统里搜这个 session_id过滤出所有 error 日志得到 trace_id9f2c7d1e4a8b3f05。把这批日志贴给 Codex 后它按parent_span_id还原出的链路是这样的ROOT span用户发起退款请求进入客服系统。AGENT_EXECUTE接待 Agent 受理规划后向退款 Agent 发出 AGENT_COMM 消息。AGENT_EXECUTE退款 Agent 收到消息提取 user_id 时取错了字段把订单号当作 user_id 放进工具入参。TOOL_CALL调用支付工具tool_error_code为 400入参摘要里user_id的值与订单号前缀完全一致。后续没有生成 TOOL_RESPONSE 对应的处理 span退款 Agent 捕获异常后没有继续上报直接返回「退款成功」。到这里断链原因已经从「哪个环节出了错」精确定位到「退款 Agent 提取参数时用错字段」。整个排障过程从原来翻两小时日志缩短到几分钟。这里的关键不是 Codex 有多聪明而是日志里有 trace_id、span_id、parent_span_id、tool_error_code 这些结构化坐标它才能按图索骥日志如果不结构化再强的模型也只能靠猜。6.2 排障边界与几条必须守住的底线这个工作流能跑通依赖几条纪律第一error 日志 100% 采样任何采样策略都不许丢错误日志第二敏感信息先脱敏再落盘手机号、账号、模型明文输出只存摘要这样把日志贴给 Codex 时不用担心泄露第三存储分层普通日志存短周期、错误日志和链路数据存更长时间按自己团队的存储成本定。另一条边界是Codex 只做日志和代码层面的分析涉及数据库诊断时SQL 由它生成、你在本地执行再把结果贴回对话生产环境的变更操作更是只能由人来执行。这一步做完如果还没创建过 Key现在去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 补上也不迟后面每次排障都会用上同一条 API 通道。跑完上面这轮排障可以回 TaoToken 控制台对一下这次调用是否正常入账先用同一把 Key 在 模型对话 发一条测试消息确认模型 ID 和 Base URL 没填错Key 不够了就在 控制台 API Keys 新建如果计划把 Codex 长期当排障助手用可以看看 Coding Plan 的套餐是否更划算。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价