- 新增 ConversionEngine 核心引擎,支持 OpenAI 和 Anthropic 协议转换 - 添加 stream decoder/encoder 实现 - 更新 provider client 支持新引擎 - 补充单元测试和集成测试 - 更新 specs 文档
38 KiB
OpenAI Chat Completion API 端到端测试用例与 Mock 设计
本文档针对 POST /chat/completions 接口,设计端到端测试用例及对应的 Mock 返回值结构。
一、测试场景总览
| 分类 | 测试用例 | 优先级 |
|---|---|---|
| 基础对话 | 1. 单轮纯文本对话 | P0 |
| 基础对话 | 2. 多轮对话(system + user + assistant + user) | P0 |
| 消息类型 | 3. developer 消息(o1+ 模型) | P1 |
| 消息类型 | 4. 带 tool_calls 的 assistant 消息 + tool 消息 | P0 |
| 消息内容 | 5. 用户消息含图片 URL(vision) | P1 |
| 消息内容 | 6. 用户消息含多模态内容(text + image) | P1 |
| 消息内容 | 7. assistant 消息含 refusal | P1 |
| 工具调用 | 8. 单工具调用(function tool) | P0 |
| 工具调用 | 9. 并行多工具调用(parallel_tool_calls) | P1 |
| 工具调用 | 10. tool_choice 为 "none" | P2 |
| 工具调用 | 11. tool_choice 指定具体工具名 | P2 |
| 工具调用 | 12. 废弃的 function_call 格式(向后兼容) | P2 |
| 参数控制 | 13. temperature + top_p | P1 |
| 参数控制 | 14. max_tokens 截断 | P1 |
| 参数控制 | 15. stop 序列截断 | P2 |
| 参数控制 | 16. frequency_penalty + presence_penalty | P2 |
| 响应格式 | 17. response_format 为 json_object | P1 |
| 响应格式 | 18. response_format 为 json_schema(structured output) | P1 |
| 流式响应 | 19. 流式文本响应(SSE) | P0 |
| 流式响应 | 20. 流式 + stream_options include_usage | P1 |
| 流式响应 | 21. 流式工具调用 | P1 |
| 推理模型 | 22. reasoning_effort 参数(o1/o3 模型) | P2 |
| 错误处理 | 23. 无效 model 返回 404 | P1 |
| 错误处理 | 24. 缺少 messages 返回 400 | P1 |
| 错误处理 | 25. 内容安全策略拒绝(content_filter) | P2 |
| 其他 | 26. n 参数多选择(multiple choices) | P2 |
| 其他 | 27. seed 参数可复现 | P2 |
| 其他 | 28. logprobs + top_logprobs | P2 |
二、测试用例详情
用例 1:单轮纯文本对话
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "你好,请介绍一下你自己"
}
]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-001",
"object": "chat.completion",
"created": 1700000000,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!我是由 OpenAI 开发的 AI 助手 GPT-4。我可以帮你回答问题、写作、编程、数学推理等任务。请问有什么我可以帮你的?"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 42,
"total_tokens": 57
},
"system_fingerprint": "fp_test123"
}
验证点:
choices[0].message.role=="assistant"choices[0].message.content非空choices[0].finish_reason=="stop"usage字段完整
用例 2:多轮对话
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "你是一个专业的编程助手,请用简洁的语言回答问题。"
},
{
"role": "user",
"content": "什么是 Go 语言的 interface?"
},
{
"role": "assistant",
"content": "Go 语言的 interface 是一种类型,它定义了一组方法签名。任何实现了这些方法的类型都自动实现了该 interface,无需显式声明。"
},
{
"role": "user",
"content": "能举个例子吗?"
}
]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-002",
"object": "chat.completion",
"created": 1700000001,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "当然。例如定义一个 `Reader` interface:\n\n```go\ntype Reader interface {\n Read(p []byte) (n int, err error)\n}\n```\n\n`os.File` 和 `bytes.Buffer` 都实现了 `Read` 方法,所以它们都自动实现了 `Reader` interface。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 120,
"completion_tokens": 65,
"total_tokens": 185
},
"system_fingerprint": "fp_test123"
}
验证点:
- 多轮上下文正确传递
- 响应与上下文连贯
用例 3:developer 消息(o1+ 模型)
请求:
{
"model": "o1",
"messages": [
{
"role": "developer",
"content": "你是一名数学专家。请逐步推理后再给出答案。"
},
{
"role": "user",
"content": "15 + 23 * 2 等于多少?"
}
]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-003",
"object": "chat.completion",
"created": 1700000002,
"model": "o1",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "根据运算优先级,先算乘法:23 * 2 = 46,再加 15:46 + 15 = 61。答案是 61。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 35,
"completion_tokens": 48,
"total_tokens": 83,
"completion_tokens_details": {
"reasoning_tokens": 20
}
},
"system_fingerprint": "fp_test123"
}
验证点:
- developer 角色被正确处理
- reasoning_tokens 在详情中体现
用例 4:工具调用 + 工具结果
请求(第一轮 - 模型调用工具):
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "北京今天天气怎么样?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}
期望 Mock 响应(200 OK,第一轮):
{
"id": "chatcmpl-test-004a",
"object": "chat.completion",
"created": 1700000003,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}
]
},
"finish_reason": "tool_calls",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 80,
"completion_tokens": 18,
"total_tokens": 98
}
}
请求(第二轮 - 提交工具结果):
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "北京今天天气怎么样?"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "北京今天晴,气温 25°C,东南风 2 级。"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
}
]
}
期望 Mock 响应(200 OK,第二轮):
{
"id": "chatcmpl-test-004b",
"object": "chat.completion",
"created": 1700000004,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "北京今天天气很好,晴天,气温 25°C,东南风 2 级,适合外出活动。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 120,
"completion_tokens": 30,
"total_tokens": 150
}
}
验证点:
- 第一轮
finish_reason=="tool_calls" tool_calls包含正确的 function name 和 arguments- 第二轮能基于工具结果生成自然语言回复
用例 5:用户消息含图片 URL(vision)
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "这张图片里有什么?"
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/images/cat.jpg",
"detail": "high"
}
}
]
}
]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-005",
"object": "chat.completion",
"created": 1700000005,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "图片中有一只橘色的猫,它正躺在沙发上晒太阳。猫的毛色是橘白相间的,眼睛是绿色的,看起来非常放松。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 280,
"completion_tokens": 45,
"total_tokens": 325
}
}
验证点:
- 图片 URL 被正确传递
- 响应描述图片内容
用例 6:用户消息含多模态内容(text + image)
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "请比较这两张图片有什么不同"
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/images/before.jpg"
}
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/images/after.jpg"
}
}
]
}
]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-006",
"object": "chat.completion",
"created": 1700000006,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "两张图片的主要区别:\n1. 第一张是白天的场景,第二张是夜晚\n2. 灯光从自然光变成了暖黄色的室内灯光\n3. 桌上的物品摆放位置有所不同"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 520,
"completion_tokens": 55,
"total_tokens": 575
}
}
用例 7:assistant 消息含 refusal
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "如何制作危险物品?"
}
]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-007",
"object": "chat.completion",
"created": 1700000007,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"refusal": "抱歉,我无法提供涉及危险活动的信息。我的设计目的是提供有益和安全的帮助。如果你有其他问题,我很乐意协助。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 35,
"total_tokens": 47
}
}
验证点:
refusal字段存在且非空content为 null 或空
用例 8:单工具调用(function tool)
同用例 4 第一轮。
用例 9:并行多工具调用
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "帮我查一下北京、上海、广州三个城市的天气"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-009",
"object": "chat.completion",
"created": 1700000009,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_001",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
},
{
"id": "call_002",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"上海\"}"
}
},
{
"id": "call_003",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"广州\"}"
}
}
]
},
"finish_reason": "tool_calls",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 95,
"completion_tokens": 55,
"total_tokens": 150
}
}
验证点:
tool_calls数组长度 == 3- 每个 call 有不同 ID 和参数
用例 10:tool_choice 为 "none"
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "随便聊聊"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
}
}
}
}
],
"tool_choice": "none"
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-010",
"object": "chat.completion",
"created": 1700000010,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "好的!今天天气不错,很适合聊天。你想聊些什么话题呢?可以是技术、生活、娱乐等任何你感兴趣的内容。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 50,
"completion_tokens": 35,
"total_tokens": 85
}
}
验证点:
- 尽管定义了 tools,响应中不包含
tool_calls - 纯文本回复
用例 11:tool_choice 指定具体工具名
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "查天气"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
}
},
{
"type": "function",
"function": {
"name": "get_news",
"description": "获取新闻",
"parameters": {"type": "object", "properties": {"topic": {"type": "string"}}}
}
}
],
"tool_choice": {
"type": "function",
"function": {"name": "get_weather"}
}
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-011",
"object": "chat.completion",
"created": 1700000011,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_weather1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{}"
}
}
]
},
"finish_reason": "tool_calls",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 80,
"completion_tokens": 15,
"total_tokens": 95
}
}
验证点:
- 强制使用了
get_weather而非get_news
用例 12:废弃的 function_call 格式(向后兼容)
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "查天气"
}
],
"functions": [
{
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
],
"function_call": "auto"
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-012",
"object": "chat.completion",
"created": 1700000012,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"function_call": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
},
"finish_reason": "function_call",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 60,
"completion_tokens": 20,
"total_tokens": 80
}
}
验证点:
- 废弃的
functions/function_call格式仍能被正确处理 finish_reason=="function_call"(而非"tool_calls")
用例 13:temperature + top_p 参数
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "写一首关于春天的短诗"
}
],
"temperature": 0.9,
"top_p": 0.95
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-013",
"object": "chat.completion",
"created": 1700000013,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "春风拂面花自开,\n柳絮飞舞满园香。\n燕归巢中呢喃语,\n万物复苏迎朝阳。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 32,
"total_tokens": 50
}
}
用例 14:max_tokens 截断
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "请详细介绍一下人工智能的发展历史"
}
],
"max_tokens": 30
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-014",
"object": "chat.completion",
"created": 1700000014,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "人工智能起源于1950年代,图灵提出了机器能否思考的问题。1956年达特茅斯会议正式确立了AI领域。此后经历了多次起伏..."
},
"finish_reason": "length",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 30,
"total_tokens": 50
}
}
验证点:
finish_reason=="length"completion_tokens== 30(等于 max_tokens)- 内容被截断
用例 15:stop 序列截断
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "从1数到10,每行一个数字"
}
],
"stop": ["5"]
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-015",
"object": "chat.completion",
"created": 1700000015,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1\n2\n3\n4\n"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 10,
"total_tokens": 28
}
}
验证点:
finish_reason=="stop"- 内容在遇到 stop 序列时截断
用例 16:frequency_penalty + presence_penalty
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "用不同的词汇描述'好'"
}
],
"frequency_penalty": 0.5,
"presence_penalty": 0.3
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-016",
"object": "chat.completion",
"created": 1700000016,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "优秀、出色、精彩、卓越、非凡、完美、绝佳、美妙、精彩、杰出"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 15,
"total_tokens": 33
}
}
用例 17:response_format 为 json_object
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "提取以下信息的姓名和年龄:张三,今年25岁,是一名工程师"
}
],
"response_format": {
"type": "json_object"
}
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-017",
"object": "chat.completion",
"created": 1700000017,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"name\": \"张三\", \"age\": 25, \"occupation\": \"工程师\"}"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 35,
"completion_tokens": 25,
"total_tokens": 60
}
}
验证点:
content是合法的 JSON 字符串
用例 18:response_format 为 json_schema(structured output)
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "创建一个用户信息记录,姓名李四,年龄30岁"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "user_info",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"email": {"type": "string"}
},
"required": ["name", "age"]
},
"strict": true
}
}
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-018",
"object": "chat.completion",
"created": 1700000018,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"name\": \"李四\", \"age\": 30}"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 85,
"completion_tokens": 18,
"total_tokens": 103
}
}
验证点:
- 输出严格符合 JSON schema 定义
- 不包含 schema 未定义的字段
用例 19:流式文本响应(SSE)
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "你好"
}
],
"stream": true
}
期望 Mock 响应(200 OK, Content-Type: text/event-stream):
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"!我"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"AI"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"助手"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":","},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"很高兴"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"为你"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"服务"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"。"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-001","object":"chat.completion.chunk","created":1700000019,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
验证点:
- 每个 chunk 的
id一致 - 第一个 chunk 的
delta包含role - 中间 chunk 的
delta只包含content - 最后一个 chunk 包含
finish_reason - 以
[DONE]结束
用例 20:流式 + stream_options include_usage
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "你好"
}
],
"stream": true,
"stream_options": {
"include_usage": true
}
}
期望 Mock 响应(200 OK, Content-Type: text/event-stream):
最后一个 chunk 之前增加一个 usage chunk:
data: {"id":"chatcmpl-stream-020","object":"chat.completion.chunk","created":1700000020,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
... (中间内容 chunk) ...
data: {"id":"chatcmpl-stream-020","object":"chat.completion.chunk","created":1700000020,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-stream-020","object":"chat.completion.chunk","created":1700000020,"model":"gpt-4o","choices":[],"usage":{"prompt_tokens":10,"completion_tokens":25,"total_tokens":35}}
data: [DONE]
验证点:
- 倒数第二个 chunk 包含
usage字段 - 该 chunk 的
choices为空数组
用例 21:流式工具调用
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "北京天气怎么样?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
}
],
"stream": true
}
期望 Mock 响应(200 OK, Content-Type: text/event-stream):
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":null},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_stream1","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"北京"}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"}\"}"}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-021","object":"chat.completion.chunk","created":1700000021,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
data: [DONE]
验证点:
- 第一个 chunk 包含
role - tool_calls 通过增量 chunk 逐步拼接
- 最后一个 chunk 的
finish_reason=="tool_calls"
用例 22:reasoning_effort 参数(o1/o3 模型)
请求:
{
"model": "o3",
"messages": [
{
"role": "user",
"content": "一个房间里有3个灯泡,房间外有3个开关,每个开关控制一个灯泡。你只能进房间一次,如何确定哪个开关控制哪个灯泡?"
}
],
"reasoning_effort": "high"
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-022",
"object": "chat.completion",
"created": 1700000022,
"model": "o3",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "解决方案:\n\n1. 打开第一个开关,等待几分钟\n2. 关闭第一个开关,打开第二个开关\n3. 立即进入房间\n\n此时:\n- 亮着的灯由第二个开关控制\n- 摸起来发热的灯由第一个开关控制(因为开过几分钟)\n- 既不亮也不热的灯由第三个开关控制"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 85,
"completion_tokens": 120,
"total_tokens": 205,
"completion_tokens_details": {
"reasoning_tokens": 80
}
}
}
验证点:
- reasoning_tokens 显著增加
- 响应展示推理结果
用例 23:无效 model 返回 404
请求:
{
"model": "nonexistent-model-xyz",
"messages": [
{
"role": "user",
"content": "你好"
}
]
}
期望 Mock 响应(404 Not Found):
{
"error": {
"message": "The model `nonexistent-model-xyz` does not exist or you do not have access to it.",
"type": "invalid_request_error",
"param": null,
"code": "model_not_found"
}
}
用例 24:缺少 messages 返回 400
请求:
{
"model": "gpt-4o"
}
期望 Mock 响应(400 Bad Request):
{
"error": {
"message": "'messages' is a required property and must be a non-empty array.",
"type": "invalid_request_error",
"param": null,
"code": "missing_required_field"
}
}
用例 25:内容安全策略拒绝
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "生成违法内容..."
}
]
}
期望 Mock 响应(400 Bad Request):
{
"error": {
"message": "The content you submitted may violate our usage policies.",
"type": "invalid_request_error",
"param": null,
"code": "content_filter"
}
}
或者作为 completion 返回(finish_reason = "content_filter"):
{
"id": "chatcmpl-test-025",
"object": "chat.completion",
"created": 1700000025,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"refusal": "我无法生成此类内容。"
},
"finish_reason": "content_filter",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 8,
"total_tokens": 23
}
}
用例 26:n 参数多选择
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "用一句话描述春天"
}
],
"n": 3
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-026",
"object": "chat.completion",
"created": 1700000026,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "春天是万物复苏、百花盛开的季节。"
},
"finish_reason": "stop",
"logprobs": null
},
{
"index": 1,
"message": {
"role": "assistant",
"content": "春风拂面,绿意盎然,处处洋溢着生机与希望。"
},
"finish_reason": "stop",
"logprobs": null
},
{
"index": 2,
"message": {
"role": "assistant",
"content": "冬雪消融,嫩芽破土,春天带着温暖悄然而至。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 60,
"total_tokens": 78
}
}
验证点:
choices数组长度为 3- 每个 choice 有不同的
index
用例 27:seed 参数可复现
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "随机生成一个5位数字"
}
],
"seed": 42,
"temperature": 0
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-027",
"object": "chat.completion",
"created": 1700000027,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "12345"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 5,
"total_tokens": 23
},
"system_fingerprint": "fp_test_seed42"
}
验证点:
- 相同 seed + temperature=0 应产生相同输出
system_fingerprint可用于验证 backend 一致性
用例 28:logprobs + top_logprobs
请求:
{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "1+1="
}
],
"logprobs": true,
"top_logprobs": 3
}
期望 Mock 响应(200 OK):
{
"id": "chatcmpl-test-028",
"object": "chat.completion",
"created": 1700000028,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "2"
},
"finish_reason": "stop",
"logprobs": {
"content": [
{
"token": "2",
"logprob": -0.001,
"bytes": [50],
"top_logprobs": [
{"token": "2", "logprob": -0.001, "bytes": [50]},
{"token": "二", "logprob": -3.5, "bytes": [201, 147]},
{"token": " two", "logprob": -5.2, "bytes": [32, 116, 119, 111]}
]
}
],
"refusal": null
}
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 1,
"total_tokens": 11
}
}
验证点:
logprobs.content数组包含每个生成 token 的概率信息top_logprobs包含前 K 个候选 token
三、Mock 响应通用结构规范
非流式响应通用结构
{
"id": "chatcmpl-<unique_id>",
"object": "chat.completion",
"created": <unix_timestamp>,
"model": "<model_name>",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "<text>" | null,
"refusal": "<text>" | null,
"tool_calls": [
{
"id": "call_<id>",
"type": "function" | "custom",
"function": {"name": "<name>", "arguments": "<json_string>"} | null,
"custom": {"name": "<name>", "input": "<json_string>"} | null
}
]
},
"finish_reason": "stop" | "length" | "tool_calls" | "content_filter" | "function_call",
"logprobs": null | { "content": [...], "refusal": null }
}
],
"usage": {
"prompt_tokens": <int>,
"completion_tokens": <int>,
"total_tokens": <int>,
"prompt_tokens_details": {
"cached_tokens": <int> | null,
"audio_tokens": <int> | null
},
"completion_tokens_details": {
"reasoning_tokens": <int> | null,
"audio_tokens": <int> | null,
"accepted_prediction_tokens": <int> | null,
"rejected_prediction_tokens": <int> | null
}
},
"system_fingerprint": "<fingerprint>" | null,
"service_tier": "auto" | "default" | "flex" | null
}
流式 Chunk 通用结构
{
"id": "chatcmpl-<unique_id>",
"object": "chat.completion.chunk",
"created": <unix_timestamp>,
"model": "<model_name>",
"choices": [
{
"index": 0,
"delta": {
"role": "assistant" | null,
"content": "<text_chunk>" | null,
"refusal": "<text_chunk>" | null,
"tool_calls": [
{
"index": <int>,
"id": "call_<id>" | null,
"type": "function" | "custom" | null,
"function": {"name": "<name>" | null, "arguments": "<partial_json>"} | null,
"custom": {"name": "<name>" | null, "input": "<partial_json>"} | null
}
]
},
"finish_reason": "stop" | "length" | "tool_calls" | "content_filter" | null,
"logprobs": null | { ... }
}
],
"usage": { ... } | null,
"system_fingerprint": "<fingerprint>" | null
}
错误响应通用结构
{
"error": {
"message": "<human_readable_message>",
"type": "invalid_request_error" | "authentication_error" | "api_error" | "rate_limit_error",
"param": null | "<parameter_name>",
"code": "<error_code>"
}
}