mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
Merge pull request #39159 from BerriAI/litellm_bedrock_converse_legacy_thinking_adaptive
fix(anthropic): upgrade legacy thinking to adaptive on adaptive-only Claude models for chat, Bedrock Converse, Invoke, Vertex AI, and Databricks
This commit is contained in:
commit
22cc97fe0a
14 changed files with 289 additions and 53 deletions
|
|
@ -1565,6 +1565,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
optional_params.pop("thinking", None)
|
||||
else:
|
||||
optional_params["thinking"] = value
|
||||
AnthropicModelInfo.translate_legacy_thinking_for_adaptive_model(
|
||||
model=model, optional_params=optional_params, custom_llm_provider=self._resolved_provider
|
||||
)
|
||||
elif param == "reasoning_effort":
|
||||
# Accept both string ("low") and dict ({"effort": "low",
|
||||
# "summary": "concise"}). The Responses->Chat parser keeps the
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ import httpx
|
|||
from pydantic import BaseModel, ConfigDict, TypeAdapter, ValidationError
|
||||
|
||||
import litellm
|
||||
from litellm.constants import DEFAULT_MODEL_CREATED_AT_TIME
|
||||
from litellm.constants import (
|
||||
DEFAULT_MODEL_CREATED_AT_TIME,
|
||||
DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET,
|
||||
)
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
get_file_ids_from_messages,
|
||||
)
|
||||
|
|
@ -534,6 +539,51 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
|||
)
|
||||
optional_params.pop("thinking", None)
|
||||
|
||||
@staticmethod
|
||||
def translate_legacy_thinking_for_adaptive_model(
|
||||
model: str,
|
||||
optional_params: MutableMapping[str, object], # mutable-ok: in-place out-param like the sibling helpers
|
||||
custom_llm_provider: str,
|
||||
) -> None:
|
||||
"""Translate legacy ``thinking.type=enabled`` to adaptive for the
|
||||
adaptive-thinking models that reject it (4.7+ and the 5 families).
|
||||
Models flagged ``supports_legacy_thinking`` (the 4.6 family) accept the
|
||||
legacy shape natively, so it is forwarded verbatim and the caller's
|
||||
``budget_tokens`` cap keeps applying. Caller-provided
|
||||
``output_config.effort`` is never overridden.
|
||||
"""
|
||||
if not AnthropicModelInfo._is_adaptive_thinking_model(model, custom_llm_provider):
|
||||
return
|
||||
if AnthropicModelInfo._supports_legacy_thinking(model, custom_llm_provider):
|
||||
return
|
||||
thinking: Final = optional_params.get("thinking")
|
||||
if not isinstance(thinking, dict) or thinking.get("type") != "enabled":
|
||||
return
|
||||
|
||||
effort: Final = AnthropicModelInfo._legacy_budget_to_effort(
|
||||
model=model,
|
||||
budget_tokens=int(thinking.get("budget_tokens") or 0),
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
existing_output_config: Final = optional_params.get("output_config")
|
||||
optional_params["thinking"] = {"type": "adaptive"}
|
||||
optional_params["output_config"] = {
|
||||
"effort": effort,
|
||||
**(existing_output_config if isinstance(existing_output_config, dict) else MappingProxyType({})),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _legacy_budget_to_effort(model: str, budget_tokens: int, custom_llm_provider: str) -> str:
|
||||
if budget_tokens >= DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET and (
|
||||
AnthropicModelInfo._supports_model_capability(model, "supports_xhigh_reasoning_effort", custom_llm_provider)
|
||||
):
|
||||
return "xhigh"
|
||||
if budget_tokens >= DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET:
|
||||
return "high"
|
||||
if budget_tokens >= DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET:
|
||||
return "medium"
|
||||
return "low"
|
||||
|
||||
def is_effort_used(
|
||||
self,
|
||||
optional_params: dict | None,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@ from typing import Any, Final
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.constants import (
|
||||
DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET,
|
||||
)
|
||||
from litellm.exceptions import AuthenticationError
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.litellm_core_utils.litellm_logging import verbose_logger
|
||||
|
|
@ -400,46 +395,6 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
existing_output_config.setdefault("effort", mapped_effort)
|
||||
optional_params["output_config"] = existing_output_config
|
||||
|
||||
@staticmethod
|
||||
def _translate_legacy_thinking_for_adaptive_model(
|
||||
model: str, optional_params: dict, custom_llm_provider: str
|
||||
) -> None:
|
||||
"""Translate legacy ``thinking.type=enabled`` to adaptive for the
|
||||
adaptive-thinking models that reject it (4.7+ and the 5 families).
|
||||
Models flagged ``supports_legacy_thinking`` (the 4.6 family) accept the
|
||||
legacy shape natively, so it is forwarded verbatim and the caller's
|
||||
``budget_tokens`` cap keeps applying. Caller-provided
|
||||
``output_config.effort`` is never overridden.
|
||||
"""
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
|
||||
if not AnthropicModelInfo._is_adaptive_thinking_model(model, custom_llm_provider):
|
||||
return
|
||||
if AnthropicModelInfo._supports_legacy_thinking(model, custom_llm_provider):
|
||||
return
|
||||
thinking: Final = optional_params.get("thinking")
|
||||
if not isinstance(thinking, dict) or thinking.get("type") != "enabled":
|
||||
return
|
||||
|
||||
budget: Final = int(thinking.get("budget_tokens") or 0)
|
||||
if budget >= DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET and (
|
||||
AnthropicConfig._supports_effort_level(model, "xhigh", custom_llm_provider)
|
||||
):
|
||||
effort = "xhigh"
|
||||
elif budget >= DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET:
|
||||
effort = "high"
|
||||
elif budget >= DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET:
|
||||
effort = "medium"
|
||||
else:
|
||||
effort = "low"
|
||||
|
||||
optional_params["thinking"] = {"type": "adaptive"}
|
||||
existing_output_config = optional_params.get("output_config")
|
||||
if not isinstance(existing_output_config, dict):
|
||||
existing_output_config = {}
|
||||
existing_output_config.setdefault("effort", effort)
|
||||
optional_params["output_config"] = existing_output_config
|
||||
|
||||
@staticmethod
|
||||
def _translate_adaptive_effort_for_non_adaptive_model(
|
||||
model: str, optional_params: dict, max_tokens: int | None, custom_llm_provider: str
|
||||
|
|
@ -606,7 +561,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
custom_llm_provider=self._resolved_provider,
|
||||
)
|
||||
|
||||
self._translate_legacy_thinking_for_adaptive_model(
|
||||
AnthropicModelInfo.translate_legacy_thinking_for_adaptive_model(
|
||||
model=model,
|
||||
optional_params=anthropic_messages_optional_request_params,
|
||||
custom_llm_provider=self._resolved_provider,
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@ def _promote_extra_body_to_optional_params(optional_params: dict) -> None:
|
|||
``output_config`` get auto-routed into ``extra_body`` by
|
||||
``add_provider_specific_params_to_optional_params``. For the Azure→Anthropic
|
||||
route those keys must reach the request body and be validated, so promote
|
||||
them. ``setdefault`` keeps explicit top-level values authoritative.
|
||||
them. The caller's values overwrite mapped top-level duplicates, matching
|
||||
the native ``anthropic`` provider, where the same passthrough lands on
|
||||
top-level ``optional_params`` after mapping.
|
||||
"""
|
||||
extra_body: Final = optional_params.get("extra_body")
|
||||
if not isinstance(extra_body, dict) or not extra_body:
|
||||
return
|
||||
for k, v in extra_body.items():
|
||||
optional_params.setdefault(k, v)
|
||||
optional_params.update(extra_body)
|
||||
optional_params.pop("extra_body", None)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -943,6 +943,9 @@ class AmazonConverseConfig(BaseConfig):
|
|||
litellm.verbose_logger.warning(DROP_UNSUPPORTED_ADAPTIVE_THINKING_WARNING, model)
|
||||
else:
|
||||
optional_params["thinking"] = value
|
||||
AnthropicModelInfo.translate_legacy_thinking_for_adaptive_model(
|
||||
model=model, optional_params=optional_params, custom_llm_provider="bedrock"
|
||||
)
|
||||
elif param == "reasoning_effort" and isinstance(value, str):
|
||||
self._handle_reasoning_effort_parameter(
|
||||
model=model, reasoning_effort=value, optional_params=optional_params
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
|
|||
# Restore original model name
|
||||
model = original_model
|
||||
|
||||
AnthropicModelInfo.translate_legacy_thinking_for_adaptive_model(
|
||||
model=original_model, optional_params=optional_params, custom_llm_provider="bedrock"
|
||||
)
|
||||
|
||||
# The stub model hides the original model from the parent's forced-tool-use backstop
|
||||
response_format_tool_choice: Final = optional_params.get("tool_choice")
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -330,6 +330,10 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig):
|
|||
) -> dict:
|
||||
is_thinking_enabled: Final = self.is_thinking_enabled(non_default_params)
|
||||
mapped_params: Final = super().map_openai_params(non_default_params, optional_params, model, drop_params)
|
||||
if "claude" in model:
|
||||
AnthropicConfig.translate_legacy_thinking_for_adaptive_model(
|
||||
model=model, optional_params=mapped_params, custom_llm_provider="databricks"
|
||||
)
|
||||
if "tools" in mapped_params:
|
||||
mapped_params["tools"] = self._map_openai_to_dbrx_tool(model=model, tools=mapped_params["tools"])
|
||||
if "max_completion_tokens" in non_default_params and replace_max_completion_tokens_with_max_tokens:
|
||||
|
|
|
|||
|
|
@ -177,6 +177,10 @@ class VertexAIAnthropicConfig(AnthropicConfig):
|
|||
# Restore original model name for any other processing
|
||||
model = original_model
|
||||
|
||||
AnthropicModelInfo.translate_legacy_thinking_for_adaptive_model(
|
||||
model=original_model, optional_params=optional_params, custom_llm_provider="vertex_ai"
|
||||
)
|
||||
|
||||
return optional_params
|
||||
|
||||
def transform_response(
|
||||
|
|
|
|||
|
|
@ -3127,6 +3127,31 @@ def test_reasoning_effort_accepts_dict_shape_for_non_adaptive_model(
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,budget_tokens,expected",
|
||||
[
|
||||
("claude-opus-4-8", 4096, ({"type": "adaptive"}, {"effort": "high"})),
|
||||
("claude-opus-4-7", 24000, ({"type": "adaptive"}, {"effort": "xhigh"})),
|
||||
("claude-opus-4-6", 4096, ({"type": "enabled", "budget_tokens": 4096}, None)),
|
||||
("claude-sonnet-4-5-20250929", 4096, ({"type": "enabled", "budget_tokens": 4096}, None)),
|
||||
],
|
||||
)
|
||||
def test_legacy_thinking_translated_to_adaptive_on_adaptive_only_models(model, budget_tokens, expected):
|
||||
"""Adaptive-only models reject thinking={type: enabled} with a 400, so the
|
||||
legacy shape must be upgraded to adaptive + output_config.effort on
|
||||
/chat/completions too, while models that accept it keep the caller's budget."""
|
||||
config = AnthropicConfig()
|
||||
|
||||
result = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": budget_tokens}, "max_tokens": 64000},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert (result["thinking"], result.get("output_config")) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bad_value",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -362,8 +362,8 @@ class TestAzureAnthropicConfig:
|
|||
)
|
||||
assert "xhigh" in str(exc_info.value)
|
||||
|
||||
def test_extra_body_promotion_does_not_clobber_top_level(self):
|
||||
"""Top-level ``optional_params`` wins over duplicates in ``extra_body``."""
|
||||
def test_extra_body_promotion_overrides_mapped_top_level(self):
|
||||
"""The caller's ``extra_body`` wins over a mapped top-level duplicate, like the native ``anthropic`` passthrough."""
|
||||
config = AzureAnthropicConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
|
@ -383,7 +383,31 @@ class TestAzureAnthropicConfig:
|
|||
headers=headers,
|
||||
)
|
||||
|
||||
assert result["output_config"] == {"effort": "low"}
|
||||
assert result["output_config"] == {"effort": "high"}
|
||||
|
||||
def test_legacy_thinking_upgrade_keeps_caller_effort_from_extra_body(self, local_model_cost_map):
|
||||
config = AzureAnthropicConfig()
|
||||
|
||||
mapped = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 1024}, "max_tokens": 100},
|
||||
optional_params={},
|
||||
model="claude-opus-4-8",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["thinking"] == {"type": "adaptive"}
|
||||
assert mapped["output_config"] == {"effort": "low"}
|
||||
|
||||
result = config.transform_request(
|
||||
model="claude-opus-4-8",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={**mapped, "extra_body": {"output_config": {"effort": "high"}}},
|
||||
litellm_params={"api_key": "test-key"},
|
||||
headers={"api-key": "test-key", "anthropic-version": "2023-06-01"},
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "adaptive"}
|
||||
assert result["output_config"] == {"effort": "high"}
|
||||
assert "extra_body" not in result
|
||||
|
||||
def test_context_management_mixed_edits_beta_headers(self):
|
||||
"""Test that context_management with both compact and other edits adds both beta headers"""
|
||||
|
|
|
|||
|
|
@ -671,3 +671,46 @@ def test_bedrock_chat_invoke_fable_5_1_response_format_avoids_forced_tool_choice
|
|||
assert "output_format" not in result
|
||||
assert "tools" in result
|
||||
assert "tool_choice" not in result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["us.anthropic.claude-sonnet-5", "us.anthropic.claude-fable-5-1"])
|
||||
def test_bedrock_chat_invoke_tool_based_response_format_still_upgrades_legacy_thinking(local_model_cost_map, model):
|
||||
result = AmazonAnthropicClaudeConfig().map_openai_params(
|
||||
non_default_params={
|
||||
"response_format": {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "test_schema",
|
||||
"schema": {"type": "object", "properties": {"result": {"type": "string"}}},
|
||||
},
|
||||
},
|
||||
"thinking": {"type": "enabled", "budget_tokens": 4096},
|
||||
"max_tokens": 8192,
|
||||
},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert "tools" in result
|
||||
assert result["thinking"] == {"type": "adaptive"}
|
||||
assert result["output_config"] == {"effort": "high"}
|
||||
|
||||
|
||||
def test_bedrock_chat_invoke_response_format_stub_still_upgrades_legacy_thinking(local_model_cost_map):
|
||||
"""Regression: the tool-based ``response_format`` path swaps in a Claude 3 stub
|
||||
model before the shared Anthropic mapping, which hid the adaptive-only model
|
||||
from the legacy ``thinking`` upgrade and left ``type=enabled`` on the wire."""
|
||||
result = AmazonAnthropicClaudeConfig().map_openai_params(
|
||||
non_default_params={
|
||||
"response_format": {"type": "json_object"},
|
||||
"thinking": {"type": "enabled", "budget_tokens": 4096},
|
||||
"max_tokens": 8192,
|
||||
},
|
||||
optional_params={},
|
||||
model="us.anthropic.claude-fable-5-1",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "adaptive"}
|
||||
assert result["output_config"] == {"effort": "high"}
|
||||
|
|
|
|||
|
|
@ -6347,6 +6347,82 @@ def test_adaptive_thinking_passes_through_on_46_plus_converse(model):
|
|||
assert optional_params.get("thinking") == {"type": "adaptive"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,budget_tokens,expected_effort",
|
||||
[
|
||||
("anthropic.claude-opus-4-8", 4096, "high"),
|
||||
("us.anthropic.claude-opus-4-8", 2000, "low"),
|
||||
("global.anthropic.claude-opus-4-8", 12000, "xhigh"),
|
||||
("us.anthropic.claude-opus-4-7", 3000, "medium"),
|
||||
("anthropic.claude-fable-5", 4096, "high"),
|
||||
],
|
||||
)
|
||||
def test_legacy_thinking_translated_to_adaptive_on_adaptive_only_converse(model, budget_tokens, expected_effort):
|
||||
"""Adaptive-only models (4.7+, 5 families) reject thinking={type: enabled}
|
||||
with a 400 on Bedrock Converse, so the legacy shape from callers like Claude
|
||||
Code must be upgraded to thinking={type: adaptive} + output_config.effort
|
||||
derived from budget_tokens, matching the /v1/messages passthrough."""
|
||||
config = AmazonConverseConfig()
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": budget_tokens}, "max_tokens": 64000},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
request = config.transform_request(
|
||||
model=model,
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert request["additionalModelRequestFields"]["thinking"] == {"type": "adaptive"}
|
||||
assert request["additionalModelRequestFields"]["output_config"] == {"effort": expected_effort}
|
||||
|
||||
|
||||
def test_legacy_thinking_translation_keeps_caller_output_config_effort_converse():
|
||||
config = AmazonConverseConfig()
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={
|
||||
"output_config": {"effort": "low"},
|
||||
"thinking": {"type": "enabled", "budget_tokens": 12000},
|
||||
"max_tokens": 64000,
|
||||
},
|
||||
optional_params={},
|
||||
model="anthropic.claude-opus-4-8",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert optional_params["thinking"] == {"type": "adaptive"}
|
||||
assert optional_params["output_config"] == {"effort": "low"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"us.anthropic.claude-opus-4-6",
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
],
|
||||
)
|
||||
def test_legacy_thinking_forwarded_verbatim_when_model_accepts_it_converse(model):
|
||||
"""The 4.6 family and pre-adaptive models accept thinking={type: enabled}
|
||||
natively, so the caller's budget_tokens cap must keep applying."""
|
||||
config = AmazonConverseConfig()
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 4096}, "max_tokens": 8192},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert optional_params["thinking"] == {"type": "enabled", "budget_tokens": 4096}
|
||||
assert "output_config" not in optional_params
|
||||
|
||||
|
||||
def test_adaptive_thinking_dropped_when_max_tokens_too_small_converse():
|
||||
"""When max_tokens can't fit even the minimum thinking budget, the raw
|
||||
adaptive block must be dropped entirely rather than translated, so the
|
||||
|
|
|
|||
|
|
@ -422,6 +422,27 @@ def test_databricks_config_probes_capabilities_under_databricks_namespace():
|
|||
assert DatabricksConfig().custom_llm_provider == "databricks"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model, expected_thinking, expected_output_config",
|
||||
[
|
||||
("databricks-claude-opus-4-8", {"type": "adaptive"}, {"effort": "high"}),
|
||||
("databricks-claude-opus-4-6", {"type": "enabled", "budget_tokens": 4096}, None),
|
||||
],
|
||||
ids=["adaptive_only_upgrades_to_adaptive", "legacy_capable_forwards_verbatim"],
|
||||
)
|
||||
def test_map_openai_params_upgrades_legacy_thinking_on_adaptive_only_claude(
|
||||
model, expected_thinking, expected_output_config
|
||||
):
|
||||
mapped = DatabricksConfig().map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 4096}},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["thinking"] == expected_thinking
|
||||
assert mapped.get("output_config") == expected_output_config
|
||||
|
||||
|
||||
def _streaming_chunk(usage=None, choices=None):
|
||||
base = {
|
||||
"id": "chatcmpl-test",
|
||||
|
|
|
|||
|
|
@ -752,3 +752,26 @@ def test_vertex_ai_fable_5_1_response_format_uses_native_output_format(local_mod
|
|||
assert "output_format" in result_params
|
||||
assert "tool_choice" not in result_params
|
||||
assert "tools" not in result_params
|
||||
|
||||
|
||||
def test_vertex_ai_anthropic_tool_based_response_format_still_upgrades_legacy_thinking(local_model_cost_map):
|
||||
result_params = VertexAIAnthropicConfig().map_openai_params(
|
||||
non_default_params={
|
||||
"response_format": {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "test_schema",
|
||||
"schema": {"type": "object", "properties": {"result": {"type": "string"}}},
|
||||
},
|
||||
},
|
||||
"thinking": {"type": "enabled", "budget_tokens": 4096},
|
||||
"max_tokens": 8192,
|
||||
},
|
||||
optional_params={},
|
||||
model="claude-opus-4-8",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert "tools" in result_params
|
||||
assert result_params["thinking"] == {"type": "adaptive"}
|
||||
assert result_params["output_config"] == {"effort": "high"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue