fix(anthropic): drop forced tool_choice when thinking is enabled (#35544)

This commit is contained in:
Roli Bosch
2026-03-04 18:09:48 -08:00
committed by GitHub
parent c8f394208b
commit fb31c91076
2 changed files with 107 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import copy
import os
import warnings
from collections.abc import Callable
from typing import Any, Literal, cast
from unittest.mock import MagicMock, patch
@@ -2520,3 +2521,89 @@ def test_context_overflow_error_backwards_compatibility() -> None:
# Verify it's both types (multiple inheritance)
assert isinstance(exc_info.value, anthropic.BadRequestError)
assert isinstance(exc_info.value, ContextOverflowError)
def test_bind_tools_drops_forced_tool_choice_when_thinking_enabled() -> None:
"""Regression test for https://github.com/langchain-ai/langchain/issues/35539.
Anthropic API rejects forced tool_choice when thinking is enabled:
"Thinking may not be enabled when tool_choice forces tool use."
bind_tools should drop forced tool_choice and warn.
"""
chat_model = ChatAnthropic(
model=MODEL_NAME,
anthropic_api_key="secret-api-key",
thinking={"type": "enabled", "budget_tokens": 5000},
)
# 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(
model=MODEL_NAME,
anthropic_api_key="secret-api-key",
)
# No thinking — tool_choice="any" should pass through
result = chat_model.bind_tools([GetWeather], tool_choice="any")
assert cast("RunnableBinding", result).kwargs["tool_choice"] == {"type": "any"}
# Thinking explicitly None
chat_model_none = ChatAnthropic(
model=MODEL_NAME,
anthropic_api_key="secret-api-key",
thinking=None,
)
result = chat_model_none.bind_tools([GetWeather], tool_choice="any")
assert cast("RunnableBinding", result).kwargs["tool_choice"] == {"type": "any"}
# Thinking explicitly disabled — should NOT drop tool_choice
chat_model_disabled = ChatAnthropic(
model=MODEL_NAME,
anthropic_api_key="secret-api-key",
thinking={"type": "disabled"},
)
result = chat_model_disabled.bind_tools([GetWeather], tool_choice="any")
assert cast("RunnableBinding", result).kwargs["tool_choice"] == {"type": "any"}