Skip to content

Commit 62f490c

Browse files
Merge remote-tracking branch 'upstream/dev' into dev
2 parents a4f2eeb + ea9c58e commit 62f490c

3 files changed

Lines changed: 644 additions & 128 deletions

File tree

backend/open_webui/routers/openai.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,79 @@ def convert_to_azure_payload(url, payload: dict, api_version: str):
794794
return url, payload
795795

796796

797+
def convert_to_responses_payload(payload: dict) -> dict:
798+
"""
799+
Convert Chat Completions payload to Responses API format.
800+
801+
Chat Completions: { messages: [{role, content}], ... }
802+
Responses API: { input: [{type: "message", role, content: [...]}], instructions: "system" }
803+
"""
804+
messages = payload.pop("messages", [])
805+
806+
system_content = ""
807+
input_items = []
808+
809+
for msg in messages:
810+
role = msg.get("role", "user")
811+
content = msg.get("content", "")
812+
813+
# Check for stored output items (from previous Responses API turn)
814+
stored_output = msg.get("output")
815+
if stored_output and isinstance(stored_output, list):
816+
input_items.extend(stored_output)
817+
continue
818+
819+
if role == "system":
820+
if isinstance(content, str):
821+
system_content = content
822+
elif isinstance(content, list):
823+
system_content = "\n".join(p.get("text", "") for p in content if p.get("type") == "text")
824+
continue
825+
826+
# Convert content format
827+
text_type = "output_text" if role == "assistant" else "input_text"
828+
829+
if isinstance(content, str):
830+
content_parts = [{"type": text_type, "text": content}]
831+
elif isinstance(content, list):
832+
content_parts = []
833+
for part in content:
834+
if part.get("type") == "text":
835+
content_parts.append({"type": text_type, "text": part.get("text", "")})
836+
elif part.get("type") == "image_url":
837+
url_data = part.get("image_url", {})
838+
url = url_data.get("url", "") if isinstance(url_data, dict) else url_data
839+
content_parts.append({"type": "input_image", "image_url": url})
840+
else:
841+
content_parts = [{"type": text_type, "text": str(content)}]
842+
843+
input_items.append({
844+
"type": "message",
845+
"role": role,
846+
"content": content_parts
847+
})
848+
849+
responses_payload = {**payload, "input": input_items}
850+
851+
if system_content:
852+
responses_payload["instructions"] = system_content
853+
854+
if "max_tokens" in responses_payload:
855+
responses_payload["max_output_tokens"] = responses_payload.pop("max_tokens")
856+
857+
return responses_payload
858+
859+
860+
861+
def convert_responses_result(response: dict) -> dict:
862+
"""
863+
Convert non-streaming Responses API result.
864+
Just add done flag - pass through raw response, frontend handles output.
865+
"""
866+
response["done"] = True
867+
return response
868+
869+
797870
@router.post("/chat/completions")
798871
async def generate_chat_completion(
799872
request: Request,
@@ -915,6 +988,8 @@ async def generate_chat_completion(
915988
request, url, key, api_config, metadata, user=user
916989
)
917990

991+
is_responses = api_config.get("api_type") == "responses"
992+
918993
if api_config.get("azure", False):
919994
api_version = api_config.get("api_version", "2023-03-15-preview")
920995
request_url, payload = convert_to_azure_payload(url, payload, api_version)
@@ -925,9 +1000,18 @@ async def generate_chat_completion(
9251000
headers["api-key"] = key
9261001

9271002
headers["api-version"] = api_version
928-
request_url = f"{request_url}/chat/completions?api-version={api_version}"
1003+
1004+
if is_responses:
1005+
payload = convert_to_responses_payload(payload)
1006+
request_url = f"{request_url}/responses?api-version={api_version}"
1007+
else:
1008+
request_url = f"{request_url}/chat/completions?api-version={api_version}"
9291009
else:
930-
request_url = f"{url}/chat/completions"
1010+
if is_responses:
1011+
payload = convert_to_responses_payload(payload)
1012+
request_url = f"{url}/responses"
1013+
else:
1014+
request_url = f"{url}/chat/completions"
9311015

9321016
payload = json.dumps(payload)
9331017

@@ -974,6 +1058,10 @@ async def generate_chat_completion(
9741058
else:
9751059
return PlainTextResponse(status_code=r.status, content=response)
9761060

1061+
# Convert Responses API result to simple format
1062+
if is_responses and isinstance(response, dict):
1063+
response = convert_responses_result(response)
1064+
9771065
return response
9781066
except Exception as e:
9791067
log.exception(e)

0 commit comments

Comments
 (0)