mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-15 04:19:08 +00:00
fix responses container id reuse routing
This commit is contained in:
parent
5b93ba0ada
commit
048ec5d082
4 changed files with 121 additions and 0 deletions
|
|
@ -585,6 +585,42 @@ class ResponsesAPIRequestUtils:
|
|||
decoded = ResponsesAPIRequestUtils._decode_container_id(container_id)
|
||||
return decoded.get("response_id", container_id)
|
||||
|
||||
@staticmethod
|
||||
def decode_container_ids_in_tools_for_request(
|
||||
tools: Optional[Iterable[Any]],
|
||||
) -> Optional[Iterable[Any]]:
|
||||
"""Decode LiteLLM-managed Responses API tool container IDs.
|
||||
|
||||
Returns the possibly updated tools with LiteLLM-managed container IDs
|
||||
replaced by the original provider-issued container IDs.
|
||||
"""
|
||||
if tools is None:
|
||||
return tools
|
||||
|
||||
updated_tools: List[Any] = []
|
||||
changed = False
|
||||
|
||||
for tool in tools:
|
||||
updated_tool = tool.copy() if isinstance(tool, dict) else tool
|
||||
if isinstance(updated_tool, dict):
|
||||
container_id = updated_tool.get("container")
|
||||
if isinstance(container_id, str):
|
||||
decoded = ResponsesAPIRequestUtils._decode_container_id(
|
||||
container_id
|
||||
)
|
||||
original_id = decoded.get("response_id", container_id)
|
||||
if original_id != container_id:
|
||||
updated_tool["container"] = original_id
|
||||
changed = True
|
||||
updated_tools.append(updated_tool)
|
||||
|
||||
if not changed:
|
||||
if isinstance(tools, list):
|
||||
return tools
|
||||
return updated_tools
|
||||
|
||||
return updated_tools
|
||||
|
||||
@staticmethod
|
||||
def _encode_container_ids_in_annotations(
|
||||
annotations: Any,
|
||||
|
|
|
|||
|
|
@ -4445,6 +4445,8 @@ class Router:
|
|||
|
||||
from litellm.litellm_core_utils.core_helpers import safe_deep_copy
|
||||
|
||||
self._decode_responses_api_tool_container_ids(kwargs)
|
||||
|
||||
# Snapshot the request kwargs before _ageneric_api_call_with_fallbacks
|
||||
# mutates them. A shallow copy alone is not enough: the primary
|
||||
# attempt mutates nested dicts in place — notably `litellm_metadata`,
|
||||
|
|
@ -4476,6 +4478,19 @@ class Router:
|
|||
)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _decode_responses_api_tool_container_ids(kwargs: Dict[str, Any]) -> None:
|
||||
from litellm.responses.utils import ResponsesAPIRequestUtils
|
||||
|
||||
# Keep authorization and deployment selection anchored to the requested
|
||||
# model. Managed container IDs are supplied by the client, so only the
|
||||
# provider-issued container ID is safe to recover from them here.
|
||||
tools = ResponsesAPIRequestUtils.decode_container_ids_in_tools_for_request(
|
||||
kwargs.get("tools")
|
||||
)
|
||||
if tools is not kwargs.get("tools"):
|
||||
kwargs["tools"] = tools
|
||||
|
||||
def _generic_api_call_with_fallbacks(self, model: str, original_function: Callable, **kwargs):
|
||||
"""
|
||||
Make a generic LLM API call through the router, this allows you to use retries/fallbacks with litellm router
|
||||
|
|
@ -5514,6 +5529,8 @@ class Router:
|
|||
client: Optional[Any] = None,
|
||||
**kwargs,
|
||||
):
|
||||
if call_type == "responses":
|
||||
self._decode_responses_api_tool_container_ids(kwargs)
|
||||
return self._generic_api_call_with_fallbacks(original_function=original_function, **kwargs)
|
||||
|
||||
return sync_wrapper
|
||||
|
|
@ -5617,6 +5634,8 @@ class Router:
|
|||
**kwargs,
|
||||
)
|
||||
elif call_type == "aresponses":
|
||||
if custom_llm_provider and "custom_llm_provider" not in kwargs:
|
||||
kwargs["custom_llm_provider"] = custom_llm_provider
|
||||
return await self._aresponses_with_streaming_fallbacks(
|
||||
original_function=original_function,
|
||||
**kwargs,
|
||||
|
|
|
|||
|
|
@ -767,6 +767,44 @@ async def test_init_responses_api_endpoints():
|
|||
assert second_call_kwargs["response_id"] == "resp_claude_123"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_router_aresponses_decodes_managed_tool_container_id_without_routing_override():
|
||||
from litellm.responses.utils import ResponsesAPIRequestUtils
|
||||
|
||||
router = Router(model_list=[])
|
||||
router._ageneric_api_call_with_fallbacks = AsyncMock(return_value={"id": "resp_1"})
|
||||
|
||||
encoded_container_id = ResponsesAPIRequestUtils._build_container_id(
|
||||
custom_llm_provider="azure",
|
||||
model_id="azure-deployment-id",
|
||||
container_id="cntr_native_123",
|
||||
)
|
||||
tools = [{"type": "code_interpreter", "container": encoded_container_id}]
|
||||
|
||||
await router.aresponses(
|
||||
model="logical-model",
|
||||
input="hello",
|
||||
tools=tools,
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
|
||||
call_kwargs = router._ageneric_api_call_with_fallbacks.call_args.kwargs
|
||||
assert call_kwargs["model"] == "logical-model"
|
||||
assert call_kwargs["custom_llm_provider"] == "openai"
|
||||
assert call_kwargs["tools"][0]["container"] == "cntr_native_123"
|
||||
assert tools[0]["container"] == encoded_container_id
|
||||
|
||||
kwargs = {
|
||||
"model": "logical-model",
|
||||
"custom_llm_provider": "openai",
|
||||
"tools": [{"type": "code_interpreter", "container": encoded_container_id}],
|
||||
}
|
||||
router._decode_responses_api_tool_container_ids(kwargs)
|
||||
assert kwargs["model"] == "logical-model"
|
||||
assert kwargs["custom_llm_provider"] == "openai"
|
||||
assert kwargs["tools"][0]["container"] == "cntr_native_123"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_init_vector_store_api_endpoints():
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -123,6 +123,34 @@ class TestResponsesAPIRequestUtils:
|
|||
)
|
||||
assert result_plain == plain_id
|
||||
|
||||
def test_decode_container_ids_in_tools_for_request(self):
|
||||
encoded_container_id = ResponsesAPIRequestUtils._build_container_id(
|
||||
custom_llm_provider="azure",
|
||||
model_id="azure-deployment-id",
|
||||
container_id="cntr_native_123",
|
||||
)
|
||||
tools = [
|
||||
{"type": "code_interpreter", "container": encoded_container_id},
|
||||
{"type": "file_search", "vector_store_ids": ["vs_123"]},
|
||||
]
|
||||
|
||||
updated_tools = (
|
||||
ResponsesAPIRequestUtils.decode_container_ids_in_tools_for_request(tools)
|
||||
)
|
||||
|
||||
assert updated_tools is not tools
|
||||
assert isinstance(updated_tools, list)
|
||||
assert updated_tools[0]["container"] == "cntr_native_123"
|
||||
assert tools[0]["container"] == encoded_container_id
|
||||
|
||||
plain_tools = [{"type": "file_search", "vector_store_ids": ["vs_123"]}]
|
||||
plain_result = (
|
||||
ResponsesAPIRequestUtils.decode_container_ids_in_tools_for_request(
|
||||
plain_tools
|
||||
)
|
||||
)
|
||||
assert plain_result is plain_tools
|
||||
|
||||
def test_update_responses_api_response_id_with_model_id_handles_dict(self):
|
||||
"""Ensure _update_responses_api_response_id_with_model_id works with dict input"""
|
||||
responses_api_response = {"id": "resp_abc123"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue