构建带工具与记忆功能的聊天机器人

目录

3.1  环境准备

1. 核心依赖安装

2. 环境配置(Qwen-plus专属)

3. 核心概念前置(重构适配Qwen-plus)

3.2  实战案例:使用工具和记忆增强聊天机器人

3.3  案例代码解析


3.1  环境准备

对于这一部分,我们需要使用langchain_tavily.Tavily搜索工具类来获取实时/联网信息,以及使用langgraph.checkpoint.memory模块中的MemorySaver类来实现记忆功能。

1. 核心依赖安装

本案例机器人的工具功能依赖LangChain官方集成包langchain-tavily,记忆功能依赖的是LangGraph框架本身,而Qwen-plus的调用可借助LangChain的兼容层实现,无须额外安装专属依赖,只需安装以下核心包即可完成环境搭建(兼容Qwen-plus调用与记忆功能):

# 安装核心依赖(LangGraph+Qwen-plus调用+环境变量管理)

pip install langgraph langchain-openai langchain-tavily python-dotenv

2. 境配置(Qwen-plus专属)

Qwen-plus的调用需要配置阿里云通义千问的API密钥,为保证密钥安全,我们采用`.env`文件进行环境变量配置,避免密钥硬编码在代码中,具体步骤如下:

(1)在项目根目录新建.env文件。

(2)在.env文件中添加Qwen-plus的API密钥配置,内容如下:

# 通义千问Qwen-plus API密钥(从阿里云控制台获取)

DASHSCOPE_API_KEY=你的通义千问API密钥

补充说明一下Qwen-plus API密钥获取路径—登录阿里云通义千问控制台→进入“API-KEY管理”→创建并复制DASHSCOPE_API_KEY(注意区分个人版与企业版密钥,个人版可满足开发调试需求)。

3. 心概念前置(重构适配Qwen-plus)

为了理解本章案例围绕机器人的工具、记忆与Qwen-plus大模型调用的协同逻辑,需先明确以下核心概念,避免混淆技术边界。

  • TavilySearch(Tavily搜索):TavilySearch 本质上是一个专为 AI Agent 和大模型(LLM)设计的搜索引擎 API。它不像 Google 那样直接给人看网页,而是把网络信息“清洗”成结构化的数据(JSON),方便 AI 读取和引用,从而解决大模型“知识过时”和“胡编乱造”的问题。
  • ToolNode:LangGraph预构建的工具节点(这里是TavilySearch),自动处理工具调用的解析、执行、结果封装,无需手动写逻辑。
  • Checkpointer(检查点):LangGraph中状态的快照,是记忆功能的核心载体,MemorySaver负责将检查点存储在内存中,与所使用的大模型(Qwen-plus)无关,仅负责状态持久化。
  • MemorySaver:LangGraph官方提供的内存版检查点存储器,轻量无外部依赖(无须数据库),适合开发、调试场景,其核心作用是保存对话状态,不参与大模型调用逻辑,因此切换Qwen-plus后,记忆功能无须任何修改。
  • thread_id(线程/会话):用于区分不同用户/对话的唯一标识,实现多会话隔离记忆,确保多个用户同时使用时,记忆不混淆,该机制与Qwen-plus调用完全独立。
  • Qwen-plus适配逻辑:借助LangChain的ChatOpenAI类兼容Qwen-plus的API接口,通过配置Qwen-plus专属的base_url和model参数,实现大模型调用与LangGraph的无缝衔接,不影响记忆功能的正常工作。

这里需要注意,MemorySaver的记忆功能与大模型类型无关,无论切换为Qwen-plus、ChatGLM等国产大模型,还是保留OpenAI,记忆的加载、保存、会话隔离逻辑完全不变,只需修改大模型的调用配置即可。

3.2  实战案例:使用工具和记忆增强聊天机器人

让我们创建一个名为Memory-Tool-Chatbots.py的文件,实现一个具有工具调用与记忆功能的聊天机器人。本案例已完成Qwen-plus适配,无须修改代码,完整可运行代码如下。

【示例4.1】Memory-Tool-Chatbots.py实现工具调用与记忆功能的聊天机器人。核心效果为:Qwen-plus大模型结合LangGraph的工具与记忆功能,实现搜索工具调用、跨轮次上下文记忆,记住用户姓名、年龄等关键信息,同时多会话隔离功能正常生效。

# -*- coding: utf-8 -*-

from dotenv import load_dotenv

from langchain_openai import ChatOpenAI

from langchain_tavily import TavilySearch

from langgraph.graph import MessagesState, StateGraph, END

from langgraph.checkpoint.memory import MemorySaver

from langgraph.prebuilt import ToolNode, tools_condition

import os

load_dotenv()

llm = ChatOpenAI(

    model="qwen-plus",

    api_key=os.getenv("DASHSCOPE_API_KEY"),

    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",

    temperature=0.7

)

search_tool = TavilySearch(max_results=2)

tools = [search_tool]

llm_with_tools = llm.bind_tools(tools)

state = MessagesState

workflow = StateGraph(state)

workflow.add_node("chat", lambda state: {"messages": llm_with_tools.invoke(state["messages"])})

workflow.add_node("tools", ToolNode(tools=tools))

workflow.add_conditional_edges("chat", tools_condition)

workflow.add_edge("tools", "chat")

workflow.set_entry_point("chat")

memory = MemorySaver()

app = workflow.compile(checkpointer=memory)

def chat_with_memory():

    print("智能对话机器人(支持记忆和搜索)")

    print("输入 exit 退出对话")

    thread_id = "user_session_1"

    config = {"configurable": {"thread_id": thread_id}}

    while True:

        try:

            user_input = input("\n你:").strip()

            if not user_input:

                continue

            if user_input.lower() in ["exit", "quit", "退出"]:

                print("机器人:再见!")

                break

            result = app.invoke(

                {"messages": [("user", user_input)]},

                config=config

            )

            if result["messages"]:

                last_message = result["messages"][-1]

                print("机器人:", last_message.content)

        except KeyboardInterrupt:

            print("\n对话已结束")

            break

        except Exception as e:

            print(f"出错:{str(e)}")

if __name__ == "__main__":

    chat_with_memory()

运行输出:

智能对话机器人(支持记忆和搜索)

输入 exit 退出对话

你:你好,我叫小明

机器人: 你好,小明!很高兴认识你。有什么问题或需要帮助的地方吗?😊

你:我今年25岁了

机器人: 恭喜你25岁!这是充满活力、成长与可能性的年纪 🌟 

如果你愿意分享,我很乐意帮你: 

- 制定职业或学习规划 

- 探讨健康生活方式(比如睡眠、运动、饮食) 

- 理财入门建议(如储蓄、基金、保险) 

- 心理调适或时间管理小技巧 

- 或者只是聊聊兴趣、目标、最近让你开心/困惑的一件事 😊 

需要哪方面的支持呢?

你:你还记得我的名字和年龄么?

机器人: 当然记得!😊 

你叫**小明**,今年**25** 

我会把这一点记在心里,让后续的交流更亲切、更贴合你的阶段和需求~ 

如果以后你想聊职业发展、生活规划、兴趣探索,或者只是想轻松聊聊,我都在这里 🌟 

需要现在开始聊点什么吗?

你:什么是LangGraph的核心组件?

机器人: 是的!根据最新资料,**LangGraph**(由 LangChain 团队开发的用于构建状态化、多步骤 AI 代理和工作流的框架)的核心组件主要包括以下四类,它们共同构成一个可循环、可中断、可调试的图状执行结构:

---

### 1. **State(状态)**

- 是整个工作流的**共享内存与数据载体**,通常定义为一个 Pydantic `BaseModel` 类。

- 所有节点(Nodes)读取和更新的是同一个 `State` 实例(支持异步/并发安全操作)。

- 示例:

  ```python

  from pydantic import BaseModel

  from typing import List, Dict

  class ChatState(BaseModel):

      messages: List[Dict[str, str]] = []

      current_input: str = ""

      should_continue: bool = True

  ```

---

### 2. **Nodes(节点)**

- 表示**可执行的逻辑单元**(如调用 LLM、运行工具、执行条件判断等)。

- 每个 Node 是一个**异步函数**,接收 `State` 并返回更新后的 `State`(或部分字段)。

- 支持多种内置节点类型,例如:

  - `ToolNode`(自动调用工具)

  - 自定义函数节点(如 `process_user_input`, `generate_ai_response`

  - LLM 调用节点、记忆节点、验证节点等。

---

### 3. **Edges(边)**

- 定义节点之间的**执行流向**,分为两类:

  - **Fixed edges**:无条件跳转(如 `workflow.add_edge("node_a", "node_b")`

  - **Conditional edges**:基于 `State` 的当前值动态路由(通过 `add_conditional_edges` + 路由函数实现),例如:

    ```python

    def route(state: ChatState) -> str:

        return "generate_response" if state.should_continue else END

    workflow.add_conditional_edges("process_input", route)

    ```

---

### 4. **Graph(图)—— StateGraph**

- 使用 `StateGraph(StateType)` 创建图对象,是编排 Nodes Edges **核心容器**

- 最终通过 `.compile()` 生成可执行的 `CompiledGraph`(支持 `.invoke()`, `.stream()`, `.astream_events()` 等)。

- 支持中断恢复、检查点(checkpoints)、human-in-the-loop 交互等高级能力。

---

🔍 **补充关键支撑技术**(虽非“组件”本身,但深度集成):

- **LangChain Core**:提供 LLMToolPromptMessage 等基础抽象;

- **LangSmith**:用于追踪、评估、调试 LangGraph 工作流(日志、trace、性能分析);

- **RAG 集成能力**:可自然嵌入检索节点(如 `RetrieverNode`)实现检索增强;

- **APIs & Tool Calling**:原生支持 OpenAI-style function calling 和自定义工具链。

---

需要我为你:

- 🧩 画一个简明架构图(文字版)?

- 🐍 提供一个可运行的「带记忆的问答机器人」完整代码示例?

- 📚 对比 LangGraph AutoGen / LlamaIndex / CrewAI 的定位差异?

欢迎随时告诉我 😊

你:

3.3  案例代码解析

本节重点解析ToolNode工具功能、MemorySaver记忆功能与Qwen-plus调用配置的协同逻辑,拆解核心代码模块,明确三者的职责边界,重点说明Qwen-plus适配的关键细节。

1. 核心模块导入解析

from dotenv import load_dotenv

from langchain_openai import ChatOpenAI  # 兼容Qwen-plus调用

from langchain_tavily import TavilySearch

from langgraph.graph import MessagesState, StateGraph, END  # 状态与工作流

from langgraph.checkpoint.memory import MemorySaver  # 记忆功能核心

from langgraph.prebuilt import ToolNode, tools_condition

import os  # 读取环境变量

ChatOpenAI类此处并非用于调用OpenAI服务,而是利用其API调用规范,适配Qwen-plus的OpenAI兼容接口,这是LangChain生态的优势—无须修改核心逻辑,即可切换不同大模型。

2. Qwen-plus调用配置

llm = ChatOpenAI(

    model="qwen-plus",  # 固定为Qwen-plus模型名

    api_key=os.getenv("DASHSCOPE_API_KEY"),  # .env读取密钥,安全无硬编码

    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", 
# Qwen-plus
专属兼容地址

    temperature=0  # 生成内容确定性,适合聊天场景

)

该配置仅影响大模型的调用,方便独立调用Tavily搜索工具与MemorySaver记忆功能,保留工具与记忆功能的逻辑实现与大模型调用的分离。核心说明(与OpenAI调用的区别)如下:

  • model参数:替换为Qwen-plus专属模型名(固定为"qwen-plus"),无须修改其他逻辑。
  • api_key参数:使用阿里云通义千问的DASHSCOPE_API_KEY,而非OpenAI的API密钥,通过os.getenv从.env读取,避免硬编码。
  • base_url参数:Qwen-plus的OpenAI兼容接口地址(固定),用于指定大模型调用的服务器地址,确保LangChain能正确请求Qwen-plus服务。

3. 核心数据结构:State(状态定义)

状态定义:MessagesState

state = MessagesState

MessagesState是LangGraph内置的状态类型,专门为对话场景设计,内部结构为{"messages": [消息列表]},自动管理用户消息与Qwen-plus的回复内容。MemorySaver会自动持久化该状态,实现对话记忆,无论大模型切换为哪种,该状态的管理逻辑完全不变。

4. 工具功能核心解析

1)工具初始化(Tavily搜索)

search_tool = TavilySearch(max_results=2)  # 初始化 Tavily 搜索工具,最多返回 2 条结果

tools = [search_tool]  # 工具列表(可扩展多个工具,如计算器、数据库查询等)

LangChain集成的Tavily搜索(TavilySearch)工具(需在.env中配置TAVILY_API_KEY),用于获取实时/联网信息(大模型本身无实时数据)。

2)绑定工具到大模型

llm_with_tools = llm.bind_tools(search_tool)

bind_tools将工具列表绑定给大模型,让大模型具备“判断是否需要调用工具”的能力。当用户问题不需要实时数据(如“1+1等于几”),大模型直接回复;当用户问题需要实时数据(如“今天北京天气”),大模型会生成工具调用请求(包含搜索关键词等参数)。

5. 记忆功能核心解析

1)MemorySaver初始化与工作流编译

# 初始化记忆存储器(内存版,轻量无依赖)

memory = MemorySaver()

# 编译工作流,传入memory开启记忆功能

app = workflow.compile(checkpointer=memory)

这是开启记忆功能的关键步骤,与Qwen-plus调用无关:

  • 初始化MemorySaver实例,用于存储对话状态(检查点)。
  • 编译工作流时,传入checkpointer=memory,告知LangGraph开启状态持久化功能。
  • 编译后的app会自动完成“记忆加载→逻辑执行→记忆保存”的闭环,与大模型类型无关。

2)会话隔离:thread_id

thread_id = "user_1001"

config = {"configurable": {"thread_id": thread_id}}

thread_id是会话唯一标识,用于区分不同用户/对话,核心作用是实现多会话记忆隔离:

  • 不同thread_id对应的记忆完全独立,不会出现“用户A的对话记忆被用户B获取”的情况。
  • 实战中可使用用户ID、会话ID作为thread_id,适配多用户场景。
  • 该机制与Qwen-plus调用无关,仅由LangGraph的记忆功能管理。

3)记忆的加载与保存机制(与Qwen-plus协同)

LangGraph记忆功能与Qwen-plus的协同流程如下(核心闭环):

  • 加载记忆:调用app.invoke()时,传入包含thread_id的config,MemorySaver自动从内存中读取该会话的历史消息(状态)。
  • 大模型调用:将加载的历史消息+当前用户输入,一同传入Qwen-plus,Qwen-plus基于完整上下文生成回复。
  • 保存记忆:Qwen-plus生成回复后,LangGraph自动将该回复追加到对话状态中,MemorySaver同步保存更新后的状态,完成记忆更新。

6. 键知识点补充(适配Qwen-plus场景)

  • 多工具选择:只需在tools列表中添加新工具(如Calculator、SQLDatabaseToolkit等),llm.bind_tools会自动让大模型支持多工具选择。
  • 无记忆与有记忆:不传入checkpointer=memory时,每次对话都是独立的,Qwen-plus无法获取历史上下文,仅能基于当前输入回复;传入后,即可实现跨轮次记忆。
  • MemorySaver特性:内存存储,程序重启后记忆会丢失,适合开发调试;生产环境可替换为RedisSaver/SqliteSaver,记忆功能逻辑不变,仅修改存储器类型。
  • Qwen-plus适配扩展:若需切换为Qwen-14B、Qwen-max等其他通义千问模型,只需修改model参数(如model="qwen-14b"),同时确保API密钥拥有对应模型的调用权限,记忆功能无须任何调整。
  • 常见问题:若Qwen-plus调用失败,优先检查.env文件中的DASHSCOPE_API_KEY是否正确、网络是否能访问阿里云通义千问接口,与MemorySaver无关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值