anthropic[patch]: support parallel_tool_calls (#29257)

Need to:
- Update docs
- Decide if this is an explicit kwarg of bind_tools
- Decide if this should be in standard test with flag for supporting
This commit is contained in:
ccurme 2025-01-17 14:41:41 -05:00 committed by GitHub
parent 628145b172
commit c616b445f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 49 additions and 0 deletions

View File

@ -819,6 +819,7 @@ class ChatAnthropic(BaseChatModel):
tool_choice: Optional[
Union[Dict[str, str], Literal["any", "auto"], str]
] = None,
parallel_tool_calls: Optional[bool] = None,
**kwargs: Any,
) -> Runnable[LanguageModelInput, BaseMessage]:
r"""Bind tool-like objects to this chat model.
@ -832,6 +833,10 @@ class ChatAnthropic(BaseChatModel):
- name of the tool as a string or as dict ``{"type": "tool", "name": "<<tool_name>>"}``: calls corresponding tool;
- ``"auto"``, ``{"type: "auto"}``, or None: automatically selects a tool (including no tool);
- ``"any"`` or ``{"type: "any"}``: force at least one tool to be called;
parallel_tool_calls: Set to ``False`` to disable parallel tool use.
Defaults to ``None`` (no specification, which allows parallel tool use).
.. versionadded:: 0.3.2
kwargs: Any additional parameters are passed directly to
:meth:`~langchain_anthropic.chat_models.ChatAnthropic.bind`.
@ -968,6 +973,19 @@ class ChatAnthropic(BaseChatModel):
f"Unrecognized 'tool_choice' type {tool_choice=}. Expected dict, "
f"str, or None."
)
if parallel_tool_calls is not None:
disable_parallel_tool_use = not parallel_tool_calls
if "tool_choice" in kwargs:
kwargs["tool_choice"]["disable_parallel_tool_use"] = (
disable_parallel_tool_use
)
else:
kwargs["tool_choice"] = {
"type": "any",
"disable_parallel_tool_use": disable_parallel_tool_use,
}
return self.bind(tools=formatted_tools, **kwargs)
def with_structured_output(

View File

@ -444,6 +444,25 @@ def test_tool_use() -> None:
assert len(chunks) > 1
class GenerateUsername(BaseModel):
"Get a username based on someone's name and hair color."
name: str
hair_color: str
def test_disable_parallel_tool_calling() -> None:
llm = ChatAnthropic(model="claude-3-5-sonnet-20241022")
llm_with_tools = llm.bind_tools([GenerateUsername], parallel_tool_calls=False)
result = llm_with_tools.invoke(
"Use the GenerateUsername tool to generate user names for:\n\n"
"Sally with green hair\n"
"Bob with blue hair"
)
assert isinstance(result, AIMessage)
assert len(result.tool_calls) == 1
def test_anthropic_with_empty_text_block() -> None:
"""Anthropic SDK can return an empty text block."""

View File

@ -630,6 +630,18 @@ def test_bind_tools_tool_choice() -> None:
assert not msg.tool_calls
def test_disable_parallel_tool_calling() -> None:
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools([GenerateUsername], parallel_tool_calls=False)
result = llm_with_tools.invoke(
"Use the GenerateUsername tool to generate user names for:\n\n"
"Sally with green hair\n"
"Bob with blue hair"
)
assert isinstance(result, AIMessage)
assert len(result.tool_calls) == 1
@pytest.mark.parametrize("model", ["gpt-4o-mini", "o1"])
def test_openai_structured_output(model: str) -> None:
class MyModel(BaseModel):