Chapter 07

Tools 与 Agent

@tool 装饰器定义工具、bind_tools 绑定模型、ReAct 循环自主执行多步任务

LangChain Tools 是什么

在 LangChain 中,Tool(工具)是一个 LLM 可以"调用"的函数,赋予模型与外部世界交互的能力。Tool 本质上是一个 Python 函数加上一个描述——LLM 读取描述后自主决定何时调用、传入什么参数。

Tool
一个带有名称(name)、描述(description)和参数 Schema 的可调用单元。LLM 根据描述判断工具的用途,根据 Schema 生成调用参数。
@tool 装饰器
LangChain 0.3.x 中将普通 Python 函数转换为 Tool 的最简洁方式。函数的 docstring 成为工具描述,类型注解成为参数 Schema。
ToolNode
LangGraph 中处理工具调用的节点,负责执行 LLM 请求的工具调用并将结果返回给模型。
ReAct(Reason + Act)
Agent 的基本工作模式:模型交替进行推理(Thought)和行动(Action),每次 Action 调用一个工具,观察结果(Observation),再决定下一步。

用 @tool 定义工具

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
import requests

# 方式一:@tool 装饰器(最简洁,推荐)
@tool
def search_wikipedia(query: str) -> str:
    """在维基百科搜索信息并返回摘要。
    
    适用于:查询历史、科学、人物、地理等知识性问题。
    不适用于:实时数据(股价、天气)或私有数据。
    
    Args:
        query: 搜索关键词,如 "Python programming language"
    """
    url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{query.replace(' ', '_')}"
    resp = requests.get(url)
    if resp.status_code == 200:
        return resp.json().get("extract", "未找到相关信息")[:500]
    return f"搜索失败,状态码: {resp.status_code}"

@tool
def calculate(expression: str) -> str:
    """计算数学表达式,返回结果。
    
    支持:加减乘除、幂运算、括号、math 模块函数(如 sqrt, sin, cos)。
    不支持:代码执行、文件操作。
    
    Args:
        expression: 数学表达式字符串,如 "2**10 + 3*5" 或 "math.sqrt(144)"
    """
    import math
    try:
        # 安全的 eval:只允许 math 模块
        result = eval(expression, {"__builtins__": {}, "math": math})
        return str(result)
    except Exception as e:
        return f"计算错误: {e}"

# 将工具绑定到模型
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools([search_wikipedia, calculate])

# 直接调用(模型决定是否调用工具)
response = llm_with_tools.invoke("Python 语言是谁发明的?另外 2 的 10 次方是多少?")
print(response.tool_calls)
# [{'name': 'search_wikipedia', 'args': {'query': 'Python programming language'}},
#  {'name': 'calculate', 'args': {'expression': '2**10'}}]

构建完整 ReAct Agent

from langchain_core.messages import HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
import json

def run_agent(question: str, tools: list, max_iterations: int = 5):
    """简易 ReAct Agent:自主调用工具直到得出答案"""
    llm = ChatOpenAI(model="gpt-4o")
    llm_with_tools = llm.bind_tools(tools)
    tools_map = {t.name: t for t in tools}
    
    messages = [HumanMessage(content=question)]
    
    for i in range(max_iterations):
        response = llm_with_tools.invoke(messages)
        messages.append(response)
        
        # 如果没有工具调用,说明模型已得出最终答案
        if not response.tool_calls:
            return response.content
        
        # 执行所有工具调用
        for tool_call in response.tool_calls:
            tool_name = tool_call["name"]
            tool_args = tool_call["args"]
            
            # 调用对应工具
            result = tools_map[tool_name].invoke(tool_args)
            
            # 将工具结果添加到消息历史
            messages.append(ToolMessage(
                content=str(result),
                tool_call_id=tool_call["id"]
            ))
    
    return "达到最大迭代次数,任务未完成"

# 使用:
answer = run_agent(
    "Python 是谁发明的?他的生日是哪天?从发明 Python 到现在过了多少天?",
    tools=[search_wikipedia, calculate]
)
print(answer)
常见误区:工具描述不清导致调用错误

LLM 完全依赖工具描述来决定何时调用工具以及如何传参。描述越清晰具体,调用越准确。描述中应包含:工具的用途、适用场景、不适用场景、参数格式示例。避免模糊描述如"处理数据"。

生产建议:使用 LangGraph 替代手写 ReAct

上面的手写 ReAct 循环是教学示例,生产环境应使用 LangGraph 构建 Agent(第8章),它提供持久化状态、错误重试、Human-in-the-loop 等企业级特性。

本章小结