← 返回文章列表
Agent 通信协议与 LLM 返回数据解析说明

Agent 通信协议与 LLM 返回数据解析说明


1. 范围和总体判断

Agent 系统通常不是由一种“Agent 协议”组成,而是几类协议叠加:

用户/业务系统
    ↓ 业务入口协议
Agent Runtime
    ├─ LLM API 协议:OpenAI、Anthropic、Gemini、Bedrock 等
    ├─ 工具协议:模型工具调用、MCP、REST、gRPC、GraphQL、数据库、Shell
    ├─ Agent 协作协议:A2A、Webhook、内部任务 API
    ├─ 异步协议:Kafka、AMQP、SQS、Pub/Sub、任务队列
    └─ 上下文传播:W3C Trace Context、OpenTelemetry、供应商 request ID

需要先区分:

类型 作用 是否是 Agent 专有协议
OpenAI Chat Completions、Responses Agent 与 LLM 交换输入和输出 LLM API 协议,不是完整 Agent 协议
MCP 模型/Agent 与工具、资源、Prompt Server 交互 Agent 生态专用协议
A2A Agent 与 Agent 发现、委派、任务和 Artifact 交互 Agent 协作协议
REST、gRPC、GraphQL Agent 调用企业 API、SaaS、数据库等 通用业务协议,承载 Agent 行动
W3C Trace Context 跨服务传播技术调用上下文 观测上下文协议,不是业务协议
OpenTelemetry 采集和传播 trace/span/log 等观测数据 观测数据规范,不是 Agent 业务协议
Kafka、AMQP、SQS 等 异步任务和消息传递 通用消息协议,承载 Agent 任务
LangChain、LangGraph、AutoGen 等框架 进程内编排和运行时回调 通常不是固定网络协议

因此,抓取网络包时不能只问“这是不是 Agent 协议”,而要按通信方向和承载内容判断:这是模型请求、模型响应、工具请求、工具响应、MCP 消息、Agent 委派、队列消息还是观测 Header。

2. Agent 协议全景

2.1 LLM API 协议

协议/接口族 主要方向 网络承载 核心对象
OpenAI Chat Completions Agent ↔ LLM HTTPS + JSON;流式 SSE messagestoolschoicestool_calls
OpenAI Responses Agent ↔ LLM HTTPS + JSON;流式 SSE inputinstructionsoutput[]function_call、response events
OpenAI Realtime Agent/客户端 ↔ 实时模型 WebSocket 或 WebRTC session.*conversation.*response.*、音频/文本/函数调用事件
Anthropic Messages Agent ↔ LLM HTTPS + JSON;流式 SSE messagescontent[]tool_usetool_result
Gemini GenerateContent Agent ↔ LLM HTTPS + JSON;流式 HTTP contentspartsfunctionCallfunctionResponse
Amazon Bedrock Invoke/Converse Agent ↔ LLM HTTPS + JSON;流式事件 model ID、messages/content、tool use、usage
OpenAI-compatible 私有 API Agent ↔ 私有模型网关 HTTPS + 厂商 JSON 由实际 Schema 决定,不能仅凭 URL 认定

这些接口的共同结构是:请求携带上下文和工具定义,响应携带文本、结构化输出、工具调用建议、用量、错误或流式增量。

2.2 MCP

MCP 的业务消息采用 JSON-RPC 2.0;传输方式可以是 Streamable HTTP、SSE 相关 HTTP 传输或本地 stdio。它解决的是 Agent/模型如何发现和调用工具、读取资源、获取 Prompt。

典型方法:

方法 作用
initialize 协商协议版本、客户端/服务端信息和 capabilities
tools/list 获取工具名称、描述和 inputSchema
tools/call 调用工具
resources/listresources/read 发现和读取资源
prompts/listprompts/get 发现和获取 Prompt
notification 无请求 ID 的通知消息

2.3 A2A 和 Agent 委派协议

A2A 及类似 Agent 协作协议的核心对象不是单次工具调用,而是:

  • Agent Card:对端 Agent 的能力、端点、认证和输入输出描述;
  • Task:一个可持续查询、更新、取消或完成的任务;
  • Message:任务中的消息和内容部分;
  • Artifact:子 Agent 生成的文件、结构化对象或其他结果;
  • Status/Update:任务进度、等待、失败、完成和回调。

它们通常通过 HTTPS/JSON、JSON-RPC、Webhook 或消息队列承载。

2.4 工具和企业 API 协议

模型工具调用本身只是模型响应中的一个对象。真正执行工具时,Agent 可以使用:

工具类型 常见网络/进程协议 请求对象 返回对象
REST HTTP/JSON method、path、query、Body status、Header、JSON/文件
GraphQL HTTP/JSON operation、query、variables dataerrors
gRPC HTTP/2 + Protobuf service/method、protobuf request protobuf response、trailers
数据库 数据库协议/Driver SQL/参数/事务 result set、row count、error
浏览器/RPA WebSocket/HTTP/自动化 SDK URL、页面/元素操作 页面对象、下载文件、网络响应
本地能力 函数调用、Shell、stdio 函数入参、argv/stdin return、stdout/stderr、exit code

2.5 异步和消息协议

Agent 的延迟任务、子任务和回调常使用 Kafka、AMQP、SQS、Pub/Sub、Redis Streams 或内部任务队列。网络或 Broker 侧看到的是:

  • publish/send:生产消息;
  • deliver/consume:投递和消费;
  • ack/nack:确认或失败;
  • retry:重试;
  • dead-letter:死信;
  • webhook/callback:异步结果回传。

队列消息的 Body 可能是 JSON、Avro、Protobuf 或二进制,必须以实际 Schema 解析。

2.6 RAG、Memory 和工作流不是单独的统一协议

RAG、Memory、工作流和 Agent 框架通常不是像 MCP 那样有一个统一的线协议名称。网络上看到的通常是它们使用的 HTTP/JSON、gRPC/Protobuf、数据库协议、Queue 或内部 RPC。

因此识别方式是“服务角色 + 接口 Schema”,而不是看到一个固定协议名:

服务角色 可能的网络格式 请求内容 返回内容
RAG/搜索 REST/JSON、gRPC、向量库协议 query、filter、top_k、index/namespace documents/chunks、score、rank、metadata
Memory REST/JSON、gRPC、数据库协议 read query、record、namespace、TTL records、content/summary、version、metadata
工作流/编排器 REST/JSON、gRPC、Queue task、run、step、dependency、schedule 状态、进度、结果、错误、重试
Agent 框架内部 进程内函数/Callback/日志 chain、node、tool、state return、exception、state update

RAG 和 Memory 可以作为 Agent 数据对象采集,但不能被误称为一种统一的“RAG 协议”或“Memory 协议”。

3. 各协议的网络格式

3.1 OpenAI Chat Completions 请求格式

典型请求:

POST /v1/chat/completions HTTP/2
Host: api.openai.com
Content-Type: application/json
Authorization: Bearer ...
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

{
  "model": "gpt-4.1",
  "messages": [
    {"role": "system", "content": "你是一个助手"},
    {"role": "user", "content": "查上海天气"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "查询天气",
        "parameters": {"type":"object","properties":{"city":{"type":"string"}}}
      }
    }
  ],
  "tool_choice": "auto",
  "stream": false
}

重点字段:

路径 含义
model 模型名称或别名
messages[] 对话上下文;每项有 role 和 content
tools[] 本次暴露给模型的工具定义
tools[].function.parameters 工具参数 JSON Schema
tool_choice 工具选择策略字段
stream 是否使用流式响应
response_format 结构化输出格式(如接口支持)

3.2 OpenAI Chat Completions 非流式响应格式

{
  "id": "chatcmpl_123",
  "object": "chat.completion",
  "created": 1710000000,
  "model": "gpt-4.1",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_01",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\"city\":\"Shanghai\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": {"prompt_tokens": 100,"completion_tokens": 20,"total_tokens": 120}
}

这是“模型要求调用工具”的响应,不是最终回答,也不是工具返回结果。此时 finish_reasontool_calls,Agent 应先读取 tool_calls[],再决定是否向工具发请求。

工具执行完成后,Agent 需要发起下一次模型请求,把工具结果作为 role=tool 消息传回:

{
  "role": "tool",
  "tool_call_id": "call_01",
  "content": "{\"city\":\"Shanghai\",\"temperature\":22}"
}

模型随后才可能返回最终文本:

{
  "id": "chatcmpl_124",
  "choices": [{
    "message": {"role":"assistant","content":"上海今天晴,气温 22℃。"},
    "finish_reason": "stop"
  }]
}

某些接口或模型可能在工具调用响应中同时给出一段说明性文本和 tool_calls,但这段文本仍属于“工具调用前的模型输出”,不能当作最终回答或工具结果。解析器只要发现 tool_calls[]finish_reason=tool_calls,就应把该响应归为“工具调用阶段”;最终回答要以工具结果回传后的下一次模型响应为准。

这张样例不是同时包含“最终文本”和“工具结果”的一条响应,而是需要按响应阶段拆开理解。

A. 模型提出工具调用时的响应

样例中的具体路径 样例值 含义
id chatcmpl_123 本次模型响应 ID
choices[0].message.role assistant 模型消息
choices[0].message.content null 本轮没有最终文本
choices[0].message.tool_calls[0].id call_01 模型工具调用 ID
choices[0].message.tool_calls[0].type function 调用类型
choices[0].message.tool_calls[0].function.name get_weather 模型要求调用的工具名
choices[0].message.tool_calls[0].function.arguments {"city":"Shanghai"} 模型生成的参数字符串,需再次 JSON 解析
choices[0].finish_reason tool_calls 本轮因为工具调用而结束
usage prompt_tokens/... 本次模型调用用量

这一条响应表示“模型提出工具调用”,不表示工具已执行,也不表示已经有最终回答。

B. 工具结果回传后的最终模型响应

这是另一条模型响应,不是上面那条响应的剩余字段:

{
  "id": "chatcmpl_124",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "上海今天晴,气温 22℃。"
    },
    "finish_reason": "stop"
  }]
}
样例中的具体路径 样例值 含义
id chatcmpl_124 第二次模型响应 ID
choices[0].message.role assistant 最终模型消息
choices[0].message.content 上海今天晴,气温 22℃。 最终文本
choices[0].message.tool_calls 通常不存在 本轮没有继续调用工具
choices[0].finish_reason stop 正常生成结束

C. 错误响应

如果 HTTP 请求失败或模型 API 返回错误,响应可能没有 choices[],而是:

{
  "error": {
    "message": "Invalid tool arguments",
    "type": "invalid_request_error",
    "param": "tools",
    "code": "invalid_value"
  }
}

这时读取 error.messageerror.typeerror.paramerror.code,不能继续读取 choices[].message.content

表格中的 [] 是“数组中任意一项”的通配写法;对你给出的具体样例,应写成 choices[0]tool_calls[0],这样才能与 JSON 逐层对应。

这里的 tool_calls[] 仍然只是模型返回内容;它不是 Agent 已经发送给工具的 HTTP 请求。

3.3 OpenAI Chat Completions 流式响应格式

响应 Header 通常为:

Content-Type: text/event-stream

Body 是 SSE 事件:

data: {"id":"chatcmpl_123","choices":[{"index":0,"delta":{"role":"assistant","content":"上海"}}]}

data: {"id":"chatcmpl_123","choices":[{"index":0,"delta":{"content":"今天晴"}}]}

data: {"id":"chatcmpl_123","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\"city\\":\\"Shanghai\\"}"}}]}}]}

data: [DONE]

解析要点:

  1. 每个 data: 是一个增量,不是完整响应;
  2. 以 response ID、choice index、tool call index 归并;
  3. delta.content 逐段拼文本;
  4. delta.tool_calls[].function.arguments 逐段拼参数;
  5. [DONE] 只表示流结束,usage 可能在最后一个事件或单独字段出现;
  6. 断连时只得到部分返回,不能把部分数据当完整响应。

3.4 OpenAI Responses 请求格式

{
  "model": "gpt-4.1",
  "instructions": "你是一个助手",
  "input": [
    {"role":"user","content":[{"type":"input_text","text":"查上海天气"}]}
  ],
  "tools": [
    {"type":"function","name":"get_weather","parameters":{"type":"object"}}
  ],
  "previous_response_id": "resp_previous",
  "stream": true
}

与 Chat 的主要差异是:

Chat Completions Responses
输入主要在 messages[] 输入在有类型的 input items
返回主要在 choices[].message 返回在有类型的 output[] items
工具 ID 常见为 tool_calls[].id 工具调用常见为 output[].call_id
工具结果常用 role=tool 工具结果常用 function_call_output item

3.5 OpenAI Responses 非流式响应格式

{
  "id": "resp_123",
  "object": "response",
  "status": "completed",
  "output": [
    {
      "type": "message",
      "id": "msg_01",
      "role": "assistant",
      "content": [{"type":"output_text","text":"上海今天晴"}]
    },
    {
      "type": "function_call",
      "id": "fc_01",
      "call_id": "call_01",
      "name": "get_weather",
      "arguments": "{\"city\":\"Shanghai\"}"
    }
  ],
  "usage": {"input_tokens":100,"output_tokens":20}
}

解析时必须遍历 output[],根据 type 分支:

output[].type 读取字段 解释
message role、content、content type、text 模型消息/文本
function_call call_id、name、arguments 模型提出工具调用
拒绝/错误类 item 原始 type 和对应字段 模型返回的拒绝或错误
其他 item 原始 type 和完整对象 扩展能力或新版本对象

3.6 OpenAI Responses 流式响应格式

Responses 的流式响应使用 SSE。与 Chat Completions 的 choices[].delta 不同,Responses 的每一条 data 是一个带 type 的事件对象:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

普通文本完整事件序列:

event: response.created
data: {"type":"response.created","response":{"id":"resp-stream-text-001","object":"response","status":"in_progress","model":"gpt-4.1","output":[]}}

event: response.output_item.added
data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg-stream-text-001","type":"message","status":"in_progress","role":"assistant","content":[]}}

event: response.content_part.added
data: {"type":"response.content_part.added","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"delta":"上海今天"}

event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"delta":"晴,气温 22℃。"}

event: response.output_text.done
data: {"type":"response.output_text.done","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"text":"上海今天晴,气温 22℃。"}

event: response.content_part.done
data: {"type":"response.content_part.done","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"part":{"type":"output_text","text":"上海今天晴,气温 22℃。","annotations":[]}}

event: response.output_item.done
data: {"type":"response.output_item.done","output_index":0,"item":{"id":"msg-stream-text-001","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"上海今天晴,气温 22℃。","annotations":[]}]}}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp-stream-text-001","object":"response","status":"completed","model":"gpt-4.1","output":[{"id":"msg-stream-text-001","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"上海今天晴,气温 22℃。","annotations":[]}]}]}}

函数调用完整事件序列:

event: response.created
data: {"type":"response.created","response":{"id":"resp-stream-tool-001","object":"response","status":"in_progress","model":"gpt-4.1","output":[]}}

event: response.output_item.added
data: {"type":"response.output_item.added","output_index":0,"item":{"id":"fc-stream-001","type":"function_call","status":"in_progress","call_id":"call-weather-stream-001","name":"get_weather","arguments":""}}

event: response.function_call_arguments.delta
data: {"type":"response.function_call_arguments.delta","item_id":"fc-stream-001","output_index":0,"delta":"{\"city\":\"Sh","sequence_number":1}

event: response.function_call_arguments.delta
data: {"type":"response.function_call_arguments.delta","item_id":"fc-stream-001","output_index":0,"delta":"anghai\"}","sequence_number":2}

event: response.function_call_arguments.done
data: {"type":"response.function_call_arguments.done","item_id":"fc-stream-001","output_index":0,"arguments":"{\"city\":\"Shanghai\"}","sequence_number":3}

event: response.output_item.done
data: {"type":"response.output_item.done","output_index":0,"item":{"id":"fc-stream-001","type":"function_call","status":"completed","call_id":"call-weather-stream-001","name":"get_weather","arguments":"{\"city\":\"Shanghai\"}"}}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp-stream-tool-001","object":"response","status":"completed","model":"gpt-4.1","output":[{"id":"fc-stream-001","type":"function_call","status":"completed","call_id":"call-weather-stream-001","name":"get_weather","arguments":"{\"city\":\"Shanghai\"}"}]}}

Responses 流式解析字段:

字段 作用
event SSE 事件名称
data.type 事件对象类型
response.id 整个 Responses 的 ID
item_id 当前输出 item 的 ID
output_index 当前 item 在 output[] 中的位置
content_index 当前 content part 在 message 中的位置
delta 文本或函数参数的增量片段
arguments 函数参数完整字符串,在 .done 事件中出现
response.completed 本次模型响应完成,不代表工具已执行
response.failed 本次模型响应失败

3.6.1 .added.delta.done 分别表示什么

这些后缀表示同一个输出对象在流式传输中的生命周期,不是不同的工具协议。

事件后缀/事件 含义 数据完整性 解析动作
response.created 一个新的 Response 开始生成 只有 Response 的初始信息 创建本次流的上下文,记录 response.id
.added 一个新的输出对象或内容对象刚被加入 只有对象类型、ID、位置等骨架字段,内容可能为空 创建 item/part,记录 item_idoutput_indexcontent_index
.delta 对已有对象追加一小段内容 不完整,可能只是文本或参数的一部分 按 ID 和索引按顺序拼接,不要单独当最终值
.done 当前对象已经完成 通常带该对象的完整值 使用完整值校验之前的 delta,并结束该 item/参数/文本段
response.output_item.done 一个完整 output item 完成 item 内部内容完整 将 message 或 function_call 视为完整输出项
response.completed 整个 Response 完成 本次模型响应完整 结束整个 Responses 流
response.failed 整个 Response 失败 可能只有部分输出 记录错误;已经收到的 delta 仍然有效但不完整

普通文本的事件层级通常是:

response.created
  → response.output_item.added
    → response.content_part.added
      → response.output_text.delta(可以有多条)
      → response.output_text.done
    → response.content_part.done
  → response.output_item.done
→ response.completed

函数调用的事件层级通常是:

response.created
  → response.output_item.added(type=function_call)
    → response.function_call_arguments.delta(可以有多条)
    → response.function_call_arguments.done
  → response.output_item.done
→ response.completed

例如:

function_call_arguments.delta:{"city":"Sh
function_call_arguments.delta:anghai"}
function_call_arguments.done:{"city":"Shanghai"}

这里前两条是参数片段,最后一条才是完整参数。response.completed 只表示 LLM 已经完成这次输出;如果输出类型是 function_call,它仍然不表示工具已经执行,Agent 还需要在流结束后调用工具。

3.7 Anthropic、Gemini、Bedrock 返回格式

接口 文本返回 工具调用返回 结束/用量
Anthropic content[]type=text content[]type=tool_use,含 name/input/id stop_reasonusage、SSE events
Gemini candidates[].content.parts[].text candidates[].content.parts[].functionCall finishReasonusageMetadata
Bedrock 模型族对应的 output/message/content tool use 对象或模型族字段 usage、stream event、错误字段
私有/OpenAI-compatible 由注册的 Schema 决定 由注册的 Schema 决定 原始字段 + 未识别字段

不能仅凭 URL 说某个响应是 OpenAI 格式;必须同时依据 Host/path、Content-Type、JSON 结构和已登记的 Schema。

常见请求/响应片段示例:

Anthropic Messages:

{
  "model": "claude-...",
  "max_tokens": 1024,
  "messages": [{"role":"user","content":"查上海天气"}],
  "tools": [{"name":"get_weather","input_schema":{"type":"object"}}]
}

响应中的文本和工具调用位于 content[]

{
  "id":"msg_123",
  "content":[
    {"type":"text","text":"我来查询"},
    {"type":"tool_use","id":"toolu_01","name":"get_weather","input":{"city":"Shanghai"}}
  ],
  "stop_reason":"tool_use",
  "usage":{"input_tokens":100,"output_tokens":20}
}

Gemini GenerateContent:

{
  "contents":[{"role":"user","parts":[{"text":"查上海天气"}]}],
  "tools":[{"function_declarations":[{"name":"get_weather","parameters":{"type":"OBJECT"}}]}]
}

响应的文本通常在 candidates[].content.parts[].text,函数调用在 candidates[].content.parts[].functionCall,调用结果回传使用 functionResponse

Bedrock 的 Invoke/Converse 请求和响应字段取决于具体模型家族;网络解析器先依据 Bedrock operation、model ID 和模型 Schema 确定字段,再读取 messages/content、tool use、usage 和流事件。不能把所有 Bedrock 模型强行映射成同一个 JSON。

3.8 OpenAI Realtime 的网络格式

Realtime 通常通过 WebSocket 事件或 WebRTC DataChannel/SDK 事件传输。WebSocket 中看到的是一个个带 type 的 JSON event,例如:

{"type":"session.update","session":{"modalities":["text","audio"]}}
{"type":"conversation.item.create","item":{"type":"message","role":"user","content":[{"type":"input_text","text":"你好"}]}}
{"type":"response.create","response":{"modalities":["text"]}}
{"type":"response.text.delta","delta":"你好"}
{"type":"response.done","response":{"status":"completed"}}

解析方式是按 WebSocket message 的 type 分支,并以 response/conversation/item ID 和事件顺序重组;WebRTC 媒体流正文通常无法通过被动网络包直接还原,需要 SDK 事件补充。

4. 完整端到端样例:Chat Completions 与 Responses

下面是两套完整的“模型调用—工具执行—最终模型回答”样例。请求和响应中的 ID、时间、域名和天气数据是演示值,不是某次真实生产抓包;字段结构、消息顺序和协议关系按公开接口格式组织。

4.1 Chat Completions:完整成功链路

4.1.1 第一次:Agent 请求模型

POST /v1/chat/completions HTTP/1.1
Host: llm-gateway.example.com
Content-Type: application/json
Authorization: Bearer REDACTED
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

{
  "model": "gpt-4.1",
  "messages": [
    {
      "role": "system",
      "content": "你是天气助手。"
    },
    {
      "role": "user",
      "content": "查上海天气"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "查询指定城市的天气",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "城市名称"
            }
          },
          "required": ["city"],
          "additionalProperties": false
        }
      }
    }
  ],
  "tool_choice": "auto",
  "parallel_tool_calls": false,
  "stream": false
}

这里 tool_choice=auto 只是告诉模型“可以自行决定是否调用工具”。它不是工具调用结果。

4.1.2 第一次:模型返回工具调用要求

HTTP/1.1 200 OK
Content-Type: application/json
x-request-id: req-model-001

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1710000000,
  "model": "gpt-4.1",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call-weather-001",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\"city\":\"Shanghai\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": {
    "prompt_tokens": 156,
    "completion_tokens": 18,
    "total_tokens": 174
  }
}

这一条只表达:

LLM 要求 Agent 调用 get_weather,参数为 {"city":"Shanghai"}。

它不证明工具已执行,也不包含工具实际返回。

4.1.3 Agent 实际请求工具

这已经不是 OpenAI 请求,而是 Agent 到实际工具的另一条通信:

POST /v1/weather/query HTTP/1.1
Host: weather.example.com
Content-Type: application/json
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-b7ad6b7169203331-01

{
  "city": "Shanghai"
}

如果 Agent 没有把 call-weather-001 放进工具 Header 或 Body,网络上就不一定能直接看到它;这时需要通过 Runtime 调用记录把这条工具请求和模型的 tool_calls[0].id 联系起来。

4.1.4 工具返回实际结果

HTTP/1.1 200 OK
Content-Type: application/json
x-request-id: weather-req-001

{
  "city": "Shanghai",
  "temperature_c": 22,
  "weather": "晴",
  "observed_at": "2026-09-02T09:30:00+08:00"
}

这是工具服务返回的数据,不是 LLM 返回的数据。

4.1.5 第二次:Agent 把完整上下文和工具结果发送给模型

Chat Completions 通常需要把对话历史和本轮工具调用过程重新放进 messages[]

POST /v1/chat/completions HTTP/1.1
Host: llm-gateway.example.com
Content-Type: application/json
Authorization: Bearer REDACTED
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-9f2b7a11d8c4e601-01

{
  "model": "gpt-4.1",
  "messages": [
    {
      "role": "system",
      "content": "你是天气助手。"
    },
    {
      "role": "user",
      "content": "查上海天气"
    },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call-weather-001",
          "type": "function",
          "function": {
            "name": "get_weather",
            "arguments": "{\"city\":\"Shanghai\"}"
          }
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call-weather-001",
      "content": "{\"city\":\"Shanghai\",\"temperature_c\":22,\"weather\":\"晴\",\"observed_at\":\"2026-09-02T09:30:00+08:00\"}"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "查询指定城市的天气",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {"type": "string"}
          },
          "required": ["city"],
          "additionalProperties": false
        }
      }
    }
  ],
  "tool_choice": "auto",
  "parallel_tool_calls": false,
  "stream": false
}

这次请求与第一次的关键差异是多了两条历史消息:

assistant.tool_calls:模型之前提出的调用
tool.tool_call_id:对应调用的工具结果

role=tool 不是工具服务自动发给 LLM 的网络响应,而是 Agent 把工具响应重新包装后发给 LLM 的消息。

4.1.6 第二次:模型返回最终文本

HTTP/1.1 200 OK
Content-Type: application/json
x-request-id: req-model-002

{
  "id": "chatcmpl-124",
  "object": "chat.completion",
  "created": 1710000002,
  "model": "gpt-4.1",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "上海今天晴,气温 22℃。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 248,
    "completion_tokens": 12,
    "total_tokens": 260
  }
}

这条响应才是最终模型回答。完整链路中有三次关键网络交互:

交互 方向 主要内容
模型调用 1 Agent → LLM / LLM → Agent 模型请求;模型提出工具调用
工具调用 Agent → Tool / Tool → Agent 真实工具请求;真实工具结果
模型调用 2 Agent → LLM / LLM → Agent 工具结果消息;最终模型文本

4.2 Responses:完整成功链路

Responses 的关键差异是:可以使用 previous_response_id 关联前一条模型响应,工具结果作为 function_call_output item 传回。

4.2.1 第一次:Agent 请求 Responses

POST /v1/responses HTTP/1.1
Host: llm-gateway.example.com
Content-Type: application/json
Authorization: Bearer REDACTED
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

{
  "model": "gpt-4.1",
  "instructions": "你是天气助手。",
  "input": [
    {
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "查上海天气"
        }
      ]
    }
  ],
  "tools": [
    {
      "type": "function",
      "name": "get_weather",
      "description": "查询指定城市的天气",
      "parameters": {
        "type": "object",
        "properties": {
          "city": {"type": "string"}
        },
        "required": ["city"],
        "additionalProperties": false
      }
    }
  ],
  "tool_choice": "auto",
  "parallel_tool_calls": false,
  "stream": false
}

4.2.2 第一次:Responses 返回 function call

HTTP/1.1 200 OK
Content-Type: application/json
x-request-id: req-responses-001

{
  "id": "resp_123",
  "object": "response",
  "created_at": 1710000100,
  "status": "completed",
  "output": [
    {
      "type": "function_call",
      "id": "fc_001",
      "call_id": "call-weather-002",
      "name": "get_weather",
      "arguments": "{\"city\":\"Shanghai\"}"
    }
  ],
  "usage": {
    "input_tokens": 156,
    "output_tokens": 18,
    "total_tokens": 174
  }
}

这里的关键字段是:

output[0].type       = function_call
output[0].call_id    = call-weather-002
output[0].name       = get_weather
output[0].arguments  = {"city":"Shanghai"}

4.2.3 Agent 实际请求天气工具

这一步是独立于 Responses API 的工具通信:

POST /v1/weather/query HTTP/1.1
Host: weather.example.com
Content-Type: application/json
Accept: application/json
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-b7ad6b7169203331-01
x-agent-run-id: run-weather-001
x-model-call-id: call-weather-002

{
  "city": "Shanghai"
}

x-agent-run-idx-model-call-id 是本示例为便于关联而使用的企业自定义 Header,不是 REST、Responses 或 MCP 的必选字段;真实抓包中可能不存在。网络采集不能因为工具请求没有 call_id Header,就推断模型没有提出工具调用。

4.2.4 天气工具返回完整结果

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
x-request-id: weather-req-002

{
  "city": "Shanghai",
  "temperature_c": 22,
  "weather": "晴",
  "observed_at": "2026-09-02T09:30:00+08:00"
}

4.2.5 第二次:Agent 使用 function_call_output 回传工具结果

POST /v1/responses HTTP/1.1
Host: llm-gateway.example.com
Content-Type: application/json
Authorization: Bearer REDACTED
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-9f2b7a11d8c4e601-01

{
  "model": "gpt-4.1",
  "previous_response_id": "resp_123",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call-weather-002",
      "output": "{\"city\":\"Shanghai\",\"temperature_c\":22,\"weather\":\"晴\"}"
    }
  ],
  "stream": false
}

这里 previous_response_id=resp_123 表示本次请求延续上一条 Responses 响应;call_id 表示这个工具结果对应上一条响应中的函数调用。

4.2.6 第二次:Responses 返回最终文本

HTTP/1.1 200 OK
Content-Type: application/json
x-request-id: req-responses-002

{
  "id": "resp_124",
  "object": "response",
  "created_at": 1710000102,
  "status": "completed",
  "output": [
    {
      "type": "message",
      "id": "msg_002",
      "status": "completed",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "上海今天晴,气温 22℃。"
        }
      ]
    }
  ],
  "usage": {
    "input_tokens": 210,
    "output_tokens": 12,
    "total_tokens": 222
  }
}

4.3 Chat Completions:完整流式工具调用事件

流式时没有一个完整的 chat.completion response,而是一组 SSE 事件。下面是一组完整的“模型提出工具调用”的事件示例:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1710000200,"model":"gpt-4.1","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call-stream-001","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}

data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1710000200,"model":"gpt-4.1","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":\"Shanghai\"}"}}]},"finish_reason":null}]}

data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1710000200,"model":"gpt-4.1","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":156,"completion_tokens":18,"total_tokens":174}}

data: [DONE]

解析器必须按以下键重组:

response id = chatcmpl-stream-001
choice index = 0
tool call index = 0
tool call id = call-stream-001
function name = get_weather
arguments fragments = "" + "{\"city\":\"Shanghai\"}"
finish_reason = tool_calls

4.4 样例中每条数据对应的网络观察点

样例数据 观察位置 是否属于 LLM API
第一次模型请求 Agent → 模型 HTTP request Body
模型 tool_calls/function_call 模型 HTTP response Body/SSE
实际工具请求 Agent → 工具 HTTP/gRPC/MCP 网络流 否,是工具协议
实际工具响应 工具 → Agent HTTP/gRPC/MCP response 否,是工具协议
role=tool Agent → 模型 的第二次 Chat request Body 是,模型上下文消息
function_call_output Agent → Responses 的第二次 request Body 是,Responses 输入 item
最终文本 第二次模型 response Body/SSE

5. MCP、A2A 和工具格式

5.1 MCP JSON-RPC 请求与响应

请求:

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {"city":"Shanghai"}
  }
}

响应:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [{"type":"text","text":"Shanghai: 22°C"}],
    "isError": false
  }
}

解析字段:

字段 含义
jsonrpc JSON-RPC 版本
id MCP 当前会话中 request/response 配对键
method MCP 请求方法
params.name 要调用的工具
params.arguments 工具参数
result.content MCP 返回的内容对象
result.isError MCP 结果错误字段
error JSON-RPC 错误对象

5.2 A2A/Agent 委派格式

A2A 和内部 Agent 委派接口的字段会随版本和实现变化,但网络上通常出现:

{
  "task_id": "task_123",
  "from_agent": "planner",
  "to_agent": "weather-agent",
  "message": {"role":"user","parts":[{"type":"text","text":"查上海天气"}]},
  "artifacts": []
}

返回可能是:

{
  "task_id": "task_123",
  "state": "completed",
  "message": {"role":"agent","parts":[{"type":"text","text":"上海 22°C"}]},
  "artifacts": []
}

解析重点是 task/message/artifact/status/进度/错误,不应把一次 HTTP response 简化成完整 Agent 任务。

5.3 REST、gRPC、GraphQL 工具返回

这些协议没有统一的 Agent 返回格式:

  • REST:先读 HTTP status/Header,再按 OpenAPI 或实际 JSON 读取;
  • gRPC:先按 HTTP/2 stream 和 protobuf message 重组,需 .proto 解码字段;
  • GraphQL:读取 dataerrors,同时保留 operation name、query、variables;
  • 数据库:从 Driver/Proxy 读取 result set、row count 和 error code;
  • Shell/本地函数:从 Runtime Hook 或端点读取 return、stdout、stderr、exit code。

工具名称可能只出现在模型响应或 Runtime,实际 HTTP path/方法只出现在工具网络请求,两者要分别采集。

6. Trace ID、request ID 和业务 ID 的区别

6.1 W3C Trace Context:它是什么,不是什么

W3C Trace Context 是一个跨服务传递调用上下文的标准。它解决的问题是:请求从 Agent 进入网关、再进入 LLM,再进入工具服务时,各服务如何知道这些调用属于同一条技术调用链,以及当前请求是由哪一个上游调用产生的。

它不是:

  • Agent 与 LLM 的业务协议;
  • Chat Completions、Responses 或 MCP 的字段规范;
  • 对话历史、session_idtask_id 或工具调用 ID;
  • 日志或链路存储系统。OpenTelemetry 可以使用它,但二者不是同一个东西。

W3C Trace Context 的核心传播载体是 traceparent,可选扩展是 tracestatebaggage 属于另一个 W3C Baggage 规范,虽然经常与 Trace Context 一起出现,但不能把它当作 traceparent 的组成部分。

6.1.1 traceparent 的完整格式

HTTP 请求中最典型的形式如下:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

字段按下面的顺序排列,使用单个连字符分隔:

version - trace-id - parent-id - trace-flags
字段 固定长度 示例 含义
version 2 个十六进制字符 00 Trace Context 格式版本;目前公开实现最常见的是 00
trace-id 32 个十六进制字符(16 字节) 4bf92f3577b34da6a3ce929d0e0e4736 整条分布式调用链的 ID;同一条链路通常保持不变
parent-id 16 个十六进制字符(8 字节) 00f067aa0ba902b7 当前发送方认为的“直接父 Span”的 ID;每一跳可能变化
trace-flags 2 个十六进制字符(1 字节) 01 标志位;最低位是 sampled 位,01 通常表示该链路应被记录/采样,00 表示未设置该位

因此,00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 的含义不是“一个 ID”,而是:

格式版本 = 00
整条链路 = 4bf92f3577b34da6a3ce929d0e0e4736
直接父 Span = 00f067aa0ba902b7
采样标志 = 01

抓包解析时至少执行以下校验:

  1. - 分成 4 段,不能多段或少段;
  2. versiontrace-idparent-idtrace-flags 都只能包含十六进制字符;
  3. trace-id 不能是全 0,parent-id 不能是全 0;
  4. HTTP 头名称大小写不敏感,但值应按规范格式保存;
  5. 不要把其他自定义头(例如 x-trace-id)按 W3C 格式强行解释。

6.1.2 一跳一 Span:为什么 parent-id 会变化

W3C 的关键关系是:trace-id 标识整条链,span-id 标识链中的一段具体操作。traceparent 没有单独叫 span-id 的字段;它把上游 Span 的 ID放在当前请求的 parent-id 中。

例如一条 Agent 调用链如下:

Agent             Gateway              LLM 服务             Tool 服务
  |                  |                    |                    |
  | trace-id=T,      |                    |                    |
  | parent=A1        |                    |                    |
  |----------------->|                    |                    |
  |                  | 创建 Gateway span G1                     |
  |                  | trace-id=T, parent=G1                     |
  |                  |------------------->|                    |
  |                  |                    | 创建 LLM span L1   |
  |                  |                    | trace-id=T,parent=L1
  |                  |                    |------------------->|

对应的请求头可以是:

# Agent -> Gateway
traceparent: 00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1111111111111111-01

# Gateway -> LLM
traceparent: 00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-2222222222222222-01

# LLM -> Tool(前提是 LLM/Agent 实际传播了 W3C 上下文)
traceparent: 00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-3333333333333333-01

这里:

  • 三个请求的 trace-id 都是 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,表示属于同一条技术调用链;
  • 1111...2222...3333... 是不同 Span 的 ID;
  • 下游请求的 parent-id 应填写上游刚创建的 Span ID;
  • 不能看到 trace-id 不变,就认为每个请求是同一个 Span。

如果某个纯转发代理没有创建自己的观测 Span,它可能原样转发收到的 traceparent;如果它被 OpenTelemetry 等框架插桩,通常会创建新的 Span 并发送新的 parent-id。抓包时应以实际报文为准,不能仅凭拓扑推测。

6.1.3 tracestate 是什么

tracestate 用于承载厂商或观测系统自己的附加链路状态,例如:

tracestate: acmevendor=opaque-value,othervendor=another-value

它是逗号分隔的键值成员,值的具体语义由对应厂商定义。采集器应原样保存,不应把其中的值当作 Agent 业务字段、用户身份或权限凭证,也不应仅凭 tracestate 推断调用关系。判断调用链的主键仍然是 traceparent 中的 trace-id,判断直接父子关系则需要比较各跳的 parent-id 与已观测的 Span。

6.1.4 不同承载协议中的位置

W3C 只规定传播字段的语义,不规定 Agent 必须使用哪一种网络传输协议。常见位置如下:

承载方式 观察位置 采集要点
HTTP/1.1、HTTP/2、HTTP/3 请求 Header,通常是 traceparent/tracestate 请求和响应属于同一 HTTP transaction;响应不要求回显 traceparent
gRPC HTTP/2 Metadata(本质上仍是 Header) 同时记录 RPC 方法、stream 和 Metadata
消息队列 消息 Header、Attributes 或 Properties 每条消息可能携带一个新的下游父上下文;不要只看消息体
WebSocket 握手 HTTP Header W3C 上下文通常只在握手传播;后续 message 是否带 trace 字段由应用自定义
SSE 建立 SSE 连接的 HTTP 请求 Header data: 中的每个事件不自动拥有 W3C traceparent

6.1.5 抓包时应如何记录

对每个出站请求或消息,至少记录:

transport = HTTP / gRPC / queue / WebSocket / SSE
direction = inbound / outbound
traceparent.raw = 原始值
traceparent.version
traceparent.trace_id
traceparent.parent_id
traceparent.trace_flags
tracestate.raw(如果存在)
host、path、method、RPC method、queue/topic、HTTP/2 stream 等承载信息

关联规则是:先用同一 HTTP transaction、HTTP/2 stream、RPC 调用或消息投递关系配对请求与响应;再用 trace_id 把不同服务的调用放入同一条链;最后用各跳的 parent_id 和观测到的 Span ID 建立父子边。不要要求 LLM 响应必须回传同一个 traceparent,也不要因为响应没有 traceparent 就认定链路断了。

6.2 trace_id 可能是什么

抓包中看到 trace_id,先看它出现在哪里:

出现位置 可能含义 是否一定是 W3C Trace ID
traceparent Header 的中间 32 位 W3C trace-id 是,格式可按 W3C 校验
自定义 Header:trace-idx-trace-id 应用/网关自定义追踪 ID 不一定
JSON Body:trace_id 业务请求、事件或供应商扩展字段 不一定
SSE data 中的 trace_id 流事件自己的关联字段 不一定
日志字段 trace_id OpenTelemetry/日志系统导出的字段 通常与 Trace 相关,但需看系统定义
x-request-idrequest_id 一次 HTTP/供应商处理请求 ID 通常不是 trace-id

不能看到字段名 trace_id 就直接认定它是 W3C Trace Context。必须同时记录 Header 名称、JSON 路径、格式长度、产生方和它是否与 traceparent 的 32 位 trace-id 相同。

6.3 模型是否原路返回 Trace ID

通常不是“Agent 发什么 Trace,模型就原样返回什么 Trace”:

  1. Agent/网关可以在模型请求 Header 中发送 traceparent
  2. 模型供应商是否继续传播该 Trace,取决于供应商内部实现;
  3. HTTP 响应不要求回显请求的 traceparent
  4. 响应可能返回供应商自己的 x-request-id、response ID 或其他 Header;
  5. 网关应通过同一个 HTTP transaction/HTTP2 stream 配对请求和响应,再用 response ID、SDK 记录和 Trace 建立关联。

因此,模型是“无状态”还是服务端保存对话,与 Trace 是否原路返回是两件事。

6.4 Trace ID 与 Agent 业务 ID

ID 解决的问题 典型位置
W3C trace-id 跨服务技术调用链 traceparent Header/Metadata
span ID Trace 内的一段具体调用 不作为独立字段出现在 traceparent;下游请求把它放进 parent-id
provider request ID 供应商处理了哪次请求 响应 Header/Body
response ID 哪个模型响应/流 模型响应 Body/SSE
session_id/conversation 哪段对话 Header/Body/Runtime
task_id 哪个业务任务 Body/Queue/A2A/Runtime
run_id/step/attempt 哪次 Agent 执行和重试 Runtime/编排器/Queue
tool call ID / call_id 哪个模型工具调用 模型响应及后续模型请求
MCP JSON-RPC id 哪个 MCP request/response JSON-RPC Body

这些 ID 在抓包中可以并存,但不能互相替代。

6.5 Session、Turn、Task、Run、Trace 的层次关系

这五个概念经常同时出现在 Agent 系统中,但并不是同一种协议字段,也不属于同一层:

概念 所属层次 解决的问题 典型生命周期 是否由 W3C 规定
Session 会话层 哪个用户、客户端或对话上下文 从会话建立到结束,可包含多轮交互
Turn 交互层 用户的一次输入以及 Agent 对这次输入的处理和回复 通常从一次用户输入开始,到该轮回复/行动完成
Task 业务/任务层 Agent 要完成的目标是什么 可以跨多个 Turn,支持排队、暂停、恢复和完成 否;A2A 等具体协议可以定义自己的 Task 对象
Run 执行层 这次具体执行尝试是哪个 一个 Task 或 Turn 可以有多次 Run;重试通常产生新的 Run 否;由 Agent 编排器或框架定义
Trace 技术观测层 这批网络调用属于哪条分布式技术调用链 从根调用开始,到所有 Span 结束 W3C 规定 Trace Context 的传播格式,但不规定 Agent 业务语义

一个常见但不是强制的关系可以表示为:

Session(会话)
 ├─ Turn 1(用户第一轮交互)
 │   └─ Task A(本轮要完成的业务目标)
 │       ├─ Run A-1(第一次执行,失败)
 │       └─ Run A-2(重试执行,成功)
 │           └─ Trace T-1(该次执行产生的技术调用链)
 └─ Turn 2(用户第二轮交互)

这只是常见建模,不是协议强制的树形结构。实际系统可能出现:

  • 一个 Turn 拆成多个 Task;
  • 一个 Task 跨越多个 Turn;
  • 一个 Run 包含多次 LLM 请求、工具调用和消息投递;
  • 一个 Run 因异步队列、跨进程或跨厂商边界而产生多个 Trace;
  • 一个 Trace 覆盖 Agent、网关、LLM、工具等多个服务,但不等于一个业务 Task。

6.5.1 这些 ID 在网络上通常出现在哪里

session_idturn_idtask_idrun_id 没有统一的 W3C Header 名称,可能由 Agent 自定义为 Header、JSON 字段、队列属性或日志字段,例如:

POST /v1/responses HTTP/1.1
traceparent: 00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-2222222222222222-01
x-session-id: sess-1001
x-turn-id: turn-07
x-task-id: task-weather-01
x-run-id: run-weather-02

也可能出现在 JSON 中:

{
  "session_id": "sess-1001",
  "turn_id": "turn-07",
  "task_id": "task-weather-01",
  "run_id": "run-weather-02",
  "input": "查上海天气"
}

上面的 Header 名称和 JSON 字段只是应用约定,不是 OpenAI、W3C 或 MCP 的通用强制字段。采集器必须记录字段的实际位置和原始名称,不能因为字段叫 run_idtask_id 就推断它一定具有统一语义。

6.5.2 与 LLM 响应 ID 的区别

一次 Run 可能包含多次模型请求,因此:

  • run_id 标识 Agent 的一次执行过程;
  • LLM 响应中的 id(例如 resp_123)标识某一次模型响应;
  • 工具调用中的 call_id/tool_call_id 标识某一次工具调用;
  • W3C trace-id 标识技术调用链;
  • session_id 标识会话上下文。

它们可以通过 Agent 自己的日志或网关字段关联,但 LLM API 不会自动把这些概念全部返回,也不能用一个 ID 替代另一个 ID。

7. 针对 LLM 返回数据的完整解析方法

7.1 第一步:判断是同步响应还是流式响应

判断依据 同步 JSON 流式
Content-Type application/json text/event-stream 或 WebSocket
Body 形态 一个完整 JSON 对象 多个 SSE event、WebSocket message 或流事件
结束判断 HTTP Body 结束 completed/done、关闭帧或连接异常
解析方法 直接 JSON parse 先事件重组,再解析每个 data/message

7.2 第二步:识别供应商和接口家族

不要仅按域名判断。结合:

  • Host 和 path;
  • 请求/响应 Content-Type;
  • 顶层 JSON 字段,例如 choicesoutputcontentcandidates
  • SSE event 名称;
  • 模型网关路由和已登记 API Schema。

识别失败时,仍然可以解析 HTTP status/Header/Body,但不应把未知 JSON 强行标为 Chat 或 Responses。

7.3 第三步:读取通用响应信息

无论哪种 LLM 接口,先尝试读取:

通用信息 可能位置
provider request ID 响应 Header,例如 x-request-id
response ID Body 顶层 id、SSE data、事件对象
模型名称 Body model、Header 或请求上下文
成功/结束状态 statusfinish_reasonstop_reasonfinishReason
用量 usageusageMetadata、流结束事件
错误 HTTP status、顶层 error、流失败事件
不完整原因 incomplete_details、断流、取消、超时、SDK 异常

7.4 第四步:识别返回内容类型

LLM 返回不只有文本,需要按类型分支:

返回类型 Chat 识别 Responses 识别 解析内容
文本 choices[].message.content output[] 文本 item/content 文本片段、顺序
工具调用 message.tool_calls[] output[].type=function_call 工具名、参数、call ID
结构化输出 message.content 中 JSON 或指定格式 output item 中结构化内容 JSON Schema/格式、原文
拒绝 finish/内容或供应商专用字段 拒绝类 output item/错误字段 拒绝文本、原因字段
图像/音频/文件 content 数组或多模态 item input/output item 内容类型 MIME、URI、二进制/引用
用量 usage usage 输入/输出/总 token 等
错误 HTTP error/顶层 error HTTP error/error/failed event code、message、类型、HTTP 状态

7.5 第五步:处理工具调用参数和结构化输出

模型返回的工具参数经常是字符串:

"arguments": "{\"city\":\"Shanghai\"}"

解析器需要二次 JSON parse,并同时保留:

  • 原始参数字符串;
  • 二次解析后的 JSON 对象;
  • 解析失败位置;
  • tool call ID/call ID;
  • 模型响应 ID、choice/output index。

结构化输出也必须区分“Body 是 JSON”与“模型生成的内容是 JSON”:前者是 HTTP 编码,后者是模型内容字段中的字符串或对象,不能混为同一层。

7.6 第六步:处理流式响应和不完整响应

对每个流式响应,至少保留:

  • response ID;
  • 事件类型;
  • SSE event ID 或 WebSocket message 序号;
  • choice/output item/tool index;
  • delta 原文;
  • 完成、失败、取消、断连状态;
  • 已重组的文本/参数和重组是否完成。

没有完成事件的流不能当作完整成功响应;只能说明“已经收到这些增量”。

7.7 Responses 流式返回:完整事件序列

Responses 流式返回和 Chat Completions 流式返回不是同一种字段结构。两者都可能使用 SSE,但:

项目 Chat Completions 流式 Responses 流式
SSE data 的主要结构 choices[].delta 一个带 type 的 Response event
文本增量 choices[].delta.content response.output_text.deltadelta
工具参数增量 choices[].delta.tool_calls[] response.function_call_arguments.deltadelta
完成标志 finish_reason[DONE] response.completed / response.failed
输出定位 choice index、tool call index item_idoutput_indexcontent_index

7.7.1 Responses 流式返回普通文本

下面是一条“模型直接返回文本”的完整最小事件序列。每个 event: 和后面的 data: 属于同一条 SSE 事件,事件之间由空行分隔。

event: response.created
data: {"type":"response.created","response":{"id":"resp-stream-text-001","object":"response","status":"in_progress","model":"gpt-4.1","output":[]}}

event: response.output_item.added
data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg-stream-text-001","type":"message","status":"in_progress","role":"assistant","content":[]}}

event: response.content_part.added
data: {"type":"response.content_part.added","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"delta":"上海今天"}

event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"delta":"晴,气温 22℃。"}

event: response.output_text.done
data: {"type":"response.output_text.done","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"text":"上海今天晴,气温 22℃。"}

event: response.content_part.done
data: {"type":"response.content_part.done","item_id":"msg-stream-text-001","output_index":0,"content_index":0,"part":{"type":"output_text","text":"上海今天晴,气温 22℃。","annotations":[]}}

event: response.output_item.done
data: {"type":"response.output_item.done","output_index":0,"item":{"id":"msg-stream-text-001","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"上海今天晴,气温 22℃。","annotations":[]}]}}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp-stream-text-001","object":"response","status":"completed","model":"gpt-4.1","output":[{"id":"msg-stream-text-001","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"上海今天晴,气温 22℃。","annotations":[]}]}]}}

解析顺序:

  1. response.created:创建一个新的 Response,取得 response.id
  2. response.output_item.added:出现一个输出项,取得 item.idoutput_index
  3. response.content_part.added:该消息中出现一个内容段,取得 content_index
  4. response.output_text.delta:按 item_id + output_index + content_index 拼接文本;
  5. response.output_text.done:得到该文本段的完整内容;
  6. response.output_item.done:得到完整的 message item;
  7. response.completed:整个 Response 完成。

7.7.2 Responses 流式返回函数调用

下面是一条“模型要求调用天气函数”的完整最小事件序列:

event: response.created
data: {"type":"response.created","response":{"id":"resp-stream-tool-001","object":"response","status":"in_progress","model":"gpt-4.1","output":[]}}

event: response.output_item.added
data: {"type":"response.output_item.added","output_index":0,"item":{"id":"fc-stream-001","type":"function_call","status":"in_progress","call_id":"call-weather-stream-001","name":"get_weather","arguments":""}}

event: response.function_call_arguments.delta
data: {"type":"response.function_call_arguments.delta","item_id":"fc-stream-001","output_index":0,"delta":"{\"city\":\"Sh"}","sequence_number":1}

event: response.function_call_arguments.delta
data: {"type":"response.function_call_arguments.delta","item_id":"fc-stream-001","output_index":0,"delta":"anghai\"}","sequence_number":2}

event: response.function_call_arguments.done
data: {"type":"response.function_call_arguments.done","item_id":"fc-stream-001","output_index":0,"arguments":"{\"city\":\"Shanghai\"}","sequence_number":3}

event: response.output_item.done
data: {"type":"response.output_item.done","output_index":0,"item":{"id":"fc-stream-001","type":"function_call","status":"completed","call_id":"call-weather-stream-001","name":"get_weather","arguments":"{\"city\":\"Shanghai\"}"}}

event: response.completed
data: {"type":"response.completed","response":{"id":"resp-stream-tool-001","object":"response","status":"completed","model":"gpt-4.1","output":[{"id":"fc-stream-001","type":"function_call","status":"completed","call_id":"call-weather-stream-001","name":"get_weather","arguments":"{\"city\":\"Shanghai\"}"}]}}

这一串事件表示:

模型流式输出了一个 function_call;
参数分两段返回;
done 事件给出了完整参数;
Response completed 表示这一次模型响应完成。

但这里仍然没有工具实际返回。收到 response.completed 后,Agent 才根据 call_idarguments 执行工具,再发起下一次 Responses 请求,并把结果作为 function_call_output 传回。

7.7.3 Responses 流式失败和中断

如果模型服务返回失败事件,可能是:

event: response.failed
data: {"type":"response.failed","response":{"id":"resp-stream-failed-001","object":"response","status":"failed","error":{"code":"server_error","message":"The model service failed"}}}

如果 TCP/TLS/SSE 连接直接断开而没有 response.completedresponse.failed,解析器只能保留已经收到的事件,并将这次流标记为“未收到终止事件”;不能把最后一条 delta 当成完整模型返回。

8. 抓到 Codex 网络包时如何判断 trace_id

对 Codex 或其他客户端抓包,建议按以下顺序判断:

  1. 看它是 HTTP Header、JSON Body、SSE data 还是 WebSocket message;
  2. 如果在 traceparent 中,按 W3C 格式读取中间 32 位;
  3. 如果字段叫 trace_id 但在 JSON Body,先作为应用/服务字段,不直接当 W3C Trace;
  4. 与同一请求的 x-request-id、response ID、HTTP2 stream ID、SSE event ID 对比;
  5. 看后续模型请求、工具请求或事件是否复用同一个值;
  6. 对外部供应商响应没有回显 traceparent 的情况,以 HTTP transaction/stream 配对请求响应,不以“响应缺少 trace_id”判断请求失败。

只有看到具体包的 Header、URL、Body 或 SSE 事件,才能进一步判断 Codex 里的 trace_id 是 W3C trace-id、应用自定义 ID、事件 ID,还是服务端内部字段。仅凭字段名称不能下结论。

9. 研发实现的最小解析流程

抓包/网关明文
  → 识别 HTTP/HTTP2/HTTP3/SSE/WebSocket/gRPC/Queue
  → 配对请求与响应,或切分流事件/消息
  → 读取 Header 中的 trace/request ID
  → 解压并解码 Body
  → 识别 LLM/MCP/A2A/工具/检索/Memory Schema
  → 解析文本、结构化对象、工具调用、错误、usage、流事件
  → 用 response ID、call ID、RPC ID、message ID、Trace 等原始字段建立对应关系

解析器遇到 TLS 不可见、未知 Schema、没有 Protobuf 定义、SSE 中断或 JSON 不完整时,应停留在能够确认的协议层,不要猜测更高层业务含义。

10. 参考规范

  • OpenAI API Reference:Chat Completions、Responses、Realtime;
  • Anthropic Messages API;
  • Google Gemini API;Amazon Bedrock Runtime API;
  • Model Context Protocol:JSON-RPC、生命周期、工具、资源和 Prompt;
  • Agent2Agent(A2A)协议:Agent Card、Task、Message、Artifact;
  • W3C Trace Context;
  • OpenTelemetry Trace Context 与语义约定;
  • HTTP/1.1、HTTP/2、HTTP/3、SSE、WebSocket、gRPC、JSON-RPC。

OpenAI 官方接口参考: