OpenAI Responses 完整交互示例
OpenAI Responses 完整交互示例
Section titled “OpenAI Responses 完整交互示例”完整交互示例
Section titled “完整交互示例”下面用同一个跑步建议场景展示 Responses 的 item 化交互。
用户问:“北京今天适合跑步吗?如果空气质量不好,请查一下官方建议。”
Responses 同时支持用户端工具和 hosted/server 工具。这个例子里:
function get_weather是用户端工具,模型返回function_call,客户端执行后下一轮用function_call_output回填。web_search是 hosted/server 工具,上游服务自己执行,流里出现 web search 状态事件和对应WebSearchToolCallitem。
Client | | 1. input + function tool + web_search tool + stream=true vproxai | | 2. 归一化 Responses 请求后转发 vOpenAI-compatible upstream | | 3. SSE: web_search 状态 + function_call arguments vproxai | | 4. 保留 SSE bytes,Responses observer 监控 terminal/tool 参数超时 vClient | | 5. 本地执行 get_weather vLocal tool runtime | | 6. 下一轮 input 带 function_call_output vproxai -> 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(), }), ], ... }))),])第一轮 SSE
Section titled “第一轮 SSE”event: response.createddata: { "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_progressdata: { "type": "response.web_search_call.in_progress", "sequence_number": 1, "output_index": 0, "item_id": "ws_01"}
event: response.web_search_call.searchingdata: { "type": "response.web_search_call.searching", "sequence_number": 2, "output_index": 0, "item_id": "ws_01"}
event: response.web_search_call.completeddata: { "type": "response.web_search_call.completed", "sequence_number": 3, "output_index": 0, "item_id": "ws_01"}
event: response.output_item.addeddata: { "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.deltadata: { "type": "response.function_call_arguments.delta", "sequence_number": 5, "item_id": "fc_01", "output_index": 1, "delta": "{\"city\":\"北京\""}
event: response.function_call_arguments.deltadata: { "type": "response.function_call_arguments.delta", "sequence_number": 6, "item_id": "fc_01", "output_index": 1, "delta": ",\"date\":\"today\"}"}
event: response.function_call_arguments.donedata: { "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.donedata: { "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.completeddata: { "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 导致客户端卡住。
本地工具结果回填
Section titled “本地工具结果回填”客户端执行 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(), ), ... })),])第二轮 SSE 最终回答
Section titled “第二轮 SSE 最终回答”event: response.output_item.addeddata: { "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.addeddata: { "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.deltadata: { "type": "response.output_text.delta", "sequence_number": 2, "item_id": "msg_01", "output_index": 0, "content_index": 0, "delta": "今天北京不太适合高强度户外跑步。"}
event: response.output_text.deltadata: { "type": "response.output_text.delta", "sequence_number": 3, "item_id": "msg_01", "output_index": 0, "content_index": 0, "delta": "空气质量为轻度污染,建议改为低强度慢跑或室内训练。"}
event: response.output_text.donedata: { "type": "response.output_text.done", "sequence_number": 4, "item_id": "msg_01", "output_index": 0, "content_index": 0, "text": "今天北京不太适合高强度户外跑步。空气质量为轻度污染,建议改为低强度慢跑或室内训练。"}
event: response.completeddata: { "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 的完整文本。