mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
Merge 57f5918b26 into a677242d6f
This commit is contained in:
commit
58cd344bbd
2 changed files with 101 additions and 3 deletions
|
|
@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, Final, Protocol, TypedDict
|
|||
import httpx
|
||||
from typing_extensions import ReadOnly
|
||||
|
||||
from litellm.exceptions import UnsupportedParamsError
|
||||
from litellm.types.llms.openai import AllMessageValues, ChatCompletionToolCallChunk
|
||||
from litellm.types.utils import (
|
||||
ChatCompletionMessageToolCall,
|
||||
|
|
@ -308,21 +309,43 @@ class SnowflakeConfig(SnowflakeBaseConfig, OpenAIGPTConfig):
|
|||
|
||||
return body
|
||||
|
||||
def _transform_tool_choice_to_anthropic(self, tool_choice: Any) -> dict[str, Any]:
|
||||
def _transform_tool_choice_to_anthropic(self, tool_choice: Any) -> dict[str, Any] | None:
|
||||
"""
|
||||
Convert tool_choice from OpenAI format to Anthropic format.
|
||||
|
||||
OpenAI string values: "auto", "required", "none"
|
||||
OpenAI dict: {"type": "function", "function": {"name": "..."}}
|
||||
Anthropic: {"type": "auto"}, {"type": "any"}, {"type": "tool", "name": "..."}
|
||||
|
||||
Returns None when the value is unsupported and `litellm.drop_params`
|
||||
is set, in which case the caller drops the parameter.
|
||||
|
||||
Unrecognized strings raise rather than defaulting to "auto". Falling
|
||||
back inverts the caller's intent — "required" means the model must
|
||||
call a tool, "auto" means it may — and the only symptom is the model
|
||||
occasionally not calling one, which reads as a model quality problem
|
||||
rather than a silently dropped constraint.
|
||||
"""
|
||||
import litellm
|
||||
|
||||
if isinstance(tool_choice, str):
|
||||
mapping: Final = {
|
||||
"auto": {"type": "auto"},
|
||||
"required": {"type": "any"},
|
||||
"none": {"type": "none"},
|
||||
}
|
||||
return mapping.get(tool_choice, {"type": "auto"})
|
||||
if tool_choice not in mapping:
|
||||
if litellm.drop_params is True:
|
||||
return None
|
||||
raise UnsupportedParamsError(
|
||||
message=(
|
||||
f"Snowflake doesn't support tool_choice={tool_choice}. "
|
||||
"Supported tool_choice values=['auto', 'required', 'none', json object]. "
|
||||
"To drop it from the call, set `litellm.drop_params = True`."
|
||||
),
|
||||
status_code=400,
|
||||
)
|
||||
return mapping[tool_choice]
|
||||
elif isinstance(tool_choice, dict):
|
||||
if tool_choice.get("type") == "function":
|
||||
func: Final = tool_choice.get("function", {})
|
||||
|
|
@ -345,7 +368,11 @@ class SnowflakeConfig(SnowflakeBaseConfig, OpenAIGPTConfig):
|
|||
optional_params["tools"] = self._transform_tools_to_anthropic(optional_params["tools"])
|
||||
|
||||
if "tool_choice" in optional_params:
|
||||
optional_params["tool_choice"] = self._transform_tool_choice_to_anthropic(optional_params["tool_choice"])
|
||||
transformed_tool_choice: Final = self._transform_tool_choice_to_anthropic(optional_params["tool_choice"])
|
||||
if transformed_tool_choice is None: # unsupported + litellm.drop_params
|
||||
optional_params.pop("tool_choice")
|
||||
else:
|
||||
optional_params["tool_choice"] = transformed_tool_choice
|
||||
|
||||
max_completion_tokens: Final = optional_params.pop("max_completion_tokens", None)
|
||||
if max_completion_tokens and "max_tokens" not in optional_params:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import pytest
|
|||
import litellm
|
||||
from litellm import completion, acompletion
|
||||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
|
||||
from litellm.exceptions import UnsupportedParamsError
|
||||
from litellm.llms.snowflake.chat.transformation import SnowflakeConfig
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
|
|
@ -118,6 +119,76 @@ class TestSnowflakeToolTransformation:
|
|||
f"got {transformed_request['tool_choice']}"
|
||||
)
|
||||
|
||||
def test_claude_tool_choice_maps_supported_string_values(self):
|
||||
"""
|
||||
Claude models route through the Anthropic request path, where OpenAI
|
||||
string tool_choice values are translated to Anthropic's object form.
|
||||
"""
|
||||
config = SnowflakeConfig()
|
||||
|
||||
expected = {
|
||||
"auto": {"type": "auto"},
|
||||
"required": {"type": "any"},
|
||||
"none": {"type": "none"},
|
||||
}
|
||||
|
||||
for value, want in expected.items():
|
||||
transformed_request = config.transform_request(
|
||||
model="snowflake/claude-3-5-sonnet",
|
||||
messages=[{"role": "user", "content": "Test"}],
|
||||
optional_params={"tool_choice": value},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert transformed_request["tool_choice"] == want, (
|
||||
f"tool_choice='{value}' should map to {want}, got {transformed_request['tool_choice']}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"tool_choice",
|
||||
["any", "Required", "REQUIRED", "required ", "requried", "tool"],
|
||||
)
|
||||
def test_claude_unsupported_tool_choice_string_raises(self, tool_choice):
|
||||
"""
|
||||
Unrecognized strings must raise, not silently fall back to "auto".
|
||||
|
||||
"required" means the model must call a tool and "auto" means it may,
|
||||
so defaulting inverts the caller's intent with no error surfaced. The
|
||||
lookup is exact-match, so casing and whitespace variants land here too.
|
||||
"""
|
||||
config = SnowflakeConfig()
|
||||
|
||||
with pytest.raises(UnsupportedParamsError) as exc_info:
|
||||
config.transform_request(
|
||||
model="snowflake/claude-3-5-sonnet",
|
||||
messages=[{"role": "user", "content": "Test"}],
|
||||
optional_params={"tool_choice": tool_choice},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "tool_choice" in str(exc_info.value)
|
||||
|
||||
def test_claude_unsupported_tool_choice_dropped_when_drop_params_set(self):
|
||||
"""With litellm.drop_params, an unsupported value is dropped, not raised."""
|
||||
config = SnowflakeConfig()
|
||||
|
||||
original = litellm.drop_params
|
||||
litellm.drop_params = True
|
||||
try:
|
||||
transformed_request = config.transform_request(
|
||||
model="snowflake/claude-3-5-sonnet",
|
||||
messages=[{"role": "user", "content": "Test"}],
|
||||
optional_params={"tool_choice": "any"},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
finally:
|
||||
litellm.drop_params = original
|
||||
|
||||
assert "tool_choice" not in transformed_request
|
||||
|
||||
def test_transform_response_with_tool_calls(self):
|
||||
"""
|
||||
Test that standard OpenAI tool_calls response format is parsed correctly.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue