chatgpt2api / services /protocol /openai_v1_response.py
tx1538's picture
Upload 179 files
9d7ddb9 verified
Raw
History Blame
8.6 kB
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)