LLM 智能体可观测性:追踪与会话,复现 10 个问题
目录
LLM 智能体的可观测性,首先要把模型调用按两个层次归组:追踪(trace)将智能体的一次运行画成时间线,会话(session)则汇总一次对话及其总费用。每次调用带上三个请求头,LLM 网关就能根据请求日志生成这两种视图,不需要接入追踪 SDK。网关是位于业务代码和模型提供商之间的代理,会记录每个请求。我们用这套配置测试了一个小型客服智能体和 Claude Code,复现了十个常见问题:三个导致响应慢,四个导致费用高,三个导致功能异常。
TL;DR
- 三个请求头(
X-Trace-Id、X-Span-Id、X-Session-Id)就能让 Synthorai 网关把请求日志组织成追踪和会话,无需追踪 SDK。 - 将三个互不依赖的子智能体检查并发执行后,耗时中位数从 3.7 s 降至 1.8 s,费用不变。
- 在 GPT-6 Luna 上,将时间戳从提示词首行移走后,从空缓存开始测试,缓存命中率从 0% 升至 73%。
- Claude Code 的一次运行花费 $0.1728,其中三个智能体各自的首次调用合计占 72%。
本文是一个七篇指南系列的总览。下面每节概述一篇指南,列出实测数据,并提供链接,方便查看代码和截图。所有测试均在 2026 年 10 月 10 日和 11 日进行。延迟数据取五次运行的中位数,其余数据均来自单次运行。代码位于 github.com/synthorai-io/use-cases。
LLM 智能体中的追踪、跨度和会话是什么?
追踪记录智能体处理一条用户消息或一项任务的全过程,跨度(span)是追踪中的一个步骤,会话则是包含多条追踪的完整对话。在我们的客服智能体中,一次两轮的客户对话对应一个会话,包含两条追踪,每条追踪中有五到六次模型调用。其中一些调用来自子智能体:主智能体将部分任务交给另一个智能体执行,后者使用自己的提示词,通常也会使用更便宜的模型。
| 概念 | 覆盖范围 | 请求头 | 哪些调用使用相同的值 |
|---|---|---|---|
| 追踪 | 智能体的一次运行 | X-Trace-Id | 本次运行的所有调用 |
| 跨度 | 本次运行中的一次模型调用 | X-Span-Id | 仅该次调用 |
| 会话 | 一次对话 | X-Session-Id | 本次对话的所有调用 |
| 智能体 | 发起调用的子智能体 | X-Agent-Id | 该子智能体的所有调用 |
| 标签 | 用户、功能、发布版本 | X-Synthorai-Metadata | 需要归组或筛选的任意调用 |
这些 ID 由你自行指定,每个最多包含 128 个可打印 ASCII 字符。标签是一个扁平的 JSON 对象,值均为字符串;每个请求最多带 10 个标签,每个值最多 128 字节。网关会在响应中返回这些 ID,不会将上述任何请求头转发给模型提供商。使用 OpenAI Python SDK 时,单个请求的请求头放在 extra_headers 中:
resp = client.chat.completions.create(
model="gpt-6-luna",
messages=messages,
extra_headers={
"X-Session-Id": session_id, # one per conversation
"X-Trace-Id": trace_id, # one per user message
"X-Span-Id": "plan", # one per call
"X-Synthorai-Metadata": '{"feature":"support-agent","_user":"u_1002"}',
},
)
如果智能体已经通过其他工具发送追踪 ID,可能无需改动。对于请求中缺失的三个 ID,网关会分别读取 OpenTelemetry、LiteLLM、Langfuse、Open WebUI、Claude Code 或 OpenAI Codex CLI 的对应请求头。OpenTelemetry 是追踪领域的开放标准,其 traceparent 请求头包含追踪 ID。用 3 个 HTTP 请求头追踪 LLM 智能体 逐步搭建了演示智能体:先用 GPT-6 Luna 规划,再调用两个本地工具,接着用 DeepSeek V4.1 Flash 并行执行三项检查,最后调用模型生成回答。演示运行共发起 21 次模型调用,花费 $0.0017。
不同问题该看哪个视图?
本系列中的每个问题,都可以在控制台的某个位置找到答案,或通过控制台之外的日志查询方式解决。控制台将每条追踪显示为瀑布图:每次模型调用对应一条横条,共用一条时间轴。并行调用的横条会重叠,存在等待关系的调用则呈阶梯状排列。
| 问题 | 查看位置 | 指南 |
|---|---|---|
| 这次运行为什么用了 8 秒? | 追踪摘要:对比网关耗时和应用耗时 | 智能体延迟 |
| 这次对话哪一轮最贵? | 会话页面,每行对应一条追踪 | 智能体费用 |
| 缓存命中率为什么是 0%? | 会话页面的缓存命中列 | 智能体费用 |
| 这个用户昨天的请求出了什么问题? | 在追踪列表中按标签搜索 | 智能体调试 |
| Claude Code 的哪个子智能体花了钱? | 会话页面的智能体表格 | 按子智能体分析 Claude Code 费用 |
| 能在聊天窗口里问这些问题吗? | Claude 中的 MCP 连接器 | 让 Claude 查询 LLM 日志 |
| 能用自己的程序分析日志吗? | MCP 工具或 REST API | 给智能体接入日志工具 |
每个追踪页面还有一个 Compare 按钮,可以在同一页面对比两条追踪:按名称配对各次调用,并将两张瀑布图放在同一时间轴上。本系列的每项修复都用这种方式核验过,对比修复前后的运行结果。
LLM 智能体为什么慢?
先看追踪顶部的耗时拆分。它将实际总耗时(用户从第一个请求发出到最后一个响应返回的等待时间)分成两部分:至少有一个模型请求尚未完成时,计入网关耗时;只有业务代码在运行时,计入应用耗时。演示追踪的总耗时为 8.5 s,其中网关耗时 7.0 s,应用耗时 1.6 s,因此应先检查模型调用。延迟指南复现了三种原因,每种都运行了五次:
| 原因 | 追踪中的表现 | 修复方式 | 修复前中位数 | 修复后中位数 |
|---|---|---|---|---|
| 互不依赖的调用逐个发送 | 横条呈阶梯状排列 | 用 asyncio.gather 并发执行,这是 Python 同时等待多个调用的方式 | 3.7 s | 1.8 s |
| 调用之间的代码执行慢 | your app 行很长 | 并发执行四次查询 | 11.0 s | 9.2 s |
| 一行就能回答的问题也开启推理 | 输出 token 数远大于实际回复 | reasoning_effort="none" | 五个回答耗时 25.6 s | 10.2 s |
在代码执行慢的案例中,两次运行的模型调用耗时基本相同,变化的是应用耗时:指南展示的两次运行中,your app 行从 4.6 s 缩短到 1.3 s。换模型无法缩短这部分时间,需要从自己的性能分析器或 OpenTelemetry 跨度入手。第三个案例使用 GPT-6 Luna。请求参数 reasoning_effort 控制模型回答前的推理量,将其设为 none 后,五个回答的输出 token 数从 1,150 降至 190。LLM 智能体为什么慢? 提供了这三个案例的代码和并排追踪对比。
LLM 智能体为什么贵?
先将追踪列表按费用排序,再打开最贵的会话,逐轮查看缓存命中和输入列。缓存命中率是从提供商的提示词缓存中读取的输入 token 占比。对于之前见过的提示词前缀,提供商按普通输入价格的一小部分收费。费用指南复现了四种原因。演示规模很小,金额均不足一美分,因此更值得参考的是比例:
| 原因 | 表现位置 | 修复前 | 修复后 |
|---|---|---|---|
| 提示词首行放了时间戳 | 缓存命中率始终为 0% | 缓存命中率 0% | 缓存命中率 73%,输入费用降低 66% |
| 历史记录每轮都重发全部工具结果 | 输入量逐轮增长 | 24,546 个输入 token | 8,053 个输入 token |
| 重试循环 | 一条追踪中调用过多,状态码均为 200 | 10 次调用 | 2 次调用 |
| 简单步骤使用了昂贵模型 | 会话页面的智能体表格 | $0.0012 | $0.0003 |
第一行使用 GPT-6 Luna,从空缓存开始测试。指南还介绍了另一种导致缓存失效的情况:发送时给每条用户消息追加一段指令,保存时却没有保留。这样每一轮重建出的提示词,都与上一轮实际发送的内容不同。按实际发送的内容保存消息后,该对话的缓存命中率从 0% 升至 76%。在历史记录案例中,输入 token 数增加到三倍,输入费用却只增加了 21%,因为其中 71% 来自缓存。最后一行中,使用 GPT-6.1 Sol 的三项子智能体检查占会话总费用的 81%,换成 DeepSeek V4.1 Flash 执行相同检查后,费用只有原来的十分之一。LLM 智能体为什么贵? 逐一分析了这些案例。
如何根据用户反馈调试 LLM 智能体?
先通过标签找到用户的运行记录,再查看追踪,定位出错的那次调用。出错调用往往也会返回状态码 200。如果每个请求都带有 _user 标签,在追踪列表中搜索 metadata._user = u_1002,就能找到该用户的会话和追踪。调试指南随后复现了三种故障:
- 返回 404 后重试。调用一个不存在的模型 ID,95 ms 后失败,费用为 $0。重试沿用追踪 ID,并使用新的跨度名称(
policy-check.retry),因此失败调用和重试会在瀑布图中相邻显示。 - 状态码为 200,回复却被截断。设置
max_tokens=40并保持推理开启时,连续三次调用 DeepSeek V4.1 Flash 都返回状态码 200,输出恰好为 40 个 token,但回复为空,因为推理耗尽了额度。追踪中同一跨度出现三次,每次输出量都等于上限。将上限提高到 2,000 并关闭推理后,只需一次调用就得到了 49 个 token 的回答。仅提高上限也能解决问题,但那次调用用了 1,876 个 token 推理,耗时 22 s。 - 第二轮返回 400。保存的历史记录丢失了一次工具调用,导致 GPT-6 Luna 在第二轮用 0.5 s 拒绝请求,未收取费用,而第一轮运行正常。
第二种故障说明,每次调用都应该记录 finish_reason,这个响应字段表示模型停止生成的原因。值为 length 表示回复触及了 max_tokens 上限,即使状态码是 200,也应算作失败。调试 LLM 智能体 列出了每个智能体都应该记录的五个字段,本文末尾也会再次列出。
不是自己写的编程智能体,也能追踪吗?
无需修改 Claude Code 就能追踪它,因为它的每个请求已经带有会话 ID 和智能体 ID。设置两个环境变量即可将请求指向网关,第三个变量用于附带各子智能体的类型:
export ANTHROPIC_BASE_URL="https://synthorai.io" # no /v1: Claude Code adds /v1/messages itself
export ANTHROPIC_API_KEY="<your gateway key>"
export CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 # optional: names sub-agent types such as Explore
Claude Code 使用 Claude Sonnet 5.5,通过两个子智能体修复一个失败测试,共发起 11 个请求,花费 $0.1728。会话页面按智能体拆分了费用:两个子智能体占 39%,缓存写入占 77%,三个智能体各自的首次调用合计占 72%。半分钟后,在缓存已预热的情况下重复同一次运行,费用为 $0.0812。按子智能体分析 Claude Code 费用 还介绍了如何按开发者和代码仓库拆分团队费用。
能让模型帮你分析追踪吗?
模型可以通过 MCP 读取追踪。MCP 即 Model Context Protocol,是向模型提供工具的标准协议;网关以 MCP 服务器的形式提供请求日志。该用哪种方式读取,取决于谁在查询:
| 查询者 | 使用方式 | 模型 token | 实测结果 |
|---|---|---|---|
| 带着问题来查询的人 | 在 Claude 中将网关添加为连接器 | 包含在聊天套餐中 | 3 个问题用了 14 次工具调用,所有数字均与账单记录一致 |
| 执行固定查询的程序 | REST API | 不需要 | 查询费用最高的 3 个会话,花费 $0 |
| 自主决定检查内容的自建智能体 | 程序中的 MCP 工具 | 需要付费 | 一次费用排查花费 $0.0015 |
在聊天场景中,Claude Fable 5.1 通过 OAuth 连接,即通过浏览器登录,无需粘贴 API key,并且只被授予请求日志的读取权限。它获得了四个只读工具,以及一枚 7 天后过期的密钥。工具返回费用、token 数和状态,不包含提示词或回复正文。在程序场景中,GPT-6 Luna 只用一次工具调用,就从 26 个会话中找出了缓存、模型选错和重试循环对应的会话,花费 $0.0015;Claude Sonnet 5.5 先查询价格,再用三次工具调用找到相同会话,花费 $0.0631。让 Claude 查询 LLM 日志 和给智能体接入日志工具 分别介绍了这两种配置。
每个智能体都应该记录什么?
五个字段就能覆盖本系列的所有问题:
- 每次运行的追踪 ID 和每次对话的会话 ID,通过请求头发送,同时写入自己的日志。
- 每次调用的跨度名称,包含步骤和尝试次数,例如
plan-2或policy-check.retry。 - 用户、功能和发布版本标签,便于从用户反馈或账单定位到相关运行。
- 每次调用响应中的
x-request-id请求头,便于事后查询该调用的计费金额。 - 每次调用的
finish_reason和输出 token 数,将length视为失败。
每次调用还应设置客户端超时和 max_tokens,两者都要留足余量。上面的空回复 200 问题,就是按可见回复长度设置上限造成的;推理模型的一次长调用可能持续数分钟。
网关侧追踪覆盖不到什么?
基于请求日志的追踪只能覆盖模型调用,看不到业务代码内部。网关根据请求间的空档推算代码运行时间,前面的慢代码案例就是如此。工具、检索和队列的跨度需要通过 OpenTelemetry SDK 采集。两种视图可以共用追踪 ID,因为缺少 X-Trace-Id 时,网关会读取 traceparent。追踪也不包含提示词和响应正文,费用则是事后统计的:如果要实时控制预算,就需要在智能体循环中,每收到一个响应便根据其中的 usage 计算费用。
常见问题
什么是 LLM 智能体可观测性?
LLM 智能体可观测性,是指能够看清智能体每次运行做了什么:调用了哪些模型、顺序如何、每次调用用了多久、花了多少钱,以及哪些调用失败了。实际落地时,就是将调用组织成追踪(一次运行)和会话(一次对话),并附上用户和功能标签。
延伸阅读
- 用 3 个 HTTP 请求头追踪 LLM 智能体:逐步搭建演示智能体并配置请求头。
- 请求头参考:本文涉及的所有请求头,以及从其他工具读取的别名。
- 控制台中的追踪和会话:列表、瀑布图和会话页面。
- LLM token 用量详解:按提供商说明每条记录中 token 计数的含义。
- 提示词缓存完整指南:提示词前缀为什么会命中或未命中缓存。