工具系统
在前面的章节中,我们学会了用 Chain 把 Prompt、Model、OutputParser 串成一条流水线(参见 01_LangChain概述与核心架构)。但现实世界的任务往往不是线性的——用户问”北京今天天气怎么样?“,模型需要先调用天气 API,拿到结果后再组织回答。这种”边思考、边行动”的能力,就是 Agent 的核心价值。
本篇从 Agent 的动机出发,依次讲解 Tool 定义、ReAct 推理循环、Tool Calling Agent 和实战案例,最终帮你构建出一个能自主调用多工具的智能体。
一、为什么需要 Agent
1.1 Chain 的局限性
在 02_LangChain底层原理 中我们学过,Chain(链)本质上是一条固定的管道:输入 -> 步骤 A -> 步骤 B -> 输出。这就像一条工厂流水线,产品从头走到尾,中间不会岔路。
这种模式在简单场景下运作良好,但面对以下情况就力不从心了:
| 局限 | 示例 |
|---|---|
| 无法动态选择路径 | 用户问天气走天气 API,问股价走股票 API——Chain 无法根据输入切换 |
| 无法根据中间结果调整 | 搜索结果不满意时需要换个关键词再搜,Chain 做不到 |
| 无法处理多步推理 | ”帮我找到最近一周涨幅最大的股票,然后查它的新闻”需要多轮工具调用 |
| 无法自主决定何时结束 | Chain 执行完固定步骤就结束,无法判断”信息是否已经足够” |
1.2 Agent 的本质
核心概念Agent(智能体) = LLM(大脑)+ Tools(工具)+ 推理循环(决策机制)
LLM 负责思考(该用什么工具?参数是什么?结果是否满意?),Tools 负责执行(调 API、查数据库、跑代码),推理循环负责协调二者反复交互直到任务完成。
1.3 费曼类比
费曼类比:有工具箱的工程师把 Agent 想象成一位经验丰富的全栈工程师,面前摆着一个大工具箱(扳手、万用表、示波器……)。
当用户报修说”空调不制冷”时,这位工程师会:
- 思考(Thought):“可能是制冷剂不足,也可能是电路问题。”
- 行动(Action):先拿出压力表测制冷剂。
- 观察(Observation):读数正常,排除制冷剂问题。
- 再思考:“那可能是压缩机电路,用万用表测一下。”
- 再行动:用万用表测量。
- 再观察:发现电容坏了。
- 最终回答:“电容损坏,需要更换。”
Chain 就像一本固定的维修手册——不管什么故障,都从第 1 页翻到最后一页。 Agent 则是这位会灵活判断的工程师——根据每一步的观察动态调整策略。
1.4 Agent vs Chain 对比表
| 维度 | Chain | Agent |
|---|---|---|
| 执行流程 | 固定、线性 | 动态、循环 |
| 决策方式 | 开发者预定义 | LLM 实时决策 |
| 工具使用 | 不主动调用工具 | 根据需要选择工具 |
| 适用场景 | 流程确定的任务(翻译、摘要) | 流程不确定的任务(问答、调研) |
| 可控性 | 高(行为可预测) | 较低(依赖 LLM 推理质量) |
| Token 消耗 | 可预估 | 不可预估(多轮推理) |
| 调试难度 | 低 | 较高(需追踪推理链) |
注意Agent 并不总是优于 Chain。如果你的任务流程明确、不需要动态决策,Chain 更简单、更可控、更省 Token。只有在需要动态决策时才引入 Agent。
二、Tool(工具)系统
2.1 什么是 Tool
在 LangChain 中,Tool 是 Agent 可以调用的外部能力单元。每个 Tool 由三部分组成:
| 组成部分 | 作用 | 示例 |
|---|---|---|
| name | 工具的唯一标识符,LLM 用它来指定调用哪个工具 | "search_web" |
| description | 工具的功能描述,LLM 靠它来决定何时使用该工具 | "搜索互联网获取实时信息" |
| function | 工具的实际执行逻辑 | 一个调用搜索 API 的 Python 函数 |
关键洞察description 是 Tool 最重要的部分——LLM 完全依靠描述文本来理解工具的用途和使用时机。一个功能强大但描述模糊的工具,Agent 可能永远不会选择它。就像工具箱里的扳手如果没有标签,工程师可能会忽略它。
2.2 使用 @tool 装饰器定义工具
@tool 装饰器是定义工具最简洁的方式,它自动从函数签名和文档字符串中提取元信息。
# pip install langchain langchain-openai
from langchain_core.tools import tool
@tooldef multiply(a: int, b: int) -> int: """将两个整数相乘。当用户需要计算乘法时使用此工具。
Args: a: 第一个整数 b: 第二个整数 """ return a * b
# 查看工具的元信息print(multiply.name) # "multiply"print(multiply.description) # "将两个整数相乘。当用户需要计算乘法时使用此工具。"print(multiply.args_schema.model_json_schema())# {'properties': {'a': {'title': 'A', 'type': 'integer'},# 'b': {'title': 'B', 'type': 'integer'}},# 'required': ['a', 'b'],# 'title': 'multiply',# 'type': 'object'}幕后原理@tool 装饰器做了三件事:
- 将函数名作为
name- 将 docstring 的第一段作为
description- 利用类型注解自动生成
args_schema(基于 Pydantic)
2.3 使用 StructuredTool 定义复杂输入工具
当工具的输入参数较复杂,或需要更精细的校验时,可以使用 StructuredTool.from_function 配合 Pydantic 模型。
# pip install langchain pydantic
from langchain_core.tools import StructuredToolfrom pydantic import BaseModel, Field
class SearchInput(BaseModel): """搜索工具的输入参数。""" query: str = Field(description="搜索关键词") max_results: int = Field(default=5, description="最大返回结果数", ge=1, le=20) 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,)
print(search_tool.name) # "search_web"print(search_tool.args_schema.model_json_schema())# 输出包含 query、max_results、language 的完整 JSON Schema2.4 内置工具与社区工具
LangChain 提供了大量开箱即用的内置工具和社区集成:
| 工具 | 包 | 用途 |
|---|---|---|
| Tavily 搜索 | langchain-community | 互联网搜索,为 AI Agent 优化 |
| Wikipedia | langchain-community | 查询维基百科 |
| PythonREPL | langchain-experimental | 执行 Python 代码 |
| Shell | langchain-community | 执行 Shell 命令 |
| Requests | langchain-community | 发送 HTTP 请求 |
| SQL Database | langchain-community | 查询 SQL 数据库 |
# pip install langchain-community tavily-python
from langchain_community.tools.tavily_search import TavilySearchResults
# 需要设置环境变量 TAVILY_API_KEYsearch = TavilySearchResults(max_results=3)results = search.invoke("LangChain 最新版本")print(results)安全提示PythonREPL 和 Shell 工具允许执行任意代码,在生产环境中务必做好沙箱隔离,切勿直接暴露给终端用户。
2.5 工具描述的写作指南
由于 LLM 完全依赖 description 来选择工具,写好描述至关重要:
| 原则 | 差的描述 | 好的描述 |
|---|---|---|
| 说清楚做什么 | "计算工具" | "计算数学表达式的结果,支持加减乘除和幂运算" |
| 说清楚何时用 | "搜索" | "当用户询问实时信息、最新新闻或需要联网查询时使用" |
| 说清楚不能做什么 | (省略) | "仅支持文本搜索,不支持图片搜索" |
| 说清楚参数格式 | (省略) | "日期参数格式为 YYYY-MM-DD" |
2.6 工具的异步支持
在高并发场景下(如 Web 服务),工具需要支持异步执行以避免阻塞:
# pip install langchain aiohttp
import aiohttpfrom langchain_core.tools import tool
@toolasync def async_search(query: str) -> str: """异步搜索工具,用于高并发场景下的互联网搜索。""" async with aiohttp.ClientSession() as session: async with session.get( "https://api.example.com/search", params={"q": query} ) as resp: data = await resp.json() return str(data["results"][:3])
# 异步调用# result = await async_search.ainvoke({"query": "LangChain Agent"})同步 / 异步兼容如果你用 @tool 装饰一个普通同步函数,LangChain 会自动在 ainvoke 时将其包装为异步执行(通过线程池)。但如果你的工具本身涉及 I/O 操作(网络请求、文件读写),建议直接编写 async 版本以获得更好的性能。
工具定义好之后,下一步是让 Agent 学会使用它们。详见 02_Agent架构与实战。