写在前文:
下面是官方对工具的流程:

创建工具
创建工具时,必须指定参数:name、description、args_schema、return_direct
初始化环境
python
import asyncio
from typing import Annotated, List
from langchain.agents import initialize_agent, AgentType
from langchain_core.messages import HumanMessage
from utils import *
from langchain_core.tools import tool, BaseTool, ToolException, Tool
1、创建工具:使用注解@tool创建一个工具函数并执行
1.1、不带注解信息
python
@tool
# @tool(description="Multiply two numbers.")
def multiply_1(a: int, b: int) -> int:
# 如果不使用"# @tool(description="Multiply two numbers.")"那么必须要有下面这个""""Multiply two numbers."""",这个虽然像是注释,相当于方法描述 主要作用就是让LLMs知道这个方法的功能,即"description"的值,
# 不添加要报错:ValueError: Function must have a docstring if description not provided.
"""Multiply two numbers."""
return a * b
print(multiply_1.name)
print(multiply_1.description)
print(multiply_1.args)
print(multiply_1.invoke({"a": 1, "b": 2}))
print("---------------Demo1:使用注解@tool 创建一个工具函数,并执行---------------")
输出:
multiply_1
Multiply two numbers.
{'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
10
1.2、携带注解信息
python
@tool
def multiply_by_max_2(
a: Annotated[int, "参数一"],
b: Annotated[List[int], "参数二,为一个list列表,选最大的一个值进行计算"],
) -> int:
"""Multiply a by the maximum of b."""
return a * max(b)
print(multiply_by_max_2.args_schema.model_json_schema())
print(multiply_by_max_2.invoke({"a": 2, "b": [1, 3, 5, 4, 9]}))
print("---------------Demo2:使用注解信息---------------")
打印出:
{'description': 'Multiply a by the maximum of b.', 'properties': {'a': {'description': '参数一', 'title': 'A', 'type': 'integer'}, 'b': {'description': '参数二,为一个list列表,选最大的一个值进行计算', 'items': {'type': 'integer'}, 'title': 'B', 'type': 'array'}}, 'required': ['a', 'b'], 'title': 'multiply_by_max_2', 'type': 'object'}
18
1.3、自定义json实体为参数
python
# 此处如果使用v1版本,可能报错"AttributeError: type object 'CalculatorInput' has no attribute 'model_json_schema'" ----- 兼容问题
from pydantic import BaseModel, Field
class CalculatorInput_3(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
@tool("multiplication-tool", args_schema=CalculatorInput_3, return_direct=True)
def multiply_3(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
print(multiply_3.name)
print(multiply_3.description)
print(multiply_3.args)
print(multiply_3.return_direct)
print("---------------Demo3:自定义json实体为参数---------------")
打印出:
multiplication-tool
Multiply two numbers.
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
True
2、创建工具:结构化工具创建StructuredTool.from_function
python
from langchain_core.tools import StructuredTool
class CalculatorInput_4(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
def multiply_4(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator_4 = StructuredTool.from_function(
func=multiply_4,
name="Calculator",
description="multiply numbers",
args_schema=CalculatorInput_4,
return_direct=True,
)
print(calculator_4.invoke({"a": 2, "b": 3}))
print(calculator_4.name)
print(calculator_4.description)
print(calculator_4.args)
print("---------------Demo4:结构化工具StructuredTool---------------")
输出:
6
Calculator
multiply numbers
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
3、创建工具:使用chain.as_tool创建工具
python
from langchain_core.language_models import GenericFakeChatModel
from langchain_core.output_parsers import StrOutputParser, PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from utils import *
# 使用chain.as_tool返回工具
prompt_5 = ChatPromptTemplate.from_messages(
[("human", "你好,我叫 {name}")]
)
llm_5 = get_llm('ollama')
chain_5 = prompt_5 | llm_5 | StrOutputParser()
as_tool_5 = chain_5.as_tool(name="Style responder", description="输入用户名,返回中文李清照的个性化问候语")
print(as_tool_5.invoke({"name":"张三"}))
print(as_tool_5.args)
print("---------------Demo5:使用langchain的chain---------------")
输出:
你好!我是张三,有什么我可以帮你的吗?😊
{'name': {'title': 'Name', 'type': 'string'}}
4、创建工具:自定义工具(同步和异步)
python
from typing import Optional, Type
from langchain_core.callbacks import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
class CalculatorInput_6(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
# 基本上是固定的
class CustomCalculatorTool_6(BaseTool):
name: str = "Calculator"
description: str = "useful for when you need to answer questions about math"
args_schema: Type[BaseModel] = CalculatorInput_6
return_direct: bool = True
def _run(self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
"""Use the tool."""
print("使用_run工具...")
return a * b
async def _arun(self, a: int, b: int, run_manager: Optional[AsyncCallbackManagerForToolRun] = None, ) -> str:
"""Use the tool asynchronously."""
print("使用_arun工具...")
# 异步函数await只能在async内部使用
return self._run(a, b, run_manager=run_manager.get_sync())
multiply_6 = CustomCalculatorTool_6()
print(multiply_6.name)
print(multiply_6.description)
print(multiply_6.args)
print(multiply_6.return_direct)
print(multiply_6.invoke({"a": 2, "b": 3}))
# 使用异步方法
async def main_6():
print(await multiply_6.ainvoke({"a": 2, "b": 4}))
asyncio.run(main_6())
输出:
Calculator
useful for when you need to answer questions about math
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
True
使用_run工具...
6
使用_arun工具...
使用_run工具...
8
5、单独使用异步方法
python
from langchain_core.tools import StructuredTool
def multiply_7(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator_7 = StructuredTool.from_function(func=multiply_7)
print(calculator_7.invoke({"a": 2, "b": 3}))
# 异步调用(需封装在异步函数中)
async def main_7():
result = await calculator_7.ainvoke({"a": 2, "b": 5})
print(result) # 输出: 10
# 运行异步函数
asyncio.run(main_7())
输出:
6
10
6、工具异常以及定义返回类型
工具异常
python
import random
from typing import List, Tuple
from langchain_core.tools import tool
def get_weather_8(city: str) -> int:
"""获取一个城市的天气情况"""
raise ToolException(f"Error: 这个错误是通过handle_tool_error=True定义 {city}.")
get_weather_tool_8 = StructuredTool.from_function(
func=get_weather_8,
handle_tool_error=True,
)
# 直接返回handle_tool_error的值。
get_weather_tool_8_1 = StructuredTool.from_function(
func=get_weather_8,
handle_tool_error="这是通过handle_tool_error字符串的形式来定义...",
)
# 自定义函数返回
def _handle_error_8(error: ToolException) -> str:
return f"T这是i通过函数的形式自定义工具异常信息: `{error.args[0]}`"
get_weather_tool_8_2 = StructuredTool.from_function(
func=get_weather_8,
handle_tool_error=_handle_error_8,
)
# 这三个都会调用get_weather_8方法,只不过使用handle_tool_error=字符串/函数时,它的返回值会覆盖get_weather_8中抛出来的错误信息
# 输出:Error: 这个错误是通过handle_tool_error=True定义 猫猫城.
print(get_weather_tool_8.invoke({"city": "猫猫城"}))
# 输出:这是通过handle_tool_error字符串的形式来定义...
print(get_weather_tool_8_1.invoke({"city": "喵喵城"}))
# 输出:T这是i通过函数的形式自定义工具异常信息: `Error: 这个错误是通过handle_tool_error=True定义 U.u城.`
print(get_weather_tool_8_2.invoke({"city": "U.u城"}))
指定工具返回类型
返回为"Tuple"
python
import random
from typing import List, Tuple
from langchain_core.tools import tool
"""
使用@Tool创建工具,以及定义返回类型为"Tuple"
"""
# 如果我们返回的参数是"Tuple"需要指定 response_format;默认是"content"
@tool(response_format="content_and_artifact")
def generate_random_ints_8(min: int, max: int, size: int) -> Tuple[str, List[int]]:
""" 工具函数必须要指定docstring否则会报错... """
array = [random.randint(min, max) for _ in range(size)]
content = f"Successfully generated array of {size} random ints in [{min}, {max}]."
return content, array
# 输出:原始调用:Successfully generated array of 10 random ints in [0, 9].
print(f"原始调用:{generate_random_ints_8.invoke({"min": 0, "max": 9, "size": 10})}")
tool_call_8 = generate_random_ints_8.invoke(
{
"name": "generate_random_ints",
"args": {"min": 0, "max": 9, "size": 10},
"id": "123", # required
"type": "tool_call", # required
}
)
# 输出:这是使用工具类型调用: content='Successfully generated array of 10 random ints in [0, 9].' name='generate_random_ints_8' tool_call_id='123' artifact=[8, 4, 1, 8, 4, 2, 1, 2, 2, 1]
print(f"这是使用工具类型调用: {tool_call_8}")
"""
我们也可以使用BaseTool自定义工具
"""
class GenerateRandomFloats_8(BaseTool):
name: str = "generate_random_floats"
description: str = "Generate size random floats in the range [min, max]."
response_format: str = "content_and_artifact"
ndigits: int = 2
def _run(self, min: float, max: float, size: int) -> Tuple[str, List[float]]:
range_ = max - min
array = [
round(min + (range_ * random.random()), ndigits=self.ndigits)
for _ in range(size)
]
content = f"Generated {size} floats in [{min}, {max}], rounded to {self.ndigits} decimals."
return content, array
rand_gen_8 = GenerateRandomFloats_8(ndigits=4)
zidingyi_8 = rand_gen_8.invoke(
{
"name": "generate_random_floats",
"args": {"min": 0.1, "max": 3.3333, "size": 3},
"id": "123",
"type": "tool_call",
}
)
# 输出:自定义工具调用: content='Generated 3 floats in [0.1, 3.3333], rounded to 4 decimals.' name='generate_random_floats' tool_call_id='123' artifact=[0.9652, 1.1374, 1.6313]
print(f"自定义工具调用: {zidingyi_8}")
7、使用内置工具/工具包
工具详情参考
中文参考:https://www.langchain.com.cn/docs/integrations/tools/
英文参考:https://python.langchain.com/docs/integrations/tools/
python
# 中文参考:https://www.langchain.com.cn/docs/integrations/tools/
# 英文参考:https://python.langchain.com/docs/integrations/tools/
from utils import *
os.environ['serpapi_api_key'] = 'xxxxxx'
os.environ["TAVILY_API_KEY"] = 'tvly-xxxxxxx'
# 提供了很多常用的网站,比如天气网站,可以查询到很多天气
tavily = TavilySearchResults(max_results=2)
# 提供了搜索功能
serpApi = SerpAPIWrapper()
print(tavily.invoke("2025年3月28日成都天气怎么样?"))
print(serpApi.run("什么是langchain"))
8、综合使用:LLMs+Tool+格式化参数模板
python
# tool的name要和后面格式化参数的class名称一致,不然需要定义Pydantic来映射
# ### 此处名称略微复杂不太规范,是为了展示那个名称和那个名称是一一对应的。
@tool("Xiang_U_Tool_Name_10")
def xiang_U_fun_10(a: int, b: int) -> int:
""" a和b参数需要"U"时才会使用
Args:
a: First integer
b: Second integer
"""
print(f'调用了U函数:[{a},{b}]')
return a + b
@tool("Xiang_T_Tool_Name_10")
def xiang_T_fun_10(a: int, b: int) -> int:
""" a和b参数需要"T"时才会使用
Args:
a: First integer
b: Second integer
"""
print(f'调用了T函数:[{a},{b}]')
return a * b
# 将工具添加到List集合中
tools_utils_10 = [xiang_T_fun_10, xiang_U_fun_10]
# 这儿需要取开通GLM模型的key...
# ### 本人测试DK系列的好像都不支持tool调用,Qwen系有一部分不支持...
# #### llama 不支持调用 Tools ,所以此处报错...
# 支持工具调用:qwen2.5:latest、llama3.1:latest、llama3-groq-tool-use
llm_10 = ChatOpenAI(base_url='https://open.bigmodel.cn/api/paas/v4/',
api_key='xxxxx.xxxxx',
model='glm-4-plus',
temperature=temperature,
verbose=verbose)
# llm_10 = ChatOllama(model='deepseek-r1:1.5b', temperature=0, verbose=True)
# 将工具集合绑定到LLM中
llm_with_tools_10 = llm_10.bind_tools(tools_utils_10)
query = "What is 8 U 7?what is 11 T 49?"
# 使用llm。正常情况下此时,"U"和"T"LLMs是不知道什么意思的,
# 但是我在@Tool工具函数中有备注"xiang_U_fun_10(a和b参数需要"U"时才会使用)、xiang_T_fun_10(a和b参数需要"T"时才会使用)",
# 所以LLMs模型会自动调用这两个方法 ----- 注意,DK系列和Qwen的好像有些模型不太支持Tools工具调用系统会报错"ollama._types.ResponseError: registry.ollama.ai/library/deepseek-r1:1.5b does not support tools (status code: 400)"
# --- 所以我使用GLM系列的模型是OK的。
ai_msg = llm_with_tools_10.invoke(query)
# 只会显示要调用的工具,但是不会真正调用:content='' additional_kwargs={'tool_calls': [{'id': 'call_-8844174561398977396', 'function': {'arguments': '{"a": 8, "b": 7}', 'name': 'Xiang_U_Tool_Name_10'}, 'type': 'function', 'index': 0}, {'id': 'call_-8844174561398977395', 'function': {'arguments': '{"a": 11, "b": 49}', 'name': 'Xiang_T_Tool_Name_10'}, 'type': 'function', 'index': 1}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 42, 'prompt_tokens': 342, 'total_tokens': 384, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'glm-4-plus', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None} id='run-0134a6a4-8487-4f6f-8ab8-49bcda5f2cb4-0' tool_calls=[{'name': 'Xiang_U_Tool_Name_10', 'args': {'a': 8, 'b': 7}, 'id': 'call_-8844174561398977396', 'type': 'tool_call'}, {'name': 'Xiang_T_Tool_Name_10', 'args': {'a': 11, 'b': 49}, 'id': 'call_-8844174561398977395', 'type': 'tool_call'}] usage_metadata={'input_tokens': 342, 'output_tokens': 42, 'total_tokens': 384, 'input_token_details': {}, 'output_token_details': {}}
print(f"只会显示要调用的工具,但是不会真正调用:{ai_msg}") # 只会显示要调用的工具,但是不会真正调用
## 获得调用工具信息
# 输出:获得调用工具信息:[{'name': 'Xiang_U_Tool_Name_10', 'args': {'a': 8, 'b': 7}, 'id': 'call_-8844184731883541155', 'type': 'tool_call'}, {'name': 'Xiang_T_Tool_Name_10', 'args': {'a': 11, 'b': 49}, 'id': 'call_-8844184731883541154', 'type': 'tool_call'}]
print(f"获得调用工具信息:{llm_with_tools_10.invoke(query).tool_calls}")
# class 名称要与def定义的 @tool 方法名称name对应,不然要报"KeyError"
class Xiang_U_Tool_Name_10(BaseModel):
"""Add two integers."""
a: int = Field(..., description="First integer")
b: int = Field(..., description="Second integer")
class Xiang_T_Tool_Name_10(BaseModel):
"""Multiply two integers."""
a: int = Field(..., description="First integer")
b: int = Field(..., description="Second integer")
"""
# 如果class名称和def定义的工具名称不对应,可以通过如下方法解决
# 定义工具与 Pydantic 模型的映射
tool_parsers = {
"xiang_u_10": PydanticOutputParser(pydantic_object=add_U_10),
"xiang_t_10": PydanticOutputParser(pydantic_object=multiply_T_10),
}
# 创建一个 OutputParser,将工具名称映射到对应的解析器
tools_parser = PydanticToolsParser(tool_parsers=tool_parsers)
# 将解析器绑定到 LLM
chain_10 = llm_with_tools_10 | tools_parser
"""
# ### 通过Pydantic 定义参数模板
from langchain_core.output_parsers import PydanticToolsParser
chain_10 = (llm_with_tools_10
| PydanticToolsParser(tools=[Xiang_U_Tool_Name_10, Xiang_T_Tool_Name_10])) # 这里指定的是class Xiang_U_Tool_Name_10
# 输出:展示Pydantic重置映射关系:[Xiang_U_Tool_Name_10(a=8, b=7), Xiang_T_Tool_Name_10(a=11, b=49)]
print(f"展示Pydantic重置映射关系:{chain_10.invoke(query)}")
# 下面才是真正调用LLM的invoke方法获取LLMs最终生成的内容 --- 有点复杂,下章会结合Agent会简化很多,所以此种方法了解即可
# ### 获得最终的消息,原理就是:将用户输入的消息+AI的回复的消息+工具返回的消息进行封装后,再次传递给LLM获得最终的结果
# ### 即手动封装消息...类似于前面的历史记录一样...
# 1、先获得工具返回的消息
messages = [HumanMessage(content=query)]
messages.append(ai_msg)
# 选择要执行的工具
for tool_call in ai_msg.tool_calls:
# tool_call = [{'name': 'Xiang_T_Tool_Name_10', 'args': {'a': 8, 'b': 7}, 'id': 'call_-8969335028076878645', 'type': 'tool_call'}]
selected_tool = {"xiang_u_tool_name_10": xiang_U_fun_10, "xiang_t_tool_name_10": xiang_T_fun_10}[
tool_call["name"].lower()]
tool_msg = selected_tool.invoke(tool_call) # 此处要想正常使用invoke,那么在定义工具的时候必须要有@tool注解(或者其他工具化的方法才行)
messages.append(tool_msg)
# 输出:工具获得的消息: content='15' name='Xiang_U_Tool_Name_10' tool_call_id='call_-8844174561398977396'
# 输出:工具获得的消息: content='539' name='Xiang_T_Tool_Name_10' tool_call_id='call_-8844174561398977395'
print(f"工具获得的消息: {tool_msg}")
# 输出:[What is 8 U 7?what is 11 T 49?]最终消息:8 U 7 的结果是 15,而 11 T 49 的结果是 539。这里的 "U" 和 "T" 可能代表某种特定的运算,但具体是什么运算没有给出。根据结果,我们可以推断 "U" 可能是加法,而 "T" 可能是乘法。
print(f"[{query}]最终消息:{llm_with_tools_10.invoke(messages).content}")