Ensure functions have docstrings for documentation
async def ainvoke(
1"""Structured tool."""23from __future__ import annotations45import functools6import textwrap7from collections.abc import Awaitable, Callable8from inspect import signature9from typing import (10 TYPE_CHECKING,11 Annotated,12 Any,13 Literal,14)1516from pydantic import Field, SkipValidation17from typing_extensions import override1819# Cannot move to TYPE_CHECKING as _run/_arun parameter annotations are needed at runtime20from langchain_core.callbacks import (21 AsyncCallbackManagerForToolRun, # noqa: TC00122 CallbackManagerForToolRun, # noqa: TC00123)24from langchain_core.runnables import RunnableConfig, run_in_executor25from langchain_core.tools.base import (26 _EMPTY_SET,27 FILTERED_ARGS,28 ArgsSchema,29 BaseTool,30 _get_runnable_config_param,31 _is_injected_arg_type,32 create_schema_from_function,33)34from langchain_core.utils.pydantic import is_basemodel_subclass3536if TYPE_CHECKING:37 from langchain_core.messages import ToolCall383940class StructuredTool(BaseTool):41 """Tool that can operate on any number of inputs."""4243 description: str = ""4445 args_schema: Annotated[ArgsSchema, SkipValidation()] = Field(46 ..., description="The tool schema."47 )48 """The input arguments' schema."""4950 func: Callable[..., Any] | None = None51 """The function to run when the tool is called."""5253 coroutine: Callable[..., Awaitable[Any]] | None = None54 """The asynchronous version of the function."""5556 # --- Runnable ---5758 # TODO: Is this needed?59 @override60 async def ainvoke(61 self,62 input: str | dict | ToolCall,63 config: RunnableConfig | None = None,64 **kwargs: Any,65 ) -> Any:66 if not self.coroutine:67 # If the tool does not implement async, fall back to default implementation68 return await run_in_executor(config, self.invoke, input, config, **kwargs)6970 return await super().ainvoke(input, config, **kwargs)7172 # --- Tool ---7374 def _run(75 self,76 *args: Any,77 config: RunnableConfig,78 run_manager: CallbackManagerForToolRun | None = None,79 **kwargs: Any,80 ) -> Any:81 """Use the tool.8283 Args:84 *args: Positional arguments to pass to the tool85 config: Configuration for the run86 run_manager: Optional callback manager to use for the run87 **kwargs: Keyword arguments to pass to the tool8889 Returns:90 The result of the tool execution91 """92 if self.func:93 if run_manager and signature(self.func).parameters.get("callbacks"):94 kwargs["callbacks"] = run_manager.get_child()95 if config_param := _get_runnable_config_param(self.func):96 kwargs[config_param] = config97 return self.func(*args, **kwargs)98 msg = "StructuredTool does not support sync invocation."99 raise NotImplementedError(msg)100101 async def _arun(102 self,103 *args: Any,104 config: RunnableConfig,105 run_manager: AsyncCallbackManagerForToolRun | None = None,106 **kwargs: Any,107 ) -> Any:108 """Use the tool asynchronously.109110 Args:111 *args: Positional arguments to pass to the tool112 config: Configuration for the run113 run_manager: Optional callback manager to use for the run114 **kwargs: Keyword arguments to pass to the tool115116 Returns:117 The result of the tool execution118 """119 if self.coroutine:120 if run_manager and signature(self.coroutine).parameters.get("callbacks"):121 kwargs["callbacks"] = run_manager.get_child()122 if config_param := _get_runnable_config_param(self.coroutine):123 kwargs[config_param] = config124 return await self.coroutine(*args, **kwargs)125126 # If self.coroutine is None, then this will delegate to the default127 # implementation which is expected to delegate to _run on a separate thread.128 return await super()._arun(129 *args, config=config, run_manager=run_manager, **kwargs130 )131132 @classmethod133 def from_function(134 cls,135 func: Callable | None = None,136 coroutine: Callable[..., Awaitable[Any]] | None = None,137 name: str | None = None,138 description: str | None = None,139 return_direct: bool = False, # noqa: FBT001,FBT002140 args_schema: ArgsSchema | None = None,141 infer_schema: bool = True, # noqa: FBT001,FBT002142 *,143 response_format: Literal["content", "content_and_artifact"] = "content",144 parse_docstring: bool = False,145 error_on_invalid_docstring: bool = False,146 **kwargs: Any,147 ) -> StructuredTool:148 """Create tool from a given function.149150 A classmethod that helps to create a tool from a function.151152 Args:153 func: The function from which to create a tool.154 coroutine: The async function from which to create a tool.155 name: The name of the tool.156157 Defaults to the function name.158 description: The description of the tool.159160 Defaults to the function docstring.161 return_direct: Whether to return the result directly or as a callback.162 args_schema: The schema of the tool's input arguments.163 infer_schema: Whether to infer the schema from the function's signature.164 response_format: The tool response format.165166 If `'content'` then the output of the tool is interpreted as the167 contents of a `ToolMessage`. If `'content_and_artifact'` then the output168 is expected to be a two-tuple corresponding to the `(content, artifact)`169 of a `ToolMessage`.170 parse_docstring: If `infer_schema` and `parse_docstring`, will attempt171 to parse parameter descriptions from Google Style function docstrings.172 error_on_invalid_docstring: if `parse_docstring` is provided, configure173 whether to raise `ValueError` on invalid Google Style docstrings.174 **kwargs: Additional arguments to pass to the tool175176 Returns:177 The tool.178179 Raises:180 ValueError: If the function is not provided.181 ValueError: If the function does not have a docstring and description182 is not provided.183 TypeError: If the `args_schema` is not a `BaseModel` or dict.184185 Examples:186 ```python187 def add(a: int, b: int) -> int:188 \"\"\"Add two numbers\"\"\"189 return a + b190 tool = StructuredTool.from_function(add)191 tool.run(1, 2) # 3192193 ```194 """195 if func is not None:196 source_function = func197 elif coroutine is not None:198 source_function = coroutine199 else:200 msg = "Function and/or coroutine must be provided"201 raise ValueError(msg)202 name = name or source_function.__name__203 if args_schema is None and infer_schema:204 # schema name is appended within function205 args_schema = create_schema_from_function(206 name,207 source_function,208 parse_docstring=parse_docstring,209 error_on_invalid_docstring=error_on_invalid_docstring,210 filter_args=_filter_schema_args(source_function),211 )212 description_ = description213 if description is None and not parse_docstring:214 description_ = source_function.__doc__ or None215 if description_ is None and args_schema:216 if isinstance(args_schema, type) and is_basemodel_subclass(args_schema):217 description_ = args_schema.__doc__218 if (219 description_220 and "A base class for creating Pydantic models" in description_221 ):222 description_ = ""223 elif not description_:224 description_ = None225 elif isinstance(args_schema, dict):226 description_ = args_schema.get("description")227 else:228 msg = (229 "Invalid args_schema: expected BaseModel or dict, "230 f"got {args_schema}"231 )232 raise TypeError(msg)233 if description_ is None:234 msg = "Function must have a docstring if description not provided."235 raise ValueError(msg)236 if description is None:237 # Only apply if using the function's docstring238 description_ = textwrap.dedent(description_).strip()239240 # Description example:241 # search_api(query: str) - Searches the API for the query.242 description_ = f"{description_.strip()}"243 return cls(244 name=name,245 func=func,246 coroutine=coroutine,247 args_schema=args_schema,248 description=description_,249 return_direct=return_direct,250 response_format=response_format,251 **kwargs,252 )253254 @functools.cached_property255 def _injected_args_keys(self) -> frozenset[str]:256 fn = self.func or self.coroutine257 if fn is None:258 return _EMPTY_SET259 return frozenset(260 k261 for k, v in signature(fn).parameters.items()262 if _is_injected_arg_type(v.annotation)263 )264265266def _filter_schema_args(func: Callable) -> list[str]:267 filter_args = list(FILTERED_ARGS)268 if config_param := _get_runnable_config_param(func):269 filter_args.append(config_param)270 # filter_args.extend(_get_non_model_params(type_hints))271 return filter_args
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.