Tools
Tools
LangChain 的工具(Tools)体系是构建 Agent 的核心,分几个层次来讲。
一、工具的本质
工具就是让 LLM 能够调用外部能力的接口。LLM 本身只能处理文本,工具让它能搜索网络、查数据库、执行代码、调用 API 等。
整个调用流程是这样的:
用户提问
│
▼
LLM 判断:需要用工具吗?用哪个?参数是什么?
│
▼
返回"工具调用请求"(不是最终答案)
│
▼
LangChain 执行工具,拿到结果
│
▼
把工具结果交还给 LLM
│
▼
LLM 综合生成最终答案LLM 自身不执行工具,它只是"决定调哪个、传什么参数"。真正执行的是 LangChain 框架。
二、定义工具的三种方式
2.1 @tool 装饰器(最常用)
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""获取指定城市的当前天气。city 参数为城市名称,如"北京"、"上海"。"""
# 实际项目中调用天气 API
return f"{city}今天晴,气温 22°C"
@tool
def calculator(expression: str) -> str:
"""计算数学表达式,如 '2 + 3 * 4'。"""
try:
result = eval(expression)
return str(result)
except Exception as e:
return f"计算错误:{e}"⚠️ 关键:函数的 docstring 非常重要,LLM 依赖它来判断"这个工具是干什么的、什么时候该用"。写得越清晰,工具被正确调用的概率越高。
工具对象本身也是 Runnable,可以直接调用:
get_weather.invoke({"city": "北京"}) # → "北京今天晴,气温 22°C"
print(get_weather.name) # → "get_weather"
print(get_weather.description) # → docstring 内容
print(get_weather.args_schema.schema()) # → 参数的 JSON Schema2.2 StructuredTool + Pydantic(复杂参数时使用)
当工具参数较多、需要精确描述每个字段时,用 Pydantic 定义参数模型:
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
class SearchInput(BaseModel):
query: str = Field(description="搜索关键词")
max_results: int = Field(default=5, description="返回结果数量,默认 5")
language: str = Field(default="zh", description="结果语言,zh 或 en")
def search_web(query: str, max_results: int = 5, language: str = "zh") -> str:
"""在网络上搜索信息"""
# 实际调用搜索 API
return f"搜索 '{query}' 的前 {max_results} 条{language}结果:..."
search_tool = StructuredTool.from_function(
func=search_web,
name="search_web",
description="搜索互联网获取最新信息,当需要实时数据或不确定的知识时使用",
args_schema=SearchInput,
)2.3 继承 BaseTool(需要最大灵活性时)
from langchain_core.tools import BaseTool
from typing import Optional, Type
class DatabaseQueryTool(BaseTool):
name: str = "database_query"
description: str = "查询内部数据库,获取用户数据、订单信息等"
# 可以在类上存放状态(如数据库连接)
db_url: str = "sqlite:///app.db"
def _run(self, query: str) -> str:
"""同步执行"""
# 执行 SQL 查询
return f"查询结果:{query}"
async def _arun(self, query: str) -> str:
"""异步执行(支持 async 场景)"""
return self._run(query)三、给 LLM 绑定工具
定义好工具后,用 .bind_tools() 告诉 LLM 它有哪些工具可用:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
tools = [get_weather, calculator, search_tool]
# 绑定工具(不会立即执行,只是让 LLM 知道工具存在)
llm_with_tools = llm.bind_tools(tools)
# 调用后,如果 LLM 判断需要工具,返回的是 AIMessage 含 tool_calls
response = llm_with_tools.invoke("北京今天天气怎么样?")
print(response.tool_calls)
# → [{"name": "get_weather", "args": {"city": "北京"}, "id": "call_xxx"}]返回的不是最终答案,而是 LLM 的"工具调用意图"。
四、手动处理工具调用(理解原理)
在用 Agent 自动处理之前,先手动走一遍完整流程,理解每一步发生了什么:
from langchain_core.messages import HumanMessage, ToolMessage
# 第1步:用户提问,LLM 决定调哪个工具
messages = [HumanMessage(content="北京今天天气怎么样,顺便算一下 123 * 456")]
response = llm_with_tools.invoke(messages)
messages.append(response) # 把 LLM 的"意图"加入历史
# 第2步:执行 LLM 指定的工具
tool_map = {t.name: t for t in tools}
for tool_call in response.tool_calls:
tool = tool_map[tool_call["name"]]
result = tool.invoke(tool_call["args"])
# 把工具结果包装成 ToolMessage 追加到消息历史
messages.append(ToolMessage(
content=result,
tool_call_id=tool_call["id"], # 必须对应 LLM 的 tool_call id
))
# 第3步:把工具结果交还 LLM,生成最终答案
final_response = llm_with_tools.invoke(messages)
print(final_response.content)
# → "北京今天晴,气温 22°C。123 × 456 = 56088"五、Agent:自动循环执行工具
手动处理适合了解原理,实际开发用 Agent 自动完成"判断 → 执行 → 再判断"的循环。
5.1 create_react_agent(最常用)
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
# 从 hub 拉取 ReAct 标准 prompt(包含 Thought/Action/Observation 格式)
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(
llm=llm,
tools=tools,
prompt=prompt,
)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 打印每一步的思考过程
max_iterations=5, # 最多循环 5 次,防止死循环
handle_parsing_errors=True,
)
result = agent_executor.invoke({
"input": "先查北京天气,再根据温度推荐今天穿什么衣服"
})
print(result["output"])verbose=True 时会打印类似这样的过程:
Thought: 我需要先查北京的天气
Action: get_weather
Action Input: {"city": "北京"}
Observation: 北京今天晴,气温 22°C
Thought: 22°C 是春秋温度,我可以给出穿衣建议了
Final Answer: 北京今天 22°C,建议穿薄外套或长袖...5.2 create_tool_calling_agent(推荐,更现代)
create_react_agent 依赖文本格式解析,容易出错。更推荐使用基于原生 function calling 的版本:
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个helpful助手,可以使用工具来回答问题。"),
MessagesPlaceholder(variable_name="chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"), # Agent 的思考过程
])
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 支持多轮对话
agent_executor.invoke({
"input": "北京今天天气怎么样?",
"chat_history": []
})💡 两者的区别:
create_react_agent用文本格式(Thought/Action)驱动,通用性强但稳定性差;create_tool_calling_agent用模型原生 function calling,更稳定,推荐 GPT-4、Claude 等支持 function calling 的模型使用。
六、工具使用的控制
6.1 强制使用工具
from langchain_openai import ChatOpenAI
# 强制调用某个具体工具
llm_forced = llm.bind_tools(tools, tool_choice="get_weather")
# 强制必须调用某个工具(至少调一个)
llm_forced = llm.bind_tools(tools, tool_choice="required")
# 默认:由 LLM 自己判断(auto)
llm_auto = llm.bind_tools(tools, tool_choice="auto")6.2 工具错误处理
@tool
def safe_calculator(expression: str) -> str:
"""安全地计算数学表达式"""
# 白名单限制,防止代码注入
allowed = set("0123456789+-*/()., ")
if not all(c in allowed for c in expression):
return "错误:表达式包含非法字符"
try:
result = eval(expression)
return str(result)
except ZeroDivisionError:
return "错误:除数不能为零"
except Exception as e:
return f"计算失败:{str(e)}"AgentExecutor 也有内置的错误回退:
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
handle_parsing_errors="工具调用出现错误,请换一种方式提问", # 出错时的回复
max_iterations=5,
max_execution_time=30, # 最长执行 30 秒
)6.3 工具结果不返回给 LLM(直接返回给用户)
@tool(return_direct=True)
def get_order_status(order_id: str) -> str:
"""查询订单状态,order_id 为订单编号"""
return f"订单 {order_id} 状态:已发货,预计明天到达"
# return_direct=True:工具结果直接作为最终答案,不再交给 LLM 加工七、内置工具
LangChain 提供了大量开箱即用的工具,不需要自己写:
# 搜索
from langchain_community.tools import TavilySearchResults
search = TavilySearchResults(max_results=3)
# 执行 Python 代码
from langchain_experimental.tools import PythonREPLTool
python_repl = PythonREPLTool()
# 读取文件
from langchain_community.tools import ReadFileTool, WriteFileTool
read_file = ReadFileTool()
# Wikipedia
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
# 直接组合进 agent
tools = [search, python_repl, wiki]
agent_executor = AgentExecutor(agent=create_tool_calling_agent(llm, tools, prompt), tools=tools)八、工具 + Memory 结合
工具 Agent 和 Memory 结合,构成有记忆的对话型 Agent:
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_with_memory = RunnableWithMessageHistory(
agent_executor,
get_session_history, # 同 Memory 章节的 session 管理函数
input_messages_key="input",
history_messages_key="chat_history",
)
# 有记忆的 Agent
agent_with_memory.invoke(
{"input": "我叫小明,帮我查北京天气"},
config={"configurable": {"session_id": "user_001"}}
)
agent_with_memory.invoke(
{"input": "我刚才叫你查哪个城市的天气?"},
config={"configurable": {"session_id": "user_001"}}
)
# → 你让我查了北京的天气,当时是晴天 22°C九、核心概念总结
工具定义
@tool → 最简单,docstring 即描述
StructuredTool → Pydantic 精确定义参数
BaseTool 子类 → 需要状态、最大灵活性
绑定工具
llm.bind_tools() → LLM 知道有哪些工具可用
调用方式
手动处理 → 理解原理,自己处理 tool_calls
AgentExecutor → 自动循环,直到得出最终答案
Agent 类型
create_react_agent → 文本格式驱动,通用
create_tool_calling_agent → function calling 驱动,稳定(推荐)