From 2cff369cdc4d62bd7016aa8a0bf040db7f871d32 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Fri, 12 Dec 2025 10:29:12 -0500 Subject: [PATCH] feat(anthropic): accept `TypedDict` for built-in tool types (#34279) Widen `bind_tools` to accept `TypedDict` via `Mapping` so that users may import and use Anthropic's built-in tool types: ```python import subprocess from anthropic.types.beta import BetaToolBash20250124Param from langchain.tools import tool tool_spec = BetaToolBash20250124Param( name="bash", type="bash_20250124", strict=True, ) @tool(extras={"provider_tool_definition": tool_spec}) def bash(*, command: str, restart: bool = False, **kw): """Execute a bash command.""" if restart: return "Bash session restarted" try: result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=30, ) return result.stdout + result.stderr except Exception as e: return f"Error: {e}" # Bind bash tool to your model ``` --------- Co-authored-by: Chester Curme --- .../langchain_anthropic/chat_models.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index 30554a0d3cd..5179200606d 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -2482,7 +2482,7 @@ class ChatAnthropic(BaseChatModel): def bind_tools( self, - tools: Sequence[dict[str, Any] | type | Callable | BaseTool], + tools: Sequence[Mapping[str, Any] | type | Callable | BaseTool], *, tool_choice: dict[str, str] | str | None = None, parallel_tool_calls: bool | None = None, @@ -2758,6 +2758,9 @@ class ChatAnthropic(BaseChatModel): See LangChain [docs](https://docs.langchain.com/oss/python/integrations/chat/anthropic#strict-tool-use) for more detail. """ # noqa: E501 + # Allows built-in tools either by their: + # - Raw `dict` format + # - Extracting extras["provider_tool_definition"] if provided on a BaseTool formatted_tools = [ tool if _is_builtin_tool(tool) @@ -2984,6 +2987,8 @@ class ChatAnthropic(BaseChatModel): if method == "function_calling": formatted_tool = cast(AnthropicTool, convert_to_anthropic_tool(schema)) + # 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": llm = self._get_llm_for_structured_output_when_thinking_is_enabled( @@ -3131,7 +3136,7 @@ class ChatAnthropic(BaseChatModel): def convert_to_anthropic_tool( - tool: dict[str, Any] | type | Callable | BaseTool, + tool: Mapping[str, Any] | type | Callable | BaseTool, *, strict: bool | None = None, ) -> AnthropicTool: @@ -3149,6 +3154,15 @@ def convert_to_anthropic_tool( Returns: `AnthropicTool` for custom/user-defined tools """ + if ( + isinstance(tool, BaseTool) + and hasattr(tool, "extras") + and isinstance(tool.extras, dict) + and "provider_tool_definition" in tool.extras + ): + # Pass through built-in tool definitions + return tool.extras["provider_tool_definition"] # type: ignore[return-value] + if isinstance(tool, dict) and all( k in tool for k in ("name", "description", "input_schema") ):