跳转到内容

OpenAI Responses 完整交互示例

返回:OpenAI Responses 协议

下面用同一个跑步建议场景展示 Responses 的 item 化交互。

用户问:“北京今天适合跑步吗?如果空气质量不好,请查一下官方建议。”

Responses 同时支持用户端工具和 hosted/server 工具。这个例子里:

  • function get_weather 是用户端工具,模型返回 function_call,客户端执行后下一轮用 function_call_output 回填。
  • web_search 是 hosted/server 工具,上游服务自己执行,流里出现 web search 状态事件和对应 WebSearchToolCall item。
Client
|
| 1. input + function tool + web_search tool + stream=true
v
proxai
|
| 2. 归一化 Responses 请求后转发
v
OpenAI-compatible upstream
|
| 3. SSE: web_search 状态 + function_call arguments
v
proxai
|
| 4. 保留 SSE bytes,Responses observer 监控 terminal/tool 参数超时
v
Client
|
| 5. 本地执行 get_weather
v
Local tool runtime
|
| 6. 下一轮 input 带 function_call_output
v
proxai -> upstream -> proxai -> Client
{
"model": "gpt-5.4",
"stream": true,
"input": [
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "北京今天适合跑步吗?如果空气质量不好,请查一下官方建议。"
}
]
}
],
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "查询指定城市的天气和空气质量摘要。",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string" },
"date": { "type": "string" }
},
"required": ["city", "date"]
},
"strict": true
},
{
"type": "web_search",
"filters": {
"allowed_domains": ["www.cma.gov.cn", "www.mee.gov.cn"]
},
"search_context_size": "low"
}
],
"tool_choice": "auto",
"parallel_tool_calls": false
}

结构映射。这里是字段映射伪代码,json! 表示 serde_json::json!... 表示其余字段省略:

RequestProjection {
model: Some("gpt-5.4".to_string()),
stream: Some(true),
tools: Some(vec![
Tool::Function(FunctionTool {
name: "get_weather".to_string(),
description: Some("查询指定城市的天气和空气质量摘要。".to_string()),
parameters: Some(json!({
"type": "object",
"properties": {
"city": { "type": "string" },
"date": { "type": "string" }
},
"required": ["city", "date"]
})),
strict: Some(true),
...
}),
Tool::WebSearch(WebSearchTool {
filters: Some(WebSearchToolFilters {
allowed_domains: Some(vec![
"www.cma.gov.cn".to_string(),
"www.mee.gov.cn".to_string(),
]),
}),
search_context_size: Some(WebSearchToolSearchContextSize::Low),
...
}),
]),
parallel_tool_calls: Some(false),
...
}

RequestProjection 刻意不保留完整 input。如果只看 wire model,输入消息对应:

InputParam::Items(vec![
InputItem::Item(Item::Message(MessageItem::Input(InputMessage {
role: InputRole::User,
content: vec![
InputContent::InputText(InputTextContent {
text: "北京今天适合跑步吗?如果空气质量不好,请查一下官方建议。".to_string(),
}),
],
...
}))),
])
event: response.created
data: {
"type": "response.created",
"sequence_number": 0,
"response": {
"id": "resp_01",
"object": "response",
"status": "in_progress",
"model": "gpt-5.4",
"output": []
}
}
event: response.web_search_call.in_progress
data: {
"type": "response.web_search_call.in_progress",
"sequence_number": 1,
"output_index": 0,
"item_id": "ws_01"
}
event: response.web_search_call.searching
data: {
"type": "response.web_search_call.searching",
"sequence_number": 2,
"output_index": 0,
"item_id": "ws_01"
}
event: response.web_search_call.completed
data: {
"type": "response.web_search_call.completed",
"sequence_number": 3,
"output_index": 0,
"item_id": "ws_01"
}
event: response.output_item.added
data: {
"type": "response.output_item.added",
"sequence_number": 4,
"output_index": 1,
"item": {
"type": "function_call",
"id": "fc_01",
"call_id": "call_weather_01",
"name": "get_weather",
"arguments": "",
"status": "in_progress"
}
}
event: response.function_call_arguments.delta
data: {
"type": "response.function_call_arguments.delta",
"sequence_number": 5,
"item_id": "fc_01",
"output_index": 1,
"delta": "{\"city\":\"北京\""
}
event: response.function_call_arguments.delta
data: {
"type": "response.function_call_arguments.delta",
"sequence_number": 6,
"item_id": "fc_01",
"output_index": 1,
"delta": ",\"date\":\"today\"}"
}
event: response.function_call_arguments.done
data: {
"type": "response.function_call_arguments.done",
"sequence_number": 7,
"item_id": "fc_01",
"output_index": 1,
"name": "get_weather",
"arguments": "{\"city\":\"北京\",\"date\":\"today\"}"
}
event: response.output_item.done
data: {
"type": "response.output_item.done",
"sequence_number": 8,
"output_index": 1,
"item": {
"type": "function_call",
"id": "fc_01",
"call_id": "call_weather_01",
"name": "get_weather",
"arguments": "{\"city\":\"北京\",\"date\":\"today\"}",
"status": "completed"
}
}
event: response.completed
data: {
"type": "response.completed",
"sequence_number": 9,
"response": {
"id": "resp_01",
"object": "response",
"status": "completed",
"model": "gpt-5.4",
"output": []
}
}

结构映射:

ResponseStreamEvent::ResponseCreated(ResponseCreatedEvent {
sequence_number: 0,
response: Response {
id: "resp_01".to_string(),
object: "response".to_string(),
status: Status::InProgress,
model: "gpt-5.4".to_string(),
output: vec![],
...
},
})
ResponseStreamEvent::ResponseWebSearchCallInProgress(
ResponseWebSearchCallInProgressEvent {
sequence_number: 1,
output_index: 0,
item_id: "ws_01".to_string(),
},
)
ResponseStreamEvent::ResponseWebSearchCallSearching(
ResponseWebSearchCallSearchingEvent {
sequence_number: 2,
output_index: 0,
item_id: "ws_01".to_string(),
},
)
ResponseStreamEvent::ResponseWebSearchCallCompleted(
ResponseWebSearchCallCompletedEvent {
sequence_number: 3,
output_index: 0,
item_id: "ws_01".to_string(),
},
)
ResponseStreamEvent::ResponseOutputItemAdded(ResponseOutputItemAddedEvent {
sequence_number: 4,
output_index: 1,
item: OutputItem::FunctionCall(FunctionToolCall {
id: Some("fc_01".to_string()),
call_id: "call_weather_01".to_string(),
name: "get_weather".to_string(),
arguments: String::new(),
status: Some(OutputStatus::InProgress),
...
}),
})
ResponseStreamEvent::ResponseFunctionCallArgumentsDelta(
ResponseFunctionCallArgumentsDeltaEvent {
sequence_number: 5,
item_id: "fc_01".to_string(),
output_index: 1,
delta: "{\"city\":\"北京\"".to_string(),
},
)
ResponseStreamEvent::ResponseFunctionCallArgumentsDelta(
ResponseFunctionCallArgumentsDeltaEvent {
sequence_number: 6,
item_id: "fc_01".to_string(),
output_index: 1,
delta: ",\"date\":\"today\"}".to_string(),
},
)
ResponseStreamEvent::ResponseFunctionCallArgumentsDone(
ResponseFunctionCallArgumentsDoneEvent {
sequence_number: 7,
item_id: "fc_01".to_string(),
output_index: 1,
name: Some("get_weather".to_string()),
arguments: "{\"city\":\"北京\",\"date\":\"today\"}".to_string(),
},
)

客户端聚合工具参数:

function_calls["fc_01"].call_id = "call_weather_01"
function_calls["fc_01"].name = "get_weather"
function_calls["fc_01"].arguments += "{\"city\":\"北京\""
function_calls["fc_01"].arguments += ",\"date\":\"today\"}"

delta 是新增参数片段,不是完整 JSON。arguments.done 到达后,客户端可以解析完整参数。proxai 的 Responses observer 会专门监控这种参数流,避免 delta 后长期没有 done 导致客户端卡住。

客户端执行 get_weather 后,下一轮请求把工具结果作为 function_call_output input item 带回。

{
"model": "gpt-5.4",
"stream": true,
"previous_response_id": "resp_01",
"input": [
{
"type": "function_call_output",
"call_id": "call_weather_01",
"output": "北京今天气温 18-27C,轻度污染,PM2.5 约 85,傍晚有风。"
}
]
}

结构映射:

RequestProjection {
model: Some("gpt-5.4".to_string()),
stream: Some(true),
previous_response_id: Some("resp_01".to_string()),
...
}
InputParam::Items(vec![
InputItem::Item(Item::FunctionCallOutput(FunctionCallOutputItemParam {
call_id: "call_weather_01".to_string(),
output: FunctionCallOutput::Text(
"北京今天气温 18-27C,轻度污染,PM2.5 约 85,傍晚有风。".to_string(),
),
...
})),
])
event: response.output_item.added
data: {
"type": "response.output_item.added",
"sequence_number": 0,
"output_index": 0,
"item": {
"type": "message",
"id": "msg_01",
"role": "assistant",
"content": [],
"status": "in_progress"
}
}
event: response.content_part.added
data: {
"type": "response.content_part.added",
"sequence_number": 1,
"item_id": "msg_01",
"output_index": 0,
"content_index": 0,
"part": {
"type": "output_text",
"text": "",
"annotations": []
}
}
event: response.output_text.delta
data: {
"type": "response.output_text.delta",
"sequence_number": 2,
"item_id": "msg_01",
"output_index": 0,
"content_index": 0,
"delta": "今天北京不太适合高强度户外跑步。"
}
event: response.output_text.delta
data: {
"type": "response.output_text.delta",
"sequence_number": 3,
"item_id": "msg_01",
"output_index": 0,
"content_index": 0,
"delta": "空气质量为轻度污染,建议改为低强度慢跑或室内训练。"
}
event: response.output_text.done
data: {
"type": "response.output_text.done",
"sequence_number": 4,
"item_id": "msg_01",
"output_index": 0,
"content_index": 0,
"text": "今天北京不太适合高强度户外跑步。空气质量为轻度污染,建议改为低强度慢跑或室内训练。"
}
event: response.completed
data: {
"type": "response.completed",
"sequence_number": 5,
"response": {
"id": "resp_02",
"object": "response",
"status": "completed",
"model": "gpt-5.4",
"output": []
}
}

结构映射和聚合规则:

ResponseStreamEvent::ResponseOutputTextDelta(ResponseTextDeltaEvent {
sequence_number: 2,
item_id: "msg_01".to_string(),
output_index: 0,
content_index: 0,
delta: "今天北京不太适合高强度户外跑步。".to_string(),
...
})
ResponseStreamEvent::ResponseOutputTextDelta(ResponseTextDeltaEvent {
sequence_number: 3,
item_id: "msg_01".to_string(),
output_index: 0,
content_index: 0,
delta: "空气质量为轻度污染,建议改为低强度慢跑或室内训练。".to_string(),
...
})
ResponseStreamEvent::ResponseOutputTextDone(ResponseTextDoneEvent {
sequence_number: 4,
item_id: "msg_01".to_string(),
output_index: 0,
content_index: 0,
text: "今天北京不太适合高强度户外跑步。空气质量为轻度污染,建议改为低强度慢跑或室内训练。".to_string(),
...
})
text[(output_index=0, content_index=0)] = ""
text[(0, 0)] += "今天北京不太适合高强度户外跑步。"
text[(0, 0)] += "空气质量为轻度污染,建议改为低强度慢跑或室内训练。"

output_text.delta 是新增文本片段;output_text.done.text 是该 content part 的完整文本。