from __future__ import annotations import base64 import time import uuid from typing import Any, Iterable, Iterator from fastapi import HTTPException from services.protocol.conversation import ( ConversationRequest, ImageOutput, encode_images, stream_image_outputs_with_pool, stream_text_deltas, text_backend, ) from utils.helper import extract_image_from_message_content, extract_response_prompt, has_response_image_generation_tool def is_text_response_request(body: dict[str, Any]) -> bool: return not has_response_image_generation_tool(body) def extract_response_image(input_value: object) -> tuple[bytes, str] | None: if isinstance(input_value, dict): images = extract_image_from_message_content(input_value.get("content")) return images[0] if images else None if not isinstance(input_value, list): return None for item in reversed(input_value): if isinstance(item, dict) and str(item.get("type") or "").strip() == "input_image": image_url = str(item.get("image_url") or "") if image_url.startswith("data:"): header, _, data = image_url.partition(",") mime = header.split(";")[0].removeprefix("data:") return base64.b64decode(data), mime or "image/png" if isinstance(item, dict): images = extract_image_from_message_content(item.get("content")) if images: return images[0] return None def messages_from_input(input_value: object, instructions: object = None) -> list[dict[str, Any]]: messages: list[dict[str, Any]] = [] system_text = str(instructions or "").strip() if system_text: messages.append({"role": "system", "content": system_text}) if isinstance(input_value, str): if input_value.strip(): messages.append({"role": "user", "content": input_value.strip()}) return messages if isinstance(input_value, dict): messages.append({ "role": str(input_value.get("role") or "user"), "content": extract_response_prompt([input_value]) or input_value.get("content") or "", }) return messages if isinstance(input_value, list): if all(isinstance(item, dict) and item.get("type") for item in input_value): text = extract_response_prompt(input_value) if text: messages.append({"role": "user", "content": text}) return messages for item in input_value: if isinstance(item, dict): messages.append({ "role": str(item.get("role") or "user"), "content": extract_response_prompt([item]) or item.get("content") or "", }) return messages def text_output_item(text: str, item_id: str | None = None, status: str = "completed") -> dict[str, Any]: return { "id": item_id or f"msg_{uuid.uuid4().hex}", "type": "message", "status": status, "role": "assistant", "content": [{"type": "output_text", "text": text, "annotations": []}], } def image_output_items(prompt: str, data: list[dict[str, Any]], item_id: str | None = None) -> list[dict[str, Any]]: output = [] for item in data: b64_json = str(item.get("b64_json") or "").strip() if b64_json: output.append({ "id": item_id or f"ig_{len(output) + 1}", "type": "image_generation_call", "status": "completed", "result": b64_json, "revised_prompt": str(item.get("revised_prompt") or prompt).strip() or prompt, }) return output def response_created(response_id: str, model: str, created: int) -> dict[str, Any]: return { "type": "response.created", "response": { "id": response_id, "object": "response", "created_at": created, "status": "in_progress", "error": None, "incomplete_details": None, "model": model, "output": [], "parallel_tool_calls": False, }, } def response_completed(response_id: str, model: str, created: int, output: list[dict[str, Any]]) -> dict[str, Any]: return { "type": "response.completed", "response": { "id": response_id, "object": "response", "created_at": created, "status": "completed", "error": None, "incomplete_details": None, "model": model, "output": output, "parallel_tool_calls": False, }, } def stream_text_response(backend, body: dict[str, Any]) -> Iterator[dict[str, Any]]: model = str(body.get("model") or "auto").strip() or "auto" messages = messages_from_input(body.get("input"), body.get("instructions")) response_id = f"resp_{uuid.uuid4().hex}" item_id = f"msg_{uuid.uuid4().hex}" created = int(time.time()) full_text = "" yield response_created(response_id, model, created) yield {"type": "response.output_item.added", "output_index": 0, "item": text_output_item("", item_id, "in_progress")} request = ConversationRequest(model=model, messages=messages) for delta in stream_text_deltas(backend, request): full_text += delta yield {"type": "response.output_text.delta", "item_id": item_id, "output_index": 0, "content_index": 0, "delta": delta} yield {"type": "response.output_text.done", "item_id": item_id, "output_index": 0, "content_index": 0, "text": full_text} item = text_output_item(full_text, item_id, "completed") yield {"type": "response.output_item.done", "output_index": 0, "item": item} yield response_completed(response_id, model, created, [item]) def stream_image_response(image_outputs: Iterable[ImageOutput], prompt: str, model: str) -> Iterator[dict[str, Any]]: response_id = f"resp_{uuid.uuid4().hex}" created = int(time.time()) yield response_created(response_id, model, created) for output in image_outputs: if output.kind == "message": text = output.text item = text_output_item(text) yield {"type": "response.output_text.delta", "item_id": item["id"], "output_index": 0, "content_index": 0, "delta": text} yield {"type": "response.output_text.done", "item_id": item["id"], "output_index": 0, "content_index": 0, "text": text} yield {"type": "response.output_item.done", "output_index": 0, "item": item} yield response_completed(response_id, model, created, [item]) return if output.kind != "result": continue items = image_output_items(prompt, output.data) if items: item = items[0] yield {"type": "response.output_item.done", "output_index": 0, "item": item} yield response_completed(response_id, model, created, [item]) return raise RuntimeError("image generation failed") def collect_response(events: Iterable[dict[str, Any]]) -> dict[str, Any]: completed = {} for event in events: if event.get("type") == "response.completed": completed = event.get("response") if isinstance(event.get("response"), dict) else {} if not completed: raise RuntimeError("response generation failed") return completed def response_events(body: dict[str, Any]) -> Iterator[dict[str, Any]]: if is_text_response_request(body): yield from stream_text_response(text_backend(), body) return prompt = extract_response_prompt(body.get("input")) if not prompt: raise HTTPException(status_code=400, detail={"error": "input text is required"}) model = str(body.get("model") or "gpt-image-2").strip() or "gpt-image-2" image_info = extract_response_image(body.get("input")) if image_info: image_data, mime_type = image_info images = encode_images([(image_data, "image.png", mime_type)]) else: images = None image_outputs = stream_image_outputs_with_pool(ConversationRequest( prompt=prompt, model=model, size=None if images else "1:1", response_format="b64_json", images=images, )) yield from stream_image_response(image_outputs, prompt, model) def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]: events = response_events(body) if body.get("stream"): return events return collect_response(events)