mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
fix(logging): warn on the Gemini 3, Magistral, Ollama, Fireworks and DeepSeek ARN reasoning drops
Gemini 3 and later now log a dropped thinking budget, Magistral logs the level it swaps for a reasoning prompt, Ollama logs a level it maps to think off, Fireworks logs chat_template_kwargs effort keys dropped for a model the map does not list as reasoning, and DeepSeek on Bedrock logs a drop when the model is addressed by an ARN that advertises the param.
This commit is contained in:
parent
1172cb6ab7
commit
5b85f78300
13 changed files with 282 additions and 16 deletions
|
|
@ -3,6 +3,8 @@ from collections.abc import Collection, Mapping
|
|||
from dataclasses import dataclass
|
||||
from typing import Final, Literal
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.constants import (
|
||||
|
|
@ -19,6 +21,7 @@ _DROP_WARNING: Final = "Dropping %s=%s for model=%r because %s"
|
|||
_DROP_PARAMS_DROP_WARNING: Final = "Dropping %s=%s for model=%r (drop_params=True) because %s"
|
||||
_NON_NUMERIC_BUDGET_REASON: Final = "it is not a number. Send budget_tokens as an integer to set a reasoning level."
|
||||
_REASONING_PARAMS: Final = ("reasoning", "reasoning_effort", "thinking")
|
||||
OPAQUE_VALUE: Final = TypeAdapter(object)
|
||||
|
||||
|
||||
def warn_dropped_reasoning(
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ from litellm.litellm_core_utils.prompt_templates.mid_conversation_system import
|
|||
message_field,
|
||||
parts_of,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import OPAQUE_VALUE, warn_dropped_reasoning
|
||||
from litellm.llms.anthropic.chat.transformation import (
|
||||
DROP_UNSUPPORTED_ADAPTIVE_THINKING_WARNING,
|
||||
DROP_UNSUPPORTED_OUTPUT_CONFIG_WARNING,
|
||||
|
|
@ -183,6 +184,8 @@ def _tools_the_model_accepts(
|
|||
|
||||
|
||||
_OUTPUT_CONFIG_BLOCK: Final = TypeAdapter(OutputConfigBlock)
|
||||
_DEEPSEEK_THINKING_REASON: Final = "DeepSeek on Bedrock reasons natively and takes no thinking field."
|
||||
_DEEPSEEK_R1_EFFORT_REASON: Final = "DeepSeek R1 on Bedrock always reasons and rejects reasoning_effort."
|
||||
|
||||
|
||||
def _has_native_text_format(output_config: object) -> bool:
|
||||
|
|
@ -635,6 +638,10 @@ class AmazonConverseConfig(BaseConfig):
|
|||
or supports_reasoning(model=base_model, custom_llm_provider=self.custom_llm_provider)
|
||||
)
|
||||
|
||||
def _warn_native_reasoning_drop(self, param: str, value: object, model: str, reason: str) -> None:
|
||||
if param in self.get_supported_openai_params(model):
|
||||
warn_dropped_reasoning(param, value, model, reason, drop_params_gated=False)
|
||||
|
||||
def _model_rejects_reasoning_effort_param(self, model: str, base_model: str) -> bool:
|
||||
"""Whether the model returns a 400 for every ``reasoning_effort`` shape on Converse.
|
||||
|
||||
|
|
@ -1138,9 +1145,8 @@ class AmazonConverseConfig(BaseConfig):
|
|||
"tool_choice": {"type": "auto", "disable_parallel_tool_use": not value}
|
||||
}
|
||||
if param == "thinking" and drop_thinking_param:
|
||||
verbose_logger.debug(
|
||||
"Dropping unsupported `thinking` param for Bedrock model=%s; it reasons natively.",
|
||||
model,
|
||||
self._warn_native_reasoning_drop(
|
||||
param, OPAQUE_VALUE.validate_python(value), model, _DEEPSEEK_THINKING_REASON
|
||||
)
|
||||
elif param == "thinking" and not self._is_openai_gpt_reasoning_model(model):
|
||||
if (
|
||||
|
|
@ -1174,10 +1180,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
drop_params=drop_params,
|
||||
)
|
||||
elif param == "reasoning_effort" and isinstance(value, str) and drop_reasoning_effort_param:
|
||||
verbose_logger.debug(
|
||||
"Dropping unsupported `reasoning_effort` param for Bedrock model=%s; it always reasons and rejects it.",
|
||||
model,
|
||||
)
|
||||
self._warn_native_reasoning_drop(param, value, model, _DEEPSEEK_R1_EFFORT_REASON)
|
||||
elif (
|
||||
param == "reasoning_effort"
|
||||
and isinstance(value, str)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
|||
strip_litellm_response_only_fields,
|
||||
unpack_legacy_defs,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import OPAQUE_VALUE, warn_dropped_reasoning
|
||||
from litellm.litellm_core_utils.url_utils import join_api_base
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import (
|
||||
|
|
@ -97,6 +98,7 @@ def _json_schema_response_format(schema: object, name: str) -> Mapping[str, obje
|
|||
|
||||
|
||||
EFFORT_KWARG_KEYS: Final = frozenset({"enable_thinking", "thinking", "reasoning_budget", "low_effort"})
|
||||
_NOT_A_REASONING_MODEL_REASON: Final = "litellm's model map does not list it as a reasoning model."
|
||||
|
||||
|
||||
def _bool_from_kwargs(kwargs: Mapping[str, object], keys: tuple[str, ...]) -> bool | None:
|
||||
|
|
@ -107,6 +109,13 @@ def _bool_from_kwargs(kwargs: Mapping[str, object], keys: tuple[str, ...]) -> bo
|
|||
return None
|
||||
|
||||
|
||||
def warn_dropped_effort_kwargs(chat_template_kwargs: object, effort: object, model: str) -> None:
|
||||
if effort != "none":
|
||||
warn_dropped_reasoning(
|
||||
"chat_template_kwargs", chat_template_kwargs, model, _NOT_A_REASONING_MODEL_REASON, drop_params_gated=False
|
||||
)
|
||||
|
||||
|
||||
def effort_from_chat_template_kwargs(kwargs: Mapping[str, object]) -> object:
|
||||
enable_thinking: Final = _bool_from_kwargs(kwargs, ("enable_thinking", "thinking"))
|
||||
if enable_thinking is False:
|
||||
|
|
@ -438,10 +447,7 @@ class FireworksAIConfig(FireworksAIMixin, OpenAIGPTConfig):
|
|||
if effort is None:
|
||||
return ()
|
||||
if not supports_reasoning(model=model, custom_llm_provider="fireworks_ai"):
|
||||
verbose_logger.debug(
|
||||
"fireworks_ai model %r does not support reasoning; dropping chat_template_kwargs effort keys.",
|
||||
model,
|
||||
)
|
||||
warn_dropped_effort_kwargs(OPAQUE_VALUE.validate_python(chat_template_kwargs), effort, model)
|
||||
return ()
|
||||
return (("reasoning_effort", effort),)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from collections.abc import Mapping
|
|||
from typing import Final
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import OPAQUE_VALUE
|
||||
from litellm.types.llms.openai import AllMessageValues, OpenAITextCompletionUserMessage
|
||||
from litellm.utils import supports_reasoning
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ from ..chat.transformation import (
|
|||
NIM_VLLM_STRIP_PARAMS,
|
||||
FireworksAIConfig,
|
||||
effort_from_chat_template_kwargs,
|
||||
warn_dropped_effort_kwargs,
|
||||
)
|
||||
from ..common_utils import FireworksAIMixin, resolve_fireworks_resource_name
|
||||
|
||||
|
|
@ -129,10 +131,7 @@ class FireworksAITextCompletionConfig(FireworksAIMixin, BaseTextCompletionConfig
|
|||
)
|
||||
return result
|
||||
if not supports_reasoning(model=model, custom_llm_provider="fireworks_ai"):
|
||||
verbose_logger.debug(
|
||||
"fireworks_ai model %r does not support reasoning; dropping chat_template_kwargs effort keys.",
|
||||
model,
|
||||
)
|
||||
warn_dropped_effort_kwargs(OPAQUE_VALUE.validate_python(chat_template_kwargs), effort, model)
|
||||
return result
|
||||
return {**result, "reasoning_effort": effort}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
|||
strip_litellm_internal_message_fields,
|
||||
strip_none_values_from_message,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import OPAQUE_VALUE, warn_dropped_reasoning
|
||||
from litellm.litellm_core_utils.url_utils import ensure_api_route
|
||||
from litellm.llms.openai.chat.gpt_transformation import (
|
||||
OpenAIChatCompletionStreamingHandler,
|
||||
|
|
@ -57,6 +58,7 @@ def _accepted_reasoning_effort(model: str, requested: str, custom_llm_provider:
|
|||
|
||||
|
||||
_NO_TOOL_CALL_TURN: Final[tuple[int, frozenset[str]]] = (-1, frozenset())
|
||||
_REASONING_PROMPT_REASON: Final = "litellm sends Magistral a reasoning system prompt instead of the requested level."
|
||||
|
||||
|
||||
def _is_json_array(value: object) -> TypeIs[Sequence[object]]: # guard-ok: a decoded JSON array is a list
|
||||
|
|
@ -216,6 +218,9 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
if param in ("reasoning_effort", "thinking") and "magistral" in model.lower():
|
||||
# Flag that we need to add reasoning system prompt
|
||||
optional_params["_add_reasoning_prompt"] = True
|
||||
warn_dropped_reasoning(
|
||||
param, OPAQUE_VALUE.validate_python(value), model, _REASONING_PROMPT_REASON, drop_params_gated=False
|
||||
)
|
||||
if param == "parallel_tool_calls":
|
||||
optional_params["parallel_tool_calls"] = value
|
||||
return optional_params
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
|||
extract_images_from_message,
|
||||
extract_reasoning_content,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import warn_dropped_reasoning
|
||||
from litellm.litellm_core_utils.url_utils import ensure_api_route
|
||||
from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator
|
||||
from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException
|
||||
|
|
@ -39,6 +40,9 @@ if TYPE_CHECKING:
|
|||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
|
||||
_THINK_ON_EFFORTS: Final = frozenset({"low", "medium", "high"})
|
||||
_THINK_OFF_REASON: Final = "Ollama takes think only as on or off, and litellm maps this level to off."
|
||||
|
||||
|
||||
class OllamaChatConfig(BaseConfig):
|
||||
"""
|
||||
|
|
@ -175,7 +179,11 @@ class OllamaChatConfig(BaseConfig):
|
|||
if model.startswith("gpt-oss"):
|
||||
optional_params["think"] = value
|
||||
else:
|
||||
optional_params["think"] = value in {"low", "medium", "high"}
|
||||
optional_params["think"] = value in _THINK_ON_EFFORTS
|
||||
if isinstance(value, str) and value not in _THINK_ON_EFFORTS and value != "none":
|
||||
warn_dropped_reasoning(
|
||||
"reasoning_effort", value, model, _THINK_OFF_REASON, drop_params_gated=False
|
||||
)
|
||||
### FUNCTION CALLING LOGIC ###
|
||||
# Ollama 0.4+ supports native tool calling - pass tools directly
|
||||
# and let Ollama handle model capability detection
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@ _SAMPLING_DEPRECATION_UNMAPPED_MODELS: Final = "models outside litellm's model m
|
|||
_NON_INTEGER_BUDGET_REASON: Final = (
|
||||
"Gemini takes budget_tokens only as an integer, so it uses its default thinking budget."
|
||||
)
|
||||
_LEVEL_ONLY_BUDGET_REASON: Final = (
|
||||
"Gemini 3 and later set reasoning by thinking level, not budget, so it uses its default level. "
|
||||
"Send reasoning_effort to pick a level."
|
||||
)
|
||||
_sampling_deprecation_lock: Final = threading.Lock()
|
||||
|
||||
|
||||
|
|
@ -1469,6 +1473,14 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
|
|||
params["includeThoughts"] = False
|
||||
else:
|
||||
params["includeThoughts"] = sends_thoughts
|
||||
if thinking_budget is not None:
|
||||
warn_dropped_reasoning(
|
||||
"thinking.budget_tokens",
|
||||
thinking_budget,
|
||||
model,
|
||||
_LEVEL_ONLY_BUDGET_REASON,
|
||||
drop_params_gated=False,
|
||||
)
|
||||
# Follow provider defaults unless explicitly opted into legacy behavior.
|
||||
if litellm.enable_gemini_default_thinking_level_low is True:
|
||||
params["thinkingLevel"] = (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import base64
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Iterator, Mapping
|
||||
|
|
@ -12,6 +13,7 @@ import pytest
|
|||
import respx
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm import ModelResponse, Router, completion
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
_bedrock_converse_messages_pt,
|
||||
|
|
@ -10789,3 +10791,49 @@ def test_a_deployment_with_a_batch_control_plane_endpoint_keeps_it_out_of_chat_r
|
|||
|
||||
assert response.choices[0].message.content == "ok"
|
||||
assert "aws_bedrock_control_plane_endpoint" not in route.calls[0].request.content.decode()
|
||||
|
||||
|
||||
def _warnings_naming(caplog: pytest.LogCaptureFixture, param: str) -> tuple[str, ...]:
|
||||
return tuple(
|
||||
r.getMessage() for r in caplog.records if r.levelno == logging.WARNING and f"Dropping {param}=" in r.getMessage()
|
||||
)
|
||||
|
||||
|
||||
_DEEPSEEK_R1_PROFILE_ARN: Final = "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.deepseek.r1-v1:0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param, value", (("thinking", {"type": "enabled", "budget_tokens": 1024}), ("reasoning_effort", "high"))
|
||||
)
|
||||
def test_bedrock_deepseek_arn_reasoning_drop_warns_once(
|
||||
param: str, value: object, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
optional_params: Final = AmazonConverseConfig().map_openai_params(
|
||||
non_default_params={param: value, "max_tokens": 100},
|
||||
optional_params={},
|
||||
model=_DEEPSEEK_R1_PROFILE_ARN,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, param)
|
||||
assert param not in optional_params
|
||||
assert len(warnings) == 1
|
||||
assert _DEEPSEEK_R1_PROFILE_ARN in warnings[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param, value", (("thinking", {"type": "enabled", "budget_tokens": 1024}), ("reasoning_effort", "high"))
|
||||
)
|
||||
def test_bedrock_deepseek_r1_drop_of_an_unadvertised_param_does_not_warn(
|
||||
param: str, value: object, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
AmazonConverseConfig().map_openai_params(
|
||||
non_default_params={param: value, "max_tokens": 100},
|
||||
optional_params={},
|
||||
model="us.deepseek.r1-v1:0",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert _warnings_naming(caplog, param) == ()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import logging
|
||||
from typing import Final
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ import respx
|
|||
from pydantic import ValidationError
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.constants import SESSION_ID_GENERATED_METADATA_KEY
|
||||
from litellm.litellm_core_utils.get_supported_openai_params import get_supported_openai_params
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
|
@ -2358,3 +2360,36 @@ def test_global_disable_flag_with_transform_messages_helper(
|
|||
)
|
||||
|
||||
assert json.loads(route.calls.last.request.content)["messages"] == [{"role": "user", "content": content}]
|
||||
|
||||
|
||||
def _warnings_naming(caplog: pytest.LogCaptureFixture, param: str) -> tuple[str, ...]:
|
||||
return tuple(
|
||||
r.getMessage() for r in caplog.records if r.levelno == logging.WARNING and f"Dropping {param}=" in r.getMessage()
|
||||
)
|
||||
|
||||
|
||||
def test_chat_template_kwargs_effort_dropped_for_non_reasoning_model_warns_once(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
FireworksAIConfig().map_extra_body_params(
|
||||
{"extra_body": {"chat_template_kwargs": {"reasoning_budget": 512}}},
|
||||
_NON_REASONING_MODEL,
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, "chat_template_kwargs")
|
||||
assert len(warnings) == 1
|
||||
assert "'reasoning_budget': 512" in warnings[0]
|
||||
assert "llama-v3-70b-instruct" in warnings[0]
|
||||
|
||||
|
||||
def test_chat_template_kwargs_thinking_off_for_non_reasoning_model_does_not_warn(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
FireworksAIConfig().map_extra_body_params(
|
||||
{"extra_body": {"chat_template_kwargs": {"enable_thinking": False}}},
|
||||
_NON_REASONING_MODEL,
|
||||
)
|
||||
|
||||
assert _warnings_naming(caplog, "chat_template_kwargs") == ()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
|
||||
|
||||
from litellm.llms.fireworks_ai.completion.transformation import (
|
||||
|
|
@ -204,3 +208,36 @@ def test_transform_text_completion_request_keeps_sdk_rejected_keys_in_extra_body
|
|||
assert "chat_template_kwargs" not in data["extra_body"]
|
||||
assert "best_of" not in data["extra_body"]
|
||||
assert "response_format" not in data
|
||||
|
||||
|
||||
def _warnings_naming(caplog: pytest.LogCaptureFixture, param: str) -> tuple[str, ...]:
|
||||
return tuple(
|
||||
r.getMessage() for r in caplog.records if r.levelno == logging.WARNING and f"Dropping {param}=" in r.getMessage()
|
||||
)
|
||||
|
||||
|
||||
def test_chat_template_kwargs_effort_dropped_for_non_reasoning_model_warns_once(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
FireworksAITextCompletionConfig().map_extra_body_params(
|
||||
{"extra_body": {"chat_template_kwargs": {"reasoning_budget": 512}}},
|
||||
_NON_REASONING_MODEL,
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, "chat_template_kwargs")
|
||||
assert len(warnings) == 1
|
||||
assert "'reasoning_budget': 512" in warnings[0]
|
||||
assert "llama-v3-70b-instruct" in warnings[0]
|
||||
|
||||
|
||||
def test_chat_template_kwargs_thinking_off_for_non_reasoning_model_does_not_warn(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
FireworksAITextCompletionConfig().map_extra_body_params(
|
||||
{"extra_body": {"chat_template_kwargs": {"enable_thinking": False}}},
|
||||
_NON_REASONING_MODEL,
|
||||
)
|
||||
|
||||
assert _warnings_naming(caplog, "chat_template_kwargs") == ()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import logging
|
||||
from typing import Final, List, cast
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import TOOL_RESULT_IMAGE_BOUNDARY
|
||||
from litellm.llms.mistral.chat.transformation import (
|
||||
MistralChatResponseIterator,
|
||||
|
|
@ -1665,3 +1667,42 @@ def test_mistral_transform_request_drops_an_empty_tool_message_whose_call_id_is_
|
|||
)
|
||||
|
||||
assert [m.get("role") for m in request["messages"]] == ["user", "assistant"]
|
||||
|
||||
|
||||
def _warnings_naming(caplog: pytest.LogCaptureFixture, param: str) -> tuple[str, ...]:
|
||||
return tuple(
|
||||
r.getMessage() for r in caplog.records if r.levelno == logging.WARNING and f"Dropping {param}=" in r.getMessage()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param, value", (("reasoning_effort", "none"), ("thinking", {"type": "enabled", "budget_tokens": 1024}))
|
||||
)
|
||||
def test_magistral_reasoning_request_replaced_by_prompt_warns_once(
|
||||
param: str, value: object, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
non_default_params={param: value},
|
||||
optional_params={},
|
||||
model="magistral-medium-latest",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, param)
|
||||
assert result["_add_reasoning_prompt"] is True
|
||||
assert len(warnings) == 1
|
||||
assert "magistral-medium-latest" in warnings[0]
|
||||
|
||||
|
||||
def test_non_magistral_reasoning_effort_is_sent_without_warning(caplog: pytest.LogCaptureFixture) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
non_default_params={"reasoning_effort": "high"},
|
||||
optional_params={},
|
||||
model="mistral-medium-latest",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["reasoning_effort"] == "high"
|
||||
assert _warnings_naming(caplog, "reasoning_effort") == ()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Final, cast
|
||||
|
|
@ -23,6 +24,7 @@ import json
|
|||
from unittest.mock import ANY, MagicMock, patch
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.types.utils import Choices, EmbeddingResponse, Message, ModelResponse, ModelResponseStream
|
||||
from unittest import mock
|
||||
|
||||
|
|
@ -1114,3 +1116,36 @@ def test_get_complete_url_ignores_a_trailing_slash_on_api_base(api_base: str):
|
|||
)
|
||||
== "http://gateway.example.com/ollama/api/chat"
|
||||
)
|
||||
|
||||
|
||||
def _warnings_naming(caplog: pytest.LogCaptureFixture, param: str) -> tuple[str, ...]:
|
||||
return tuple(
|
||||
r.getMessage() for r in caplog.records if r.levelno == logging.WARNING and f"Dropping {param}=" in r.getMessage()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("effort", ("minimal", "xhigh"))
|
||||
def test_reasoning_effort_mapped_to_think_off_warns_once(effort: str, caplog: pytest.LogCaptureFixture) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = OllamaChatConfig().map_openai_params(
|
||||
non_default_params={"reasoning_effort": effort}, optional_params={}, model="qwen3", drop_params=False
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, "reasoning_effort")
|
||||
assert result["think"] is False
|
||||
assert len(warnings) == 1
|
||||
assert f"reasoning_effort={effort!r}" in warnings[0]
|
||||
assert "qwen3" in warnings[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("effort, think", (("none", False), ("low", True), ("high", True)))
|
||||
def test_reasoning_effort_with_a_matching_think_value_does_not_warn(
|
||||
effort: str, think: bool, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = OllamaChatConfig().map_openai_params(
|
||||
non_default_params={"reasoning_effort": effort}, optional_params={}, model="qwen3", drop_params=False
|
||||
)
|
||||
|
||||
assert result["think"] is think
|
||||
assert _warnings_naming(caplog, "reasoning_effort") == ()
|
||||
|
|
|
|||
|
|
@ -1730,6 +1730,40 @@ def test_gemini_integer_thinking_budget_is_sent_without_warning(caplog: pytest.L
|
|||
assert _budget_drop_warnings(caplog) == ()
|
||||
|
||||
|
||||
def test_gemini_3_thinking_budget_drop_warns_once(caplog: pytest.LogCaptureFixture) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = VertexGeminiConfig().map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 2048}},
|
||||
optional_params={},
|
||||
model="gemini-3-pro-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
warnings: Final = _budget_drop_warnings(caplog)
|
||||
assert "thinkingBudget" not in result["thinkingConfig"]
|
||||
assert len(warnings) == 1
|
||||
assert "thinking.budget_tokens=2048" in warnings[0]
|
||||
assert "gemini-3-pro-preview" in warnings[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"thinking",
|
||||
({"type": "enabled"}, {"type": "enabled", "budget_tokens": 0}, {"type": "disabled", "budget_tokens": 2048}),
|
||||
)
|
||||
def test_gemini_3_thinking_without_a_dropped_budget_does_not_warn(
|
||||
thinking: dict[str, object], caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
VertexGeminiConfig().map_openai_params(
|
||||
non_default_params={"thinking": thinking},
|
||||
optional_params={},
|
||||
model="gemini-3-pro-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert _budget_drop_warnings(caplog) == ()
|
||||
|
||||
|
||||
def test_vertex_ai_map_tools():
|
||||
v = VertexGeminiConfig()
|
||||
optional_params = {}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue