跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

LangGraph 工具调用实战:实现 ReAct 搜索机器人

介绍 LangGraph 中的工具调用功能,通过集成 Tavily 搜索引擎实现 ReAct(推理 + 行动)模式。内容包括核心概念、环境配置、代码实现(状态定义、工具节点、路由函数)、图结构构建及运行效果分析。重点讲解了如何让 LLM 主动调用外部工具获取实时信息,解决了纯 LLM 知识截止等问题。

内存管理发布于 2026/4/5更新于 2026/9/1084 浏览
LangGraph 工具调用实战:实现 ReAct 搜索机器人

前言

在 LangGraph 的进阶领域中,工具调用(Tool Calling)允许 AI 理解用户需求并调用外部工具获取数据。本文将带你掌握 ReAct(Reasoning + Acting)模式的完整实现,让 AI 能够主动调用外部工具获取实时信息。


一、核心概念

1.1 什么是工具调用

工具调用(Tool Calling)是 LLM 的重要能力,它允许 AI:

  1. 推理(Reasoning):理解用户需求,判断需要什么信息
  2. 行动(Acting):调用外部工具获取数据
  3. 观察(Observation):整合工具结果生成回答

这就是经典的 ReAct 模式。

1.2 为什么需要工具调用

场景纯 LLM带工具调用的 LLM
实时信息❌ 知识截止,无法回答✅ 调用搜索工具获取
数学计算❌ 容易出错✅ 调用计算器精确计算
数据库查询❌ 无法访问✅ 调用 SQL 工具查询
API 调用❌ 无法执行✅ 调用 API 工具操作

工具调用让 AI 从'纸上谈兵'变为'实干家'。

1.3 核心组件

┌─────────────────────────────────────────────────────────┐
│                 工具调用架构                           │
├─────────────────────────────────────────────────────────┤
│  1. 工具定义       │  TavilySearch、Calculator 等      │
│  2. 工具绑定       │  llm.bind_tools(tools)            │
│  3. 工具节点       │  BasicToolNode 执行工具调用       │
│  4. 条件路由       │  route_tools 判断是否需要工具     │
│  5. 循环执行       │  chatbot ↔ tools 形成 ReAct 循环  │
└─────────────────────────────────────────────────────────┘

二、环境准备

2.1 安装依赖

pip install langgraph langchain langchain-openai langchain-community pydantic python-dotenv typing-extensions

2.2 配置 API 密钥

创建 .env 文件:

# API 密钥
SILICONFLOW_API_KEY=your_siliconflow_key
# Tavily 搜索引擎 API 密钥
TAVILY_API_KEY=your_tavily_key

获取 Tavily API Key:访问 Tavily 注册获取免费 API Key。


三、代码实现

3.1 完整代码

"""
LangGraph 教程 - 为聊天机器人添加工具
本示例演示如何为 StateGraph 聊天机器人添加网页搜索工具。
当聊天机器人无法凭记忆回答问题时,可以使用工具查找相关信息。
"""

# 过滤警告信息
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=UserWarning)

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import ToolMessage
from langchain_community.tools.tavily_search import TavilySearchResults
from dotenv import load_dotenv
import json
import os

# 加载环境变量
load_dotenv()

# 检查 Tavily API Key 是否设置
if not os.getenv("TAVILY_API_KEY"):
    raise ValueError("TAVILY_API_KEY 未设置,请在 .env 文件中配置")


# ==================== 1. 定义状态 ====================
class State(TypedDict):
    """
    定义图的状态结构。
    messages: 消息列表,使用 add_messages reducer 函数
              确保新消息追加到列表,而不是覆盖
    """
    messages: Annotated[list, add_messages]


# ==================== 2. 定义工具节点 ====================
class BasicToolNode:
    """
    工具节点:运行 LLM 请求的工具。
    检查状态中的最新消息,如果消息包含 tool_calls,
    则调用相应的工具。
    """
    def __init__(self, tools: list) -> None:
        """
        初始化工具节点。
        Args:
            tools: 工具列表
        """
        self.tools_by_name = {tool.name: tool for tool in tools}

    def __call__(self, inputs: dict):
        """
        执行工具调用。
        Args:
            inputs: 包含消息列表的字典
        Returns:
            包含工具执行结果的消息字典
        """
        if messages := inputs.get("messages", []):
            message = messages[-1]
        else:
            raise ValueError("No message found in input")
        outputs = []
        for tool_call in message.tool_calls:
            # 调用工具
            tool_result = self.tools_by_name[tool_call["name"]].invoke(
                tool_call["args"]
            )
            # 创建工具消息
            outputs.append(
                ToolMessage(
                    content=json.dumps(tool_result, ensure_ascii=False),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}


# ==================== 3. 定义路由函数 ====================
def route_tools(state: State):
    """
    条件边路由函数。
    检查聊天机器人输出中是否包含 tool_calls。
    - 如果有工具调用,路由到 "tools" 节点
    - 如果没有,路由到 END(结束)
    Args:
        state: 当前图状态
    Returns:
        下一个节点的名称或 END
    """
    if isinstance(state, list):
        ai_message = state[-1]
    elif messages := state.get("messages", []):
        ai_message = messages[-1]
    else:
        raise ValueError(f"No messages found in input state to tool_edge: {state}")
    # 检查是否有工具调用
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"
    return END


# ==================== 4. 搜索工具 ====================
# 使用 Tavily 真实搜索工具
# 需要安装:pip install langchain-community
# Tavily API Key 已在上面的代码中设置


# ==================== 5. 创建图 ====================
def create_graph():
    """
    创建并编译 StateGraph。
    Returns:
        编译后的图对象
    """
    # 创建图构建器
    graph_builder = StateGraph(State)
    # 初始化模型
    llm = ChatOpenAI(
        model="Qwen/Qwen3-Next-80B-A3B-Instruct",
        openai_api_key=os.getenv("SILICONFLOW_API_KEY"),
        openai_api_base="https://api.siliconflow.cn/v1",
        temperature=0.7
    )
    # 创建 Tavily 搜索工具
    tool = TavilySearchResults(max_results=2)
    tools = [tool]
    # 绑定工具到 LLM
    llm_with_tools = llm.bind_tools(tools)
    # 定义聊天机器人节点
    def chatbot(state: State):
        """
        聊天机器人节点。
        Args:
            state: 当前状态
        Returns:
            包含 LLM 响应的字典
        """
        return {"messages": [llm_with_tools.invoke(state["messages"])]}
    # 添加节点
    graph_builder.add_node("chatbot", chatbot)
    # 创建工具节点并添加
    tool_node = BasicToolNode(tools=tools)
    graph_builder.add_node("tools", tool_node)
    # 添加边
    graph_builder.add_edge(START, "chatbot")
    # 添加条件边:从 chatbot 到 tools 或 END
    graph_builder.add_conditional_edges(
        "chatbot",
        route_tools,
        {"tools": "tools", END: END}
    )
    # 添加边:从 tools 回到 chatbot(形成循环)
    graph_builder.add_edge("tools", "chatbot")
    # 编译图
    return graph_builder.compile()


# ==================== 6. 运行聊天机器人 ====================
def stream_graph_updates(graph, user_input: str):
    """
    流式处理图更新。
    Args:
        graph: 编译后的图对象
        user_input: 用户输入的消息
    """
    for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
        for value in event.values():
            if "messages" in value:
                last_message = value["messages"][-1]
                # 只打印 AI 消息,不打印工具消息
                if hasattr(last_message, "content") and last_message.content:
                    if not isinstance(last_message, ToolMessage):
                        print("助手:", last_message.content)


def main():
    """主函数 - 运行交互式聊天机器人。"""
    print("🤖 LangGraph 工具增强聊天机器人已启动!")
    print("=" * 50)
    print("提示:")
    print("  - 输入 'quit'、'exit' 或 'q' 退出对话")
    print("  - 聊天机器人可以使用搜索工具回答实时问题\n")
    # 创建图
    graph = create_graph()
    while True:
        try:
            # 获取用户输入
            user_input = input("用户:")
            # 检查退出命令
            if user_input.lower() in ["quit", "exit", "q"]:
                print("\n👋 再见!")
                break
            # 处理用户输入并获取响应
            stream_graph_updates(graph, user_input)
            print()  # 空行分隔对话
        except KeyboardInterrupt:
            print("\n\n👋 再见!")
            break
        except Exception as e:
            print(f"发生错误:{e}")
            break


if __name__ == "__main__":
    main()

3.2 代码解析

3.2.1 工具绑定
# 创建 Tavily 搜索工具
tool = TavilySearchResults(max_results=2)
tools = [tool]
# 绑定工具到 LLM
llm_with_tools = llm.bind_tools(tools)

关键点:

  • bind_tools() 将工具信息注入 LLM 的系统提示
  • LLM 根据用户输入判断是否需要调用工具
  • 如果需要,LLM 输出包含 tool_calls 的特殊消息
3.2.2 工具节点实现
class BasicToolNode:
    def __init__(self, tools: list) -> None:
        # 创建工具名称到工具对象的映射
        self.tools_by_name = {tool.name: tool for tool in tools}
    def __call__(self, inputs: dict):
        message = inputs.get("messages", [])[-1]
        outputs = []
        for tool_call in message.tool_calls:
            # 调用对应工具
            tool_result = self.tools_by_name[tool_call["name"]].invoke(
                tool_call["args"]
            )
            # 创建 ToolMessage
            outputs.append(
                ToolMessage(
                    content=json.dumps(tool_result),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}

执行流程:

  1. 解析 LLM 的 tool_calls 请求
  2. 查找并执行对应工具
  3. 将结果封装为 ToolMessage
  4. 返回更新后的状态
3.2.3 条件路由
def route_tools(state: State):
    """条件边路由函数。"""
    ai_message = state.get("messages", [])[-1]
    # 检查是否有工具调用
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"  # 有工具调用,路由到 tools 节点
    return END  # 没有工具调用,结束

作用:根据当前状态动态决定下一步执行哪个节点。

3.2.4 图结构
# 添加节点
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tool_node)
# 添加边
graph_builder.add_edge(START, "chatbot")
graph_builder.add_conditional_edges(
    "chatbot",
    route_tools,
    {"tools": "tools", END: END}
)
graph_builder.add_edge("tools", "chatbot")  # 形成循环

图结构:

START ──▶ chatbot ──┬──▶ END(无工具调用)
                  │
                  └──▶ tools ──▶ chatbot(循环)
                     (有工具调用)

四、运行效果

4.1 执行步骤

python 03 添加工具.py

4.2 输出结果

(此处省略图片展示)


五、ReAct 模式详解

5.1 执行流程图解

用户:"今天重庆天气怎么样?"
    │
    ▼
┌─────────────────────────────────────┐
│  Step 1: chatbot 节点               │
│  LLM 判断需要调用工具               │
│  输出:AIMessage with tool_calls    │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 2: route_tools 检查           │
│  发现 tool_calls                    │
│  返回:"tools"                      │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 3: tools 节点                 │
│  调用 TavilySearch                  │
│  搜索:"重庆今天天气"               │
│  输出:ToolMessage with results     │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 4: chatbot 节点(循环)       │
│  LLM 接收 ToolMessage               │
│  生成最终回答                       │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Step 5: route_tools 检查           │
│  无 tool_calls                      │
│  返回:END                          │
└─────────────────────────────────────┘
    │
    ▼
输出:"根据搜索结果,重庆今天天气晴朗..."

5.2 消息流转

消息历史:
├── UserMessage: "今天重庆天气怎么样?"
├── AIMessage: tool_calls(调用意图)
├── ToolMessage: 搜索结果
└── AIMessage: 最终回答

六、踩坑记录

问题原因解决方案
API Key 泄露风险硬编码在代码中使用 .env 文件存储
工具调用不触发LLM 未正确绑定工具确认 bind_tools() 调用
ToolMessage 格式错误content 不是 JSON使用 json.dumps() 序列化
无限循环忘记返回 END检查 route_tools 逻辑
工具结果丢失tool_call_id 不匹配确保 ID 与请求一致

七、总结

通过本文,你学会了:

  1. ✅ 工具绑定:使用 bind_tools() 让 LLM 知道可用工具
  2. ✅ 工具节点:实现 BasicToolNode 执行工具调用
  3. ✅ 条件路由:使用 add_conditional_edges() 动态控制流程
  4. ✅ ReAct 模式:理解推理 - 行动 - 观察的完整循环
  5. ✅ 消息类型:掌握 AIMessage、ToolMessage 的使用

进阶方向

  • 多工具支持:添加计算器、数据库查询等工具
  • 工具选择:让 LLM 智能选择最合适的工具
  • 错误处理:添加工具调用失败的容错机制
  • 人机协作:在关键步骤添加人工确认

参考资料

  • LangGraph 官方教程 - 添加工具
  • Tavily 搜索引擎
  • ReAct 论文
  • LangChain 工具调用文档

目录

  1. 前言
  2. 一、核心概念
  3. 1.1 什么是工具调用
  4. 1.2 为什么需要工具调用
  5. 1.3 核心组件
  6. 二、环境准备
  7. 2.1 安装依赖
  8. 2.2 配置 API 密钥
  9. API 密钥
  10. Tavily 搜索引擎 API 密钥
  11. 三、代码实现
  12. 3.1 完整代码
  13. 过滤警告信息
  14. 加载环境变量
  15. 检查 Tavily API Key 是否设置
  16. ==================== 1. 定义状态 ====================
  17. ==================== 2. 定义工具节点 ====================
  18. ==================== 3. 定义路由函数 ====================
  19. ==================== 4. 搜索工具 ====================
  20. 使用 Tavily 真实搜索工具
  21. 需要安装:pip install langchain-community
  22. Tavily API Key 已在上面的代码中设置
  23. ==================== 5. 创建图 ====================
  24. ==================== 6. 运行聊天机器人 ====================
  25. 3.2 代码解析
  26. 3.2.1 工具绑定
  27. 创建 Tavily 搜索工具
  28. 绑定工具到 LLM
  29. 3.2.2 工具节点实现
  30. 3.2.3 条件路由
  31. 3.2.4 图结构
  32. 添加节点
  33. 添加边
  34. 四、运行效果
  35. 4.1 执行步骤
  36. 4.2 输出结果
  37. 五、ReAct 模式详解
  38. 5.1 执行流程图解
  39. 5.2 消息流转
  40. 六、踩坑记录
  41. 七、总结
  42. 进阶方向
  43. 参考资料

更多推荐文章

查看全部
  • C++ 容器适配器:优先级队列与反向迭代器实现原理
  • ELK 日志分析方案为何如此火热?
  • 深圳 2015 年 3 月软件开发人员薪资水平分析
  • 深入理解 YOLOv11 算法核心模块与结构
  • 前端高频场景面试题与实战解答
  • 程序员 Python 副业方向与接单实战建议
  • 线性代数与空间解析几何在几何体数据结构中的应用
  • C++ 笔试刷题:模拟、动态规划与回文判断
  • 无人机硬件组装与核心部件选型指南
  • 鸿蒙车载互联实战:用分布式技术重构出行体验
  • OpenCode 开源 AI 编程助手:从安装到高级配置实战
  • 现代 C++ 数学表达式解释器实现
  • 二叉树的链式存储与遍历实现
  • Spring AI Alibaba 使用指南及 ReactAgent 入门
  • Flink 外部资源框架:作业原生申请 GPU/FPGA 资源
  • 基于 ComfyUI 工作流的 Stable Diffusion 服装替换指南
  • 基于 Llama 3 构建 RAG 语音助手:集成 Qdrant、Whisper 与 LangChain
  • 全球老龄化背景下的护理机器人发展研究
  • 宇树机器人 G1 二次开发:导航仿真与地图转换教程
  • 贪心算法和动态规划:原理、区别与 C++ 示例

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online