mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(azure): match deployment segments case-insensitively and keep relay helpers immutable
This commit is contained in:
parent
5e056a264e
commit
a892e67c40
4 changed files with 30 additions and 13 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import re
|
||||
from collections.abc import Callable, Collection, Mapping, Sequence
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Final, Optional
|
||||
|
||||
import httpx
|
||||
|
|
@ -55,9 +56,7 @@ def logged_responses_stream(all_chunks: Sequence[str], logging_obj: Logging) ->
|
|||
"""A streaming logging object assembles the logged response from the terminal event, not from its body."""
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
|
||||
terminal_event: Final = OpenAIResponsesAPIConfig.parse_terminal_event_from_stream_chunks(
|
||||
all_chunks=list(all_chunks)
|
||||
)
|
||||
terminal_event: Final = OpenAIResponsesAPIConfig.parse_terminal_event_from_stream_chunks(all_chunks=all_chunks)
|
||||
if terminal_event is None:
|
||||
return None
|
||||
logging_obj.call_type = (
|
||||
|
|
@ -83,7 +82,11 @@ def foreign_azure_deployment(
|
|||
if match is None:
|
||||
return None
|
||||
deployment: Final = match.group(1)
|
||||
return None if deployment == model_group or deployment in served_models() else deployment
|
||||
folded: Final = deployment.casefold()
|
||||
if folded == model_group.casefold():
|
||||
return None
|
||||
served: Final = frozenset(name.casefold() for name in served_models())
|
||||
return None if folded in served else deployment
|
||||
|
||||
|
||||
def without_api_version(api_base: str) -> str:
|
||||
|
|
@ -119,7 +122,9 @@ class AzurePassthroughConfig(BasePassthroughConfig):
|
|||
relay_base: Final = without_api_version(base_target_url) if caller_api_version else base_target_url
|
||||
complete_url: Final = BaseAzureLLM._get_base_azure_url(
|
||||
api_base=relay_base,
|
||||
litellm_params={**litellm_params, "api_version": caller_api_version or litellm_params.get("api_version")},
|
||||
litellm_params=MappingProxyType(
|
||||
{**litellm_params, "api_version": caller_api_version or litellm_params.get("api_version")}
|
||||
),
|
||||
route=native_endpoint,
|
||||
)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -620,7 +620,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
return event_pydantic_model.model_construct(**parsed_chunk)
|
||||
|
||||
@staticmethod
|
||||
def parse_terminal_event_from_stream_chunks(all_chunks: list[str]) -> ResponsesTerminalEvent | None:
|
||||
def parse_terminal_event_from_stream_chunks(all_chunks: Sequence[str]) -> ResponsesTerminalEvent | None:
|
||||
for chunk_str in reversed(all_chunks):
|
||||
for event_model in (ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@ def is_passthrough_request_using_router_model(request_body: dict, llm_router: li
|
|||
return False
|
||||
|
||||
|
||||
class RelayRejection(TypedDict):
|
||||
error: ReadOnly[str]
|
||||
|
||||
|
||||
def _deployment_model_name(litellm_params: LiteLLMParamsTypedDict) -> str:
|
||||
model: Final = litellm_params.get("model", "")
|
||||
try:
|
||||
|
|
@ -1543,13 +1547,11 @@ async def _relay_azure_router_model(
|
|||
endpoint, model, lambda: _models_served_by_group(llm_router, model)
|
||||
)
|
||||
if foreign_deployment is not None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": f"deployment '{foreign_deployment}' in the path is not served by model group '{model}'; "
|
||||
"put the model group name in the deployments segment"
|
||||
},
|
||||
)
|
||||
rejection: Final[RelayRejection] = {
|
||||
"error": f"deployment '{foreign_deployment}' in the path is not served by model group '{model}'; "
|
||||
"put the model group name in the deployments segment"
|
||||
}
|
||||
raise HTTPException(status_code=400, detail=rejection)
|
||||
try:
|
||||
result: Final = await llm_router.allm_passthrough_route(
|
||||
model=model,
|
||||
|
|
|
|||
|
|
@ -456,16 +456,26 @@ def test_azure_passthrough_is_streaming_request_reads_the_stream_flag(request_da
|
|||
("gpt/openai/deployments/gpt/chat/completions", None),
|
||||
("openai/deployments/gpt/chat/completions", None),
|
||||
("gpt/openai/deployments/gpt-5.4-mini/chat/completions", None),
|
||||
("gpt/openai/deployments/GPT-5.4-MINI/chat/completions", None),
|
||||
("gpt/openai/deployments/Gpt/chat/completions", None),
|
||||
("gpt/models/chat/completions", None),
|
||||
("gpt/openai/deployments/gpt-5.4/chat/completions", "gpt-5.4"),
|
||||
("gpt/openai/deployments/other-group/chat/completions", "other-group"),
|
||||
("openai/deployments/victim/gpt/chat/completions", "victim"),
|
||||
("gpt/openai/deployments/GPT-5.4/chat/completions", "GPT-5.4"),
|
||||
],
|
||||
)
|
||||
def test_foreign_azure_deployment_names_a_segment_outside_the_group(endpoint, expected):
|
||||
assert foreign_azure_deployment(endpoint, "gpt", lambda: frozenset({"gpt-5.4-mini"})) == expected
|
||||
|
||||
|
||||
def test_foreign_azure_deployment_skips_the_router_when_the_segment_is_the_group_itself():
|
||||
def served_models():
|
||||
raise AssertionError("the router must not be consulted for the group's own name")
|
||||
|
||||
assert foreign_azure_deployment("gpt/openai/deployments/Gpt/chat/completions", "gpt", served_models) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint, expected",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue