| from __future__ import annotations |
|
|
| import json |
| import time |
| import unittest |
|
|
| import requests |
|
|
| from test.utils import save_image |
|
|
| AUTH_KEY = "chatgpt2api" |
| BASE_URL = "http://localhost:8000" |
| TEXT_MODEL = "auto" |
| IMAGE_MODEL = "gpt-image-2" |
| CODEX_IMAGE_MODEL = "codex-gpt-image-2" |
|
|
|
|
| class ResponsesTests(unittest.TestCase): |
| @staticmethod |
| def _iter_sse_payloads(response: requests.Response): |
| for line in response.iter_lines(): |
| if not line: |
| continue |
| text = line.decode("utf-8", errors="replace") |
| yield text |
|
|
| def test_text_response_http(self): |
| """测试 Responses 文本的非流式 HTTP 调用。""" |
| response = requests.post( |
| f"{BASE_URL}/v1/responses", |
| headers={"Authorization": f"Bearer {AUTH_KEY}"}, |
| json={ |
| "model": TEXT_MODEL, |
| "input": [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "input_text", "text": "你好,请简单介绍一下你自己。"}, |
| ], |
| } |
| ], |
| }, |
| timeout=300, |
| ) |
| self.assertEqual(response.status_code, 200, response.text) |
| print("responses text non-stream status:") |
| print(response.status_code) |
| print("responses text non-stream result:") |
| try: |
| payload = response.json() |
| print(json.dumps(payload, ensure_ascii=False, indent=2)) |
| self.assertEqual(payload.get("object"), "response") |
| self.assertEqual(payload.get("status"), "completed") |
| self.assertTrue(isinstance(payload.get("output"), list) and payload.get("output")) |
| except Exception: |
| print(response.text) |
| raise |
|
|
| def test_text_response_stream_http(self): |
| """测试 Responses 文本的流式 HTTP 调用。""" |
| response = requests.post( |
| f"{BASE_URL}/v1/responses", |
| headers={"Authorization": f"Bearer {AUTH_KEY}"}, |
| json={ |
| "model": TEXT_MODEL, |
| "input": [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "input_text", "text": "你好,请简单介绍一下你自己。"}, |
| ], |
| } |
| ], |
| "stream": True, |
| }, |
| stream=True, |
| timeout=300, |
| ) |
| self.assertEqual(response.status_code, 200, response.text) |
| self.assertTrue( |
| response.headers.get("content-type", "").startswith("text/event-stream"), |
| response.headers.get("content-type", ""), |
| ) |
| started_at = time.time() |
| print("responses text stream status:") |
| print(response.status_code) |
| print("responses text stream chunks:") |
| event_types = [] |
| for text in self._iter_sse_payloads(response): |
| print(f"{time.time() - started_at:6.2f}s {text}") |
| if not text.startswith("data:"): |
| continue |
| payload_text = text[5:].strip() |
| if payload_text == "[DONE]": |
| break |
| try: |
| payload = json.loads(payload_text) |
| except Exception: |
| continue |
| event_type = str(payload.get("type") or "") |
| if event_type: |
| event_types.append(event_type) |
| self.assertIn("response.created", event_types) |
| self.assertIn("response.completed", event_types) |
|
|
| def test_image_response_http(self): |
| """测试 Responses 画图的非流式 HTTP 调用。""" |
| response = requests.post( |
| f"{BASE_URL}/v1/responses", |
| headers={"Authorization": f"Bearer {AUTH_KEY}"}, |
| json={ |
| "model": IMAGE_MODEL, |
| "input": [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "input_text", "text": "我想做一张南京城市宣传海报图。"}, |
| ], |
| } |
| ], |
| "tools": [{"type": "image_generation"}], |
| }, |
| timeout=300, |
| ) |
| self.assertEqual(response.status_code, 200, response.text) |
| saved_paths = [] |
| try: |
| payload = response.json() |
| except Exception: |
| payload = {} |
| for index, item in enumerate(payload.get("output") or [], start=1): |
| if not isinstance(item, dict): |
| continue |
| image_b64 = str(item.get("result") or "") |
| if image_b64: |
| saved_paths.append(save_image(image_b64, f"responses_image_non_stream_{index}")) |
| print("responses image non-stream status:") |
| print(response.status_code) |
| print("responses image non-stream result:") |
| print(json.dumps(payload, ensure_ascii=False, indent=2)) |
| print("responses image non-stream saved files:") |
| for path in saved_paths: |
| print(path) |
|
|
| def test_image_response_stream_http(self): |
| """测试 Responses 画图的流式 HTTP 调用。""" |
| response = requests.post( |
| f"{BASE_URL}/v1/responses", |
| headers={"Authorization": f"Bearer {AUTH_KEY}"}, |
| json={ |
| "model": IMAGE_MODEL, |
| "input": [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "input_text", "text": "我想做一张南京城市宣传海报图。"}, |
| ], |
| } |
| ], |
| "tools": [{"type": "image_generation"}], |
| "stream": True, |
| }, |
| stream=True, |
| timeout=300, |
| ) |
| self.assertEqual(response.status_code, 200, response.text) |
| self.assertTrue( |
| response.headers.get("content-type", "").startswith("text/event-stream"), |
| response.headers.get("content-type", ""), |
| ) |
| started_at = time.time() |
| saved_paths = [] |
| print("responses image stream status:") |
| print(response.status_code) |
| print("responses image stream chunks:") |
| for text in self._iter_sse_payloads(response): |
| print(f"{time.time() - started_at:6.2f}s {text}") |
| if not text.startswith("data:"): |
| continue |
| payload_text = text[5:].strip() |
| if payload_text == "[DONE]": |
| break |
| try: |
| payload = json.loads(payload_text) |
| except Exception: |
| continue |
| if payload.get("type") != "response.output_item.done": |
| continue |
| item = payload.get("item") or {} |
| if str(item.get("type") or "") != "image_generation_call": |
| continue |
| image_b64 = str(item.get("result") or "") |
| if image_b64: |
| saved_paths.append(save_image(image_b64, f"responses_image_stream_{len(saved_paths) + 1}")) |
| print("responses image stream saved files:") |
| for path in saved_paths: |
| print(path) |
|
|
| def test_codex_image_response_http(self): |
| """测试 Responses 的 codex 画图非流式 HTTP 调用。""" |
| response = requests.post( |
| f"{BASE_URL}/v1/responses", |
| headers={"Authorization": f"Bearer {AUTH_KEY}"}, |
| json={ |
| "model": CODEX_IMAGE_MODEL, |
| "input": [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "input_text", "text": "我想做一张南京城市宣传海报图。"}, |
| ], |
| } |
| ], |
| "tools": [{"type": "image_generation"}], |
| }, |
| timeout=300, |
| ) |
| self.assertEqual(response.status_code, 200, response.text) |
| saved_paths = [] |
| try: |
| payload = response.json() |
| except Exception: |
| payload = {} |
| for index, item in enumerate(payload.get("output") or [], start=1): |
| if not isinstance(item, dict): |
| continue |
| image_b64 = str(item.get("result") or "") |
| if image_b64: |
| saved_paths.append(save_image(image_b64, f"responses_codex_image_non_stream_{index}")) |
| print("responses codex image non-stream status:") |
| print(response.status_code) |
| print("responses codex image non-stream result:") |
| print(json.dumps(payload, ensure_ascii=False, indent=2)) |
| print("responses codex image non-stream saved files:") |
| for path in saved_paths: |
| print(path) |
|
|
| def test_codex_image_response_stream_http(self): |
| """测试 Responses 的 codex 画图流式 HTTP 调用。""" |
| response = requests.post( |
| f"{BASE_URL}/v1/responses", |
| headers={"Authorization": f"Bearer {AUTH_KEY}"}, |
| json={ |
| "model": CODEX_IMAGE_MODEL, |
| "input": [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "input_text", "text": "我想做一张南京城市宣传海报图。"}, |
| ], |
| } |
| ], |
| "tools": [{"type": "image_generation"}], |
| "stream": True, |
| }, |
| stream=True, |
| timeout=300, |
| ) |
| self.assertEqual(response.status_code, 200, response.text) |
| self.assertTrue( |
| response.headers.get("content-type", "").startswith("text/event-stream"), |
| response.headers.get("content-type", ""), |
| ) |
| started_at = time.time() |
| saved_paths = [] |
| print("responses codex image stream status:") |
| print(response.status_code) |
| print("responses codex image stream chunks:") |
| for text in self._iter_sse_payloads(response): |
| print(f"{time.time() - started_at:6.2f}s {text}") |
| if not text.startswith("data:"): |
| continue |
| payload_text = text[5:].strip() |
| if payload_text == "[DONE]": |
| break |
| try: |
| payload = json.loads(payload_text) |
| except Exception: |
| continue |
| if payload.get("type") != "response.output_item.done": |
| continue |
| item = payload.get("item") or {} |
| if str(item.get("type") or "") != "image_generation_call": |
| continue |
| image_b64 = str(item.get("result") or "") |
| if image_b64: |
| saved_paths.append(save_image(image_b64, f"responses_codex_image_stream_{len(saved_paths) + 1}")) |
| print("responses codex image stream saved files:") |
| for path in saved_paths: |
| print(path) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|