mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
Merge a6388d8f69 into 77765fd302
This commit is contained in:
commit
bd77b07a7f
2 changed files with 163 additions and 1 deletions
|
|
@ -8,7 +8,7 @@ This module handles transforming between:
|
|||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Final, cast
|
||||
from typing import TYPE_CHECKING, Any, Final, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -23,7 +23,13 @@ from litellm.types.llms.openai import (
|
|||
ResponsesAPIResponse,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openai.types.responses.response_text_config_param import (
|
||||
ResponseTextConfigParam as ResponseText,
|
||||
)
|
||||
|
||||
_STEP_TYPE_ROLES: Final = MappingProxyType({"user_input": "user", "model_output": "assistant"})
|
||||
_JSON_MIME_TYPE: Final = "application/json"
|
||||
|
||||
|
||||
class LiteLLMResponsesInteractionsConfig:
|
||||
|
|
@ -77,6 +83,13 @@ class LiteLLMResponsesInteractionsConfig:
|
|||
if "max_output_tokens" in generation_config:
|
||||
responses_request["max_output_tokens"] = generation_config["max_output_tokens"]
|
||||
|
||||
text_param: Final = LiteLLMResponsesInteractionsConfig._transform_response_format_to_text_param(
|
||||
response_format=optional_params.get("response_format"),
|
||||
response_mime_type=optional_params.get("response_mime_type"),
|
||||
)
|
||||
if text_param is not None:
|
||||
responses_request["text"] = text_param
|
||||
|
||||
# Pass through other optional params that match
|
||||
passthrough_params: Final = ["stream", "store", "metadata", "user"]
|
||||
for param in passthrough_params:
|
||||
|
|
@ -88,6 +101,63 @@ class LiteLLMResponsesInteractionsConfig:
|
|||
|
||||
return responses_request
|
||||
|
||||
@staticmethod
|
||||
def _transform_response_format_to_text_param(
|
||||
response_format: object,
|
||||
response_mime_type: str | None,
|
||||
) -> "ResponseText | None":
|
||||
"""
|
||||
Transform an Interactions API JSON constraint to the Responses API `text` parameter.
|
||||
|
||||
The constraint arrives in one of two shapes:
|
||||
- current schema: one or more polymorphic entries,
|
||||
e.g. {"type": "text", "mime_type": "application/json", "schema": {...}}
|
||||
- legacy schema: the bare JSON schema in `response_format`, with the mime type
|
||||
in `response_mime_type`
|
||||
|
||||
The legacy check mirrors GoogleAIStudioInteractionsConfig.transform_interactions_request,
|
||||
so both surfaces agree on which shape they are looking at.
|
||||
"""
|
||||
is_legacy: Final = bool(
|
||||
response_mime_type
|
||||
and not isinstance(response_format, list)
|
||||
and (not isinstance(response_format, Mapping) or "mime_type" not in response_format)
|
||||
)
|
||||
if is_legacy:
|
||||
return LiteLLMResponsesInteractionsConfig._build_text_param(
|
||||
mime_type=response_mime_type,
|
||||
schema=response_format,
|
||||
)
|
||||
|
||||
entries: Final = response_format if isinstance(response_format, list) else [response_format]
|
||||
text_entry: Final = next(
|
||||
(entry for entry in entries if isinstance(entry, Mapping) and entry.get("type") == "text"),
|
||||
None,
|
||||
)
|
||||
if text_entry is None:
|
||||
return None
|
||||
return LiteLLMResponsesInteractionsConfig._build_text_param(
|
||||
mime_type=text_entry.get("mime_type"),
|
||||
schema=text_entry.get("schema"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_text_param(mime_type: object, schema: object) -> "ResponseText | None":
|
||||
if mime_type is not None and mime_type != _JSON_MIME_TYPE:
|
||||
return None
|
||||
if isinstance(schema, Mapping) and schema:
|
||||
return {
|
||||
"format": {
|
||||
"type": "json_schema",
|
||||
"name": "response_schema",
|
||||
"schema": dict(schema),
|
||||
"strict": False,
|
||||
}
|
||||
}
|
||||
if mime_type == _JSON_MIME_TYPE:
|
||||
return {"format": {"type": "json_object"}}
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _transform_interactions_input_to_responses_input(
|
||||
input: InteractionInput,
|
||||
|
|
|
|||
|
|
@ -98,3 +98,95 @@ class TestBridgeInputTransformation:
|
|||
[{"type": "user_input", "content": [image_part]}]
|
||||
)
|
||||
assert transformed == [{"role": "user", "content": [image_part]}]
|
||||
|
||||
|
||||
class TestBridgeResponseFormat:
|
||||
"""The bridge used to drop response_format and response_mime_type entirely, so a caller
|
||||
who asked the Interactions API for JSON silently got free text back from the Responses API.
|
||||
"""
|
||||
|
||||
def test_json_schema_response_format_becomes_text_format(self):
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"greeting": {"type": "string"}, "score": {"type": "number"}},
|
||||
"required": ["greeting", "score"],
|
||||
}
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Say hello and give a score of 1.",
|
||||
optional_params={
|
||||
"response_format": {"type": "text", "mime_type": "application/json", "schema": schema},
|
||||
},
|
||||
)
|
||||
assert request["text"] == {
|
||||
"format": {
|
||||
"type": "json_schema",
|
||||
"name": "response_schema",
|
||||
"schema": schema,
|
||||
"strict": False,
|
||||
}
|
||||
}
|
||||
|
||||
def test_legacy_schema_and_response_mime_type_become_text_format(self):
|
||||
schema = {"type": "object", "properties": {"greeting": {"type": "string"}}}
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Say hello.",
|
||||
optional_params={"response_format": schema, "response_mime_type": "application/json"},
|
||||
)
|
||||
assert request["text"]["format"]["type"] == "json_schema"
|
||||
assert request["text"]["format"]["schema"] == schema
|
||||
|
||||
def test_json_mime_type_without_schema_becomes_json_object(self):
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Say hello.",
|
||||
optional_params={"response_mime_type": "application/json"},
|
||||
)
|
||||
assert request["text"] == {"format": {"type": "json_object"}}
|
||||
|
||||
def test_image_response_format_entry_is_skipped(self):
|
||||
schema = {"type": "object", "properties": {"caption": {"type": "string"}}}
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Describe this.",
|
||||
optional_params={
|
||||
"response_format": [
|
||||
{"type": "image", "aspect_ratio": "1:1"},
|
||||
{"type": "text", "mime_type": "application/json", "schema": schema},
|
||||
],
|
||||
},
|
||||
)
|
||||
assert request["text"]["format"]["schema"] == schema
|
||||
|
||||
def test_image_only_response_format_leaves_text_unset(self):
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Draw a cat.",
|
||||
optional_params={"response_format": [{"type": "image", "aspect_ratio": "1:1"}]},
|
||||
)
|
||||
assert "text" not in request
|
||||
|
||||
def test_non_json_mime_type_leaves_text_unset(self):
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Say hello.",
|
||||
optional_params={"response_format": {"type": "text", "mime_type": "text/plain"}},
|
||||
)
|
||||
assert "text" not in request
|
||||
|
||||
def test_unrecognized_entry_type_is_not_read_as_a_bare_schema(self):
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Say hello.",
|
||||
optional_params={"response_format": {"type": "audio", "mime_type": "audio/wav"}},
|
||||
)
|
||||
assert "text" not in request
|
||||
|
||||
def test_request_without_response_format_is_unchanged(self):
|
||||
request = LiteLLMResponsesInteractionsConfig.transform_interactions_request_to_responses_request(
|
||||
model="gemini-3.5-flash",
|
||||
input="Say hello.",
|
||||
optional_params={},
|
||||
)
|
||||
assert "text" not in request
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue