diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index 419723e7428..5ed5d314664 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -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: diff --git a/libs/partners/anthropic/tests/unit_tests/test_chat_models.py b/libs/partners/anthropic/tests/unit_tests/test_chat_models.py index e1a9d69db0e..9b0e3b7cfd8 100644 --- a/libs/partners/anthropic/tests/unit_tests/test_chat_models.py +++ b/libs/partners/anthropic/tests/unit_tests/test_chat_models.py @@ -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({})