
LANGCHAIN — Structured Tools
Digital design is like painting, except the paint never dries. — Neville Brody
Structured Tools are an exciting new feature in LangChain that enable more complex, multi-faceted interactions between language models and tools, making it easier to build innovative, adaptable, and powerful applications. In this article, we’ll explore what structured tools are, how to implement your own, and how to use them with the new StructuredChatAgent.
What is a “Structured Tool”?
A structured tool represents an action an agent can take. It wraps any function you provide to let an agent easily interface with it. A Structured Tool object is defined by its name, description, args_schema, and _run and _arun functions.
Let’s dive into an example of how to implement a structured tool. Suppose you wanted a tool to interact with Hugging Face models via the requests library. You can create a structured tool using the following code:
import requests
from langchain.tools.base import StructuredTool
API_KEY = "<MY-API-KEY>"
def get_huggingface_models(
path: Optional[str] = None, query_params: Optional[dict] = None
) -> dict:
"""Tool that calls GET on <https://huggingface.co/models*> apis. Valid params include "search":"search", "author":"author", "filter":"filter" and "sort":"sort"."""
base_url = "<https://huggingface.co/api/models>"
headers = {"authorization": f"Bearer {API_KEY}"}
result = requests.get(base_url + (path or ""), params=query_params, headers=headers)
return result.json()
get_huggingface_models_tool = StructuredTool.from_function(get_huggingface_models)
models = get_huggingface_models_tool.run({"query_params": {"search": "gpt-j"}})
print(models)Behind the scenes, this infers the args_schema from the function’s signature. This is used to tell the agent that it can provide query parameters to search as well as a path parameter to call other child endpoints.
If you want more control over the tool definition, you can subclass the BaseTool directly. For instance, maybe you want the API key to be loaded automatically from the environment variables. Here's an example of a custom structured tool subclass:
from typing import Optional, Type
import aiohttp
import requests
from langchain.callbacks.manager import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
from langchain.tools import BaseTool
from pydantic import BaseModel, BaseSettings, Field
class GetHuggingFaceModelsToolSchema(BaseModel):
path: str = Field(default="", description="the api path")
query_params: Optional[dict] = Field(
default=None, description="Optional search parameters"
)
class GetHuggingFaceModelsTool(BaseTool, BaseSettings):
"""My custom tool."""
name: str = "get_huggingface_models"
description: str = """Tool that calls GET on <https://huggingface.co/models*> apis. Valid params include "search":"search", "author":"author", "filter":"filter" and "sort":"sort"."""
args_schema: Type[GetHuggingFaceModelsToolSchema] = GetHuggingFaceModelsToolSchema
base_url: str = "<https://huggingface.co/api/models>"
api_key: str = Field(..., env="HUGGINGFACE_API_KEY")
@property
def _headers(self) -> dict:
return {"authorization": f"Bearer {self.api_key}"}
def _run(
self,
path: str = "",
query_params: Optional[dict] = None,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> dict:
"""Run the tool"""
result = requests.get(
self.base_url + path, params=query_params, headers=self._headers
)
return result.json()
async def _arun(
self,
path: str = "",
query_params: Optional[dict] = None,
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
) -> dict:
"""Run the tool asynchronously."""
async with aiohttp.ClientSession() as session:
async with session.get(
self.base_url + path, params=query_params, headers=self._headers
) as response:
return await response.json()
get_models_tool = GetHuggingFaceModelsTool()
models = get_models_tool.run({"query_params": {"search": "gpt-j"}})
print(models)