本文介绍了创建工具的三种常用方法,并说明如何在大模型和智能体中有效使用这些工具。
一个标准的工具通常包含以下三个核心要素:
name:工具的唯一标识名称,用于在调用时被大模型识别和引用。docstring:工具的功能描述,应包含简洁的首行摘要、参数说明及返回值信息,帮助大模型理解何时以及如何调用该工具。args_schema:定义工具输入参数的数据结构(通常基于 Pydantic 的BaseModel),明确指定每个参数的名称、类型和语义,确保 LLM 能生成合法的调用请求。
这三者共同构成了工具与大模型之间的"接口契约",是实现可靠工具调用的关键基础。
一、创建工具的三种方法
1、使用装饰器来创建工具(简单工具)
在智能体(Agent)开发中,工具(Tool)是其执行特定任务的核心组件。为确保代码可读性、可维护性和与其他模块(如 LLM 调用接口)的兼容性,工具函数的 docstring 书写应遵循一定的规范。以下是推荐的 docstring 书写规范(推荐使用Google风格)。
python
def search_web(query: str) -> str:
"""根据用户提供的关键词在互联网上搜索最新信息。
此工具用于获取实时或近期的网络信息,适用于回答时效性强的问题,
如新闻、股价、天气等。不适用于需要深度推理或内部知识的问题。
Args:
query (str): 用户希望搜索的关键词或问题,应尽量简洁明确。
Returns:
str: 搜索结果摘要,包含最相关的1-3条信息,每条不超过100字。
若无结果,返回"未找到相关信息。"
"""
docstring关键要素详解
| 要素 | 说明 |
|---|---|
| 首行摘要 | 用一句话 概括工具的核心功能,以动词开头(例如:"查询用户订单状态"、"计算两个日期之间的天数"、"生成营销文案")。该摘要会被 LLM 用于快速理解工具用途。 |
| 详细描述(可选) | 补充说明工具的典型使用场景、前置条件、数据范围限制、调用频率限制、依赖服务状态等。有助于 LLM 判断是否应调用此工具及如何正确使用。 |
| Args | 必须列出所有输入参数 ,每个参数需注明: • 参数名 • 类型(如 str, int, List[str]) • 语义说明(如"用户手机号,需为11位中国大陆号码") LLM 会严格依据此部分构造函数调用参数。 |
| Returns | 明确返回值的类型和结构(如 dict 包含哪些字段,str 的格式等)。帮助 LLM 理解结果含义,以便在后续推理或回答中正确引用。 |
| Raises(可选) | 若工具可能抛出特定异常(如 ValueError、APIError),可简要说明触发条件。但在 Agent 框架中,异常通常由运行时统一捕获处理,因此一般可省略。 |
python
from langchain_community.chat_models import ChatZhipuAI
from langchain.agents import create_agent
from langchain.tools import tool
from dotenv import load_dotenv
load_dotenv(override=True)
# 来个虚拟的查询天气工具
@tool
def search_weather(city: str) -> str:
"""查询指定城市的天气情况。
Args:
city: 待查询的城市名称
Returns:
查询到的天气信息。
"""
return f"您需要查询的城市为{city},该城市的天气为晴天!"
model = ChatZhipuAI(
model="glm-4.5-flash"
)
agent = create_agent(
model=model,
tools=[search_weather],
system_prompt="你是一个善于助人的小助手。"
)
(1)非流式输出
python
inputs = {"messages":[{"role": "user", "content": "帮我查询上海今天的天气。"}]}
response = agent.invoke(inputs)
# 返回的响应是一个字典,messages列表中可以取出最后一个,就是我们想要的答案
response['messages'][-1].pretty_print()
(2)流式输出
流式输出的两种常用的模式
- values(默认):在每一步执行后,返回整个状态(state)的完整快照。
python
inputs = {"messages": "北京的天气如何?"}
for step in agent.stream(inputs):
print(step)
inputs = {"messages": "北京的天气如何?"}
for chunk in agent.stream(inputs, stream_mode='values'):
for step, data in chunk.items():
print(f"step: {step}")
print(f"content: {data[-1].content}")
- messages:逐 token 流式输出 LLM 的响应,并附带元数据。"打字机效果"
python
inputs = {"messages": "长沙今天天气。"}
for chunk in agent.stream(inputs, stream_mode='messages'):
print(chunk[0].content, end="")
2、继承BaseTool来创建工具(复杂工具)
(1)如何创建
python
from langchain.tools import BaseTool
# 继承BaseTool来创建工具
class MyWeatherSearchTool(BaseTool):
name: str = "weather_search" # 定义工具的名字
description: str = "查询指定城市的天气情况。"
# args_schema: Type[BaseModel] = <自定义> # 工具参数的定义
def _run(self, city: str) -> str: # _run是同步的
# 在这里面编写工具的业务逻辑
return f"您需要查询的城市为{city},该城市的天气为晴天!"
async def _arun(self, city: str) -> str:
return self._run(city)
web_search = MyWeatherSearchTool()
result = web_search.invoke({"city": "上海"})
print(type(result))
print(result)
(2)使用Pydantic来定义工具参数
Pydantic 是一个功能强大的 Python 库,用于数据验证和序列化。它通过定义类(继承自 pydantic.BaseModel)来声明数据结构,支持运行时的类型验证、数据转换和序列化(如 JSON)。
python
# 导入 LangChain 工具基类,用于自定义工具
from langchain.tools import BaseTool
# 导入 Pydantic 的 BaseModel 和 Field,用于定义结构化输入参数
from pydantic import BaseModel, Field
# 导入 Type 用于类型注解(args_schema 需要)
from typing import Type
# 定义工具的输入参数模型
class WeatherSearchInput(BaseModel):
# 每个字段使用 Field 描述其含义,LLM 会据此理解如何构造参数
city: str = Field(..., description="要查询天气的城市名称,例如 '北京'、'上海'")
# 自定义天气查询工具:继承 BaseTool
class MyWeatherSearchTool(BaseTool):
# 工具的唯一标识名
name: str = "weather_search"
# 工具的描述,告诉 LLM 这个工具能做什么
# 建议以动词开头,清晰说明功能和输入要求
description: str = "查询指定城市的当前天气情况。输入应为城市名称(如 '广州')。"
# 指定该工具接收的参数结构,必须是 BaseModel 的子类
args_schema: Type[BaseModel] = WeatherSearchInput
def _run(self, city: str) -> str:
"""
同步执行逻辑(由 invoke() 调用)。
参数 `city` 会自动从输入中解析并传入(基于 args_schema)。
"""
# 这里可以替换为真实 API 调用(如和心知天气、OpenWeather 等对接)
# 当前为模拟返回
return f"您需要查询的城市为 {city},该城市的天气为晴天!"
async def _arun(self, city: str) -> str:
"""
异步执行逻辑(由 ainvoke() 调用)。
在本例中直接复用同步逻辑;实际项目中可实现真正的异步请求。
"""
return self._run(city)
# 实例化工具对象
web_search = MyWeatherSearchTool()
# 调用工具(传递参数字典,键名需与 WeatherSearchInput 中的字段一致)
result = web_search.invoke({"city": "上海"})
# 打印结果类型(通常是 str,但某些情况下可能是 ToolResponse 等)
print(type(result)) # <class 'str'>
# 打印工具返回的具体内容
print(result) # 您需要查询的城市为 上海,该城市的天气为晴天!
3、从MCP中获取工具
需要安装必要的库(langchain提供的MCP客户端的库)
bash
pip install langchain-mcp-adapters
(1)创建MCP服务器
关于MCP服务怎么搭建,工具怎么编写,在这篇文章中只简单给出示例
python
from fastmcp import FastMCP
mcp = FastMCP()
@mcp.tool
def add(a: int, b:int) -> int:
"""当输入两个数字的时候,用这个工具将两个数据相加。
Args:
a: 第一个数
b: 第二个数
Returns:
两数之和
"""
return a+b
if __name__ == "__main__":
# 创建MCP Server
mcp.run(transport="streamable-http", port=3210)
(2)创建MCP客户端
用MultiServerMCPClient 创建的客户端默认是无状态的。每次调用工具都会创建一个新的 MCP ClientSession ,执行工具,然后进行清理。
python
# 导入 LangChain MCP(Model Context Protocol)适配器中的多服务器客户端
# 用于连接符合 MCP 协议的远程工具服务器(如自定义的工具微服务)
from langchain_mcp_adapters.client import MultiServerMCPClient
# 创建一个 MultiServerMCPClient 实例,用于管理多个 MCP 工具服务器
# 参数是一个字典,key 是工具组/服务的逻辑名称(如 "add"),value 是连接配置
client = MultiServerMCPClient(
{
"add": {
# 指定传输协议类型:这里使用基于 HTTP 流式响应的协议(streamable-http)
"transport": "streamable-http",
# MCP 服务的完整 URL(必须包含 /mcp 路径,这是 MCP 协议的标准 endpoint)
# 注意:确保本地 3210 端口的服务已启动且支持 MCP
"url": "http://127.0.0.1:3210/mcp",
}
}
)
# 异步获取该 MCP 服务器暴露的所有可用工具(返回一个 Tool 列表)
# 【注意】:此行必须在 async 函数中执行,或在支持 await 的环境(如 Jupyter Notebook)中运行
tools = await client.get_tools()
# 查看 tools 变量内容(调试用)
print(tools)
# 查看 tools 的类型
print(type(tools))
# 查看第一个工具的详细信息
print(tools[0])
# 获取第一个工具的名称
tools[0].name
# 获取第一个工具的功能描述
tools[0].description
# 获取工具的参数结构(Pydantic BaseModel 子类),定义了输入字段及类型
tools[0].args_schema
# 异步调用第一个工具,传入符合其参数 schema 的字典
# 工具接受两个参数 a 和 b(如加法工具)
result = await tools[0].ainvoke({"a": 10, "b": 20})
# 打印调用结果
print("调用结果:", result)
二、在Model中使用工具
使用模型的bind_tools方法来注册工具
python
# 导入 LangChain 提供的便捷函数,用于快速初始化聊天模型(如 DeepSeek、OpenAI 等)
from langchain.chat_models import init_chat_model
# 导入 dotenv 模块,用于从 .env 文件加载环境变量(如 API 密钥)
from dotenv import load_dotenv
# 加载项目根目录下的 .env 文件中的环境变量
# override=True 表示:即使系统中已存在同名环境变量,也强制使用 .env 中的值
# 这确保了使用正确的 DEEPSEEK_API_KEY
load_dotenv(override=True)
# 使用 init_chat_model 初始化 DeepSeek 的对话模型
# 注意:该函数会自动读取环境变量 DEEPSEEK_API_KEY,无需显式传入
# 模型名称 "deepseek-chat" 是 DeepSeek 官方支持的标准聊天模型标识
model = init_chat_model(model="deepseek-chat")
# 将自定义工具 `search_weather` 绑定到模型上,启用"工具调用"(Tool Calling)能力
# - `search_weather` 必须是一个符合 LangChain 工具规范的对象(如 BaseTool 子类实例)
# - 绑定后,模型在回答问题时,若需要外部信息(如天气),会主动请求调用此工具
model = model.bind_tools([search_weather])
# 向模型发送用户问题:"长沙今天天气如何?"
# 由于模型本身无法获取实时天气,但已绑定天气查询工具,
# 因此它不会直接编造答案,而是返回一个"工具调用请求"
response = model.invoke("长沙今天天气如何?")
# 查看模型返回的工具调用指令
print(response.tool_calls)
三、在Agent中使用工具
python
from langchain.agents import create_agent
from langchain_community.chat_models import ChatZhipuAI
# 导入 LangGraph 提供的内存检查点存储器(InMemorySaver)
# 用于在对话中保存状态(如消息历史),实现多轮对话记忆(基于 thread_id 区分会话)
from langgraph.checkpoint.memory import InMemorySaver
from dotenv import load_dotenv
load_dotenv(override=True)
# 初始化智谱 AI 的聊天模型实例
# API Key 会自动从环境变量 ZHIPUAI_API_KEY 中读取
model = ChatZhipuAI(
model='glm-4-flash'
)
# 创建一个基于内存的检查点存储器(checkpoint saver)
# 作用:在 Agent 执行过程中保存中间状态(如对话历史、工具调用记录)
# 注意:InMemorySaver 是临时存储,程序重启后数据丢失;生产环境可替换为 Redis、SQLite 等
checkpointer = InMemorySaver()
# 配置会话线程 ID(用于区分不同用户的对话上下文)
# - "thread_id": "222" 表示当前对话属于 ID 为 "222" 的会话线程
# - 同一线程内的多次调用会共享记忆(通过 checkpointer 自动加载历史)
config = {
"configurable": {"thread_id": "222"}
}
# 使用指定模型、工具列表和检查点存储器创建智能体(Agent)
# - model: 决策核心(LLM)
# - tools: Agent 可调用的工具列表(如计算器、搜索等)
# - checkpointer: 启用状态持久化,支持多轮对话
agent = create_agent(
model=model,
tools=tools, # 注意:此处 `tools` 需已在前面定义(如 [CalculatorTool(), ...])
checkpointer=checkpointer
)
# 构造用户输入消息
# 格式必须为 {"messages": [...]},且每条消息包含 role 和 content
# 这是 LangGraph Agent 的标准输入格式
inputs = {"messages": [{"role": "user", "content": "我想计算三千五百加七万等于多少?"}]}
# 异步调用智能体(ainvoke 用于异步执行)
# - input: 用户输入
# - config: 传入会话配置(含 thread_id),用于加载/保存该会话的状态
# 返回值 resp 是一个包含完整对话状态的字典(含最终 AI 回复)
resp = await agent.ainvoke(
input=inputs,
config=config
)
# 打印智能体的完整响应(包含 messages 列表)
# 最后一条消息通常是 AI 的最终回答
print(resp)