LangChain开发(八)自定义工具调用

自定义工具

在构建代理时,需要为其提供一个Tool列表,以便代理可以使用这些工具。除了实际调用的函数之外,Tool由几个组件组成:

属性类型描述
namestr在提供给LLM或代理的工具集必须是唯一的
descriptionstr描述工具的功能。LLM或代理将使用此描述作为上下文。
args_schemaPydantic BaseModel可选但建议, 可用于提供更多信息(例如,few_shot示例)或验证预期参数
return_directboolean仅对代理相关。当为True时,在调用给定工具后,代理将停止并将结果返回给用户

LangChain提供了三种创建工具的方式:

  1. 使用@tool装饰器 ——定义自定义工具的最简单方式
  2. 使用StructuredTool.from_function 类方法 —— 这类似于@tool装饰器,但允许更多配置和同步和异步实现的规范。
  3. 通过子类BaseTool —— 这是最灵活的方法,它提供了最大程度的控制,但需要更多的工作量和代码。@toolstructuredTool.from_function类方法对于大多数用例已经足够了。 如果工具具有精心选择的名称、描述和JSON模式,模型的性能会更好。

@tool装饰器

这个@tool装饰器是自定自定义工具的最简单方式。该装饰器默认使用函数名称作为工具名称,但可以通过字符串作为第一个参数来覆盖。此外,装饰器使用函数的文档字符串作为工具的描述——因此必须提供文档字符串。

普通工具

# 导入工具装饰器库
from langchain_core.tools import tool

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

# 打印工具的属性
print(multiply.name)
print(multiply.description)
print(multiply.args)

输出:

multiply
Multiply two numbers.
{'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}

异步实现

# 导入工具装饰器库
from langchain_core.tools import tool


@tool
async def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b


# 打印工具的属性
print(multiply.name)
print(multiply.description)
print(multiply.args)

BaseModel

自定义名称和参数

还可以通过将它们传递给工具装饰器来自定义工具名称和JSON参数

# 导入工具装饰器库
from langchain_core.tools import tool
from pydantic import BaseModel, Field


class CalculatorInput(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")


@tool("multiplication-tool", args_schema=CalculatorInput, return_direct=True)
def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b


# 打印工具的属性
print(multiply.name)
print(multiply.description)
print(multiply.args)
print(multiply.return_direct)

print(multiply.invoke({"a": 2, "b": 3}))

输出

multiplication-tool
Multiply two numbers.
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
True
6

这个写法多了一些描述

StructuredTool

structuredTool.from_function类方法提供了比@tool装饰器更多的可配置性,而无需太多额外的代码

支持在一个方法里面动态调用同步和异步方法

from langchain_core.tools import StructuredTool
import asyncio

def multiply(a: int, b: int) -> int:
    """Multiply two numbers"""
    return a * b

async def amultiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

async def main():
    # func 参数:指定一个同步函数。当你在同步上下文中调用工具时,它会使用这个同步函数来执行操作。
    # oroutine 参数:指定一个异步函数。当你在异步上下文中调用工具时,它会使用这个异步函数来执行操作。
    calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)
    # invoke是同步调用
    print(calculator.invoke({"a": 2, "b": 3}))
    # ainvoke是异步调用
    print(await calculator.ainvoke({"a": 2, "b": 5}))

asyncio.run(main())

输出

6
10

配置自定义参数

from langchain_core.tools import StructuredTool
import asyncio
from pydantic import BaseModel, Field


class CalculatorInput(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")

def multiply(a: int, b: int) -> int:
    """Multiply two numbers"""
    return a * b

async def amultiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

async def main():
    calculator = StructuredTool.from_function(
        func=multiply,
        name="calculator",
        description="multiply numbers",
        args_schema=CalculatorInput,
        return_direct=True,
    )
    print(calculator.invoke({"a": 2, "b": 3}))
    print(calculator.name)
    print(calculator.description)
    print(calculator.args)

asyncio.run(main())

处理工具错误

如果您正在使用带有代理的工具,可能需要一个错误处理策略,以便代理可以从错误中回复并继续执行。一个简单的策略是在工具内部抛出ToolException,并使用handle_tool_error指定一个错误处理程序。当指定了错误处理程序时,异常将被补货,错误处理程序将决定从工具返回哪个输出。可以将handle_tool_error设置为True、字符串值或函数。如果是函数,该函数应该以ToolException作为参数,并返回一个值。请注意,仅仅抛出ToolException是不会生效的。您需要首先设置工具的handle_tool_error,因为其默认值是False

handle_tool_error=True/False

from langchain_core.tools import StructuredTool
# 导入工具出现异常的时候,处理的库
from langchain_core.tools import ToolException

def get_weather(city: str) -> int:
    """获取给定城市的天气"""
    raise ToolException(f"错误,没有名为{city}的城市。")

get_weather_tool = StructuredTool.from_function(
    func=get_weather,
    # 默认情况下,如果函数抛出ToolException,则将ToolException的message作为响应。
    # 如果设置为True,则将返回ToolException异常文本,False将会抛出ToolException
    handle_tool_error=True,
)
response = get_weather_tool.invoke({"city": "测试"})
print(response)

下面是一个使用默认的handle_tool_error=True行为的示例。

handle_tool_error=True 时输出示例

错误,没有名为测试的城市。

handle_tool_error=False 时输出示例

langchain_core.tools.base.ToolException: 错误,没有名为测试的城市。

handle_tool_error=异常信息

我们可以将handle_tool_error设置为一个始终返回的字符串

from langchain_core.tools import StructuredTool
# 导入工具出现异常的时候,处理的库
from langchain_core.tools import ToolException

def get_weather(city: str) -> int:
    """获取给定城市的天气"""
    raise ToolException(f"错误:没有名为{city}的城市。")

get_weather_tool = StructuredTool.from_function(
    func=get_weather,
    handle_tool_error="没找到这个城市",
)

response = get_weather_tool.invoke({"city": "测试"})
print(response)

handle_tool_error=函数

from langchain_core.tools import StructuredTool
from langchain_core.tools import ToolException

def get_weather(city: str) -> int:
    """获取给定城市的天气"""
    raise ToolException(f"错误:没有名为{city}的城市。")

def _handle_error(error: ToolException) -> str:
    return f"工具执行期间发生错误:`{error.args[0]}`"

get_weather_tool = StructuredTool.from_function(
    func=get_weather,
    handle_tool_error=_handle_error,
)

response = get_weather_tool.invoke({"city": "测试"})
print(response)

输出示例:

工具执行期间发生错误:`错误:没有名为测试的城市。`

调用内置工具包和拓展工具

工具

工具是代理、链或聊天模型/LLM用来与世界交互的接口。一个工具由以下组件组成:

  1. 工具的名称
  2. 工具的功能描述
  3. 工具输入的JSON模式
  4. 要调用的函数
  5. 工具的结果是否应直接返回给用户(仅对代理相关)名称、描述和JSON模式作为上下文提供给LLM,允许LLM适当地确定如何使用工具。给定一组可用工具和提示,LLM可以请求调用一个或多个工具,并提供恰当的参数。通常,在设计供聊天模型或LLM使用的工具时,要牢记以下几点:
    • 经过微调以进行工具调用的聊天模型将比未经微调的模型更擅长进行工具调用。
    • 未经微调的模型可能根本无法使用工具,特别是如果工具复杂或需要多次调用工具。
    • 如果工具具有精心选择的名称、描述和JSON模式,则模型的性能将更好。
    • 简单的工具通常比更复杂的工具更容易让模型使用。

LangChain拥有大量第三方工具。

https://python.langchain.com/docs/integrations/tools/

在使用第三方工具时,请确保了解工具的工作原理、权限情况。请阅读其文档,并检查是否需要从安全角度考虑任何事项。请查看安全只能获取更多信息。

让我们尝试一下维基百科集成(需要魔法)。

pip install wikipedia

代码:

from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

api_wrapper = WikipediaAPIWrapper(top_k_result=1, doc_content_chars_max=100)
tool = WikipediaQueryRun(api_wrapper=api_wrapper)
print(tool.invoke({"query": "langchain"}))

print(f"Name: {tool.name}")
print(f"Description: {tool.description}")
print(f"args schema: {tool.args}")
print(f"returns directly?: {tool.return_direct}")

输出示例:

Page: LangChain
Summary: LangChain is a software framework that helps facilitate the integration of 
Name: wikipedia
Description: A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.
args schema: {'query': {'description': 'query to look up on wikipedia', 'title': 'Query', 'type': 'string'}}
returns directly?: False

自定义默认工具

我们还可以修改内置工具的名称、描述和参数的JSON模式

在自定义参数的JSON模式时,重要的是输入保持与函数相同,因此您不应更改它。但您可以轻松为每个输入定义自定义描述。

from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from pydantic import BaseModel, Field

api_wrapper = WikipediaAPIWrapper(top_k_result=1, doc_content_chars_max=100)

class WikiInputs(BaseModel):
    """维基百科工具的输入"""
    query: str = Field(
        description="query to look up in Wikipedia, should be 3 or less words"
    )

tool = WikipediaQueryRun(
    name="wiki-tool",
    description="look ip things in wikipedia",
    args_schema=WikiInputs,
    api_wrapper=api_wrapper,
    return_direct=True,
)

print(tool.run("langchain"))

输出示例:

Page: LangChain
Summary: LangChain is a software framework that helps facilitate the integration of 

如果使用内置工具包

工具包是一组旨在一起使用以执行特定任务的工具。它们具有便捷的加载方法。

要获取可用的现成工具包完整列表,请访问集成。

所有工具包都公开了一个get_tools方法,该方法返回一个工具列表。

通常您应该这样使用它们:

# 初始化一个工具包
toolkit = ExampleTookit()
# 获取工具列表
tools = toolkit.get_tools() 

例如:使用SQLDatabaseToolkit读取langchian.db数据库表结构

from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_community.utilities import SQLDatabase
from langchain_openai import ChatOpenAI
from langchain_community.agent_toolkits.sql.base import create_sql_agent
from langchain.agents.agent_types import AgentType
import os

llm = ChatOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model="qwen-turbo",
)
db = SQLDatabase.from_uri("sqlite:///langchain.db")
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
print(toolkit.get_tools())

agent_executor = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=False,
    agent_type=AgentType.OPENAI_FUNCTIONS,
)

result = agent_executor.invoke("描述 langchain_test 表结构")
print(result)

建表语句

create table langchain_test
(
    name integer,
    code integer,
    id   integer
        constraint id
            primary key
);

输出示例:

[QuerySQLDatabaseTool(description="Input to this tool is a detailed and correct SQL query, output is a result from the database. If the query is not correct, an error message will be returned. If an error is returned, rewrite the query, check the query, and try again. If you encounter an issue with Unknown column 'xxxx' in 'field list', use sql_db_schema to query the correct table fields.", db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x00000223BE228E60>), InfoSQLDatabaseTool(description='Input to this tool is a comma-separated list of tables, output is the schema and sample rows for those tables. Be sure that the tables actually exist by calling sql_db_list_tables first! Example Input: table1, table2, table3', db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x00000223BE228E60>), ListSQLDatabaseTool(db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x00000223BE228E60>), QuerySQLCheckerTool(description='Use this tool to double check if your query is correct before executing it. Always use this tool before executing a query with sql_db_query!', db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x00000223BE228E60>, llm=ChatOpenAI(client=<openai.resources.chat.completions.completions.Completions object at 0x00000223BE156030>, async_client=<openai.resources.chat.completions.completions.AsyncCompletions object at 0x00000223BE228D10>, root_client=<openai.OpenAI object at 0x00000223BBCC6A20>, root_async_client=<openai.AsyncOpenAI object at 0x00000223BE206B40>, model_name='qwen-turbo', model_kwargs={}, openai_api_key=SecretStr('**********'), openai_api_base='https://dashscope.aliyuncs.com/compatible-mode/v1'), llm_chain=LLMChain(verbose=False, prompt=PromptTemplate(input_variables=['dialect', 'query'], input_types={}, partial_variables={}, template='\n{query}\nDouble check the {dialect} query above for common mistakes, including:\n- Using NOT IN with NULL values\n- Using UNION when UNION ALL should have been used\n- Using BETWEEN for exclusive ranges\n- Data type mismatch in predicates\n- Properly quoting identifiers\n- Using the correct number of arguments for functions\n- Casting to the correct data type\n- Using the proper columns for joins\n\nIf there are any of the above mistakes, rewrite the query. If there are no mistakes, just reproduce the original query.\n\nOutput the final SQL query only.\n\nSQL Query: '), llm=ChatOpenAI(client=<openai.resources.chat.completions.completions.Completions object at 0x00000223BE156030>, async_client=<openai.resources.chat.completions.completions.AsyncCompletions object at 0x00000223BE228D10>, root_client=<openai.OpenAI object at 0x00000223BBCC6A20>, root_async_client=<openai.AsyncOpenAI object at 0x00000223BE206B40>, model_name='qwen-turbo', model_kwargs={}, openai_api_key=SecretStr('**********'), openai_api_base='https://dashscope.aliyuncs.com/compatible-mode/v1'), output_parser=StrOutputParser(), llm_kwargs={}))]
{'input': '描述 langchain_test 表结构', 'output': "The table 'langchain_test' has the following structure:\n\n- It contains four columns: 'name', 'code', 'id'.\n- The column 'id' is the primary key.\n\nThere are three rows in this table."}

源码地址

https://github.com/lys1313013/langchain-example/tree/main/09-tools

参考资料

B站:2025吃透LangChain大模型全套教程(LLM+RAG+OpenAI+Agent)第7集

### 如何在LangChain中实现自定义API 为了实现在LangChain中的自定义API,主要方式之一是通过创建继承于`BaseLanguageModel`或其他适当基类的新类来完成。这允许开发者根据特定需求调整模型的行为,同时保持与其他组件的良好兼容性[^1]。 对于具体的实施细节,在构建自定义LLM时,重点在于重写`__call__`方法以便定义当实例被当作函数调用时的具体行为。此过程涉及准备输入数据结构(如消息列表),执行必要的网络请求或本地计算,并最终返回预期格式的结果字符串[^5]。 下面是一个简单的例子展示了如何创建一个名为`CustomLLM1`的语言模型类: ```python from typing import List, Optional class CustomLLM1: def __call__(self, prompt: str) -> str: # 准备发送给目标服务的消息体 messages = [{"role": "user", "content": prompt}] # 执行远程调用并获取回复 response = self.llama_completion(messages) return response def llama_completion(self, messages: List[dict]) -> str: # 实现与外部API交互的逻辑;此处仅为示意 return "Hello from llama!" ``` 上述代码片段提供了一个基础框架用于开发定制化的语言处理模块。值得注意的是,除了直接操作HTTP请求外,还可以考虑采用更高层次的服务客户端库简化通信流程,比如使用gRPC、RESTful API等现代Web协议和服务端点。 此外,如果希望进一步增强灵活性和支持异步操作,则可以在设计阶段就考虑到这一点,确保所选的技术栈能够良好支持这类特性。例如,Python提供了丰富的异步编程工具集,可以帮助提升系统的并发能力和响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丶只有影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值