feat(anthropic): support adaptive thinking mode (#36293)

Thank you!
This commit is contained in:
vladpostoaca-stripe
2026-03-29 02:53:55 +02:00
committed by GitHub
parent 4d9842da67
commit fe2f105ce7
2 changed files with 67 additions and 3 deletions

View File

@@ -1652,7 +1652,7 @@ class ChatAnthropic(BaseChatModel):
# _get_llm_for_structured_output_when_thinking_is_enabled.
if (
self.thinking is not None
and self.thinking.get("type") == "enabled"
and self.thinking.get("type") in ("enabled", "adaptive")
and "tool_choice" in kwargs
and kwargs["tool_choice"].get("type") in ("any", "tool")
):
@@ -1781,7 +1781,10 @@ class ChatAnthropic(BaseChatModel):
# The result of convert_to_anthropic_tool for 'method=function_calling' will
# always be an AnthropicTool
tool_name = formatted_tool["name"]
if self.thinking is not None and self.thinking.get("type") == "enabled":
if self.thinking is not None and self.thinking.get("type") in (
"enabled",
"adaptive",
):
llm = self._get_llm_for_structured_output_when_thinking_is_enabled(
schema,
formatted_tool,
@@ -1993,7 +1996,7 @@ def _tools_in_params(params: dict) -> bool:
def _thinking_in_params(params: dict) -> bool:
return params.get("thinking", {}).get("type") == "enabled"
return params.get("thinking", {}).get("type") in ("enabled", "adaptive")
def _documents_in_params(params: dict) -> bool:

View File

@@ -29,6 +29,7 @@ from langchain_anthropic.chat_models import (
_format_messages,
_is_builtin_tool,
_merge_messages,
_thinking_in_params,
convert_to_anthropic_tool,
)
@@ -2675,6 +2676,57 @@ def test_bind_tools_drops_forced_tool_choice_when_thinking_enabled() -> None:
assert len(w) == 1
def test_bind_tools_drops_forced_tool_choice_when_adaptive_thinking() -> None:
"""Adaptive thinking has the same forced tool_choice restriction as enabled."""
chat_model = ChatAnthropic(
model=MODEL_NAME,
anthropic_api_key="secret-api-key",
thinking={"type": "adaptive"},
)
# tool_choice="any" should be dropped with warning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
result = chat_model.bind_tools([GetWeather], tool_choice="any")
assert "tool_choice" not in cast("RunnableBinding", result).kwargs
assert len(w) == 1
assert "thinking is enabled" in str(w[0].message)
# tool_choice="auto" should NOT be dropped (auto is allowed)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
result = chat_model.bind_tools([GetWeather], tool_choice="auto")
assert cast("RunnableBinding", result).kwargs["tool_choice"] == {"type": "auto"}
assert len(w) == 0
# tool_choice=specific tool name should be dropped with warning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
result = chat_model.bind_tools([GetWeather], tool_choice="GetWeather")
assert "tool_choice" not in cast("RunnableBinding", result).kwargs
assert len(w) == 1
# tool_choice=dict with type "tool" should be dropped with warning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
result = chat_model.bind_tools(
[GetWeather],
tool_choice={"type": "tool", "name": "GetWeather"},
)
assert "tool_choice" not in cast("RunnableBinding", result).kwargs
assert len(w) == 1
# tool_choice=dict with type "any" should also be dropped
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
result = chat_model.bind_tools(
[GetWeather],
tool_choice={"type": "any"},
)
assert "tool_choice" not in cast("RunnableBinding", result).kwargs
assert len(w) == 1
def test_bind_tools_keeps_forced_tool_choice_when_thinking_disabled() -> None:
"""When thinking is not enabled, forced tool_choice should pass through."""
chat_model = ChatAnthropic(
@@ -2703,3 +2755,12 @@ def test_bind_tools_keeps_forced_tool_choice_when_thinking_disabled() -> None:
)
result = chat_model_disabled.bind_tools([GetWeather], tool_choice="any")
assert cast("RunnableBinding", result).kwargs["tool_choice"] == {"type": "any"}
def test_thinking_in_params_recognizes_adaptive() -> None:
"""_thinking_in_params should recognize both enabled and adaptive types."""
assert _thinking_in_params({"thinking": {"type": "enabled", "budget_tokens": 5000}})
assert _thinking_in_params({"thinking": {"type": "adaptive"}})
assert not _thinking_in_params({"thinking": {"type": "disabled"}})
assert not _thinking_in_params({"thinking": {}})
assert not _thinking_in_params({})