mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-15 14:36:54 +00:00
docs: udpated api reference (#25172)
- Move the API reference into the vercel build - Update api reference organization and styling
This commit is contained in:
@@ -1,3 +1,13 @@
|
||||
"""``langchain-core`` defines the base abstractions for the LangChain ecosystem.
|
||||
|
||||
The interfaces for core components like chat models, LLMs, vector stores, retrievers,
|
||||
and more are defined here. The universal invocation protocol (Runnables) along with
|
||||
a syntax for combining components (LangChain Expression Language) are also defined here.
|
||||
|
||||
No third-party integrations are defined here. The dependencies are kept purposefully
|
||||
very lightweight.
|
||||
"""
|
||||
|
||||
from importlib import metadata
|
||||
|
||||
from langchain_core._api import (
|
||||
|
58
libs/core/langchain_core/tools/__init__.py
Normal file
58
libs/core/langchain_core/tools/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""**Tools** are classes that an Agent uses to interact with the world.
|
||||
|
||||
Each tool has a **description**. Agent uses the description to choose the right
|
||||
tool for the job.
|
||||
|
||||
**Class hierarchy:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
RunnableSerializable --> BaseTool --> <name>Tool # Examples: AIPluginTool, BaseGraphQLTool
|
||||
<name> # Examples: BraveSearch, HumanInputRun
|
||||
|
||||
**Main helpers:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
|
||||
""" # noqa: E501
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langchain_core.tools.base import (
|
||||
FILTERED_ARGS as FILTERED_ARGS,
|
||||
)
|
||||
from langchain_core.tools.base import (
|
||||
BaseTool as BaseTool,
|
||||
)
|
||||
from langchain_core.tools.base import (
|
||||
BaseToolkit as BaseToolkit,
|
||||
)
|
||||
from langchain_core.tools.base import (
|
||||
InjectedToolArg as InjectedToolArg,
|
||||
)
|
||||
from langchain_core.tools.base import SchemaAnnotationError as SchemaAnnotationError
|
||||
from langchain_core.tools.base import (
|
||||
ToolException as ToolException,
|
||||
)
|
||||
from langchain_core.tools.base import (
|
||||
_get_runnable_config_param as _get_runnable_config_param,
|
||||
)
|
||||
from langchain_core.tools.base import (
|
||||
create_schema_from_function as create_schema_from_function,
|
||||
)
|
||||
from langchain_core.tools.convert import (
|
||||
convert_runnable_to_tool as convert_runnable_to_tool,
|
||||
)
|
||||
from langchain_core.tools.convert import tool as tool
|
||||
from langchain_core.tools.render import (
|
||||
render_text_description as render_text_description,
|
||||
)
|
||||
from langchain_core.tools.render import (
|
||||
render_text_description_and_args as render_text_description_and_args,
|
||||
)
|
||||
from langchain_core.tools.retriever import (
|
||||
create_retriever_tool as create_retriever_tool,
|
||||
)
|
||||
from langchain_core.tools.simple import Tool as Tool
|
||||
from langchain_core.tools.structured import StructuredTool as StructuredTool
|
@@ -1,38 +1,16 @@
|
||||
"""**Tools** are classes that an Agent uses to interact with the world.
|
||||
|
||||
Each tool has a **description**. Agent uses the description to choose the right
|
||||
tool for the job.
|
||||
|
||||
**Class hierarchy:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
RunnableSerializable --> BaseTool --> <name>Tool # Examples: AIPluginTool, BaseGraphQLTool
|
||||
<name> # Examples: BraveSearch, HumanInputRun
|
||||
|
||||
**Main helpers:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
|
||||
""" # noqa: E501
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import textwrap
|
||||
import uuid
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from contextvars import copy_context
|
||||
from functools import partial
|
||||
from inspect import signature
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
@@ -42,51 +20,37 @@ from typing import (
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
get_type_hints,
|
||||
)
|
||||
|
||||
from typing_extensions import Annotated, TypeVar, cast, get_args, get_origin
|
||||
from typing_extensions import Annotated, TypeVar, get_args, get_origin
|
||||
|
||||
from langchain_core._api import deprecated
|
||||
from langchain_core.callbacks import (
|
||||
AsyncCallbackManager,
|
||||
AsyncCallbackManagerForToolRun,
|
||||
BaseCallbackManager,
|
||||
CallbackManager,
|
||||
CallbackManagerForToolRun,
|
||||
)
|
||||
from langchain_core.callbacks.manager import (
|
||||
Callbacks,
|
||||
)
|
||||
from langchain_core.load.serializable import Serializable
|
||||
from langchain_core.messages.tool import ToolCall, ToolMessage
|
||||
from langchain_core.prompts import (
|
||||
BasePromptTemplate,
|
||||
PromptTemplate,
|
||||
aformat_document,
|
||||
format_document,
|
||||
)
|
||||
from langchain_core.load import Serializable
|
||||
from langchain_core.messages import ToolCall, ToolMessage
|
||||
from langchain_core.pydantic_v1 import (
|
||||
BaseModel,
|
||||
Extra,
|
||||
Field,
|
||||
ValidationError,
|
||||
create_model,
|
||||
root_validator,
|
||||
validate_arguments,
|
||||
)
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.runnables import (
|
||||
Runnable,
|
||||
RunnableConfig,
|
||||
RunnableSerializable,
|
||||
ensure_config,
|
||||
)
|
||||
from langchain_core.runnables.config import (
|
||||
_set_config_context,
|
||||
patch_config,
|
||||
run_in_executor,
|
||||
)
|
||||
from langchain_core.runnables.config import _set_config_context
|
||||
from langchain_core.runnables.utils import asyncio_accepts_context
|
||||
from langchain_core.utils.function_calling import (
|
||||
_parse_google_docstring,
|
||||
@@ -746,682 +710,6 @@ class ChildTool(BaseTool):
|
||||
return self.run(tool_input, callbacks=callbacks)
|
||||
|
||||
|
||||
class Tool(BaseTool):
|
||||
"""Tool that takes in function or coroutine directly."""
|
||||
|
||||
description: str = ""
|
||||
func: Optional[Callable[..., str]]
|
||||
"""The function to run when the tool is called."""
|
||||
coroutine: Optional[Callable[..., Awaitable[str]]] = None
|
||||
"""The asynchronous version of the function."""
|
||||
|
||||
# --- Runnable ---
|
||||
|
||||
async def ainvoke(
|
||||
self,
|
||||
input: Union[str, Dict, ToolCall],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if not self.coroutine:
|
||||
# If the tool does not implement async, fall back to default implementation
|
||||
return await run_in_executor(config, self.invoke, input, config, **kwargs)
|
||||
|
||||
return await super().ainvoke(input, config, **kwargs)
|
||||
|
||||
# --- Tool ---
|
||||
|
||||
@property
|
||||
def args(self) -> dict:
|
||||
"""The tool's input arguments.
|
||||
|
||||
Returns:
|
||||
The input arguments for the tool.
|
||||
"""
|
||||
if self.args_schema is not None:
|
||||
return self.args_schema.schema()["properties"]
|
||||
# For backwards compatibility, if the function signature is ambiguous,
|
||||
# assume it takes a single string input.
|
||||
return {"tool_input": {"type": "string"}}
|
||||
|
||||
def _to_args_and_kwargs(self, tool_input: Union[str, Dict]) -> Tuple[Tuple, Dict]:
|
||||
"""Convert tool input to pydantic model."""
|
||||
args, kwargs = super()._to_args_and_kwargs(tool_input)
|
||||
# For backwards compatibility. The tool must be run with a single input
|
||||
all_args = list(args) + list(kwargs.values())
|
||||
if len(all_args) != 1:
|
||||
raise ToolException(
|
||||
f"""Too many arguments to single-input tool {self.name}.
|
||||
Consider using StructuredTool instead."""
|
||||
f" Args: {all_args}"
|
||||
)
|
||||
return tuple(all_args), {}
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool."""
|
||||
if self.func:
|
||||
if run_manager and signature(self.func).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.func):
|
||||
kwargs[config_param] = config
|
||||
return self.func(*args, **kwargs)
|
||||
raise NotImplementedError("Tool does not support sync invocation.")
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool asynchronously."""
|
||||
if self.coroutine:
|
||||
if run_manager and signature(self.coroutine).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.coroutine):
|
||||
kwargs[config_param] = config
|
||||
return await self.coroutine(*args, **kwargs)
|
||||
|
||||
# NOTE: this code is unreachable since _arun is only called if coroutine is not
|
||||
# None.
|
||||
return await super()._arun(
|
||||
*args, config=config, run_manager=run_manager, **kwargs
|
||||
)
|
||||
|
||||
# TODO: this is for backwards compatibility, remove in future
|
||||
def __init__(
|
||||
self, name: str, func: Optional[Callable], description: str, **kwargs: Any
|
||||
) -> None:
|
||||
"""Initialize tool."""
|
||||
super(Tool, self).__init__( # type: ignore[call-arg]
|
||||
name=name, func=func, description=description, **kwargs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_function(
|
||||
cls,
|
||||
func: Optional[Callable],
|
||||
name: str, # We keep these required to support backwards compatibility
|
||||
description: str,
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
coroutine: Optional[
|
||||
Callable[..., Awaitable[Any]]
|
||||
] = None, # This is last for compatibility, but should be after func
|
||||
**kwargs: Any,
|
||||
) -> Tool:
|
||||
"""Initialize tool from a function.
|
||||
|
||||
Args:
|
||||
func: The function to create the tool from.
|
||||
name: The name of the tool.
|
||||
description: The description of the tool.
|
||||
return_direct: Whether to return the output directly. Defaults to False.
|
||||
args_schema: The schema of the tool's input arguments. Defaults to None.
|
||||
coroutine: The asynchronous version of the function. Defaults to None.
|
||||
kwargs: Additional arguments to pass to the tool.
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
|
||||
Raises:
|
||||
ValueError: If the function is not provided.
|
||||
"""
|
||||
if func is None and coroutine is None:
|
||||
raise ValueError("Function and/or coroutine must be provided")
|
||||
return cls(
|
||||
name=name,
|
||||
func=func,
|
||||
coroutine=coroutine,
|
||||
description=description,
|
||||
return_direct=return_direct,
|
||||
args_schema=args_schema,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class StructuredTool(BaseTool):
|
||||
"""Tool that can operate on any number of inputs."""
|
||||
|
||||
description: str = ""
|
||||
args_schema: TypeBaseModel = Field(..., description="The tool schema.")
|
||||
"""The input arguments' schema."""
|
||||
func: Optional[Callable[..., Any]]
|
||||
"""The function to run when the tool is called."""
|
||||
coroutine: Optional[Callable[..., Awaitable[Any]]] = None
|
||||
"""The asynchronous version of the function."""
|
||||
|
||||
# --- Runnable ---
|
||||
|
||||
# TODO: Is this needed?
|
||||
async def ainvoke(
|
||||
self,
|
||||
input: Union[str, Dict, ToolCall],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if not self.coroutine:
|
||||
# If the tool does not implement async, fall back to default implementation
|
||||
return await run_in_executor(config, self.invoke, input, config, **kwargs)
|
||||
|
||||
return await super().ainvoke(input, config, **kwargs)
|
||||
|
||||
# --- Tool ---
|
||||
|
||||
@property
|
||||
def args(self) -> dict:
|
||||
"""The tool's input arguments."""
|
||||
return self.args_schema.schema()["properties"]
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool."""
|
||||
if self.func:
|
||||
if run_manager and signature(self.func).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.func):
|
||||
kwargs[config_param] = config
|
||||
return self.func(*args, **kwargs)
|
||||
raise NotImplementedError("StructuredTool does not support sync invocation.")
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool asynchronously."""
|
||||
if self.coroutine:
|
||||
if run_manager and signature(self.coroutine).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.coroutine):
|
||||
kwargs[config_param] = config
|
||||
return await self.coroutine(*args, **kwargs)
|
||||
|
||||
# NOTE: this code is unreachable since _arun is only called if coroutine is not
|
||||
# None.
|
||||
return await super()._arun(
|
||||
*args, config=config, run_manager=run_manager, **kwargs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_function(
|
||||
cls,
|
||||
func: Optional[Callable] = None,
|
||||
coroutine: Optional[Callable[..., Awaitable[Any]]] = None,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
infer_schema: bool = True,
|
||||
*,
|
||||
response_format: Literal["content", "content_and_artifact"] = "content",
|
||||
parse_docstring: bool = False,
|
||||
error_on_invalid_docstring: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> StructuredTool:
|
||||
"""Create tool from a given function.
|
||||
|
||||
A classmethod that helps to create a tool from a function.
|
||||
|
||||
Args:
|
||||
func: The function from which to create a tool.
|
||||
coroutine: The async function from which to create a tool.
|
||||
name: The name of the tool. Defaults to the function name.
|
||||
description: The description of the tool.
|
||||
Defaults to the function docstring.
|
||||
return_direct: Whether to return the result directly or as a callback.
|
||||
Defaults to False.
|
||||
args_schema: The schema of the tool's input arguments. Defaults to None.
|
||||
infer_schema: Whether to infer the schema from the function's signature.
|
||||
Defaults to True.
|
||||
response_format: The tool response format. If "content" then the output of
|
||||
the tool is interpreted as the contents of a ToolMessage. If
|
||||
"content_and_artifact" then the output is expected to be a two-tuple
|
||||
corresponding to the (content, artifact) of a ToolMessage.
|
||||
Defaults to "content".
|
||||
parse_docstring: if ``infer_schema`` and ``parse_docstring``, will attempt
|
||||
to parse parameter descriptions from Google Style function docstrings.
|
||||
Defaults to False.
|
||||
error_on_invalid_docstring: if ``parse_docstring`` is provided, configure
|
||||
whether to raise ValueError on invalid Google Style docstrings.
|
||||
Defaults to False.
|
||||
kwargs: Additional arguments to pass to the tool
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
|
||||
Raises:
|
||||
ValueError: If the function is not provided.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def add(a: int, b: int) -> int:
|
||||
\"\"\"Add two numbers\"\"\"
|
||||
return a + b
|
||||
tool = StructuredTool.from_function(add)
|
||||
tool.run(1, 2) # 3
|
||||
"""
|
||||
|
||||
if func is not None:
|
||||
source_function = func
|
||||
elif coroutine is not None:
|
||||
source_function = coroutine
|
||||
else:
|
||||
raise ValueError("Function and/or coroutine must be provided")
|
||||
name = name or source_function.__name__
|
||||
if args_schema is None and infer_schema:
|
||||
# schema name is appended within function
|
||||
args_schema = create_schema_from_function(
|
||||
name,
|
||||
source_function,
|
||||
parse_docstring=parse_docstring,
|
||||
error_on_invalid_docstring=error_on_invalid_docstring,
|
||||
filter_args=_filter_schema_args(source_function),
|
||||
)
|
||||
description_ = description
|
||||
if description is None and not parse_docstring:
|
||||
description_ = source_function.__doc__ or None
|
||||
if description_ is None and args_schema:
|
||||
description_ = args_schema.__doc__ or None
|
||||
if description_ is None:
|
||||
raise ValueError(
|
||||
"Function must have a docstring if description not provided."
|
||||
)
|
||||
if description is None:
|
||||
# Only apply if using the function's docstring
|
||||
description_ = textwrap.dedent(description_).strip()
|
||||
|
||||
# Description example:
|
||||
# search_api(query: str) - Searches the API for the query.
|
||||
description_ = f"{description_.strip()}"
|
||||
return cls(
|
||||
name=name,
|
||||
func=func,
|
||||
coroutine=coroutine,
|
||||
args_schema=args_schema, # type: ignore[arg-type]
|
||||
description=description_,
|
||||
return_direct=return_direct,
|
||||
response_format=response_format,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
# TODO: Type args_schema as TypeBaseModel if we can get mypy to correctly recognize
|
||||
# pydantic v2 BaseModel classes.
|
||||
def tool(
|
||||
*args: Union[str, Callable, Runnable],
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type] = None,
|
||||
infer_schema: bool = True,
|
||||
response_format: Literal["content", "content_and_artifact"] = "content",
|
||||
parse_docstring: bool = False,
|
||||
error_on_invalid_docstring: bool = True,
|
||||
) -> Callable:
|
||||
"""Make tools out of functions, can be used with or without arguments.
|
||||
|
||||
Args:
|
||||
*args: The arguments to the tool.
|
||||
return_direct: Whether to return directly from the tool rather
|
||||
than continuing the agent loop. Defaults to False.
|
||||
args_schema: optional argument schema for user to specify.
|
||||
Defaults to None.
|
||||
infer_schema: Whether to infer the schema of the arguments from
|
||||
the function's signature. This also makes the resultant tool
|
||||
accept a dictionary input to its `run()` function.
|
||||
Defaults to True.
|
||||
response_format: The tool response format. If "content" then the output of
|
||||
the tool is interpreted as the contents of a ToolMessage. If
|
||||
"content_and_artifact" then the output is expected to be a two-tuple
|
||||
corresponding to the (content, artifact) of a ToolMessage.
|
||||
Defaults to "content".
|
||||
parse_docstring: if ``infer_schema`` and ``parse_docstring``, will attempt to
|
||||
parse parameter descriptions from Google Style function docstrings.
|
||||
Defaults to False.
|
||||
error_on_invalid_docstring: if ``parse_docstring`` is provided, configure
|
||||
whether to raise ValueError on invalid Google Style docstrings.
|
||||
Defaults to True.
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
|
||||
Requires:
|
||||
- Function must be of type (str) -> str
|
||||
- Function must have a docstring
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
@tool
|
||||
def search_api(query: str) -> str:
|
||||
# Searches the API for the query.
|
||||
return
|
||||
|
||||
@tool("search", return_direct=True)
|
||||
def search_api(query: str) -> str:
|
||||
# Searches the API for the query.
|
||||
return
|
||||
|
||||
@tool(response_format="content_and_artifact")
|
||||
def search_api(query: str) -> Tuple[str, dict]:
|
||||
return "partial json of results", {"full": "object of results"}
|
||||
|
||||
.. versionadded:: 0.2.14
|
||||
Parse Google-style docstrings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@tool(parse_docstring=True)
|
||||
def foo(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.
|
||||
|
||||
Args:
|
||||
bar: The bar.
|
||||
baz: The baz.
|
||||
\"\"\"
|
||||
return bar
|
||||
|
||||
foo.args_schema.schema()
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
"title": "fooSchema",
|
||||
"description": "The foo.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bar": {
|
||||
"title": "Bar",
|
||||
"description": "The bar.",
|
||||
"type": "string"
|
||||
},
|
||||
"baz": {
|
||||
"title": "Baz",
|
||||
"description": "The baz.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
|
||||
Note that parsing by default will raise ``ValueError`` if the docstring
|
||||
is considered invalid. A docstring is considered invalid if it contains
|
||||
arguments not in the function signature, or is unable to be parsed into
|
||||
a summary and "Args:" blocks. Examples below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# No args section
|
||||
def invalid_docstring_1(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.\"\"\"
|
||||
return bar
|
||||
|
||||
# Improper whitespace between summary and args section
|
||||
def invalid_docstring_2(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.
|
||||
Args:
|
||||
bar: The bar.
|
||||
baz: The baz.
|
||||
\"\"\"
|
||||
return bar
|
||||
|
||||
# Documented args absent from function signature
|
||||
def invalid_docstring_3(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.
|
||||
|
||||
Args:
|
||||
banana: The bar.
|
||||
monkey: The baz.
|
||||
\"\"\"
|
||||
return bar
|
||||
"""
|
||||
|
||||
def _make_with_name(tool_name: str) -> Callable:
|
||||
def _make_tool(dec_func: Union[Callable, Runnable]) -> BaseTool:
|
||||
if isinstance(dec_func, Runnable):
|
||||
runnable = dec_func
|
||||
|
||||
if runnable.input_schema.schema().get("type") != "object":
|
||||
raise ValueError("Runnable must have an object schema.")
|
||||
|
||||
async def ainvoke_wrapper(
|
||||
callbacks: Optional[Callbacks] = None, **kwargs: Any
|
||||
) -> Any:
|
||||
return await runnable.ainvoke(kwargs, {"callbacks": callbacks})
|
||||
|
||||
def invoke_wrapper(
|
||||
callbacks: Optional[Callbacks] = None, **kwargs: Any
|
||||
) -> Any:
|
||||
return runnable.invoke(kwargs, {"callbacks": callbacks})
|
||||
|
||||
coroutine = ainvoke_wrapper
|
||||
func = invoke_wrapper
|
||||
schema: Optional[Type[BaseModel]] = runnable.input_schema
|
||||
description = repr(runnable)
|
||||
elif inspect.iscoroutinefunction(dec_func):
|
||||
coroutine = dec_func
|
||||
func = None
|
||||
schema = args_schema
|
||||
description = None
|
||||
else:
|
||||
coroutine = None
|
||||
func = dec_func
|
||||
schema = args_schema
|
||||
description = None
|
||||
|
||||
if infer_schema or args_schema is not None:
|
||||
return StructuredTool.from_function(
|
||||
func,
|
||||
coroutine,
|
||||
name=tool_name,
|
||||
description=description,
|
||||
return_direct=return_direct,
|
||||
args_schema=schema,
|
||||
infer_schema=infer_schema,
|
||||
response_format=response_format,
|
||||
parse_docstring=parse_docstring,
|
||||
error_on_invalid_docstring=error_on_invalid_docstring,
|
||||
)
|
||||
# If someone doesn't want a schema applied, we must treat it as
|
||||
# a simple string->string function
|
||||
if dec_func.__doc__ is None:
|
||||
raise ValueError(
|
||||
"Function must have a docstring if "
|
||||
"description not provided and infer_schema is False."
|
||||
)
|
||||
return Tool(
|
||||
name=tool_name,
|
||||
func=func,
|
||||
description=f"{tool_name} tool",
|
||||
return_direct=return_direct,
|
||||
coroutine=coroutine,
|
||||
response_format=response_format,
|
||||
)
|
||||
|
||||
return _make_tool
|
||||
|
||||
if len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], Runnable):
|
||||
return _make_with_name(args[0])(args[1])
|
||||
elif len(args) == 1 and isinstance(args[0], str):
|
||||
# if the argument is a string, then we use the string as the tool name
|
||||
# Example usage: @tool("search", return_direct=True)
|
||||
return _make_with_name(args[0])
|
||||
elif len(args) == 1 and callable(args[0]):
|
||||
# if the argument is a function, then we use the function name as the tool name
|
||||
# Example usage: @tool
|
||||
return _make_with_name(args[0].__name__)(args[0])
|
||||
elif len(args) == 0:
|
||||
# if there are no arguments, then we use the function name as the tool name
|
||||
# Example usage: @tool(return_direct=True)
|
||||
def _partial(func: Callable[[str], str]) -> BaseTool:
|
||||
return _make_with_name(func.__name__)(func)
|
||||
|
||||
return _partial
|
||||
else:
|
||||
raise ValueError("Too many arguments for tool decorator")
|
||||
|
||||
|
||||
class RetrieverInput(BaseModel):
|
||||
"""Input to the retriever."""
|
||||
|
||||
query: str = Field(description="query to look up in retriever")
|
||||
|
||||
|
||||
def _get_relevant_documents(
|
||||
query: str,
|
||||
retriever: BaseRetriever,
|
||||
document_prompt: BasePromptTemplate,
|
||||
document_separator: str,
|
||||
callbacks: Callbacks = None,
|
||||
) -> str:
|
||||
docs = retriever.invoke(query, config={"callbacks": callbacks})
|
||||
return document_separator.join(
|
||||
format_document(doc, document_prompt) for doc in docs
|
||||
)
|
||||
|
||||
|
||||
async def _aget_relevant_documents(
|
||||
query: str,
|
||||
retriever: BaseRetriever,
|
||||
document_prompt: BasePromptTemplate,
|
||||
document_separator: str,
|
||||
callbacks: Callbacks = None,
|
||||
) -> str:
|
||||
docs = await retriever.ainvoke(query, config={"callbacks": callbacks})
|
||||
return document_separator.join(
|
||||
[await aformat_document(doc, document_prompt) for doc in docs]
|
||||
)
|
||||
|
||||
|
||||
def create_retriever_tool(
|
||||
retriever: BaseRetriever,
|
||||
name: str,
|
||||
description: str,
|
||||
*,
|
||||
document_prompt: Optional[BasePromptTemplate] = None,
|
||||
document_separator: str = "\n\n",
|
||||
) -> Tool:
|
||||
"""Create a tool to do retrieval of documents.
|
||||
|
||||
Args:
|
||||
retriever: The retriever to use for the retrieval
|
||||
name: The name for the tool. This will be passed to the language model,
|
||||
so should be unique and somewhat descriptive.
|
||||
description: The description for the tool. This will be passed to the language
|
||||
model, so should be descriptive.
|
||||
document_prompt: The prompt to use for the document. Defaults to None.
|
||||
document_separator: The separator to use between documents. Defaults to "\n\n".
|
||||
|
||||
Returns:
|
||||
Tool class to pass to an agent.
|
||||
"""
|
||||
document_prompt = document_prompt or PromptTemplate.from_template("{page_content}")
|
||||
func = partial(
|
||||
_get_relevant_documents,
|
||||
retriever=retriever,
|
||||
document_prompt=document_prompt,
|
||||
document_separator=document_separator,
|
||||
)
|
||||
afunc = partial(
|
||||
_aget_relevant_documents,
|
||||
retriever=retriever,
|
||||
document_prompt=document_prompt,
|
||||
document_separator=document_separator,
|
||||
)
|
||||
return Tool(
|
||||
name=name,
|
||||
description=description,
|
||||
func=func,
|
||||
coroutine=afunc,
|
||||
args_schema=RetrieverInput,
|
||||
)
|
||||
|
||||
|
||||
ToolsRenderer = Callable[[List[BaseTool]], str]
|
||||
|
||||
|
||||
def render_text_description(tools: List[BaseTool]) -> str:
|
||||
"""Render the tool name and description in plain text.
|
||||
|
||||
Args:
|
||||
tools: The tools to render.
|
||||
|
||||
Returns:
|
||||
The rendered text.
|
||||
|
||||
Output will be in the format of:
|
||||
|
||||
.. code-block:: markdown
|
||||
|
||||
search: This tool is used for search
|
||||
calculator: This tool is used for math
|
||||
"""
|
||||
descriptions = []
|
||||
for tool in tools:
|
||||
if hasattr(tool, "func") and tool.func:
|
||||
sig = signature(tool.func)
|
||||
description = f"{tool.name}{sig} - {tool.description}"
|
||||
else:
|
||||
description = f"{tool.name} - {tool.description}"
|
||||
|
||||
descriptions.append(description)
|
||||
return "\n".join(descriptions)
|
||||
|
||||
|
||||
def render_text_description_and_args(tools: List[BaseTool]) -> str:
|
||||
"""Render the tool name, description, and args in plain text.
|
||||
|
||||
Args:
|
||||
tools: The tools to render.
|
||||
|
||||
Returns:
|
||||
The rendered text.
|
||||
|
||||
Output will be in the format of:
|
||||
|
||||
.. code-block:: markdown
|
||||
|
||||
search: This tool is used for search, args: {"query": {"type": "string"}}
|
||||
calculator: This tool is used for math, \
|
||||
args: {"expression": {"type": "string"}}
|
||||
"""
|
||||
tool_strings = []
|
||||
for tool in tools:
|
||||
args_schema = str(tool.args)
|
||||
if hasattr(tool, "func") and tool.func:
|
||||
sig = signature(tool.func)
|
||||
description = f"{tool.name}{sig} - {tool.description}"
|
||||
else:
|
||||
description = f"{tool.name} - {tool.description}"
|
||||
tool_strings.append(f"{description}, args: {args_schema}")
|
||||
return "\n".join(tool_strings)
|
||||
|
||||
|
||||
class BaseToolkit(BaseModel, ABC):
|
||||
"""Base Toolkit representing a collection of related tools."""
|
||||
|
||||
@abstractmethod
|
||||
def get_tools(self) -> List[BaseTool]:
|
||||
"""Get the tools in the toolkit."""
|
||||
|
||||
|
||||
def _is_tool_call(x: Any) -> bool:
|
||||
return isinstance(x, dict) and x.get("type") == "tool_call"
|
||||
|
||||
@@ -1538,94 +826,6 @@ def _stringify(content: Any) -> str:
|
||||
return str(content)
|
||||
|
||||
|
||||
def _get_description_from_runnable(runnable: Runnable) -> str:
|
||||
"""Generate a placeholder description of a runnable."""
|
||||
input_schema = runnable.input_schema.schema()
|
||||
return f"Takes {input_schema}."
|
||||
|
||||
|
||||
def _get_schema_from_runnable_and_arg_types(
|
||||
runnable: Runnable,
|
||||
name: str,
|
||||
arg_types: Optional[Dict[str, Type]] = None,
|
||||
) -> Type[BaseModel]:
|
||||
"""Infer args_schema for tool."""
|
||||
if arg_types is None:
|
||||
try:
|
||||
arg_types = get_type_hints(runnable.InputType)
|
||||
except TypeError as e:
|
||||
raise TypeError(
|
||||
"Tool input must be str or dict. If dict, dict arguments must be "
|
||||
"typed. Either annotate types (e.g., with TypedDict) or pass "
|
||||
f"arg_types into `.as_tool` to specify. {str(e)}"
|
||||
)
|
||||
fields = {key: (key_type, Field(...)) for key, key_type in arg_types.items()}
|
||||
return create_model(name, **fields) # type: ignore
|
||||
|
||||
|
||||
def convert_runnable_to_tool(
|
||||
runnable: Runnable,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
arg_types: Optional[Dict[str, Type]] = None,
|
||||
) -> BaseTool:
|
||||
"""Convert a Runnable into a BaseTool.
|
||||
|
||||
Args:
|
||||
runnable: The runnable to convert.
|
||||
args_schema: The schema for the tool's input arguments. Defaults to None.
|
||||
name: The name of the tool. Defaults to None.
|
||||
description: The description of the tool. Defaults to None.
|
||||
arg_types: The types of the arguments. Defaults to None.
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
"""
|
||||
if args_schema:
|
||||
runnable = runnable.with_types(input_type=args_schema)
|
||||
description = description or _get_description_from_runnable(runnable)
|
||||
name = name or runnable.get_name()
|
||||
|
||||
schema = runnable.input_schema.schema()
|
||||
if schema.get("type") == "string":
|
||||
return Tool(
|
||||
name=name,
|
||||
func=runnable.invoke,
|
||||
coroutine=runnable.ainvoke,
|
||||
description=description,
|
||||
)
|
||||
else:
|
||||
|
||||
async def ainvoke_wrapper(
|
||||
callbacks: Optional[Callbacks] = None, **kwargs: Any
|
||||
) -> Any:
|
||||
return await runnable.ainvoke(kwargs, config={"callbacks": callbacks})
|
||||
|
||||
def invoke_wrapper(callbacks: Optional[Callbacks] = None, **kwargs: Any) -> Any:
|
||||
return runnable.invoke(kwargs, config={"callbacks": callbacks})
|
||||
|
||||
if (
|
||||
arg_types is None
|
||||
and schema.get("type") == "object"
|
||||
and schema.get("properties")
|
||||
):
|
||||
args_schema = runnable.input_schema
|
||||
else:
|
||||
args_schema = _get_schema_from_runnable_and_arg_types(
|
||||
runnable, name, arg_types=arg_types
|
||||
)
|
||||
|
||||
return StructuredTool.from_function(
|
||||
name=name,
|
||||
func=invoke_wrapper,
|
||||
coroutine=ainvoke_wrapper,
|
||||
description=description,
|
||||
args_schema=args_schema,
|
||||
)
|
||||
|
||||
|
||||
def _get_type_hints(func: Callable) -> Optional[Dict[str, Type]]:
|
||||
if isinstance(func, functools.partial):
|
||||
func = func.func
|
||||
@@ -1657,14 +857,6 @@ def _is_injected_arg_type(type_: Type) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _filter_schema_args(func: Callable) -> List[str]:
|
||||
filter_args = list(FILTERED_ARGS)
|
||||
if config_param := _get_runnable_config_param(func):
|
||||
filter_args.append(config_param)
|
||||
# filter_args.extend(_get_non_model_params(type_hints))
|
||||
return filter_args
|
||||
|
||||
|
||||
def _get_all_basemodel_annotations(
|
||||
cls: Union[TypeBaseModel, Any], *, default_to_bound: bool = True
|
||||
) -> Dict[str, Type]:
|
||||
@@ -1747,3 +939,11 @@ def _replace_type_vars(
|
||||
return _py_38_safe_origin(origin)[new_args]
|
||||
else:
|
||||
return type_
|
||||
|
||||
|
||||
class BaseToolkit(BaseModel, ABC):
|
||||
"""Base Toolkit representing a collection of related tools."""
|
||||
|
||||
@abstractmethod
|
||||
def get_tools(self) -> List[BaseTool]:
|
||||
"""Get the tools in the toolkit."""
|
312
libs/core/langchain_core/tools/convert.py
Normal file
312
libs/core/langchain_core/tools/convert.py
Normal file
@@ -0,0 +1,312 @@
|
||||
import inspect
|
||||
from typing import Any, Callable, Dict, Literal, Optional, Type, Union, get_type_hints
|
||||
|
||||
from langchain_core.callbacks import Callbacks
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field, create_model
|
||||
from langchain_core.runnables import Runnable
|
||||
from langchain_core.tools.base import BaseTool
|
||||
from langchain_core.tools.simple import Tool
|
||||
from langchain_core.tools.structured import StructuredTool
|
||||
|
||||
|
||||
def tool(
|
||||
*args: Union[str, Callable, Runnable],
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type] = None,
|
||||
infer_schema: bool = True,
|
||||
response_format: Literal["content", "content_and_artifact"] = "content",
|
||||
parse_docstring: bool = False,
|
||||
error_on_invalid_docstring: bool = True,
|
||||
) -> Callable:
|
||||
"""Make tools out of functions, can be used with or without arguments.
|
||||
|
||||
Args:
|
||||
*args: The arguments to the tool.
|
||||
return_direct: Whether to return directly from the tool rather
|
||||
than continuing the agent loop. Defaults to False.
|
||||
args_schema: optional argument schema for user to specify.
|
||||
Defaults to None.
|
||||
infer_schema: Whether to infer the schema of the arguments from
|
||||
the function's signature. This also makes the resultant tool
|
||||
accept a dictionary input to its `run()` function.
|
||||
Defaults to True.
|
||||
response_format: The tool response format. If "content" then the output of
|
||||
the tool is interpreted as the contents of a ToolMessage. If
|
||||
"content_and_artifact" then the output is expected to be a two-tuple
|
||||
corresponding to the (content, artifact) of a ToolMessage.
|
||||
Defaults to "content".
|
||||
parse_docstring: if ``infer_schema`` and ``parse_docstring``, will attempt to
|
||||
parse parameter descriptions from Google Style function docstrings.
|
||||
Defaults to False.
|
||||
error_on_invalid_docstring: if ``parse_docstring`` is provided, configure
|
||||
whether to raise ValueError on invalid Google Style docstrings.
|
||||
Defaults to True.
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
|
||||
Requires:
|
||||
- Function must be of type (str) -> str
|
||||
- Function must have a docstring
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
@tool
|
||||
def search_api(query: str) -> str:
|
||||
# Searches the API for the query.
|
||||
return
|
||||
|
||||
@tool("search", return_direct=True)
|
||||
def search_api(query: str) -> str:
|
||||
# Searches the API for the query.
|
||||
return
|
||||
|
||||
@tool(response_format="content_and_artifact")
|
||||
def search_api(query: str) -> Tuple[str, dict]:
|
||||
return "partial json of results", {"full": "object of results"}
|
||||
|
||||
.. versionadded:: 0.2.14
|
||||
Parse Google-style docstrings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@tool(parse_docstring=True)
|
||||
def foo(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.
|
||||
|
||||
Args:
|
||||
bar: The bar.
|
||||
baz: The baz.
|
||||
\"\"\"
|
||||
return bar
|
||||
|
||||
foo.args_schema.schema()
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
"title": "fooSchema",
|
||||
"description": "The foo.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bar": {
|
||||
"title": "Bar",
|
||||
"description": "The bar.",
|
||||
"type": "string"
|
||||
},
|
||||
"baz": {
|
||||
"title": "Baz",
|
||||
"description": "The baz.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
|
||||
Note that parsing by default will raise ``ValueError`` if the docstring
|
||||
is considered invalid. A docstring is considered invalid if it contains
|
||||
arguments not in the function signature, or is unable to be parsed into
|
||||
a summary and "Args:" blocks. Examples below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# No args section
|
||||
def invalid_docstring_1(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.\"\"\"
|
||||
return bar
|
||||
|
||||
# Improper whitespace between summary and args section
|
||||
def invalid_docstring_2(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.
|
||||
Args:
|
||||
bar: The bar.
|
||||
baz: The baz.
|
||||
\"\"\"
|
||||
return bar
|
||||
|
||||
# Documented args absent from function signature
|
||||
def invalid_docstring_3(bar: str, baz: int) -> str:
|
||||
\"\"\"The foo.
|
||||
|
||||
Args:
|
||||
banana: The bar.
|
||||
monkey: The baz.
|
||||
\"\"\"
|
||||
return bar
|
||||
"""
|
||||
|
||||
def _make_with_name(tool_name: str) -> Callable:
|
||||
def _make_tool(dec_func: Union[Callable, Runnable]) -> BaseTool:
|
||||
if isinstance(dec_func, Runnable):
|
||||
runnable = dec_func
|
||||
|
||||
if runnable.input_schema.schema().get("type") != "object":
|
||||
raise ValueError("Runnable must have an object schema.")
|
||||
|
||||
async def ainvoke_wrapper(
|
||||
callbacks: Optional[Callbacks] = None, **kwargs: Any
|
||||
) -> Any:
|
||||
return await runnable.ainvoke(kwargs, {"callbacks": callbacks})
|
||||
|
||||
def invoke_wrapper(
|
||||
callbacks: Optional[Callbacks] = None, **kwargs: Any
|
||||
) -> Any:
|
||||
return runnable.invoke(kwargs, {"callbacks": callbacks})
|
||||
|
||||
coroutine = ainvoke_wrapper
|
||||
func = invoke_wrapper
|
||||
schema: Optional[Type[BaseModel]] = runnable.input_schema
|
||||
description = repr(runnable)
|
||||
elif inspect.iscoroutinefunction(dec_func):
|
||||
coroutine = dec_func
|
||||
func = None
|
||||
schema = args_schema
|
||||
description = None
|
||||
else:
|
||||
coroutine = None
|
||||
func = dec_func
|
||||
schema = args_schema
|
||||
description = None
|
||||
|
||||
if infer_schema or args_schema is not None:
|
||||
return StructuredTool.from_function(
|
||||
func,
|
||||
coroutine,
|
||||
name=tool_name,
|
||||
description=description,
|
||||
return_direct=return_direct,
|
||||
args_schema=schema,
|
||||
infer_schema=infer_schema,
|
||||
response_format=response_format,
|
||||
parse_docstring=parse_docstring,
|
||||
error_on_invalid_docstring=error_on_invalid_docstring,
|
||||
)
|
||||
# If someone doesn't want a schema applied, we must treat it as
|
||||
# a simple string->string function
|
||||
if dec_func.__doc__ is None:
|
||||
raise ValueError(
|
||||
"Function must have a docstring if "
|
||||
"description not provided and infer_schema is False."
|
||||
)
|
||||
return Tool(
|
||||
name=tool_name,
|
||||
func=func,
|
||||
description=f"{tool_name} tool",
|
||||
return_direct=return_direct,
|
||||
coroutine=coroutine,
|
||||
response_format=response_format,
|
||||
)
|
||||
|
||||
return _make_tool
|
||||
|
||||
if len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], Runnable):
|
||||
return _make_with_name(args[0])(args[1])
|
||||
elif len(args) == 1 and isinstance(args[0], str):
|
||||
# if the argument is a string, then we use the string as the tool name
|
||||
# Example usage: @tool("search", return_direct=True)
|
||||
return _make_with_name(args[0])
|
||||
elif len(args) == 1 and callable(args[0]):
|
||||
# if the argument is a function, then we use the function name as the tool name
|
||||
# Example usage: @tool
|
||||
return _make_with_name(args[0].__name__)(args[0])
|
||||
elif len(args) == 0:
|
||||
# if there are no arguments, then we use the function name as the tool name
|
||||
# Example usage: @tool(return_direct=True)
|
||||
def _partial(func: Callable[[str], str]) -> BaseTool:
|
||||
return _make_with_name(func.__name__)(func)
|
||||
|
||||
return _partial
|
||||
else:
|
||||
raise ValueError("Too many arguments for tool decorator")
|
||||
|
||||
|
||||
def _get_description_from_runnable(runnable: Runnable) -> str:
|
||||
"""Generate a placeholder description of a runnable."""
|
||||
input_schema = runnable.input_schema.schema()
|
||||
return f"Takes {input_schema}."
|
||||
|
||||
|
||||
def _get_schema_from_runnable_and_arg_types(
|
||||
runnable: Runnable,
|
||||
name: str,
|
||||
arg_types: Optional[Dict[str, Type]] = None,
|
||||
) -> Type[BaseModel]:
|
||||
"""Infer args_schema for tool."""
|
||||
if arg_types is None:
|
||||
try:
|
||||
arg_types = get_type_hints(runnable.InputType)
|
||||
except TypeError as e:
|
||||
raise TypeError(
|
||||
"Tool input must be str or dict. If dict, dict arguments must be "
|
||||
"typed. Either annotate types (e.g., with TypedDict) or pass "
|
||||
f"arg_types into `.as_tool` to specify. {str(e)}"
|
||||
)
|
||||
fields = {key: (key_type, Field(...)) for key, key_type in arg_types.items()}
|
||||
return create_model(name, **fields) # type: ignore
|
||||
|
||||
|
||||
def convert_runnable_to_tool(
|
||||
runnable: Runnable,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
arg_types: Optional[Dict[str, Type]] = None,
|
||||
) -> BaseTool:
|
||||
"""Convert a Runnable into a BaseTool.
|
||||
|
||||
Args:
|
||||
runnable: The runnable to convert.
|
||||
args_schema: The schema for the tool's input arguments. Defaults to None.
|
||||
name: The name of the tool. Defaults to None.
|
||||
description: The description of the tool. Defaults to None.
|
||||
arg_types: The types of the arguments. Defaults to None.
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
"""
|
||||
if args_schema:
|
||||
runnable = runnable.with_types(input_type=args_schema)
|
||||
description = description or _get_description_from_runnable(runnable)
|
||||
name = name or runnable.get_name()
|
||||
|
||||
schema = runnable.input_schema.schema()
|
||||
if schema.get("type") == "string":
|
||||
return Tool(
|
||||
name=name,
|
||||
func=runnable.invoke,
|
||||
coroutine=runnable.ainvoke,
|
||||
description=description,
|
||||
)
|
||||
else:
|
||||
|
||||
async def ainvoke_wrapper(
|
||||
callbacks: Optional[Callbacks] = None, **kwargs: Any
|
||||
) -> Any:
|
||||
return await runnable.ainvoke(kwargs, config={"callbacks": callbacks})
|
||||
|
||||
def invoke_wrapper(callbacks: Optional[Callbacks] = None, **kwargs: Any) -> Any:
|
||||
return runnable.invoke(kwargs, config={"callbacks": callbacks})
|
||||
|
||||
if (
|
||||
arg_types is None
|
||||
and schema.get("type") == "object"
|
||||
and schema.get("properties")
|
||||
):
|
||||
args_schema = runnable.input_schema
|
||||
else:
|
||||
args_schema = _get_schema_from_runnable_and_arg_types(
|
||||
runnable, name, arg_types=arg_types
|
||||
)
|
||||
|
||||
return StructuredTool.from_function(
|
||||
name=name,
|
||||
func=invoke_wrapper,
|
||||
coroutine=ainvoke_wrapper,
|
||||
description=description,
|
||||
args_schema=args_schema,
|
||||
)
|
65
libs/core/langchain_core/tools/render.py
Normal file
65
libs/core/langchain_core/tools/render.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from inspect import signature
|
||||
from typing import Callable, List
|
||||
|
||||
from langchain_core.tools.base import BaseTool
|
||||
|
||||
ToolsRenderer = Callable[[List[BaseTool]], str]
|
||||
|
||||
|
||||
def render_text_description(tools: List[BaseTool]) -> str:
|
||||
"""Render the tool name and description in plain text.
|
||||
|
||||
Args:
|
||||
tools: The tools to render.
|
||||
|
||||
Returns:
|
||||
The rendered text.
|
||||
|
||||
Output will be in the format of:
|
||||
|
||||
.. code-block:: markdown
|
||||
|
||||
search: This tool is used for search
|
||||
calculator: This tool is used for math
|
||||
"""
|
||||
descriptions = []
|
||||
for tool in tools:
|
||||
if hasattr(tool, "func") and tool.func:
|
||||
sig = signature(tool.func)
|
||||
description = f"{tool.name}{sig} - {tool.description}"
|
||||
else:
|
||||
description = f"{tool.name} - {tool.description}"
|
||||
|
||||
descriptions.append(description)
|
||||
return "\n".join(descriptions)
|
||||
|
||||
|
||||
def render_text_description_and_args(tools: List[BaseTool]) -> str:
|
||||
"""Render the tool name, description, and args in plain text.
|
||||
|
||||
Args:
|
||||
tools: The tools to render.
|
||||
|
||||
Returns:
|
||||
The rendered text.
|
||||
|
||||
Output will be in the format of:
|
||||
|
||||
.. code-block:: markdown
|
||||
|
||||
search: This tool is used for search, args: {"query": {"type": "string"}}
|
||||
calculator: This tool is used for math, \
|
||||
args: {"expression": {"type": "string"}}
|
||||
"""
|
||||
tool_strings = []
|
||||
for tool in tools:
|
||||
args_schema = str(tool.args)
|
||||
if hasattr(tool, "func") and tool.func:
|
||||
sig = signature(tool.func)
|
||||
description = f"{tool.name}{sig} - {tool.description}"
|
||||
else:
|
||||
description = f"{tool.name} - {tool.description}"
|
||||
tool_strings.append(f"{description}, args: {args_schema}")
|
||||
return "\n".join(tool_strings)
|
91
libs/core/langchain_core/tools/retriever.py
Normal file
91
libs/core/langchain_core/tools/retriever.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
|
||||
from langchain_core.callbacks import Callbacks
|
||||
from langchain_core.prompts import (
|
||||
BasePromptTemplate,
|
||||
PromptTemplate,
|
||||
aformat_document,
|
||||
format_document,
|
||||
)
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.tools.simple import Tool
|
||||
|
||||
|
||||
class RetrieverInput(BaseModel):
|
||||
"""Input to the retriever."""
|
||||
|
||||
query: str = Field(description="query to look up in retriever")
|
||||
|
||||
|
||||
def _get_relevant_documents(
|
||||
query: str,
|
||||
retriever: BaseRetriever,
|
||||
document_prompt: BasePromptTemplate,
|
||||
document_separator: str,
|
||||
callbacks: Callbacks = None,
|
||||
) -> str:
|
||||
docs = retriever.invoke(query, config={"callbacks": callbacks})
|
||||
return document_separator.join(
|
||||
format_document(doc, document_prompt) for doc in docs
|
||||
)
|
||||
|
||||
|
||||
async def _aget_relevant_documents(
|
||||
query: str,
|
||||
retriever: BaseRetriever,
|
||||
document_prompt: BasePromptTemplate,
|
||||
document_separator: str,
|
||||
callbacks: Callbacks = None,
|
||||
) -> str:
|
||||
docs = await retriever.ainvoke(query, config={"callbacks": callbacks})
|
||||
return document_separator.join(
|
||||
[await aformat_document(doc, document_prompt) for doc in docs]
|
||||
)
|
||||
|
||||
|
||||
def create_retriever_tool(
|
||||
retriever: BaseRetriever,
|
||||
name: str,
|
||||
description: str,
|
||||
*,
|
||||
document_prompt: Optional[BasePromptTemplate] = None,
|
||||
document_separator: str = "\n\n",
|
||||
) -> Tool:
|
||||
"""Create a tool to do retrieval of documents.
|
||||
|
||||
Args:
|
||||
retriever: The retriever to use for the retrieval
|
||||
name: The name for the tool. This will be passed to the language model,
|
||||
so should be unique and somewhat descriptive.
|
||||
description: The description for the tool. This will be passed to the language
|
||||
model, so should be descriptive.
|
||||
document_prompt: The prompt to use for the document. Defaults to None.
|
||||
document_separator: The separator to use between documents. Defaults to "\n\n".
|
||||
|
||||
Returns:
|
||||
Tool class to pass to an agent.
|
||||
"""
|
||||
document_prompt = document_prompt or PromptTemplate.from_template("{page_content}")
|
||||
func = partial(
|
||||
_get_relevant_documents,
|
||||
retriever=retriever,
|
||||
document_prompt=document_prompt,
|
||||
document_separator=document_separator,
|
||||
)
|
||||
afunc = partial(
|
||||
_aget_relevant_documents,
|
||||
retriever=retriever,
|
||||
document_prompt=document_prompt,
|
||||
document_separator=document_separator,
|
||||
)
|
||||
return Tool(
|
||||
name=name,
|
||||
description=description,
|
||||
func=func,
|
||||
coroutine=afunc,
|
||||
args_schema=RetrieverInput,
|
||||
)
|
157
libs/core/langchain_core/tools/simple.py
Normal file
157
libs/core/langchain_core/tools/simple.py
Normal file
@@ -0,0 +1,157 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from inspect import signature
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Type, Union
|
||||
|
||||
from langchain_core.callbacks import (
|
||||
AsyncCallbackManagerForToolRun,
|
||||
CallbackManagerForToolRun,
|
||||
)
|
||||
from langchain_core.messages import ToolCall
|
||||
from langchain_core.pydantic_v1 import BaseModel
|
||||
from langchain_core.runnables import RunnableConfig, run_in_executor
|
||||
from langchain_core.tools.base import (
|
||||
BaseTool,
|
||||
ToolException,
|
||||
_get_runnable_config_param,
|
||||
)
|
||||
|
||||
|
||||
class Tool(BaseTool):
|
||||
"""Tool that takes in function or coroutine directly."""
|
||||
|
||||
description: str = ""
|
||||
func: Optional[Callable[..., str]]
|
||||
"""The function to run when the tool is called."""
|
||||
coroutine: Optional[Callable[..., Awaitable[str]]] = None
|
||||
"""The asynchronous version of the function."""
|
||||
|
||||
# --- Runnable ---
|
||||
|
||||
async def ainvoke(
|
||||
self,
|
||||
input: Union[str, Dict, ToolCall],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if not self.coroutine:
|
||||
# If the tool does not implement async, fall back to default implementation
|
||||
return await run_in_executor(config, self.invoke, input, config, **kwargs)
|
||||
|
||||
return await super().ainvoke(input, config, **kwargs)
|
||||
|
||||
# --- Tool ---
|
||||
|
||||
@property
|
||||
def args(self) -> dict:
|
||||
"""The tool's input arguments.
|
||||
|
||||
Returns:
|
||||
The input arguments for the tool.
|
||||
"""
|
||||
if self.args_schema is not None:
|
||||
return self.args_schema.schema()["properties"]
|
||||
# For backwards compatibility, if the function signature is ambiguous,
|
||||
# assume it takes a single string input.
|
||||
return {"tool_input": {"type": "string"}}
|
||||
|
||||
def _to_args_and_kwargs(self, tool_input: Union[str, Dict]) -> Tuple[Tuple, Dict]:
|
||||
"""Convert tool input to pydantic model."""
|
||||
args, kwargs = super()._to_args_and_kwargs(tool_input)
|
||||
# For backwards compatibility. The tool must be run with a single input
|
||||
all_args = list(args) + list(kwargs.values())
|
||||
if len(all_args) != 1:
|
||||
raise ToolException(
|
||||
f"""Too many arguments to single-input tool {self.name}.
|
||||
Consider using StructuredTool instead."""
|
||||
f" Args: {all_args}"
|
||||
)
|
||||
return tuple(all_args), {}
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool."""
|
||||
if self.func:
|
||||
if run_manager and signature(self.func).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.func):
|
||||
kwargs[config_param] = config
|
||||
return self.func(*args, **kwargs)
|
||||
raise NotImplementedError("Tool does not support sync invocation.")
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool asynchronously."""
|
||||
if self.coroutine:
|
||||
if run_manager and signature(self.coroutine).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.coroutine):
|
||||
kwargs[config_param] = config
|
||||
return await self.coroutine(*args, **kwargs)
|
||||
|
||||
# NOTE: this code is unreachable since _arun is only called if coroutine is not
|
||||
# None.
|
||||
return await super()._arun(
|
||||
*args, config=config, run_manager=run_manager, **kwargs
|
||||
)
|
||||
|
||||
# TODO: this is for backwards compatibility, remove in future
|
||||
def __init__(
|
||||
self, name: str, func: Optional[Callable], description: str, **kwargs: Any
|
||||
) -> None:
|
||||
"""Initialize tool."""
|
||||
super(Tool, self).__init__( # type: ignore[call-arg]
|
||||
name=name, func=func, description=description, **kwargs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_function(
|
||||
cls,
|
||||
func: Optional[Callable],
|
||||
name: str, # We keep these required to support backwards compatibility
|
||||
description: str,
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
coroutine: Optional[
|
||||
Callable[..., Awaitable[Any]]
|
||||
] = None, # This is last for compatibility, but should be after func
|
||||
**kwargs: Any,
|
||||
) -> Tool:
|
||||
"""Initialize tool from a function.
|
||||
|
||||
Args:
|
||||
func: The function to create the tool from.
|
||||
name: The name of the tool.
|
||||
description: The description of the tool.
|
||||
return_direct: Whether to return the output directly. Defaults to False.
|
||||
args_schema: The schema of the tool's input arguments. Defaults to None.
|
||||
coroutine: The asynchronous version of the function. Defaults to None.
|
||||
kwargs: Additional arguments to pass to the tool.
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
|
||||
Raises:
|
||||
ValueError: If the function is not provided.
|
||||
"""
|
||||
if func is None and coroutine is None:
|
||||
raise ValueError("Function and/or coroutine must be provided")
|
||||
return cls(
|
||||
name=name,
|
||||
func=func,
|
||||
coroutine=coroutine,
|
||||
description=description,
|
||||
return_direct=return_direct,
|
||||
args_schema=args_schema,
|
||||
**kwargs,
|
||||
)
|
203
libs/core/langchain_core/tools/structured.py
Normal file
203
libs/core/langchain_core/tools/structured.py
Normal file
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import textwrap
|
||||
from inspect import signature
|
||||
from typing import Any, Awaitable, Callable, Dict, List, Literal, Optional, Type, Union
|
||||
|
||||
from langchain_core.callbacks import (
|
||||
AsyncCallbackManagerForToolRun,
|
||||
CallbackManagerForToolRun,
|
||||
)
|
||||
from langchain_core.messages import ToolCall
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from langchain_core.runnables import RunnableConfig, run_in_executor
|
||||
from langchain_core.tools.base import (
|
||||
FILTERED_ARGS,
|
||||
BaseTool,
|
||||
_get_runnable_config_param,
|
||||
create_schema_from_function,
|
||||
)
|
||||
from langchain_core.utils.pydantic import TypeBaseModel
|
||||
|
||||
|
||||
class StructuredTool(BaseTool):
|
||||
"""Tool that can operate on any number of inputs."""
|
||||
|
||||
description: str = ""
|
||||
args_schema: TypeBaseModel = Field(..., description="The tool schema.")
|
||||
"""The input arguments' schema."""
|
||||
func: Optional[Callable[..., Any]]
|
||||
"""The function to run when the tool is called."""
|
||||
coroutine: Optional[Callable[..., Awaitable[Any]]] = None
|
||||
"""The asynchronous version of the function."""
|
||||
|
||||
# --- Runnable ---
|
||||
|
||||
# TODO: Is this needed?
|
||||
async def ainvoke(
|
||||
self,
|
||||
input: Union[str, Dict, ToolCall],
|
||||
config: Optional[RunnableConfig] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if not self.coroutine:
|
||||
# If the tool does not implement async, fall back to default implementation
|
||||
return await run_in_executor(config, self.invoke, input, config, **kwargs)
|
||||
|
||||
return await super().ainvoke(input, config, **kwargs)
|
||||
|
||||
# --- Tool ---
|
||||
|
||||
@property
|
||||
def args(self) -> dict:
|
||||
"""The tool's input arguments."""
|
||||
return self.args_schema.schema()["properties"]
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool."""
|
||||
if self.func:
|
||||
if run_manager and signature(self.func).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.func):
|
||||
kwargs[config_param] = config
|
||||
return self.func(*args, **kwargs)
|
||||
raise NotImplementedError("StructuredTool does not support sync invocation.")
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
*args: Any,
|
||||
config: RunnableConfig,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool asynchronously."""
|
||||
if self.coroutine:
|
||||
if run_manager and signature(self.coroutine).parameters.get("callbacks"):
|
||||
kwargs["callbacks"] = run_manager.get_child()
|
||||
if config_param := _get_runnable_config_param(self.coroutine):
|
||||
kwargs[config_param] = config
|
||||
return await self.coroutine(*args, **kwargs)
|
||||
|
||||
# NOTE: this code is unreachable since _arun is only called if coroutine is not
|
||||
# None.
|
||||
return await super()._arun(
|
||||
*args, config=config, run_manager=run_manager, **kwargs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_function(
|
||||
cls,
|
||||
func: Optional[Callable] = None,
|
||||
coroutine: Optional[Callable[..., Awaitable[Any]]] = None,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
infer_schema: bool = True,
|
||||
*,
|
||||
response_format: Literal["content", "content_and_artifact"] = "content",
|
||||
parse_docstring: bool = False,
|
||||
error_on_invalid_docstring: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> StructuredTool:
|
||||
"""Create tool from a given function.
|
||||
|
||||
A classmethod that helps to create a tool from a function.
|
||||
|
||||
Args:
|
||||
func: The function from which to create a tool.
|
||||
coroutine: The async function from which to create a tool.
|
||||
name: The name of the tool. Defaults to the function name.
|
||||
description: The description of the tool.
|
||||
Defaults to the function docstring.
|
||||
return_direct: Whether to return the result directly or as a callback.
|
||||
Defaults to False.
|
||||
args_schema: The schema of the tool's input arguments. Defaults to None.
|
||||
infer_schema: Whether to infer the schema from the function's signature.
|
||||
Defaults to True.
|
||||
response_format: The tool response format. If "content" then the output of
|
||||
the tool is interpreted as the contents of a ToolMessage. If
|
||||
"content_and_artifact" then the output is expected to be a two-tuple
|
||||
corresponding to the (content, artifact) of a ToolMessage.
|
||||
Defaults to "content".
|
||||
parse_docstring: if ``infer_schema`` and ``parse_docstring``, will attempt
|
||||
to parse parameter descriptions from Google Style function docstrings.
|
||||
Defaults to False.
|
||||
error_on_invalid_docstring: if ``parse_docstring`` is provided, configure
|
||||
whether to raise ValueError on invalid Google Style docstrings.
|
||||
Defaults to False.
|
||||
kwargs: Additional arguments to pass to the tool
|
||||
|
||||
Returns:
|
||||
The tool.
|
||||
|
||||
Raises:
|
||||
ValueError: If the function is not provided.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def add(a: int, b: int) -> int:
|
||||
\"\"\"Add two numbers\"\"\"
|
||||
return a + b
|
||||
tool = StructuredTool.from_function(add)
|
||||
tool.run(1, 2) # 3
|
||||
"""
|
||||
|
||||
if func is not None:
|
||||
source_function = func
|
||||
elif coroutine is not None:
|
||||
source_function = coroutine
|
||||
else:
|
||||
raise ValueError("Function and/or coroutine must be provided")
|
||||
name = name or source_function.__name__
|
||||
if args_schema is None and infer_schema:
|
||||
# schema name is appended within function
|
||||
args_schema = create_schema_from_function(
|
||||
name,
|
||||
source_function,
|
||||
parse_docstring=parse_docstring,
|
||||
error_on_invalid_docstring=error_on_invalid_docstring,
|
||||
filter_args=_filter_schema_args(source_function),
|
||||
)
|
||||
description_ = description
|
||||
if description is None and not parse_docstring:
|
||||
description_ = source_function.__doc__ or None
|
||||
if description_ is None and args_schema:
|
||||
description_ = args_schema.__doc__ or None
|
||||
if description_ is None:
|
||||
raise ValueError(
|
||||
"Function must have a docstring if description not provided."
|
||||
)
|
||||
if description is None:
|
||||
# Only apply if using the function's docstring
|
||||
description_ = textwrap.dedent(description_).strip()
|
||||
|
||||
# Description example:
|
||||
# search_api(query: str) - Searches the API for the query.
|
||||
description_ = f"{description_.strip()}"
|
||||
return cls(
|
||||
name=name,
|
||||
func=func,
|
||||
coroutine=coroutine,
|
||||
args_schema=args_schema, # type: ignore[arg-type]
|
||||
description=description_,
|
||||
return_direct=return_direct,
|
||||
response_format=response_format,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def _filter_schema_args(func: Callable) -> List[str]:
|
||||
filter_args = list(FILTERED_ARGS)
|
||||
if config_param := _get_runnable_config_param(func):
|
||||
filter_args.append(config_param)
|
||||
# filter_args.extend(_get_non_model_params(type_hints))
|
||||
return filter_args
|
@@ -172,10 +172,10 @@ def convert_python_function_to_openai_function(
|
||||
Returns:
|
||||
The OpenAI function description.
|
||||
"""
|
||||
from langchain_core import tools
|
||||
from langchain_core.tools.base import create_schema_from_function
|
||||
|
||||
func_name = _get_python_function_name(function)
|
||||
model = tools.create_schema_from_function(
|
||||
model = create_schema_from_function(
|
||||
func_name,
|
||||
function,
|
||||
filter_args=(),
|
||||
|
@@ -39,15 +39,17 @@ from langchain_core.runnables import (
|
||||
)
|
||||
from langchain_core.tools import (
|
||||
BaseTool,
|
||||
InjectedToolArg,
|
||||
SchemaAnnotationError,
|
||||
StructuredTool,
|
||||
Tool,
|
||||
ToolException,
|
||||
tool,
|
||||
)
|
||||
from langchain_core.tools.base import (
|
||||
InjectedToolArg,
|
||||
SchemaAnnotationError,
|
||||
_get_all_basemodel_annotations,
|
||||
_is_message_content_block,
|
||||
_is_message_content_type,
|
||||
tool,
|
||||
)
|
||||
from langchain_core.utils.function_calling import convert_to_openai_function
|
||||
from langchain_core.utils.pydantic import PYDANTIC_MAJOR_VERSION, _create_subset_model
|
||||
|
Reference in New Issue
Block a user