mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-18 16:16:33 +00:00
Update some Tools Docs (#3913)
Haven't gotten to all of them, but this: - Updates some of the tools notebooks to actually instantiate a tool (many just show a 'utility' rather than a tool. More changes to come in separate PR) - Move the `Tool` and decorator definitions to `langchain/tools/base.py` (but still export from `langchain.agents`) - Add scene explain to the load_tools() function - Add unit tests for public apis for the langchain.tools and langchain.agents modules
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Core toolkit implementations."""
|
||||
|
||||
from langchain.tools.base import BaseTool, StructuredTool
|
||||
from langchain.tools.base import BaseTool, StructuredTool, Tool, tool
|
||||
from langchain.tools.bing_search.tool import BingSearchResults, BingSearchRun
|
||||
from langchain.tools.ddg_search.tool import DuckDuckGoSearchResults, DuckDuckGoSearchRun
|
||||
from langchain.tools.file_management.copy import CopyFileTool
|
||||
@@ -41,6 +41,7 @@ __all__ = [
|
||||
"APIOperation",
|
||||
"BaseTool",
|
||||
"BaseTool",
|
||||
"BaseTool",
|
||||
"BingSearchResults",
|
||||
"BingSearchRun",
|
||||
"ClickTool",
|
||||
@@ -49,7 +50,6 @@ __all__ = [
|
||||
"DeleteFileTool",
|
||||
"DuckDuckGoSearchResults",
|
||||
"DuckDuckGoSearchRun",
|
||||
"DuckDuckGoSearchRun",
|
||||
"ExtractHyperlinksTool",
|
||||
"ExtractTextTool",
|
||||
"FileSearchTool",
|
||||
@@ -65,15 +65,16 @@ __all__ = [
|
||||
"NavigateTool",
|
||||
"OpenAPISpec",
|
||||
"ReadFileTool",
|
||||
"SceneXplainTool",
|
||||
"ShellTool",
|
||||
"StructuredTool",
|
||||
"WriteFileTool",
|
||||
"BaseTool",
|
||||
"SceneXplainTool",
|
||||
"VectorStoreQAWithSourcesTool",
|
||||
"Tool",
|
||||
"VectorStoreQATool",
|
||||
"VectorStoreQAWithSourcesTool",
|
||||
"WikipediaQueryRun",
|
||||
"WolframAlphaQueryRun",
|
||||
"ZapierNLARunAction",
|
||||
"WriteFileTool",
|
||||
"ZapierNLAListActions",
|
||||
"ZapierNLARunAction",
|
||||
"tool",
|
||||
]
|
||||
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import partial
|
||||
from inspect import signature
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Type, Union
|
||||
|
||||
@@ -13,6 +14,7 @@ from pydantic import (
|
||||
create_model,
|
||||
root_validator,
|
||||
validate_arguments,
|
||||
validator,
|
||||
)
|
||||
from pydantic.main import ModelMetaclass
|
||||
|
||||
@@ -298,6 +300,113 @@ class BaseTool(ABC, BaseModel, metaclass=ToolMetaclass):
|
||||
return self.run(tool_input, callbacks=callbacks)
|
||||
|
||||
|
||||
class Tool(BaseTool):
|
||||
"""Tool that takes in function or coroutine directly."""
|
||||
|
||||
description: str = ""
|
||||
func: Callable[..., str]
|
||||
"""The function to run when the tool is called."""
|
||||
coroutine: Optional[Callable[..., Awaitable[str]]] = None
|
||||
"""The asynchronous version of the function."""
|
||||
|
||||
@validator("func", pre=True, always=True)
|
||||
def validate_func_not_partial(cls, func: Callable) -> Callable:
|
||||
"""Check that the function is not a partial."""
|
||||
if isinstance(func, partial):
|
||||
raise ValueError("Partial functions not yet supported in tools.")
|
||||
return func
|
||||
|
||||
@property
|
||||
def args(self) -> dict:
|
||||
"""The tool's input arguments."""
|
||||
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 ValueError(
|
||||
f"Too many arguments to single-input tool {self.name}."
|
||||
f" Args: {all_args}"
|
||||
)
|
||||
return tuple(all_args), {}
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool."""
|
||||
new_argument_supported = signature(self.func).parameters.get("callbacks")
|
||||
return (
|
||||
self.func(
|
||||
*args,
|
||||
callbacks=run_manager.get_child() if run_manager else None,
|
||||
**kwargs,
|
||||
)
|
||||
if new_argument_supported
|
||||
else self.func(*args, **kwargs)
|
||||
)
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
*args: Any,
|
||||
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Use the tool asynchronously."""
|
||||
if self.coroutine:
|
||||
new_argument_supported = signature(self.coroutine).parameters.get(
|
||||
"callbacks"
|
||||
)
|
||||
return (
|
||||
await self.coroutine(
|
||||
*args,
|
||||
callbacks=run_manager.get_child() if run_manager else None,
|
||||
**kwargs,
|
||||
)
|
||||
if new_argument_supported
|
||||
else await self.coroutine(*args, **kwargs)
|
||||
)
|
||||
raise NotImplementedError("Tool does not support async")
|
||||
|
||||
# TODO: this is for backwards compatibility, remove in future
|
||||
def __init__(
|
||||
self, name: str, func: Callable, description: str, **kwargs: Any
|
||||
) -> None:
|
||||
"""Initialize tool."""
|
||||
super(Tool, self).__init__(
|
||||
name=name, func=func, description=description, **kwargs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_function(
|
||||
cls,
|
||||
func: Callable,
|
||||
name: str, # We keep these required to support backwards compatibility
|
||||
description: str,
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
**kwargs: Any,
|
||||
) -> Tool:
|
||||
"""Initialize tool from a function."""
|
||||
return cls(
|
||||
name=name,
|
||||
func=func,
|
||||
description=description,
|
||||
return_direct=return_direct,
|
||||
args_schema=args_schema,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class StructuredTool(BaseTool):
|
||||
"""Tool that can operate on any number of inputs."""
|
||||
|
||||
@@ -385,3 +494,79 @@ class StructuredTool(BaseTool):
|
||||
return_direct=return_direct,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def tool(
|
||||
*args: Union[str, Callable],
|
||||
return_direct: bool = False,
|
||||
args_schema: Optional[Type[BaseModel]] = None,
|
||||
infer_schema: 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.
|
||||
args_schema: optional argument schema for user to specify
|
||||
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.
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
def _make_with_name(tool_name: str) -> Callable:
|
||||
def _make_tool(func: Callable) -> BaseTool:
|
||||
if infer_schema or args_schema is not None:
|
||||
return StructuredTool.from_function(
|
||||
func,
|
||||
name=tool_name,
|
||||
return_direct=return_direct,
|
||||
args_schema=args_schema,
|
||||
infer_schema=infer_schema,
|
||||
)
|
||||
# If someone doesn't want a schema applied, we must treat it as
|
||||
# a simple string->string function
|
||||
assert func.__doc__ is not None, "Function must have a docstring"
|
||||
return Tool(
|
||||
name=tool_name,
|
||||
func=func,
|
||||
description=f"{tool_name} tool",
|
||||
return_direct=return_direct,
|
||||
)
|
||||
|
||||
return _make_tool
|
||||
|
||||
if 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")
|
||||
|
@@ -49,7 +49,7 @@ def marshal_spec(txt: str) -> dict:
|
||||
return yaml.safe_load(txt)
|
||||
|
||||
|
||||
class AIPLuginToolSchema(BaseModel):
|
||||
class AIPluginToolSchema(BaseModel):
|
||||
"""AIPLuginToolSchema."""
|
||||
|
||||
tool_input: Optional[str] = ""
|
||||
@@ -58,7 +58,7 @@ class AIPLuginToolSchema(BaseModel):
|
||||
class AIPluginTool(BaseTool):
|
||||
plugin: AIPlugin
|
||||
api_spec: str
|
||||
args_schema: Type[AIPLuginToolSchema] = AIPLuginToolSchema
|
||||
args_schema: Type[AIPluginToolSchema] = AIPluginToolSchema
|
||||
|
||||
@classmethod
|
||||
def from_plugin_url(cls, url: str) -> AIPluginTool:
|
||||
|
Reference in New Issue
Block a user