请求头
网关认三个可选请求头:两个把你自己的链路上下文带进我们的日志,一个让同一段会话固定落在同一个上游,使 prompt caching 持续命中。三个都不带也完全不影响调用。
| 请求头 | 网关的处理 | 用途 |
|---|---|---|
X-Trace-Id | 回显在响应上,并记录在这次调用上,用来把它和同一个 trace 的其他调用归在一起。 | 把一次调用对回你自己的链路追踪系统。 |
X-Span-Id | 回显在响应上,并记录在这次调用上;在 trace 的瀑布图里作为这次调用的名字。 | 标识该链路中的某一个 span。 |
X-Session-Id | 回显在响应上,并记录在这次调用上。带同一个值的连续请求会被固定到同一条渠道、同一把上游 key。 | 让一段会话里的 prompt caching 持续命中。 |
X-Request-ID | 不采信你传入的值。你传的值会以 X-Client-Request-ID 回显;响应里的 X-Request-ID 永远是网关自己生成的 UUIDv7。 | 网关侧对这次调用的唯一标识 —— 提工单时报这个。 |
怎么发
curl https://synthorai.io/v1/chat/completions \
-H "Authorization: Bearer $SYNTHORAI_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Session-Id: conv-8f3a2b" \
-H "X-Trace-Id: 7c1d9e40f2b84a17" \
-H "X-Span-Id: 2f9b41c8" \
-d '{
"model": "claude-sonnet-5",
"messages": [{"role": "user", "content": "Hello"}]
}' 其他工具已经在发的请求头
如果一个调用没有带我们的这三个请求头,网关会读取编程 agent、SDK 或追踪标准本来就会发送的对应请求头,这些运行无需你做任何改动就能在链路追踪里归组。
| 请求头 | 视为 | 发送方 |
|---|---|---|
X-Claude-Code-Session-Id | 会话 | Claude Code |
X-Litellm-Session-Id | 会话 | LiteLLM SDK, OpenHands |
Session-Id | 会话 | OpenAI Codex CLI |
Helicone-Session-Id | 会话 | Helicone |
X-OpenWebUI-Chat-Id | 会话 | Open WebUI |
X-Opencode-Session-Id | 会话 | OpenCode |
baggage: session.id=… | 会话 | W3C Baggage (OpenTelemetry) |
baggage: gen_ai.conversation.id=… | 会话 | W3C Baggage (OpenTelemetry) |
baggage: langfuse_session_id=… | 会话 | Langfuse SDK |
X-Session-Affinity | 会话 | OpenClaw, OpenCode, Kilo Code |
X-Litellm-Trace-Id | Trace | LiteLLM SDK |
X-Portkey-Trace-Id | Trace | Portkey |
traceparent | Trace;其中的 parent id 视为 span | W3C Trace Context (OpenTelemetry) |
X-Claude-Code-Prompt-Id | Trace | Claude Code |
X-Interaction-Id | Trace | GitHub Copilot |
X-Codex-Turn-Metadata | Trace | OpenAI Codex CLI |
baggage: langfuse_trace_id=… | Trace | Langfuse SDK |
X-Portkey-Span-Id | Span | Portkey |
X-OpenWebUI-User-Id | 终端用户(_user 标签) | Open WebUI |
baggage: user.id=… | 终端用户(_user 标签) | W3C Baggage (OpenTelemetry) |
baggage: langfuse_user_id=… | 终端用户(_user 标签) | Langfuse SDK |
两者同时存在时,总是以我们自己的请求头为准。Session-Id 和 X-Codex-Turn-Metadata 只在 Codex CLI 的请求里读取,X-Interaction-Id 只在 GitHub Copilot 的请求里读取,按 User-Agent 识别。完全没带 trace ID 的调用,响应里的 X-Trace-Id 是网关自己的请求 ID。
在 /v1/chat/completions 和 /v1/responses 上,会话 ID 也可以放在请求体里,字段名是 session_id(用 OpenAI SDK 时放进 extra_body)。请求头优先于它;请求发给供应商之前,这个字段会被去掉。
OpenClaw 每次模型调用都带 traceparent,它的运行不用配置就会归成 trace;要按会话分组,在模型上开启 compat.sendSessionAffinityHeaders。Open WebUI 开启 ENABLE_FORWARD_USER_INFO_HEADERS 后会带上对话 id 和用户 id;同时发来的姓名和邮箱请求头不读取。不发这些信息的 agent(Cursor、Aider、Cline、Continue 和大多数 agent 框架)需要在自定义请求头里加上 X-Session-Id。
W3C Trace Context 与 Baggage
traceparent 给出 trace,其中的 parent id 作为 span;开启 OpenTelemetry 的 HTTP 自动埋点后会自动带上。其余信息放在 baggage 里,键名沿用 OpenTelemetry:会话用 session.id 或 gen_ai.conversation.id,agent 用 gen_ai.agent.id 和 gen_ai.agent.name,终端用户用 user.id。这些需要你的代码自己写进 baggage:OpenTelemetry SDK 不会把 span 属性复制进去。agent 名称会作为 agent 的类型保存,只接受字母、数字、.、_ 和 -。tracestate 不读取。
会话里的 agent
会启动子 agent 的编程 agent,会在每次调用上说明它来自哪个 agent。网关把这些信息随调用一起保存,于是会话可以按 agent 看花费,链路里能看出哪些调用是压缩或辅助调用,以及两次调用之间运行了哪些工具。这些请求头都不影响路由:子 agent 仍然走它所在会话的上游。
| 请求头 | 视为 | 发送方 |
|---|---|---|
X-Agent-Id | Agent | Synthorai |
X-Parent-Agent-Id | 启动它的 agent 或会话 | Synthorai |
X-Claude-Code-Agent-Id | Agent | Claude Code |
X-Claude-Code-Parent-Agent-Id | 启动它的 agent 或会话 | Claude Code |
Thread-Id | Agent | OpenAI Codex CLI |
X-Codex-Parent-Thread-Id | 启动它的 agent 或会话 | OpenAI Codex CLI |
baggage: gen_ai.agent.id=… | Agent | W3C Baggage (OpenTelemetry) |
X-Parent-Session-Id | 启动它的 agent 或会话 | OpenCode, Kilo Code |
X-Opencode-Parent-Session-Id | 启动它的 agent 或会话 | OpenCode |
X-Claude-Code-Request-Class | 调用类型 | Claude Code |
X-Claude-Code-Agent-Type | 调用类型 | Claude Code |
X-Claude-Code-Compaction | 调用类型 | Claude Code |
X-Claude-Code-Context-Compacted | 调用类型 | Claude Code |
X-Openai-Subagent | 调用类型 | OpenAI Codex CLI |
baggage: gen_ai.agent.name=… | 调用类型 | W3C Baggage (OpenTelemetry) |
X-Claude-Code-Prev-Tool-Durations | 工具耗时 | Claude Code |
Claude Code 默认会发送 agent ID。调用类型和工具耗时只有在它运行的环境里设置了 CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 才会发送。
会保存:这些 ID、agent 的类型、调用类型,以及工具名称和以毫秒计的耗时。不会保存:你给 agent 起的名字,以及工具读取或写入的任何内容。
给请求打标签
在 X-Synthorai-Metadata 里放一个值都是字符串的 JSON 对象,说明这个请求是做什么用的:哪个功能、哪个环境、你的哪个用户。这些标签会显示在日志的请求详情里,可以用来筛选日志、链路和会话(metadata.feature = checkout),在 Analytics 的 Breakdown 里按标签汇总花费,也会随导出一起发出。
curl https://synthorai.io/v1/chat/completions \
-H "Authorization: Bearer $SYNTHORAI_API_KEY" \
-H 'X-Synthorai-Metadata: {"feature":"checkout","env":"prod","_user":"u_123"}' \
-d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"Hi"}]}' 最多 10 个标签。键由 1 到 32 个 a-z 0-9 _ . - 组成;值是不超过 128 字节的字符串。_user 表示这个请求是为哪个终端用户发起的;其他以下划线开头的键是保留的。不符合规则的标签会被丢弃,请求照常执行:响应头 X-Synthorai-Metadata-Dropped 会列出被丢弃的键。
标签随请求一起保存,保存时间与请求日志相同;按标签筛选覆盖最近 90 天。不要在标签里放提示词内容、个人数据或密钥;看起来像凭据的值会存成 [redacted]。标签不会发给供应商。
会话亲和
缓存条目是绑在供应商侧的某一把 API key 上的。一条渠道可能配了多把 key,没有提示时网关会把请求分散到这些 key 上,于是一段会话的第二轮可能落到一把从没见过你前缀的 key 上,又付一次写入费用。
同一段会话的每次调用都带同一个值,不同会话用不同的值。服务端不存任何会话表、也没有过期:这个值经哈希决定选哪条渠道、哪把 key,因此同一个值永远解析到同一处。
在 /v1/chat/completions 与 /v1/responses 这两个 OpenAI 兼容端点上,可以完全不带这个头:如果请求体里有 prompt_cache_key,网关就用它作为亲和键。/v1/messages 与 Gemini 端点请用这个头。
亲和是 best-effort:它只决定在同等优先的候选里选谁。故障转移、限流冷却、重试以及你显式指定的路由偏好都优先于它。同一个模型在我们可能选中的每条渠道上,目录价都是一样的。
限制
- 每个头上限 128 字节。超出会被截断。含控制字符或可打印 ASCII 之外字符的值会被整体丢弃 —— 既不回显也不记日志。这些值会进入响应头和日志行,这条限制用于挡住头注入与日志伪造。
- 它们止步于网关。三个头都不会转发给模型供应商:出站请求只带鉴权、Content-Type 和各供应商专有头。
- 三个头都不是计费身份。计费、去重和对账一律以网关自己生成的
X-Request-ID为准,绝不使用由你控制的值。
出问题时
- 报上你发的
X-Trace-Id,或响应里的X-Request-ID—— 任一个都能让我们定位到那一次具体调用。 - 你的 trace ID 会显示在日志和链路追踪里对应的调用上,提工单前可以自己先查。
- 想确认亲和是否生效:用同一个
X-Session-Id把同一段长前缀连发几次,看日志里的缓存列。第一次是写入缓存,后面几次应当变成读取;换一个值,写入应当重新出现。
延伸阅读: Prompt Caching(提示词缓存) · 链路追踪与会话