OpenAI Chat Completions 完整交互示例
OpenAI Chat Completions 完整交互示例
Section titled “OpenAI Chat Completions 完整交互示例”完整交互示例
Section titled “完整交互示例”下面用同一个跑步建议场景展示 Chat Completions 的工具调用过程。
用户问:“北京今天适合跑步吗?如果空气质量不好,请参考公开信息给建议。”
Chat Completions 的工具调用核心是本地工具:模型返回 tool_calls,客户端执行工具后用下一轮 tool message 回填。这个协议没有 Responses/Anthropic 那样的服务端工具 item/block 生命周期;web_search_options 这类能力是请求级选项,只影响上游服务行为,不会在 SSE 中形成独立的 server-tool-use/result 事件。
Client | | 1. messages + tools(function get_weather) + web_search_options + stream=true vproxai | | 2. 原样转发 openai_chat_completions vOpenAI-compatible upstream | | 3. SSE: delta.tool_calls(function arguments) vproxai | | 4. 原始 SSE bytes 透传,Chat observer 记录 finish_reason/usage vClient | | 5. 本地执行 get_weather vLocal tool runtime | | 6. 下一轮 messages 带 tool role 结果 vproxai -> upstream -> proxai -> Client{ "model": "gpt-5.4", "stream": true, "messages": [ { "role": "system", "content": "你是一个简洁的出行建议助手。" }, { "role": "user", "content": "北京今天适合跑步吗?如果空气质量不好,请参考公开信息给建议。" } ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的天气和空气质量摘要。", "parameters": { "type": "object", "properties": { "city": { "type": "string" }, "date": { "type": "string" } }, "required": ["city", "date"] }, "strict": true } } ], "tool_choice": "auto", "parallel_tool_calls": false, "web_search_options": { "search_context_size": "low", "user_location": { "type": "approximate", "approximate": { "city": "Beijing", "country": "CN", "timezone": "Asia/Shanghai" } } }}对应结构映射。这里是字段映射伪代码,json! 表示 serde_json::json!,... 表示其余可选字段省略:
CreateChatCompletionRequest { model: "gpt-5.4".to_string(), stream: Some(true), messages: vec![ ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: ChatCompletionRequestSystemMessageContent::Text( "你是一个简洁的出行建议助手。".to_string(), ), ... }), ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "北京今天适合跑步吗?如果空气质量不好,请参考公开信息给建议。".to_string(), ), ... }), ], tools: Some(vec![ ChatCompletionTools::Function(ChatCompletionTool { function: FunctionObject { 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_choice: Some(ChatCompletionToolChoiceOption::Mode(ToolChoiceOptions::Auto)), parallel_tool_calls: Some(false), web_search_options: Some(WebSearchOptions { search_context_size: Some(WebSearchContextSize::Low), user_location: Some(WebSearchUserLocation { r#type: WebSearchUserLocationType::Approximate, approximate: WebSearchLocation { city: Some("Beijing".to_string()), country: Some("CN".to_string()), timezone: Some("Asia/Shanghai".to_string()), ... }, }), }), ...}第一轮 SSE
Section titled “第一轮 SSE”Chat Completions 流式响应里,工具调用参数在 choices[].delta.tool_calls[].function.arguments 中增量到达。
data: { "id": "chatcmpl_01", "object": "chat.completion.chunk", "created": 1770000000, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": { "role": "assistant" }, "finish_reason": null } ]}
data: { "id": "chatcmpl_01", "object": "chat.completion.chunk", "created": 1770000000, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": { "tool_calls": [ { "index": 0, "id": "call_weather_01", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"北京\"" } } ] }, "finish_reason": null } ]}
data: { "id": "chatcmpl_01", "object": "chat.completion.chunk", "created": 1770000000, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": { "tool_calls": [ { "index": 0, "function": { "arguments": ",\"date\":\"today\"}" } } ] }, "finish_reason": null } ]}
data: { "id": "chatcmpl_01", "object": "chat.completion.chunk", "created": 1770000000, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": {}, "finish_reason": "tool_calls" } ], "usage": { "prompt_tokens": 140, "completion_tokens": 24, "total_tokens": 164 }}
data: [DONE]对应结构映射:
CreateChatCompletionStreamResponse { id: "chatcmpl_01".to_string(), object: "chat.completion.chunk".to_string(), created: 1770000000, model: "gpt-5.4".to_string(), choices: vec![ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { role: Some(Role::Assistant), ... }, ... }], ...}CreateChatCompletionStreamResponse { choices: vec![ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { tool_calls: Some(vec![ ChatCompletionMessageToolCallChunk { index: 0, id: Some("call_weather_01".to_string()), r#type: Some(FunctionType::Function), function: Some(FunctionCallStream { name: Some("get_weather".to_string()), arguments: Some("{\"city\":\"北京\"".to_string()), }), }, ]), ... }, ... }], ...}
CreateChatCompletionStreamResponse { choices: vec![ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { tool_calls: Some(vec![ ChatCompletionMessageToolCallChunk { index: 0, function: Some(FunctionCallStream { arguments: Some(",\"date\":\"today\"}".to_string()), ... }), ... }, ]), ... }, ... }], ...}CreateChatCompletionStreamResponse { choices: vec![ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { ... }, finish_reason: Some(FinishReason::ToolCalls), ... }], usage: Some(CompletionUsage { ... }), ...}客户端聚合规则:
tool_calls[(choice.index=0, tool.index=0)].id = "call_weather_01"tool_calls[(0, 0)].name = "get_weather"tool_calls[(0, 0)].arguments += "{\"city\":\"北京\""tool_calls[(0, 0)].arguments += ",\"date\":\"today\"}"当 finish_reason = "tool_calls" 到达时,arguments 才可以作为完整 JSON 解析。
本地工具结果回填
Section titled “本地工具结果回填”客户端执行本地 get_weather 后,下一轮请求需要带上 assistant 的 tool_calls 和对应的 tool message。
{ "model": "gpt-5.4", "stream": true, "messages": [ { "role": "user", "content": "北京今天适合跑步吗?如果空气质量不好,请参考公开信息给建议。" }, { "role": "assistant", "tool_calls": [ { "id": "call_weather_01", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"北京\",\"date\":\"today\"}" } } ] }, { "role": "tool", "tool_call_id": "call_weather_01", "content": "北京今天气温 18-27C,轻度污染,PM2.5 约 85,傍晚有风。" } ]}结构映射:
CreateChatCompletionRequest { model: "gpt-5.4".to_string(), stream: Some(true), messages: vec![ ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "北京今天适合跑步吗?如果空气质量不好,请参考公开信息给建议。".to_string(), ), ... }), ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { tool_calls: Some(vec![ ChatCompletionMessageToolCalls::Function(ChatCompletionMessageToolCall { id: "call_weather_01".to_string(), function: FunctionCall { name: "get_weather".to_string(), arguments: "{\"city\":\"北京\",\"date\":\"today\"}".to_string(), }, }), ]), ... }), ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage { tool_call_id: "call_weather_01".to_string(), content: ChatCompletionRequestToolMessageContent::Text( "北京今天气温 18-27C,轻度污染,PM2.5 约 85,傍晚有风。".to_string(), ), }), ], ...}第二轮 SSE 最终回答
Section titled “第二轮 SSE 最终回答”data: { "id": "chatcmpl_02", "object": "chat.completion.chunk", "created": 1770000010, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": { "role": "assistant", "content": "今天北京不太适合高强度户外跑步。" }, "finish_reason": null } ]}
data: { "id": "chatcmpl_02", "object": "chat.completion.chunk", "created": 1770000010, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": { "content": "空气质量为轻度污染,建议改为低强度慢跑或室内训练。" }, "finish_reason": null } ]}
data: { "id": "chatcmpl_02", "object": "chat.completion.chunk", "created": 1770000010, "model": "gpt-5.4", "choices": [ { "index": 0, "delta": {}, "finish_reason": "stop" } ]}
data: [DONE]结构映射和聚合规则:
CreateChatCompletionStreamResponse { choices: vec![ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { role: Some(Role::Assistant), content: Some("今天北京不太适合高强度户外跑步。".to_string()), ... }, ... }], ...}
CreateChatCompletionStreamResponse { choices: vec![ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { content: Some("空气质量为轻度污染,建议改为低强度慢跑或室内训练。".to_string()), ... }, ... }], ...}assistant_text[0] = ""assistant_text[0] += "今天北京不太适合高强度户外跑步。"assistant_text[0] += "空气质量为轻度污染,建议改为低强度慢跑或室内训练。"delta.content 是新增文本片段,不是完整文本。finish_reason = "stop" 表示该 choice 的最终回答结束。