libs/core/langchain_core/tools/simple.py PYTHON 205 lines View on github.com → Search inside
1"""Tool that takes in function or coroutine directly."""23from __future__ import annotations45from collections.abc import Awaitable, Callable6from inspect import signature7from typing import (8    TYPE_CHECKING,9    Any,10)1112from typing_extensions import override1314# Cannot move to TYPE_CHECKING as _run/_arun parameter annotations are needed at runtime15from langchain_core.callbacks import (16    AsyncCallbackManagerForToolRun,  # noqa: TC00117    CallbackManagerForToolRun,  # noqa: TC00118)19from langchain_core.runnables import RunnableConfig, run_in_executor20from langchain_core.tools.base import (21    ArgsSchema,22    BaseTool,23    ToolException,24    _get_runnable_config_param,25)2627if TYPE_CHECKING:28    from langchain_core.messages import ToolCall293031class Tool(BaseTool):32    """Tool that takes in function or coroutine directly."""3334    description: str = ""3536    func: Callable[..., str] | None37    """The function to run when the tool is called."""3839    coroutine: Callable[..., Awaitable[str]] | None = None40    """The asynchronous version of the function."""4142    # --- Runnable ---4344    @override45    async def ainvoke(46        self,47        input: str | dict | ToolCall,48        config: RunnableConfig | None = None,49        **kwargs: Any,50    ) -> Any:51        if not self.coroutine:52            # If the tool does not implement async, fall back to default implementation53            return await run_in_executor(config, self.invoke, input, config, **kwargs)5455        return await super().ainvoke(input, config, **kwargs)5657    # --- Tool ---5859    @property60    def args(self) -> dict:61        """The tool's input arguments.6263        Returns:64            The input arguments for the tool.65        """66        if self.args_schema is not None:67            return super().args68        # For backwards compatibility, if the function signature is ambiguous,69        # assume it takes a single string input.70        return {"tool_input": {"type": "string"}}7172    def _to_args_and_kwargs(73        self, tool_input: str | dict, tool_call_id: str | None74    ) -> tuple[tuple, dict]:75        """Convert tool input to Pydantic model.7677        Args:78            tool_input: The input to the tool.79            tool_call_id: The ID of the tool call.8081        Raises:82            ToolException: If the tool input is invalid.8384        Returns:85            The Pydantic model args and kwargs.86        """87        args, kwargs = super()._to_args_and_kwargs(tool_input, tool_call_id)88        # For backwards compatibility. The tool must be run with a single input89        all_args = list(args) + list(kwargs.values())90        if len(all_args) != 1:91            msg = (92                f"""Too many arguments to single-input tool {self.name}.93                Consider using StructuredTool instead."""94                f" Args: {all_args}"95            )96            raise ToolException(msg)97        return tuple(all_args), {}9899    def _run(100        self,101        *args: Any,102        config: RunnableConfig,103        run_manager: CallbackManagerForToolRun | None = None,104        **kwargs: Any,105    ) -> Any:106        """Use the tool.107108        Args:109            *args: Positional arguments to pass to the tool110            config: Configuration for the run111            run_manager: Optional callback manager to use for the run112            **kwargs: Keyword arguments to pass to the tool113114        Returns:115            The result of the tool execution116        """117        if self.func:118            if run_manager and signature(self.func).parameters.get("callbacks"):119                kwargs["callbacks"] = run_manager.get_child()120            if config_param := _get_runnable_config_param(self.func):121                kwargs[config_param] = config122            return self.func(*args, **kwargs)123        msg = "Tool does not support sync invocation."124        raise NotImplementedError(msg)125126    async def _arun(127        self,128        *args: Any,129        config: RunnableConfig,130        run_manager: AsyncCallbackManagerForToolRun | None = None,131        **kwargs: Any,132    ) -> Any:133        """Use the tool asynchronously.134135        Args:136            *args: Positional arguments to pass to the tool137            config: Configuration for the run138            run_manager: Optional callback manager to use for the run139            **kwargs: Keyword arguments to pass to the tool140141        Returns:142            The result of the tool execution143        """144        if self.coroutine:145            if run_manager and signature(self.coroutine).parameters.get("callbacks"):146                kwargs["callbacks"] = run_manager.get_child()147            if config_param := _get_runnable_config_param(self.coroutine):148                kwargs[config_param] = config149            return await self.coroutine(*args, **kwargs)150151        # NOTE: this code is unreachable since _arun is only called if coroutine is not152        # None.153        return await super()._arun(154            *args, config=config, run_manager=run_manager, **kwargs155        )156157    # TODO: this is for backwards compatibility, remove in future158    def __init__(159        self, name: str, func: Callable | None, description: str, **kwargs: Any160    ) -> None:161        """Initialize tool."""162        super().__init__(name=name, func=func, description=description, **kwargs)163164    @classmethod165    def from_function(166        cls,167        func: Callable | None,168        name: str,  # We keep these required to support backwards compatibility169        description: str,170        return_direct: bool = False,  # noqa: FBT001,FBT002171        args_schema: ArgsSchema | None = None,172        coroutine: Callable[..., Awaitable[Any]]173        | None = None,  # This is last for compatibility, but should be after func174        **kwargs: Any,175    ) -> Tool:176        """Initialize tool from a function.177178        Args:179            func: The function to create the tool from.180            name: The name of the tool.181            description: The description of the tool.182            return_direct: Whether to return the output directly.183            args_schema: The schema of the tool's input arguments.184            coroutine: The asynchronous version of the function.185            **kwargs: Additional arguments to pass to the tool.186187        Returns:188            The tool.189190        Raises:191            ValueError: If the function is not provided.192        """193        if func is None and coroutine is None:194            msg = "Function and/or coroutine must be provided"195            raise ValueError(msg)196        return cls(197            name=name,198            func=func,199            coroutine=coroutine,200            description=description,201            return_direct=return_direct,202            args_schema=args_schema,203            **kwargs,204        )

Code quality findings 3

Ensure functions have docstrings for documentation
missing-docstring
async def ainvoke(
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
all_args = list(args) + list(kwargs.values())
Ensure functions have docstrings for documentation
missing-docstring
def from_function(

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.