LangChain 对话记忆 ———— Checkpointer 在默认情况下,每次 agent.invoke() 都是独立的,Agent 不记得之前聊过什么。Checkpointer 让 Agent 能够记住对话历史,实现真正的多轮对话。 在没有 Checkpinter 的时候,第二轮调用 agent 回答时,不会记住第一轮说过的话。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from dotenv import load_dotenvload_dotenv() from langchain.agents import create_agentfrom langchain.chat_models import init_chat_modelfrom langchain.messages import HumanMessagemodel = init_chat_model("deepseek:deepseek-v4-flash" , temperature=0 ) agent = create_agent( model=model, system_prompt="你是菜鸟教程 RUNOOB 的助手。" , ) result1 = agent.invoke({ "messages" : [HumanMessage(content="我叫小明" )] }) print (f"第一轮: {result1['messages' ][-1 ].content} " )result2 = agent.invoke({ "messages" : [HumanMessage(content="我叫什么名字?" )] }) print (f"第二轮: {result2['messages' ][-1 ].content} " )
这时候我们可以使用 Checkpointer 来记住对话内容,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from langgraph.checkpoint.memory import InMemorySaverfrom langchain.agents import create_agentfrom langchain.chat_models import init_chat_modelfrom langchain.messages import HumanMessagecheckpointer = InMemorySaver() model = init_chat_model("deepseek:deepseek-v4-flash" , temperature=0 ) agent = create_agent( model=model, checkpointer=checkpointer, system_prompt="你是菜鸟教程 RUNOOB 的助手。" , ) config = {"configurable" : {"thread_id" : "user-001" }} result1 = agent.invoke( {"messages" : [HumanMessage(content="我叫小明,我在学 Python" )]}, config=config, ) print (f"第一轮: {result1['messages' ][-1 ].content} " )result2 = agent.invoke( {"messages" : [HumanMessage(content="我叫什么名字?我在学什么?" )]}, config=config, ) print (f"第二轮: {result2['messages' ][-1 ].content} " )
我们先创建一个内存,在 Agent 创建的时候传入该内存,然后在 config 中使用 thread_id 来标记对话线程,这样 Agent 就会知道哪些话是同一对话里的,可以做到自动关联。
Checkpointer 的工作原理是这样的,它在每次 Agent 执行后自动保存状态快照(checkpoint)。下一次使用相同 thread_id 调用时,自动从最近的 checkpoint 恢复状态。具体工作流程:调用 agent.invoke(),传入 config(含 thread_id),Agent 检查是否有该 thread_id 的 checkpoint,如果有,加载历史消息,追加新消息后继续,执行完成后,自动保存新的 checkpoint。
Checkpointer 一般有三种类型如下:
类型
存储位置
持久化
适用场景
InMemorySaver
内存
否
开发调试、测试
SqliteSaver
SQLite 文件
是
单机部署、小规模应用
PostgresSaver
PostgreSQL
是
生产环境、多实例共享
LangChain 跨会话存储 ———— store Checkpointer 解决了”单个对话内记忆”的问题。但如果你需要在不同对话之间共享数据——比如用户偏好、学习进度——就需要用到 Store。
Store 使用 命名空间 + 键 的层级结构来组织数据,其基本操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from langgraph.store.memory import InMemoryStorestore = InMemoryStore() store.put( ("users" , "user_001" ), "preferences" , { "theme" : "dark" , "language" : "zh-CN" , "level" : "入门" , } ) store.put( ("users" , "user_001" ), "progress" , { "completed_courses" : ["HTML 基础" , "Python 基础" ], "total_hours" : 35 , } ) prefs = store.get(("users" , "user_001" ), "preferences" ) print (f"偏好设置: {prefs.value} " )progress = store.get(("users" , "user_001" ), "progress" ) print (f"学习进度: {progress.value} " )all_user_data = store.search(("users" , "user_001" )) print (f"\n用户的所有数据 ({len (all_user_data)} 项):" )for item in all_user_data: print (f" {item.key} : {item.value} " ) store.delete(("users" , "user_001" ), "preferences" ) deleted = store.get(("users" , "user_001" ), "preferences" ) print (f"\n删除后: {deleted} " )
将 Store 传给 create_agent(),Agent 中的所有工具都能通过 InjectedStore 访问它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @tool def query_course_info ( course_name: str , store: Annotated[BaseStore, InjectedStore( )], ) -> str : """查询菜鸟教程 RUNOOB 中课程的详细信息。 Args: course_name: 课程名称 """ item = store.get(("runoob" , "courses" ), "catalog" ) catalog = item.value if item else {} if course_name in catalog: info = catalog[course_name] return ( f"《{course_name} 》- 价格:{info['price' ]} ," f"时长:{info['hours' ]} 小时,难度:{info['level' ]} " ) return f"未找到课程《{course_name} 》"
InMemoryStore 的数据在程序重启后丢失。生产环境可以使用 PostgresStore 等持久化方案:
1 2 3 4 5 6 7 from langgraph.store.memory import InMemoryStorestore = InMemoryStore()
人工介入 在生产环境中,有些操作需要人工确认,例如发送邮件、执行删除、处理支付等等。人工介入(HITL,Human-in-the-Loop)让 Agent 在关键时刻暂停,等待人工审批后继续。
interrupt() —— 在工具中暂停执行 interrupt() 函数可以让工具执行到一半时暂停,等待外部输入后再继续,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from langgraph.types import interruptdef send_email (to: str , subject: str , body: str ) -> str : """发送邮件(需要人工审批)""" approval = interrupt({ "action" : "send_email" , "to" : to, "subject" : subject, "body" : body, "message" : "请确认是否发送此邮件?" }) if approval.get("approved" ): return f"邮件已发送给 {to} " else : return f"邮件发送已被拒绝:{approval.get('reason' , '用户取消' )} "
interrupt() 的工作流程如下:
工具调用 interrupt(),Agent 暂停执行。
外部系统获取中断信息,展示给用户
用户做出决定后,通过 Command(resume=…) 恢复执行
interrupt() 返回用户传入的值,工具继续执行
审批流程的完整示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from dotenv import load_dotenvload_dotenv() from langgraph.types import interrupt, Commandfrom langgraph.checkpoint.memory import InMemorySaverfrom langchain.tools import toolfrom langchain.agents import create_agentfrom langchain.chat_models import init_chat_modelfrom langchain.messages import HumanMessage@tool def delete_course (course_name: str ) -> str : """删除课程(需要审批)。 Args: course_name: 要删除的课程名称 """ approval = interrupt({ "action" : "delete_course" , "course" : course_name, "message" : f"确认删除课程《{course_name} 》?此操作不可撤销。" }) if approval.get("confirmed" ): return f"课程《{course_name} 》已删除" else : return f"删除操作已取消" checkpointer = InMemorySaver() model = init_chat_model("deepseek:deepseek-v4-flash" , temperature=0 ) agent = create_agent( model=model, tools=[delete_course], checkpointer=checkpointer, system_prompt="你是菜鸟教程 RUNOOB 的管理员助手。" , ) config = {"configurable" : {"thread_id" : "admin-001" }} print ("=== 开始执行 ===" )result = agent.invoke( {"messages" : [HumanMessage(content="请删除课程《过时的 Java 教程》" )]}, config=config, )
除了在工具中使用 interrupt(),我们还可以在 create_agent() 中设置全局中断点,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from langgraph.checkpoint.memory import InMemorySavercheckpointer = InMemorySaver() agent = create_agent( model="deepseek:deepseek-v4-flash" , tools=[some_tool], checkpointer=checkpointer, interrupt_before=["tools" ], )
下面是一个实际 Web 应用中的人工干预实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def handle_user_message (thread_id: str , message: str ): config = {"configurable" : {"thread_id" : thread_id}} result = agent.invoke( {"messages" : [HumanMessage(content=message)]}, config=config, ) state = agent.get_state(config) if state.tasks and state.tasks[0 ].interrupts: return { "status" : "pending_approval" , "interrupt" : state.tasks[0 ].interrupts[0 ].value, "thread_id" : thread_id, } return { "status" : "completed" , "reply" : result["messages" ][-1 ].content, } def handle_approval (thread_id: str , approved: bool , reason: str = "" ): config = {"configurable" : {"thread_id" : thread_id}} result = agent.invoke( Command(resume={"confirmed" : approved, "reason" : reason}), config=config, ) return {"status" : "completed" , "reply" : result["messages" ][-1 ].content}
LangChain 多 Agent 当一个任务太复杂,单个 Agent 难以胜任时,我们可以创建多个各司其职的 Agent,让它们像团队一样协作。
单个 Agent 可能会存在以下问题:system_prompt 太长会导致模型注意力分散,工具太多会增加模型选择工具的出错概率,不同类型的任务需要不同的专业知识和行为风格。
方式一、子 Agent 作为工具 将 Agent 编译成 CompiledStateGraph,然后作为一个工具注册给父 Agent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 from dotenv import load_dotenvload_dotenv() from langchain.tools import toolfrom langchain.agents import create_agentfrom langchain.chat_models import init_chat_modelfrom langchain.messages import HumanMessagemodel = init_chat_model("deepseek:deepseek-v4-flash" , temperature=0 ) @tool def get_weather (city: str ) -> str : """查询天气""" data = {"杭州" : "晴,25°C" , "北京" : "多云,18°C" } return data.get(city, f"{city} : 数据暂缺" ) weather_agent = create_agent( model=model, tools=[get_weather], name="weather_expert" , system_prompt="你是天气专家,专门回答天气相关问题。回答要简洁。" , ) @tool def calculate (expression: str ) -> str : """计算数学表达式""" result = eval (expression, {"__builtins__" : {}}, {}) return f"{expression} = {result} " math_agent = create_agent( model=model, tools=[calculate], name="math_expert" , system_prompt="你是数学专家,专门进行数学计算。回答要简洁。" , ) @tool def ask_weather_expert (question: str ) -> str : """向天气专家咨询天气相关问题。 Args: question: 关于天气的问题 """ result = weather_agent.invoke( {"messages" : [HumanMessage(content=question)]} ) return result["messages" ][-1 ].content @tool def ask_math_expert (question: str ) -> str : """向数学专家咨询数学计算问题。 Args: question: 数学计算问题 """ result = math_agent.invoke( {"messages" : [HumanMessage(content=question)]} ) return result["messages" ][-1 ].content coordinator = create_agent( model=model, tools=[ask_weather_expert, ask_math_expert], system_prompt="""你是协调助手。根据用户问题选择合适的专家: - 天气相关问题 → 使用 ask_weather_expert - 数学计算问题 → 使用 ask_math_expert - 如果同时涉及多个领域,依次咨询各个专家""" ,) result = coordinator.invoke({ "messages" : [HumanMessage( content="杭州今天天气怎么样?如果温度是 25 度,换算成华氏度是多少?" "(公式:华氏度 = 摄氏度 × 9/5 + 32)" )] }) print (result["messages" ][-1 ].content)
方式二、用 name 参数区分 Agent 当你将子 Agent 作为工具嵌入时,设置 name 参数有助于追踪消息来源:
1 2 3 4 5 6 7 8 9 10 agent = create_agent( model=model, tools=[...], name="customer_service" , )
方式三、Middleware 实现 Agent 路由 更加复杂的多 Agent 场景可以通过 Middleware 实现动态路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from langchain.agents.middleware import before_modelgeneral_tools = [tool_a, tool_b] admin_tools = [tool_c, tool_d] @before_model def route_by_user_role (state, runtime ): """根据用户角色动态切换可用工具""" context = runtime.context if context is None : return None user_role = context.get("user_role" , "user" ) if user_role == "admin" : available_tools = general_tools + admin_tools else : available_tools = general_tools return None
多 Agent 有以下几种架构:
模式
结构
适用场景
协调者模式
一个父Agent —> 多个子 Agent 工具
任务类型明确可分类
接力模式
Agent A 的输出 -> Agent B 的输入
流水线式处理(生成 -> 审核 -> 润色)
辩论模式
多个 Agent 并行输出 -> 汇总决策
需要多角度分析问题