mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
fix: strip eager_input_streaming for non-Claude providers next to input_examples
This commit is contained in:
parent
3d3a46fc1f
commit
1adbfbfbb1
2 changed files with 76 additions and 40 deletions
|
|
@ -1146,37 +1146,35 @@ def responses_api_bridge_check(
|
|||
return model_info, model
|
||||
|
||||
|
||||
def _should_allow_input_examples(custom_llm_provider: str | None, model: str) -> bool:
|
||||
_ANTHROPIC_ONLY_TOOL_KEYS: Final = frozenset({"input_examples", "eager_input_streaming"})
|
||||
|
||||
|
||||
def _is_claude_tool_target(custom_llm_provider: str | None, model: str) -> bool:
|
||||
if custom_llm_provider == "anthropic":
|
||||
return True
|
||||
if custom_llm_provider == "azure_ai" or custom_llm_provider == "bedrock" or custom_llm_provider == "vertex_ai":
|
||||
return "claude" in model.lower()
|
||||
model_lower: Final = model.lower()
|
||||
if custom_llm_provider == "bedrock":
|
||||
return "claude" in model_lower or ("arn:" in model_lower and ":bedrock:" in model_lower)
|
||||
if custom_llm_provider == "azure_ai" or custom_llm_provider == "vertex_ai":
|
||||
return "claude" in model_lower
|
||||
return False
|
||||
|
||||
|
||||
def _drop_input_examples_from_tool(tool: dict) -> dict:
|
||||
tool_copy: Final = tool.copy()
|
||||
tool_copy.pop("input_examples", None)
|
||||
function = tool_copy.get("function")
|
||||
if isinstance(function, dict):
|
||||
function = function.copy()
|
||||
function.pop("input_examples", None)
|
||||
tool_copy["function"] = function
|
||||
return tool_copy
|
||||
def _without_anthropic_only_tool_keys(tool: dict) -> dict:
|
||||
kept: Final = {key: value for key, value in tool.items() if key not in _ANTHROPIC_ONLY_TOOL_KEYS}
|
||||
function: Final = tool.get("function")
|
||||
if not isinstance(function, dict):
|
||||
return kept
|
||||
return {
|
||||
**kept,
|
||||
"function": {key: value for key, value in function.items() if key not in _ANTHROPIC_ONLY_TOOL_KEYS},
|
||||
}
|
||||
|
||||
|
||||
def _drop_input_examples_from_tools(
|
||||
tools: list[dict] | None,
|
||||
) -> list[dict] | None:
|
||||
def _drop_anthropic_only_tool_keys(tools: list[dict] | None) -> list[dict] | None:
|
||||
if tools is None:
|
||||
return None
|
||||
cleaned_tools: Final[list[dict]] = []
|
||||
for tool in tools:
|
||||
if isinstance(tool, dict):
|
||||
cleaned_tools.append(_drop_input_examples_from_tool(tool))
|
||||
else:
|
||||
cleaned_tools.append(tool)
|
||||
return cleaned_tools
|
||||
return [_without_anthropic_only_tool_keys(tool) if isinstance(tool, dict) else tool for tool in tools]
|
||||
|
||||
|
||||
class _ProxyAuthHeadersProvider(Protocol):
|
||||
|
|
@ -5360,8 +5358,8 @@ def completion(
|
|||
api_base=api_base,
|
||||
)
|
||||
|
||||
if not _should_allow_input_examples(custom_llm_provider=custom_llm_provider, model=model):
|
||||
tools = _drop_input_examples_from_tools(tools=tools)
|
||||
if not _is_claude_tool_target(custom_llm_provider=custom_llm_provider, model=model):
|
||||
tools = _drop_anthropic_only_tool_keys(tools=tools)
|
||||
|
||||
if provider_specific_header is not None:
|
||||
headers.update(
|
||||
|
|
|
|||
|
|
@ -349,28 +349,66 @@ def test_bedrock_latency_optimized_inference():
|
|||
assert json_data["performanceConfig"]["latency"] == "optimized"
|
||||
|
||||
|
||||
def test_strip_input_examples_for_non_anthropic_providers():
|
||||
@pytest.mark.parametrize(
|
||||
("custom_llm_provider", "model", "expected"),
|
||||
[
|
||||
("anthropic", "claude-sonnet-5", True),
|
||||
("bedrock", "us.anthropic.claude-sonnet-5-20260501-v1:0", True),
|
||||
("bedrock", "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123", True),
|
||||
("bedrock", "us.amazon.nova-2-lite-v1:0", False),
|
||||
("vertex_ai", "claude-sonnet-5", True),
|
||||
("vertex_ai", "gemini-3.8-flash", False),
|
||||
("azure_ai", "claude-sonnet-4-6", True),
|
||||
("azure_ai", "gpt-5.6", False),
|
||||
("openai", "gpt-5.6", False),
|
||||
("gemini", "gemini-3.8-flash", False),
|
||||
],
|
||||
)
|
||||
def test_is_claude_tool_target(custom_llm_provider: str, model: str, expected: bool):
|
||||
assert litellm_main._is_claude_tool_target(custom_llm_provider=custom_llm_provider, model=model) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["input_examples", "eager_input_streaming"])
|
||||
def test_drop_anthropic_only_tool_keys_strips_tool_and_function_levels(key: str):
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "example_tool",
|
||||
"input_examples": [{"foo": "bar"}],
|
||||
"function": {
|
||||
"name": "example_tool",
|
||||
"input_examples": [{"foo": "bar"}],
|
||||
},
|
||||
}
|
||||
{"type": "function", "name": "example_tool", key: True, "function": {"name": "example_tool", key: True}},
|
||||
"opaque_tool",
|
||||
]
|
||||
|
||||
assert not litellm_main._should_allow_input_examples(
|
||||
custom_llm_provider="openai", model="gpt-4o-mini"
|
||||
cleaned = litellm_main._drop_anthropic_only_tool_keys(tools=tools)
|
||||
|
||||
assert cleaned == [
|
||||
{"type": "function", "name": "example_tool", "function": {"name": "example_tool"}},
|
||||
"opaque_tool",
|
||||
]
|
||||
assert tools[0][key] is True
|
||||
assert tools[0]["function"][key] is True
|
||||
|
||||
|
||||
def test_completion_strips_eager_input_streaming_before_openai(respx_mock: respx.MockRouter, openai_api_response):
|
||||
api_base: Final = "http://localhost:12346/v1"
|
||||
mock_route: Final = respx_mock.post(url__regex=rf"{api_base}/chat/completions.*").mock(
|
||||
return_value=httpx.Response(status_code=200, json=openai_api_response)
|
||||
)
|
||||
|
||||
cleaned = litellm_main._drop_input_examples_from_tools(tools=tools)
|
||||
litellm.completion(
|
||||
model="openai/gpt-5.6",
|
||||
messages=[{"role": "user", "content": "Write the file"}],
|
||||
tools=[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {"name": "write_file", "parameters": {"type": "object", "properties": {}}},
|
||||
"eager_input_streaming": True,
|
||||
}
|
||||
],
|
||||
api_base=api_base,
|
||||
api_key="fake_openai_api_key",
|
||||
)
|
||||
|
||||
assert isinstance(cleaned, list)
|
||||
assert "input_examples" not in cleaned[0]
|
||||
assert "input_examples" not in cleaned[0]["function"]
|
||||
assert mock_route.called
|
||||
sent_tool: Final = json.loads(respx_mock.calls[0].request.content)["tools"][0]
|
||||
assert "eager_input_streaming" not in sent_tool
|
||||
assert sent_tool["function"]["name"] == "write_file"
|
||||
|
||||
|
||||
def test_custom_provider_with_extra_headers():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue