feat(proxy): add global raw response model opt-out

This commit is contained in:
mateo-berri 2026-11-04 18:54:37 -08:00
parent dc741a6e16
commit 9a627285bc
7 changed files with 60 additions and 5 deletions

View file

@ -2480,6 +2480,14 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
Documents all the fields supported by `general_settings` in config.yaml
"""
return_raw_model_name: bool = Field(
False,
description=(
"Preserve the upstream response model instead of replacing it with the requested model alias. "
"Applies to streaming and non-streaming responses. The per-request raw-model opt-out remains supported."
),
)
use_spend_logs_ledger: bool = Field(
False,
description="Apply request accounting from stored SpendLogs deliveries. Enable only after all accounting writers are upgraded and ledger activation gates pass.",

View file

@ -357,8 +357,10 @@ def resolve_litellm_call_id(client_call_id: str | None) -> str:
return str(uuid.uuid4())
def should_return_raw_model_name(request_data: dict[str, object]) -> bool:
return any(
def should_return_raw_model_name(
request_data: dict[str, object], general_settings: Mapping[str, object] | None = None
) -> bool:
return (general_settings is not None and general_settings.get("return_raw_model_name") is True) or any(
isinstance(metadata, dict) and metadata.get(RETURN_RAW_MODEL_NAME_METADATA_KEY) is True
for metadata in (request_data.get("metadata"), request_data.get("litellm_metadata"))
)
@ -3314,7 +3316,9 @@ class ProxyBaseLLMRequestProcessing:
proxy_logging_obj=proxy_logging_obj,
request=request,
restamp_model=(
None if should_return_raw_model_name(self.data) else requested_model_from_client
None
if should_return_raw_model_name(self.data, general_settings)
else requested_model_from_client
),
)
selected_data_generator = wrap_sse_stream_with_keepalive_pings(
@ -3480,7 +3484,7 @@ class ProxyBaseLLMRequestProcessing:
response_obj=response,
requested_model=requested_model_from_client,
log_context=f"litellm_call_id={logging_obj.litellm_call_id}",
return_raw_model_name=should_return_raw_model_name(self.data),
return_raw_model_name=should_return_raw_model_name(self.data, general_settings),
)
fastapi_response.headers.update(

View file

@ -199,6 +199,7 @@ model_list:
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
return_raw_model_name: false
# Opt-in: let CheckBatchCost track cost for unmanaged batches created with a raw
# gs:// (Vertex) or s3:// (Bedrock) input_file_id. Requires a matching deployment
# configured for the batched model. Defaults to false.

View file

@ -10268,8 +10268,9 @@ def _restamp_streaming_chunk_model(
model_mismatch_logged: bool,
fallback_was_attempted: bool = False,
fallback_model_from_metadata: str | None = None,
response_model_settings: Mapping[str, object] | None = None,
) -> tuple[object, bool]:
if should_return_raw_model_name(request_data):
if should_return_raw_model_name(request_data, response_model_settings):
return chunk, model_mismatch_logged
target_model: Final = fallback_model_from_metadata if fallback_was_attempted else requested_model_from_client
@ -10918,6 +10919,9 @@ async def async_data_generator(
model_mismatch_logged=model_mismatch_logged,
fallback_was_attempted=fallback_was_attempted,
fallback_model_from_metadata=fallback_model_from_metadata,
response_model_settings=cast( # cast-ok: proxy configuration validates the legacy untyped settings global
Mapping[str, object], general_settings
),
)
if strip_stream_usage and is_injected_stream_usage_artifact(chunk):
@ -20592,6 +20596,7 @@ _GENERAL_SETTINGS_CONFIG_LIST_FIELD_TYPES: Final[Mapping[str, str]] = MappingPro
"mcp_xff_num_trusted_hops": "Integer",
"mcp_prefer_client_id_metadata_document": "Boolean",
"always_include_stream_usage": "Boolean",
"return_raw_model_name": "Boolean",
"forward_client_headers_to_llm_api": "Boolean",
"mcp_required_fields": "List",
"mcp_disabled_capabilities": "List",

View file

@ -320,6 +320,20 @@ def test_restamp_streaming_chunk_model_respects_raw_model_name_toggle(return_raw
assert logged is (not return_raw_model_name)
@pytest.mark.parametrize("global_opt_out", [False, True])
def test_restamp_streaming_chunk_model_respects_global_raw_model_name(global_opt_out: bool) -> None:
chunk: Final = _simple_chunk(model="provider-model")
new_chunk, logged = _restamp_streaming_chunk_model(
chunk=chunk,
requested_model_from_client="public-alias",
request_data={},
model_mismatch_logged=False,
response_model_settings={"return_raw_model_name": global_opt_out},
)
assert new_chunk.model == ("provider-model" if global_opt_out else "public-alias")
assert logged is (not global_opt_out)
def test_restamp_streaming_chunk_model_overrides_model_on_dict():
chunk = {"model": "internal", "choices": []}
new_chunk, logged = _restamp_streaming_chunk_model(

View file

@ -3277,6 +3277,23 @@ class TestOverrideOpenAIResponseModel:
def test_raw_model_name_toggle_metadata(self, request_data, expected):
assert should_return_raw_model_name(request_data) is expected
@pytest.mark.parametrize("global_opt_out", [False, True])
@pytest.mark.parametrize("request_opt_out", [False, True])
def test_global_raw_model_name_preserves_upstream_response(
self, global_opt_out: bool, request_opt_out: bool
) -> None:
response_obj: Final = {"model": "provider-model"}
_override_openai_response_model(
response_obj=response_obj,
requested_model="public-alias",
log_context="test_context",
return_raw_model_name=should_return_raw_model_name(
{"litellm_metadata": {RETURN_RAW_MODEL_NAME_METADATA_KEY: request_opt_out}},
{"return_raw_model_name": global_opt_out},
),
)
assert response_obj["model"] == ("provider-model" if global_opt_out or request_opt_out else "public-alias")
def test_override_model_preserves_fallback_model_when_fallback_occurred_object(
self,
):

View file

@ -30799,6 +30799,12 @@ export interface components {
* @default 3600
*/
responses_websocket_session_limit_seconds: number;
/**
* Return Raw Model Name
* @description Preserve the upstream response model instead of replacing it with the requested model alias. Applies to streaming and non-streaming responses. The per-request raw-model opt-out remains supported.
* @default false
*/
return_raw_model_name: boolean;
/**
* Return Wildcard Routes
* @description When true, `/v1/models` lists wildcard routes like `openai/*` unless the request passes `return_wildcard_routes=false`.