From d87d2321e8e1d68cfcaceb1d01645e330904d460 Mon Sep 17 00:00:00 2001 From: Chester Curme Date: Thu, 16 Jan 2025 18:37:02 -0500 Subject: [PATCH 1/4] update anthropic --- .../langchain_anthropic/chat_models.py | 10 ++++++++++ .../integration_tests/test_chat_models.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index fd64b824a8df7..00c6603bc3d1d 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -968,6 +968,16 @@ class GetPrice(BaseModel): f"Unrecognized 'tool_choice' type {tool_choice=}. Expected dict, " f"str, or None." ) + + if kwargs.get("parallel_tool_calls") == False: # noqa: E712 + if "tool_choice" in kwargs: + kwargs["tool_choice"]["disable_parallel_tool_use"] = True + else: + kwargs["tool_choice"] = { + "type": "any", + "disable_parallel_tool_use": True, + } + kwargs.pop("parallel_tool_calls", None) return self.bind(tools=formatted_tools, **kwargs) def with_structured_output( diff --git a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py index 9f2eaba45549d..3da7bb94f9cf8 100644 --- a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py +++ b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py @@ -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.""" From 996aab60e6b459dee6b7ac0daa9e06f47abdcce3 Mon Sep 17 00:00:00 2001 From: Chester Curme Date: Thu, 16 Jan 2025 18:37:44 -0500 Subject: [PATCH 2/4] add test to openai --- .../tests/integration_tests/chat_models/test_base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libs/partners/openai/tests/integration_tests/chat_models/test_base.py b/libs/partners/openai/tests/integration_tests/chat_models/test_base.py index 49d894603d726..525020192ba0d 100644 --- a/libs/partners/openai/tests/integration_tests/chat_models/test_base.py +++ b/libs/partners/openai/tests/integration_tests/chat_models/test_base.py @@ -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): From 88dfe1bd7482728f23729b603c1f38a5f41b5700 Mon Sep 17 00:00:00 2001 From: Chester Curme Date: Thu, 16 Jan 2025 19:35:25 -0500 Subject: [PATCH 3/4] add to method signature --- .../anthropic/langchain_anthropic/chat_models.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index 00c6603bc3d1d..bbec1c8f3c85d 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -819,6 +819,7 @@ def bind_tools( 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. @@ -969,15 +970,18 @@ class GetPrice(BaseModel): f"str, or None." ) - if kwargs.get("parallel_tool_calls") == False: # noqa: E712 + 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"] = True + kwargs["tool_choice"]["disable_parallel_tool_use"] = ( + disable_parallel_tool_use + ) else: kwargs["tool_choice"] = { "type": "any", - "disable_parallel_tool_use": True, + "disable_parallel_tool_use": disable_parallel_tool_use, } - kwargs.pop("parallel_tool_calls", None) + return self.bind(tools=formatted_tools, **kwargs) def with_structured_output( From 2bab7a4da7aa15ac733c47165bccdd6130e0a176 Mon Sep 17 00:00:00 2001 From: Chester Curme Date: Fri, 17 Jan 2025 14:27:55 -0500 Subject: [PATCH 4/4] document param --- libs/partners/anthropic/langchain_anthropic/chat_models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index bbec1c8f3c85d..842d30ae00da3 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -833,6 +833,10 @@ def bind_tools( - name of the tool as a string or as dict ``{"type": "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`.