{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 19 循环与记忆\n", "\n", "## 学习目标\n", "1. 理解LangGraph中循环和持久化记忆的实现方式\n", "2. 掌握MemorySaver的使用,实现对话状态持久化\n", "3. 能够构建支持多轮交互的智能体流程" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 为什么需要循环和记忆\n", "\n", "在之前的课程中,我们构建的图流程都是**单次执行**的:\n", "\n", "```\n", "START -> node_a -> node_b -> END\n", "```\n", "\n", "这种流程跑一次就结束了。但真实场景中,很多需求需要**多次循环**和**记忆状态**:\n", "\n", "- **聊天机器人**:需要记住之前说过的话,才能进行多轮对话\n", "- **任务分解**:可能需要反复调用工具,直到任务完成\n", "- **信息收集**:需要逐步收集用户的信息,直到完整\n", "- **多用户系统**:每个用户的对话历史需要独立保存\n", "\n", "简单来说:\n", "\n", "- **循环**:让流程可以重复执行某些节点\n", "- **记忆**:让状态可以在多次执行之间保持" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 循环的实现方式\n", "\n", "在LangGraph中,循环主要通过**条件边**来实现。关键思路是:\n", "\n", "1. 执行某个节点\n", "2. 判断是否需要继续循环\n", "3. 如果需要,回到之前的节点重新执行\n", "4. 如果不需要,走向结束\n", "\n", "流程图如下:\n", "\n", "```\n", "START -> work_node -> 判断节点\n", " |\n", " ┌─────────┴─────────┐\n", " ▼ ▼\n", " 继续循环 结束\n", " | |\n", " └───────► work_node ◄──┘\n", "```\n", "\n", "用条件边实现就是:`判断节点` 根据状态返回 `'loop'` 或 `'end'`,然后:\n", "\n", "- `'loop'` → 回到 `work_node`\n", "- `'end'` → 走到 `END`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. 第一个例子:简单的计数器循环\n", "\n", "我们用一个计数器来演示循环的基本实现。\n", "\n", "流程目标:\n", "- 从0开始计数\n", "- 每次加1\n", "- 当计数达到5时停止\n", "- 每次循环都打印当前计数值" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing_extensions import TypedDict\n", "from langgraph.graph import StateGraph, START, END\n", "\n", "class CounterState(TypedDict):\n", " count: int\n", "\n", "def increment_node(state: CounterState):\n", " new_count = state['count'] + 1\n", " print(f'当前计数:{new_count}')\n", " return {'count': new_count}\n", "\n", "def should_continue(state: CounterState):\n", " if state['count'] < 5:\n", " return 'loop'\n", " return 'end'\n", "\n", "builder = StateGraph(CounterState)\n", "builder.add_node('increment', increment_node)\n", "\n", "builder.add_edge(START, 'increment')\n", "builder.add_conditional_edges(\n", " 'increment',\n", " should_continue,\n", " {\n", " 'loop': 'increment',\n", " 'end': END\n", " }\n", ")\n", "\n", "graph = builder.compile()\n", "result = graph.invoke({'count': 0})\n", "print(result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 代码解释\n", "\n", "1. **State 定义**:`CounterState` 只包含一个字段 `count`,用于存储当前计数值。\n", "\n", "2. **increment_node**:每次执行时,从状态中取出 `count`,加1后放回状态。同时打印当前计数值。\n", "\n", "3. **should_continue**:路由函数,判断是否继续循环。如果 `count < 5` 返回 `'loop'`,否则返回 `'end'`。\n", "\n", "4. **条件边设置**:\n", " - 来源节点是 `'increment'`\n", " - 路由函数是 `should_continue`\n", " - 如果返回 `'loop'`,就回到 `'increment'` 节点(形成循环)\n", " - 如果返回 `'end'`,就走到 `END`\n", "\n", "5. **执行**:初始状态是 `{'count': 0}`,图会自动循环执行,直到 `count` 达到5。\n", "\n", "运行结果显示计数从1到5,说明循环成功执行了5次。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. 状态累积:消息历史的保存\n", "\n", "循环的核心价值在于**状态累积**。最常见的场景是保存对话历史。\n", "\n", "在聊天机器人中,每次用户输入和AI回复都需要保存下来,这样AI才能理解上下文。\n", "\n", "我们来构建一个简单的对话系统,每次循环都会把消息追加到历史中。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing_extensions import TypedDict\n", "from langgraph.graph import StateGraph, START, END\n", "\n", "class ChatState(TypedDict):\n", " messages: list\n", " current_user_input: str\n", "\n", "def process_message(state: ChatState):\n", " user_input = state['current_user_input']\n", " \n", " responses = {\n", " '你好': '你好!我是一个AI助手。',\n", " '我叫张三': '你好张三!很高兴认识你。',\n", " '我今天心情很好': '太好了!祝你有美好的一天!'\n", " }\n", " \n", " assistant_reply = responses.get(user_input, '抱歉,我不太理解。')\n", "\n", " new_message = {'user': user_input, 'assistant': assistant_reply}\n", " \n", " return {\n", " 'messages': state['messages'] + [new_message],\n", " 'current_user_input': ''\n", " }\n", "\n", "def route(state: ChatState):\n", " if state['current_user_input']:\n", " return 'process'\n", " return END\n", "\n", "builder = StateGraph(ChatState)\n", "builder.add_node('process', process_message)\n", "\n", "builder.add_edge(START, 'process')\n", "builder.add_conditional_edges(\n", " 'process',\n", " route,\n", " {\n", " 'process': 'process',\n", " END: END\n", " }\n", ")\n", "\n", "graph = builder.compile()\n", "\n", "state = {'messages': [], 'current_user_input': '你好'}\n", "state = graph.invoke(state)\n", "\n", "state['current_user_input'] = '我叫张三'\n", "state = graph.invoke(state)\n", "\n", "state['current_user_input'] = '我今天心情很好'\n", "state = graph.invoke(state)\n", "\n", "print(state['messages'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 代码解释\n", "\n", "1. **State 定义**:`ChatState` 包含两个字段:\n", " - `messages`:保存所有对话历史的列表\n", " - `current_user_input`:当前用户输入\n", "\n", "2. **process_message**:处理消息的核心逻辑:\n", " - 从状态中获取当前用户输入\n", " - 根据预设的规则生成助手回复\n", " - 将新消息追加到 `messages` 列表中(关键!)\n", " - 清空 `current_user_input`\n", "\n", "3. **route**:路由函数判断是否有新输入需要处理。\n", "\n", "4. **执行流程**:\n", " - 第一次调用:输入'你好',消息列表变成 `[{'user': '你好', ...}]`\n", " - 第二次调用:输入'我叫张三',消息列表变成两条记录\n", " - 第三次调用:输入'我今天心情很好',消息列表变成三条记录\n", "\n", "运行结果显示消息历史被正确累积,这就是状态持久化的基础。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. MemorySaver:持久化状态存储\n", "\n", "上面的例子中,我们手动传递状态。但在真实应用中,状态需要:\n", "\n", "- 在多次请求之间保持\n", "- 支持多个用户同时使用(会话隔离)\n", "- 重启后不丢失\n", "\n", "LangGraph 提供了 `MemorySaver` 来解决这个问题。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing_extensions import TypedDict\n", "from langgraph.graph import StateGraph, START, END\n", "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "class ChatState(TypedDict):\n", " messages: list\n", " current_user_input: str\n", "\n", "def process_message(state: ChatState):\n", " user_input = state['current_user_input']\n", " \n", " responses = {\n", " '你好': '你好!我是一个AI助手。',\n", " '我叫张三': '你好张三!很高兴认识你。',\n", " '我今天心情很好': '太好了!祝你有美好的一天!'\n", " }\n", " \n", " assistant_reply = responses.get(user_input, '抱歉,我不太理解。')\n", "\n", " new_message = {'user': user_input, 'assistant': assistant_reply}\n", " \n", " return {\n", " 'messages': state['messages'] + [new_message],\n", " 'current_user_input': ''\n", " }\n", "\n", "def route(state: ChatState):\n", " if state['current_user_input']:\n", " return 'process'\n", " return END\n", "\n", "builder = StateGraph(ChatState)\n", "builder.add_node('process', process_message)\n", "\n", "builder.add_edge(START, 'process')\n", "builder.add_conditional_edges(\n", " 'process',\n", " route,\n", " {\n", " 'process': 'process',\n", " END: END\n", " }\n", ")\n", "\n", "memory = MemorySaver()\n", "graph = builder.compile(checkpointer=memory)\n", "\n", "config = {'configurable': {'thread_id': 'session_1'}}\n", "\n", "graph.invoke({'messages': [], 'current_user_input': '你好'}, config)\n", "graph.invoke({'current_user_input': '我叫张三'}, config)\n", "graph.invoke({'current_user_input': '我今天心情很好'}, config)\n", "\n", "final_state = graph.get_state(config)\n", "print(final_state.values['messages'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 代码解释\n", "\n", "1. **导入 MemorySaver**:从 `langgraph.checkpoint.memory` 导入。\n", "\n", "2. **创建 MemorySaver 实例**:`memory = MemorySaver()`\n", "\n", "3. **编译时传入 checkpointer**:`graph = builder.compile(checkpointer=memory)`\n", "\n", "4. **配置会话ID**:\n", " ```python\n", " config = {'configurable': {'thread_id': 'session_1'}}\n", " ```\n", " `thread_id` 是会话的唯一标识,不同用户用不同的 `thread_id`。\n", "\n", "5. **多次调用**:每次调用只传入新的输入,不需要手动传递状态。\n", "\n", "6. **获取状态**:`graph.get_state(config)` 可以获取指定会话的当前状态。\n", "\n", "关键区别:之前需要手动传递 `state`,现在只需传入 `config`,MemorySaver 会自动保存和恢复状态。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. 多会话隔离\n", "\n", "MemorySaver 的一个重要特性是**会话隔离**。不同的 `thread_id` 会维护独立的状态。\n", "\n", "下面的例子演示两个用户同时使用同一个图,但各自的对话历史完全独立。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing_extensions import TypedDict\n", "from langgraph.graph import StateGraph, START, END\n", "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "class ChatState(TypedDict):\n", " messages: list\n", " current_user_input: str\n", "\n", "def process_message(state: ChatState):\n", " user_input = state['current_user_input']\n", " \n", " responses = {\n", " '你好': '你好!我是一个AI助手。',\n", " '我是Alice': '你好Alice!很高兴认识你。',\n", " '我是Bob': '你好Bob!很高兴认识你。'\n", " }\n", " \n", " assistant_reply = responses.get(user_input, '抱歉,我不太理解。')\n", "\n", " new_message = {'user': user_input, 'assistant': assistant_reply}\n", " \n", " return {\n", " 'messages': state['messages'] + [new_message],\n", " 'current_user_input': ''\n", " }\n", "\n", "def route(state: ChatState):\n", " if state['current_user_input']:\n", " return 'process'\n", " return END\n", "\n", "builder = StateGraph(ChatState)\n", "builder.add_node('process', process_message)\n", "\n", "builder.add_edge(START, 'process')\n", "builder.add_conditional_edges(\n", " 'process',\n", " route,\n", " {\n", " 'process': 'process',\n", " END: END\n", " }\n", ")\n", "\n", "memory = MemorySaver()\n", "graph = builder.compile(checkpointer=memory)\n", "\n", "config_a = {'configurable': {'thread_id': 'user_a'}}\n", "config_b = {'configurable': {'thread_id': 'user_b'}}\n", "\n", "graph.invoke({'messages': [], 'current_user_input': '你好'}, config_a)\n", "graph.invoke({'current_user_input': '我是Alice'}, config_a)\n", "\n", "graph.invoke({'messages': [], 'current_user_input': 'Hi'}, config_b)\n", "graph.invoke({'current_user_input': '我是Bob'}, config_b)\n", "\n", "state_a = graph.get_state(config_a)\n", "state_b = graph.get_state(config_b)\n", "\n", "print('用户A的对话历史:')\n", "print(state_a.values['messages'])\n", "print()\n", "print('用户B的对话历史:')\n", "print(state_b.values['messages'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 代码解释\n", "\n", "1. **创建两个配置**:\n", " - `config_a` 使用 `thread_id: 'user_a'`\n", " - `config_b` 使用 `thread_id: 'user_b'`\n", "\n", "2. **分别调用**:\n", " - 用户A发送了'你好'和'我是Alice'\n", " - 用户B发送了'Hi'和'我是Bob'\n", "\n", "3. **获取各自状态**:\n", " - 用户A的消息历史只有自己的两条消息\n", " - 用户B的消息历史只有自己的两条消息\n", "\n", "运行结果清楚地展示了会话隔离:两个用户的对话历史完全独立,互不干扰。\n", "\n", "这就是多用户聊天系统的核心原理。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. 使用标准消息类型管理对话\n", "\n", "在实际应用中,我们通常使用 LangChain 的标准消息类型(如 `HumanMessage`、`AIMessage`)来管理对话。\n", "\n", "为了让 MemorySaver 能够自动追加消息而不是覆盖,我们需要使用 `add_messages` reducer。\n", "\n", "这种方式的特点:\n", "- 使用标准的消息类型,便于与LangChain组件集成\n", "- 自动追加消息,避免手动拼接\n", "- 方便与 LLM 集成" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing_extensions import TypedDict\n", "from typing import Annotated\n", "from langgraph.graph import StateGraph, START, END\n", "from langgraph.graph.message import add_messages\n", "from langgraph.checkpoint.memory import MemorySaver\n", "from langchain_core.messages import HumanMessage, AIMessage\n", "\n", "class ChatState(TypedDict):\n", " messages: Annotated[list, add_messages]\n", "\n", "def process_message(state: ChatState):\n", " last_message = state['messages'][-1]\n", " \n", " if isinstance(last_message, HumanMessage):\n", " user_input = last_message.content\n", " \n", " responses = {\n", " '你好': '你好!我是一个AI助手。',\n", " '我叫张三': '你好张三!很高兴认识你。',\n", " }\n", " \n", " assistant_reply = responses.get(user_input, '抱歉,我不太理解。')\n", " \n", " return {'messages': [AIMessage(content=assistant_reply)]}\n", " \n", " return state\n", "\n", "def route(state: ChatState):\n", " last_message = state['messages'][-1]\n", " if isinstance(last_message, HumanMessage):\n", " return 'process'\n", " return END\n", "\n", "builder = StateGraph(ChatState)\n", "builder.add_node('process', process_message)\n", "\n", "builder.add_edge(START, 'process')\n", "builder.add_conditional_edges(\n", " 'process',\n", " route,\n", " {\n", " 'process': 'process',\n", " END: END\n", " }\n", ")\n", "\n", "memory = MemorySaver()\n", "graph = builder.compile(checkpointer=memory)\n", "\n", "config = {'configurable': {'thread_id': 'session_with_messages'}}\n", "\n", "graph.invoke({'messages': [HumanMessage(content='你好')]}, config)\n", "graph.invoke({'messages': [HumanMessage(content='我叫张三')]}, config)\n", "graph.invoke({'messages': [HumanMessage(content='今天天气怎么样')]}, config)\n", "\n", "final_state = graph.get_state(config)\n", "for msg in final_state.values['messages']:\n", " if isinstance(msg, HumanMessage):\n", " print(f'Human: {msg.content}')\n", " elif isinstance(msg, AIMessage):\n", " print(f'AI: {msg.content}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 代码解释\n", "\n", "1. **导入 add_messages reducer**:`from langgraph.graph.message import add_messages`\n", " - reducer 用于控制状态字段的更新方式\n", " - `add_messages` 会自动将新消息追加到列表中,而不是覆盖\n", "\n", "2. **定义 ChatState**:使用 `Annotated[list, add_messages]` 定义消息字段\n", " - `Annotated` 是 Python 的类型提示工具,用于添加元数据\n", " - 这里告诉 LangGraph 对 `messages` 字段使用 `add_messages` reducer\n", "\n", "3. **导入消息类型**:\n", " - `HumanMessage`:用户消息\n", " - `AIMessage`:AI回复\n", "\n", "4. **process_message**:\n", " - 获取最后一条消息\n", " - 判断是否是用户消息\n", " - 如果是,生成回复并返回 `{'messages': [AIMessage(...)]}`\n", " - 由于使用了 `add_messages`,返回的新消息会自动追加到历史中\n", "\n", "5. **route**:\n", " - 如果最后一条是用户消息,继续处理\n", " - 如果是AI消息,说明已经处理过了,结束\n", "\n", "6. **调用方式**:每次传入 `{'messages': [HumanMessage(content=...)]}`\n", " - MemorySaver 会自动保存状态\n", " - `add_messages` reducer 会自动追加新消息\n", "\n", "这种方式的优点:\n", "- 使用标准的消息类型,便于与LangChain组件集成\n", "- 自动追加消息,避免手动拼接\n", "- 后续可以直接将消息列表传给LLM" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. 综合案例:带工具调用的循环智能体\n", "\n", "现在我们把循环、记忆和工具调用结合起来,构建一个完整的智能体。\n", "\n", "流程目标:\n", "- 用户询问关于天气或时间的问题\n", "- 智能体判断是否需要调用工具\n", "- 如果需要,调用工具获取信息\n", "- 如果不需要,直接回答\n", "- 循环直到任务完成\n", "- 使用MemorySaver保存对话历史" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from typing_extensions import TypedDict\n", "from langgraph.graph import StateGraph, START, END\n", "from langgraph.checkpoint.memory import MemorySaver\n", "from langchain_core.messages import HumanMessage, AIMessage, ToolMessage\n", "\n", "class AgentState(TypedDict):\n", " messages: list\n", " next_tool: str\n", "\n", "def decide_tool(state: AgentState):\n", " last_message = state['messages'][-1]\n", " user_input = last_message.content\n", " \n", " print(f'用户问了:{user_input}')\n", " \n", " if '几点' in user_input or '时间' in user_input:\n", " print('需要调用工具:get_time')\n", " return {'next_tool': 'get_time'}\n", " elif '天气' in user_input:\n", " print('需要调用工具:get_weather')\n", " return {'next_tool': 'get_weather'}\n", " else:\n", " print('不需要调用工具,直接回答')\n", " return {'next_tool': None}\n", "\n", "def call_tool(state: AgentState):\n", " tool_name = state['next_tool']\n", " \n", " if tool_name == 'get_time':\n", " result = '2024-01-15 14:30:00'\n", " elif tool_name == 'get_weather':\n", " result = '晴天,25度'\n", " else:\n", " result = '未知工具'\n", " \n", " print(f'调用工具 {tool_name},结果:{result}')\n", " \n", " return {\n", " 'messages': state['messages'] + [ToolMessage(content=result, tool_call_id='1')],\n", " 'next_tool': None\n", " }\n", "\n", "def summarize(state: AgentState):\n", " last_message = state['messages'][-1]\n", " \n", " if isinstance(last_message, ToolMessage):\n", " tool_result = last_message.content\n", " reply = f'总结回答:{tool_result}'\n", " else:\n", " reply = '抱歉,我不太理解你的问题。'\n", " \n", " print(reply)\n", " \n", " return {\n", " 'messages': state['messages'] + [AIMessage(content=reply)]\n", " }\n", "\n", "def route_after_decide(state: AgentState):\n", " if state['next_tool']:\n", " return 'call_tool'\n", " return 'summarize'\n", "\n", "def route_after_summarize(state: AgentState):\n", " last_message = state['messages'][-1]\n", " if isinstance(last_message, HumanMessage):\n", " return 'decide_tool'\n", " return END\n", "\n", "builder = StateGraph(AgentState)\n", "builder.add_node('decide_tool', decide_tool)\n", "builder.add_node('call_tool', call_tool)\n", "builder.add_node('summarize', summarize)\n", "\n", "builder.add_edge(START, 'decide_tool')\n", "builder.add_conditional_edges(\n", " 'decide_tool',\n", " route_after_decide,\n", " {\n", " 'call_tool': 'call_tool',\n", " 'summarize': 'summarize'\n", " }\n", ")\n", "builder.add_edge('call_tool', 'summarize')\n", "builder.add_conditional_edges(\n", " 'summarize',\n", " route_after_summarize,\n", " {\n", " 'decide_tool': 'decide_tool',\n", " END: END\n", " }\n", ")\n", "\n", "memory = MemorySaver()\n", "graph = builder.compile(checkpointer=memory)\n", "\n", "config = {'configurable': {'thread_id': 'tool_agent_session'}}\n", "\n", "graph.invoke({'messages': [HumanMessage(content='现在几点了')], 'next_tool': None}, config)\n", "\n", "current_state = graph.get_state(config)\n", "new_messages = current_state.values['messages'] + [HumanMessage(content='今天天气怎么样')]\n", "graph.invoke({'messages': new_messages, 'next_tool': None}, config)\n", "\n", "final_state = graph.get_state(config)\n", "print()\n", "print('完整对话历史:')\n", "for msg in final_state.values['messages']:\n", " if isinstance(msg, HumanMessage):\n", " print(f'Human: {msg.content}')\n", " elif isinstance(msg, AIMessage):\n", " print(f'AI: {msg.content}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 代码解释\n", "\n", "这个案例展示了一个完整的智能体流程:\n", "\n", "1. **AgentState**:使用 TypedDict 定义状态,包含:\n", " - `messages`:消息列表\n", " - `next_tool`:下一个要调用的工具\n", "\n", "2. **decide_tool**:判断是否需要调用工具\n", " - 如果用户问时间,返回 `{'next_tool': 'get_time'}`\n", " - 如果用户问天气,返回 `{'next_tool': 'get_weather'}`\n", " - 如果不需要工具,返回 `{'next_tool': None}`\n", "\n", "3. **call_tool**:执行工具调用\n", " - 根据 `next_tool` 调用相应工具\n", " - 将结果追加到 `messages` 列表\n", "\n", "4. **summarize**:总结回答\n", " - 如果最后是工具消息,提取结果并生成回答\n", " - 将回答追加到 `messages` 列表\n", "\n", "5. **route_after_decide**:decide_tool 之后的路由\n", " - 如果 `next_tool` 有值,进入 `call_tool`\n", " - 如果 `next_tool` 为 None,进入 `summarize`\n", "\n", "6. **route_after_summarize**:summarize 之后的路由\n", " - 如果最后是用户消息,回到 `decide_tool`\n", " - 否则结束\n", "\n", "流程图:\n", "\n", "```\n", "START -> decide_tool -> [条件边] -> call_tool -> summarize\n", " ^ |\n", " | |\n", " +--------------+\n", "```\n", "\n", "这个流程可以处理多轮对话,每轮都可以调用工具并保存历史。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9. 总结\n", "\n", "### 核心知识点\n", "\n", "1. **循环实现**:通过条件边让流程回到之前的节点,形成循环\n", "2. **状态累积**:在节点中不断追加数据到状态字段(如消息列表)\n", "3. **MemorySaver**:自动保存和恢复状态,无需手动传递\n", "4. **会话隔离**:通过 `thread_id` 区分不同用户的会话\n", "5. **add_messages reducer**:使用 `Annotated[list, add_messages]` 实现消息自动追加\n", "\n", "### 关键API\n", "\n", "| API | 作用 |\n", "| --- | --- |\n", "| `MemorySaver()` | 创建内存状态存储 |\n", "| `builder.compile(checkpointer=memory)` | 编译时启用状态持久化 |\n", "| `config = {'configurable': {'thread_id': ...}}` | 设置会话ID |\n", "| `graph.get_state(config)` | 获取指定会话的状态 |\n", "| `add_messages` | 自动追加消息的 reducer |\n", "\n", "### 实践要点\n", "\n", "- 循环的关键是路由函数返回某个已存在的节点名\n", "- 状态累积需要在节点中返回新的状态值\n", "- 使用 `Annotated[list, add_messages]` 可以让 MemorySaver 自动追加消息\n", "- MemorySaver 适合开发和测试,生产环境可使用其他存储后端\n", "- 会话隔离通过 `thread_id` 实现,必须为每个用户分配唯一ID" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 练习\n", "\n", "1. 修改计数器示例,让它从10递减到0\n", "2. 扩展对话示例,添加更多回复规则\n", "3. 创建一个多轮问答系统,支持用户追问\n", "4. 尝试使用不同的 `thread_id` 模拟三个用户同时对话" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.4" } }, "nbformat": 4, "nbformat_minor": 4 }