mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(mistral): reject a tool_choice Mistral cannot take unless drop_params is set
This commit is contained in:
parent
87cd93e85f
commit
f8a9da87e0
2 changed files with 67 additions and 19 deletions
|
|
@ -203,15 +203,31 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
match tool_choice:
|
||||
case str(choice) if choice in ("auto", "none"):
|
||||
return choice
|
||||
case "required" | "any":
|
||||
return "any"
|
||||
case {"type": "function", "function": {"name": str(name)}} if name:
|
||||
return ChatCompletionToolChoiceObjectParam(
|
||||
type="function", function=ChatCompletionToolChoiceFunctionParam(name=name)
|
||||
)
|
||||
case str():
|
||||
return "any"
|
||||
case _:
|
||||
pass
|
||||
return None
|
||||
return None
|
||||
|
||||
def _tool_choice_to_send(
|
||||
self, model: str, tool_choice: object, drop_params: bool
|
||||
) -> str | ChatCompletionToolChoiceObjectParam | None:
|
||||
mapped: Final = self._map_tool_choice(tool_choice)
|
||||
if mapped is not None or drop_params or litellm.drop_params is True:
|
||||
return mapped
|
||||
raise UnsupportedParamsError(
|
||||
message=(
|
||||
f"{self.custom_llm_provider} does not support tool_choice={tool_choice!r}, for model={model}, "
|
||||
"since the Mistral API takes only 'auto', 'none', 'any', 'required', or "
|
||||
"{'type': 'function', 'function': {'name': <tool name>}}. "
|
||||
"To drop it, set `litellm.drop_params=True` or for proxy:\n\n`litellm_settings:\n drop_params: true`\n"
|
||||
),
|
||||
llm_provider=self.custom_llm_provider,
|
||||
model=model,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_mistral_reasoning_system_prompt() -> str:
|
||||
|
|
@ -266,8 +282,10 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
if (
|
||||
param == "tool_choice"
|
||||
and (
|
||||
mapped_tool_choice := self._map_tool_choice(
|
||||
tool_choice=cast(object, value) # cast-ok: untyped request params, the match narrows it
|
||||
mapped_tool_choice := self._tool_choice_to_send(
|
||||
model,
|
||||
cast(object, value), # cast-ok: untyped request params, the match narrows it
|
||||
drop_params,
|
||||
)
|
||||
)
|
||||
is not None
|
||||
|
|
|
|||
|
|
@ -2075,7 +2075,17 @@ class TestMistralToolChoice:
|
|||
}
|
||||
]
|
||||
|
||||
def _request_body(self, tool_choice: str | Mapping[str, object]) -> Mapping[str, object]:
|
||||
UNUSABLE_TOOL_CHOICES: Final[Sequence[str | Mapping[str, object]]] = (
|
||||
{"type": "function", "function": {}},
|
||||
{"type": "function", "function": {"name": ""}},
|
||||
{"type": "function", "function": {"name": 123}},
|
||||
{"type": "function"},
|
||||
"foo",
|
||||
)
|
||||
|
||||
def _request_body(
|
||||
self, tool_choice: str | Mapping[str, object], drop_params: bool = False
|
||||
) -> Mapping[str, object]:
|
||||
import litellm
|
||||
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
|
|
@ -2083,6 +2093,7 @@ class TestMistralToolChoice:
|
|||
custom_llm_provider="mistral",
|
||||
tools=list(self.TOOLS),
|
||||
tool_choice=tool_choice,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
return MistralConfig().transform_request(
|
||||
model="mistral-large-latest",
|
||||
|
|
@ -2097,6 +2108,24 @@ class TestMistralToolChoice:
|
|||
|
||||
assert body["tool_choice"] == {"type": "function", "function": {"name": "get_weather"}}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"custom_llm_provider, model",
|
||||
[("mistral", "mistral-large-latest"), ("codestral", "codestral-latest"), ("vertex_ai", "mistral-small-2503")],
|
||||
)
|
||||
def test_named_function_is_kept_on_every_mistral_route(
|
||||
self, local_model_cost_map: None, custom_llm_provider: str, model: str
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
tools=list(self.TOOLS),
|
||||
tool_choice={"type": "function", "function": {"name": "get_weather"}},
|
||||
)
|
||||
|
||||
assert optional_params["tool_choice"] == {"type": "function", "function": {"name": "get_weather"}}
|
||||
|
||||
def test_keys_mistral_does_not_define_are_not_replayed(self, local_model_cost_map: None) -> None:
|
||||
body: Final = self._request_body(
|
||||
{
|
||||
|
|
@ -2115,16 +2144,17 @@ class TestMistralToolChoice:
|
|||
def test_enum_tool_choice_is_mapped(self, local_model_cost_map: None, tool_choice: str, expected: str) -> None:
|
||||
assert self._request_body(tool_choice)["tool_choice"] == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"tool_choice",
|
||||
[
|
||||
{"type": "function", "function": {}},
|
||||
{"type": "function", "function": {"name": ""}},
|
||||
{"type": "function", "function": {"name": 123}},
|
||||
{"type": "function"},
|
||||
],
|
||||
)
|
||||
def test_function_objects_without_a_usable_name_are_left_out(
|
||||
self, local_model_cost_map: None, tool_choice: Mapping[str, object]
|
||||
@pytest.mark.parametrize("tool_choice", UNUSABLE_TOOL_CHOICES)
|
||||
def test_unusable_tool_choice_is_rejected_without_drop_params(
|
||||
self, local_model_cost_map: None, tool_choice: str | Mapping[str, object]
|
||||
) -> None:
|
||||
assert "tool_choice" not in self._request_body(tool_choice)
|
||||
import litellm
|
||||
|
||||
with pytest.raises(litellm.UnsupportedParamsError, match="tool_choice"):
|
||||
self._request_body(tool_choice)
|
||||
|
||||
@pytest.mark.parametrize("tool_choice", UNUSABLE_TOOL_CHOICES)
|
||||
def test_unusable_tool_choice_is_dropped_with_drop_params(
|
||||
self, local_model_cost_map: None, tool_choice: str | Mapping[str, object]
|
||||
) -> None:
|
||||
assert "tool_choice" not in self._request_body(tool_choice, drop_params=True)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue