mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Merge b7d3af4cba into 49affa7c01
This commit is contained in:
commit
12cd6ee1b4
3 changed files with 375 additions and 0 deletions
|
|
@ -1214,6 +1214,11 @@ def responses(
|
|||
|
||||
# Decode any litellm-encoded encrypted-content item IDs back to their original IDs
|
||||
input = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input(input)
|
||||
input = ResponsesAPIRequestUtils._normalize_function_call_ids_in_input(
|
||||
request_input=input,
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
# Call the handler with _is_async flag instead of directly calling the async handler
|
||||
if custom_llm_provider is None:
|
||||
|
|
|
|||
|
|
@ -534,6 +534,114 @@ class ResponsesAPIRequestUtils:
|
|||
|
||||
return request_input
|
||||
|
||||
@staticmethod
|
||||
def _normalize_call_id_for_provider(
|
||||
call_id: str,
|
||||
model: str | None,
|
||||
custom_llm_provider: str | None,
|
||||
) -> str:
|
||||
"""Strip Gemini thought signatures from call_id when replaying to non-Gemini models."""
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
from litellm.utils import _is_gemini_model, _remove_thought_signature_from_id
|
||||
|
||||
if _is_gemini_model(model=model, custom_llm_provider=custom_llm_provider):
|
||||
return call_id
|
||||
return _remove_thought_signature_from_id(call_id, THOUGHT_SIGNATURE_SEPARATOR)
|
||||
|
||||
@staticmethod
|
||||
def _provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider: str | None,
|
||||
model: str | None = None,
|
||||
) -> bool:
|
||||
"""True when the target Responses adapter expects OpenAI fc_ item ids."""
|
||||
if custom_llm_provider is None:
|
||||
return False
|
||||
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider=custom_llm_provider,
|
||||
model=model,
|
||||
)
|
||||
return isinstance(config, OpenAIResponsesAPIConfig)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_function_call_item_id_for_provider(
|
||||
item_id: str,
|
||||
model: str | None,
|
||||
custom_llm_provider: str | None,
|
||||
) -> str:
|
||||
"""Rewrite foreign provider function_call item ids to OpenAI fc_ format."""
|
||||
item_id = ResponsesAPIRequestUtils._normalize_call_id_for_provider(
|
||||
call_id=item_id,
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
if not ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
model=model,
|
||||
):
|
||||
return item_id
|
||||
|
||||
if item_id.startswith("call_"):
|
||||
return f"fc_{item_id[len('call_') :]}"
|
||||
if item_id.startswith("tooluse_"):
|
||||
return f"fc_{item_id[len('tooluse_') :]}"
|
||||
if item_id.startswith("toolu_vrtx_"):
|
||||
return f"fc_{item_id[len('toolu_vrtx_') :]}"
|
||||
|
||||
return item_id
|
||||
|
||||
@staticmethod
|
||||
def _normalize_function_call_ids_in_input(
|
||||
request_input: Any,
|
||||
model: str | None,
|
||||
custom_llm_provider: str | None,
|
||||
) -> Any:
|
||||
"""Normalize function_call / function_call_output IDs before upstream replay.
|
||||
|
||||
- Strips Gemini thought signatures from call_id for non-Gemini targets.
|
||||
- Rewrites foreign function_call item ids (call_, tooluse_) to fc_ for OpenAI-compatible targets.
|
||||
"""
|
||||
if not isinstance(request_input, list):
|
||||
return request_input
|
||||
|
||||
for item in request_input:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
|
||||
item_type = item.get("type")
|
||||
if item_type == "function_call":
|
||||
call_id = item.get("call_id")
|
||||
if call_id and isinstance(call_id, str):
|
||||
item["call_id"] = ResponsesAPIRequestUtils._normalize_call_id_for_provider(
|
||||
call_id=call_id,
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
item_id = item.get("id")
|
||||
if item_id and isinstance(item_id, str):
|
||||
item["id"] = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id=item_id,
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
elif item_type == "function_call_output":
|
||||
call_id = item.get("call_id")
|
||||
if call_id and isinstance(call_id, str):
|
||||
item["call_id"] = ResponsesAPIRequestUtils._normalize_call_id_for_provider(
|
||||
call_id=call_id,
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
return request_input
|
||||
|
||||
@staticmethod
|
||||
def _build_responses_api_response_id(
|
||||
custom_llm_provider: str | None,
|
||||
|
|
|
|||
|
|
@ -199,6 +199,268 @@ class TestResponsesAPIRequestUtils:
|
|||
assert decoded.get("custom_llm_provider") == "azure"
|
||||
assert decoded.get("response_id") == "cntr_x"
|
||||
|
||||
def test_normalize_call_id_strips_thought_signature_for_non_gemini(self):
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
|
||||
call_id = f"call_abc123{THOUGHT_SIGNATURE_SEPARATOR}sig_xyz"
|
||||
result = ResponsesAPIRequestUtils._normalize_call_id_for_provider(
|
||||
call_id=call_id,
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
assert result == "call_abc123"
|
||||
|
||||
def test_normalize_call_id_preserves_thought_signature_for_gemini(self):
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
|
||||
call_id = f"call_abc123{THOUGHT_SIGNATURE_SEPARATOR}sig_xyz"
|
||||
result = ResponsesAPIRequestUtils._normalize_call_id_for_provider(
|
||||
call_id=call_id,
|
||||
model="gemini-2.5-flash",
|
||||
custom_llm_provider="gemini",
|
||||
)
|
||||
assert result == call_id
|
||||
|
||||
def test_normalize_function_call_item_id_rewrites_for_openai(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="call_abc123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
assert result == "fc_abc123"
|
||||
|
||||
result_tooluse = (
|
||||
ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="tooluse_abc123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
)
|
||||
assert result_tooluse == "fc_abc123"
|
||||
|
||||
result_vertex = (
|
||||
ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="toolu_vrtx_abc123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
)
|
||||
assert result_vertex == "fc_abc123"
|
||||
|
||||
def test_normalize_function_call_item_id_rewrites_for_azure(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="call_abc123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="azure",
|
||||
)
|
||||
assert result == "fc_abc123"
|
||||
|
||||
result_tooluse = (
|
||||
ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="tooluse_abc123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="azure",
|
||||
)
|
||||
)
|
||||
assert result_tooluse == "fc_abc123"
|
||||
|
||||
def test_normalize_function_call_item_id_rewrites_for_xai(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="call_abc123",
|
||||
model="grok-4",
|
||||
custom_llm_provider="xai",
|
||||
)
|
||||
assert result == "fc_abc123"
|
||||
|
||||
result_tooluse = (
|
||||
ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="tooluse_abc123",
|
||||
model="grok-4",
|
||||
custom_llm_provider="xai",
|
||||
)
|
||||
)
|
||||
assert result_tooluse == "fc_abc123"
|
||||
|
||||
def test_normalize_function_call_item_id_rewrites_for_hosted_vllm(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="toolu_vrtx_abc123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="hosted_vllm",
|
||||
)
|
||||
assert result == "fc_abc123"
|
||||
|
||||
def test_normalize_function_call_item_id_no_rewrite_for_anthropic(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="call_abc123",
|
||||
model="claude-3-5-sonnet",
|
||||
custom_llm_provider="anthropic",
|
||||
)
|
||||
assert result == "call_abc123"
|
||||
|
||||
def test_provider_uses_openai_function_call_item_ids(self):
|
||||
assert (
|
||||
ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider="openai",
|
||||
model="gpt-4o",
|
||||
)
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider="azure",
|
||||
model="gpt-4o",
|
||||
)
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider="xai",
|
||||
model="grok-4",
|
||||
)
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider="hosted_vllm",
|
||||
model="gpt-4o",
|
||||
)
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider="anthropic",
|
||||
model="claude-3-5-sonnet",
|
||||
)
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
ResponsesAPIRequestUtils._provider_uses_openai_function_call_item_ids(
|
||||
custom_llm_provider=None,
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
def test_normalize_function_call_item_id_leaves_unmatched_prefix(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="fc_already_normalized",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
assert result == "fc_already_normalized"
|
||||
|
||||
result_other = (
|
||||
ResponsesAPIRequestUtils._normalize_function_call_item_id_for_provider(
|
||||
item_id="random_id_123",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
)
|
||||
assert result_other == "random_id_123"
|
||||
|
||||
def test_normalize_function_call_ids_in_input(self):
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
|
||||
request_input = [
|
||||
{
|
||||
"type": "function_call",
|
||||
"id": "tooluse_xyz",
|
||||
"call_id": f"call_abc{THOUGHT_SIGNATURE_SEPARATOR}sig",
|
||||
"name": "get_weather",
|
||||
"arguments": "{}",
|
||||
},
|
||||
{
|
||||
"type": "function_call_output",
|
||||
"call_id": f"call_abc{THOUGHT_SIGNATURE_SEPARATOR}sig",
|
||||
"output": "sunny",
|
||||
},
|
||||
]
|
||||
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_ids_in_input(
|
||||
request_input=request_input,
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
|
||||
assert result[0]["id"] == "fc_xyz"
|
||||
assert result[0]["call_id"] == "call_abc"
|
||||
assert result[1]["call_id"] == "call_abc"
|
||||
|
||||
def test_normalize_function_call_ids_in_input_for_azure(self):
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
|
||||
request_input = [
|
||||
{
|
||||
"type": "function_call",
|
||||
"id": "call_abc123",
|
||||
"call_id": f"call_abc{THOUGHT_SIGNATURE_SEPARATOR}sig",
|
||||
"name": "get_weather",
|
||||
"arguments": "{}",
|
||||
},
|
||||
"skip-me",
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": "hi",
|
||||
},
|
||||
]
|
||||
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_ids_in_input(
|
||||
request_input=request_input,
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="azure",
|
||||
)
|
||||
|
||||
assert result[0]["id"] == "fc_abc123"
|
||||
assert result[0]["call_id"] == "call_abc"
|
||||
assert result[1] == "skip-me"
|
||||
assert result[2]["type"] == "message"
|
||||
|
||||
def test_normalize_function_call_ids_in_input_for_xai(self):
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
|
||||
request_input = [
|
||||
{
|
||||
"type": "function_call",
|
||||
"id": "tooluse_xyz",
|
||||
"call_id": f"call_abc{THOUGHT_SIGNATURE_SEPARATOR}sig",
|
||||
"name": "get_weather",
|
||||
"arguments": "{}",
|
||||
},
|
||||
{
|
||||
"type": "function_call_output",
|
||||
"call_id": f"call_abc{THOUGHT_SIGNATURE_SEPARATOR}sig",
|
||||
"output": "sunny",
|
||||
},
|
||||
]
|
||||
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_ids_in_input(
|
||||
request_input=request_input,
|
||||
model="grok-4",
|
||||
custom_llm_provider="xai",
|
||||
)
|
||||
|
||||
assert result[0]["id"] == "fc_xyz"
|
||||
assert result[0]["call_id"] == "call_abc"
|
||||
assert result[1]["call_id"] == "call_abc"
|
||||
|
||||
def test_normalize_function_call_ids_in_input_non_list(self):
|
||||
result = ResponsesAPIRequestUtils._normalize_function_call_ids_in_input(
|
||||
request_input="just a string",
|
||||
model="gpt-4o",
|
||||
custom_llm_provider="openai",
|
||||
)
|
||||
assert result == "just a string"
|
||||
|
||||
|
||||
class TestResponseAPILoggingUtils:
|
||||
def test_is_response_api_usage_true(self):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue