)
1. 为什么你的 Agent 聊三句就“失忆”LangGraph 长短期记忆系统到底解决什么问题如果你正在用 LangGraph 搭 Agent大概率遇到过这种尴尬第一轮告诉它“我叫 Ada对花生过敏”第三轮问它“帮我推荐个菜”它热情洋溢地推了一盘宫保鸡丁。不是模型笨是它压根没把上一轮的话带进这一轮的上下文里。这就是 Agent 记忆系统要解决的核心问题。所谓记忆系统说白了就是给 Agent 装两套“记事本”一套是短期记忆管当前这条对话线里说过什么保证多轮对话不跑题另一套是长期记忆跨对话、跨会话地记住用户是谁、偏好什么、干过什么让 Agent 越用越懂你。LangGraph 把这两套东西抽象成了两个组件——Checkpointer负责短期Store负责长期配合thread_id和namespace做隔离设计得相当干净。适合谁来读这篇三类人一是刚上手 LangGraph、想搞明白checkpointer和store到底怎么配合的开发者二是已经能跑通单轮 Agent、但多轮对话一塌糊涂、想补上记忆这块短板的工程师三是准备做 Multi-Agent 或带 MCP 工具调用的复杂系统、需要跨会话召回能力的团队。这篇不空谈概念从环境准备到可复制代码到报错排查一条龙走完最后你本地能跑出一个“记得住名字、跨会话还能召回”的 Agent。模型调用这块我用 TaoToken 的统一 Key 通道接入一个 Key 打通对话模型和 Embedding省得在多个平台之间来回切配置。下面所有代码里的BASE_URL、TOKEN、MODEL_NAME三个变量都指向 TaoToken 的 API 通道你换成自己的 Key 就能直接跑。2. TaoToken 统一 Key 接入一次配置打通对话与向量模型在动手写记忆逻辑之前先把模型通道铺好。很多教程卡人的地方就在这——对话模型一个 KeyEmbedding 模型又一个 Key环境变量满天飞跑一半报 401 都不知道是哪个 Key 失效了。TaoToken 的思路是统一入口对话、Embedding、后续可能用到的工具调用都走同一个 Base URL 和同一个 Key配置量直接砍半。先说清楚它是什么、能做什么。TaoToken 提供的是 OpenAI 兼容的 API 通道也就是说你原来用langchain_openai或init_chat_model写的代码只需要改base_url和api_key两个参数其余逻辑一行不用动。它适合谁适合不想在多个模型供应商之间维护一堆 Key、又想在 LangGraph 里快速切换模型的开发者。你可以在模型对话页面先验证通道是否通再去控制台生成 API Key最后照着接入文档把配置贴进代码。具体操作路径是这样的先到模型对话页发一条测试消息确认通道正常然后进控制台创建 API Key接着打开接入文档对照参数。这三步走完你手里就有了BASE_URL、TOKEN、MODEL_NAME三件套。注意Base URL 用https://taotoken.net/api不要带多余的路径后缀LangChain 的 OpenAI 兼容层会自动拼接/chat/completions和/embeddings。这里有个我踩过的坑init_chat_model的model_provider必须显式写成openai哪怕你用的模型名字看起来不像 GPT 系列。因为 LangChain 是靠model_provider决定用哪套请求协议而不是靠模型名猜。写成openai之后它会走标准的 OpenAI 兼容格式TaoToken 这边就能正常解析。配置建议用环境变量管理别硬编码在代码里。建一个.env文件TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_API_KEY你的Key TAOTOKEN_MODEL_ID你的模型ID然后在 Python 里用os.getenv读取。这样你本地调试、服务器部署、CI 跑测试改一处环境变量就行代码零改动。如果你后面要接 Coding Plan 做长期编码任务或者用 Claude Code 做 Agent 开发这套 Key 也能复用不用重新申请。模型通道铺好之后短期记忆和长期记忆的实现就顺理成章了。下一节直接上可复制的 LangGraph 配置。3. 可复制配置LangGraph 短期缓冲 长期向量存储的完整代码这一节是全文的技术核心我把短期记忆Checkpointer和长期记忆Store的配置拆成可复制的片段你按顺序贴进项目就能跑。先给结论短期记忆靠thread_id隔离对话线长期记忆靠namespace隔离用户空间两者通过graph.compile(checkpointer..., store...)同时注入。先装依赖pip install -U langgraph langchain langchain-openai langgraph-checkpoint-postgres psycopg[binary,pool]如果你只想先跑内存版验证逻辑Postgres 相关的包可以晚点装。下面先给内存版配置跑通之后再换数据库。短期记忆配置InMemorySaverimport os from langchain.chat_models import init_chat_model from langgraph.checkpoint.memory import InMemorySaver from langgraph.prebuilt import create_react_agent BASE_URL os.getenv(TAOTOKEN_BASE_URL) TOKEN os.getenv(TAOTOKEN_API_KEY) MODEL_NAME os.getenv(TAOTOKEN_MODEL_ID) model init_chat_model( modelMODEL_NAME, model_provideropenai, base_urlBASE_URL, api_keyTOKEN, temperature0, ) checkpointer InMemorySaver() agent create_react_agent( modelmodel, tools[], checkpointercheckpointer, )这段代码的关键在checkpointercheckpointer。没有它每次invoke都是无状态的有了它只要thread_id相同LangGraph 就会在多次调用之间维持对话状态。thread_id通过config传入config {configurable: {thread_id: session-1}} response agent.invoke( {messages: [{role: user, content: 你好我叫 Ada}]}, config, )长期记忆配置InMemoryStore 语义检索长期记忆需要 Embedding 支持语义检索。TaoToken 的通道同时提供对话和 Embedding 接口配置方式一致from langgraph.store.memory import InMemoryStore from langchain_openai import OpenAIEmbeddings embeddings OpenAIEmbeddings( model你的Embedding模型ID, base_urlBASE_URL, api_keyTOKEN, ) store InMemoryStore( index{ embed: embeddings, dims: 1536, } )dims要和你实际用的 Embedding 模型输出维度对齐写错了会在store.put时报维度不匹配。如果你不确定维度先单独调一次 Embedding 接口看返回的向量长度。把两者同时注入图from langgraph.graph import StateGraph, MessagesState, START from langgraph.store.base import BaseStore from langchain_core.runnables import RunnableConfig def call_model( state: MessagesState, config: RunnableConfig, *, store: BaseStore, ): user_id config[configurable][user_id] namespace (memories, user_id) memories store.search( namespace, querystr(state[messages][-1].content), ) info \n.join([d.value[data] for d in memories]) system_msg f你是一个助手已知用户信息{info} last_message state[messages][-1] if 记住 in last_message.content: store.put(namespace, user_name, {data: 用户名字是 Ada}) response model.invoke( [{role: system, content: system_msg}] state[messages] ) return {messages: response} builder StateGraph(MessagesState) builder.add_node(call_model) builder.add_edge(START, call_model) graph builder.compile(checkpointercheckpointer, storestore)注意call_model函数签名里的*, store: BaseStore这是 LangGraph 的依赖注入约定——只要你在节点函数里声明store参数框架就会自动把compile时传入的 store 实例注入进来。同理config参数会自动拿到调用时传的configurable字典。生产环境换 Postgres内存版跑通后把InMemorySaver和InMemoryStore换成 Postgres 版本代码结构不变from langgraph.checkpoint.postgres import PostgresSaver from langgraph.store.postgres import PostgresStore DB_URI postgresql://postgres:postgreslocalhost:5432/postgres?sslmodedisable with ( PostgresStore.from_conn_string(DB_URI) as store, PostgresSaver.from_conn_string(DB_URI) as checkpointer, ): store.setup() checkpointer.setup() graph builder.compile(checkpointercheckpointer, storestore)setup()只需在第一次调用时执行它会自动建表。跑完之后你去数据库里看会多出checkpoints、checkpoint_writes、store等几张表短期记忆的快照和长期记忆的向量都存在里面。配置到这就齐了。下一节验证请求看它到底记不记得住。4. 端到端验证多轮对话记忆保持与跨会话召回实测配置写完不验证等于没写。这一节我分两个场景测一是同一thread_id下的多轮记忆保持二是不同thread_id之间的长期记忆召回。两个都过了才算真正跑通。场景一短期记忆保持config {configurable: {thread_id: t1, user_id: u1}} r1 graph.invoke( {messages: [{role: user, content: 你好我叫 Ada记住这个名字}]}, config, ) print(第一轮:, r1[messages][-1].content) r2 graph.invoke( {messages: [{role: user, content: 我叫什么名字}]}, config, ) print(第二轮:, r2[messages][-1].content)预期输出第二轮应该能答出“Ada”。如果答不出来先检查thread_id是否一致再检查checkpointer是否真的传进了compile。场景二跨会话长期记忆召回换一个thread_id但保持user_id不变config2 {configurable: {thread_id: t2, user_id: u1}} r3 graph.invoke( {messages: [{role: user, content: 我的名字是什么}]}, config2, ) print(新会话:, r3[messages][-1].content)这里的关键是user_id相同所以namespace (memories, u1)一致store.search能检索到之前写入的记忆。如果user_id也换了那就真的什么都查不到——这是设计如此不是 bug。验证 Store 里到底存了什么items store.search((memories, u1), query名字) for item in items: print(item.key, item.value, item.score)score是语义相似度得分越接近 1 越相关。如果你看到score是None说明index没配好Embedding 没生效检索退化成了精确匹配。验证 Checkpointer 的状态历史state graph.get_state(config) print(当前消息数:, len(state.values[messages])) history list(graph.get_state_history(config)) print(历史快照数:, len(history))get_state_history会按时间倒序返回该线程的所有检查点你可以用它做“时间旅行”——回滚到某一轮重新执行。这在调试 Agent 决策逻辑时特别好用。两个场景都跑通说明你的记忆系统已经能用了。但实际部署时报错往往比想象中多。下一节把常见坑列出来。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 逐个击破记忆系统跑不起来八成不是 LangGraph 的问题而是模型通道或配置细节出了岔子。我把实际遇到过的几类报错整理出来对照着查。报错一401 Unauthorizedopenai.AuthenticationError: Error code: 401 - {error: {message: Invalid API key}}原因通常是 Key 没读到、Key 失效、或者base_url写错导致请求打到了别的服务。排查顺序先打印os.getenv(TAOTOKEN_API_KEY)看是不是None再确认.env文件有没有被load_dotenv()加载最后检查base_url是不是https://taotoken.net/api多一个斜杠或少一个/api都可能 401。如果 Key 是从控制台新生成的确认没有复制到多余空格。报错二local proxy failed / Connection errorhttpx.ConnectError: [Errno 111] Connection refused这类报错一般是本地网络环境或代理配置干扰。检查你的HTTP_PROXY、HTTPS_PROXY环境变量是否指向了一个不可用的地址。如果你在容器里跑确认容器能访问外网。另外base_url如果写成了localhost或内网地址也会连接失败。把base_url明确写成https://taotoken.net/api再试。报错三reading choices / KeyError: choicesKeyError: choices这个报错说明返回的 JSON 结构里没有choices字段通常是请求打到了非 OpenAI 兼容的端点或者模型 ID 写错了导致服务端返回了错误信息。检查MODEL_NAME是否和你在模型对话页验证时用的一致。还有一种情况是model_provider没写openaiLangChain 用了别的协议去解析响应自然找不到choices。报错四OAuth / 认证流程相关如果你在接 Claude Code 或某些需要 OAuth 的工具链时遇到认证跳转失败先确认你用的是 API Key 模式而不是 OAuth 模式。TaoToken 的通道走的是标准 API Key 认证不需要浏览器跳转。在 Claude Code 的配置里把认证方式设为 API Key填入TAOTOKEN_API_KEYBase URL 填https://taotoken.net/api模型 ID 填你验证过的那个。三件套齐全OAuth 相关的报错就不会出现。报错五Store 维度不匹配ValueError: Embedding dimension mismatchInMemoryStore的dims参数必须和 Embedding 模型实际输出维度一致。解决办法单独调一次 Embedding 接口len(response.data[0].embedding)就是真实维度把它填进dims。报错六thread_id 没传导致记忆失效这个不算报错但最容易被忽略。如果你invoke时没传config或者config里没有configurable.thread_idLangGraph 每次都会当成新会话记忆自然不生效。养成习惯所有invoke和stream调用都带上完整的config。排查完这些你的记忆系统基本就稳了。最后说下后续怎么扩展。6. 从单 Agent 到 Multi-Agent记忆系统的下一步与统一通道收尾单 Agent 的记忆跑通之后往 Multi-Agent 扩展是自然路径。核心思路不变每个子 Agent 可以有自己独立的checkpointer记忆隔离也可以共享父图的checkpointer记忆继承。在 LangGraph 里子图编译时传checkpointerTrue就表示用父图的检查点不传就用自己的。这个开关决定了多个 Agent 之间是“各自记事”还是“共享大脑”。长期记忆在 Multi-Agent 场景下更有价值。比如一个 Supervisor 协调搜索助手、预订助手、查询助手用户在前一个会话里说过的偏好后一个会话里查询助手也能通过共享的store和相同的user_id召回。这就是跨 Agent、跨会话的记忆协同。如果你要接 MCP 工具记忆系统同样能配合。MCP 负责把外部工具的能力标准化暴露出来Store 负责把工具调用产生的结果持久化。两者不冲突反而是互补的——MCP 解决“能调什么”记忆解决“调过什么、结果是什么”。模型通道这块TaoToken 的统一 Key 在 Multi-Agent 场景下优势更明显多个 Agent 共用一套 Base URL 和 Key不用为每个 Agent 单独配模型凭证。你可以在 Coding Plan 页面看长期编码任务的套餐在 API Keys 页面管理你的 Key在接入文档里对照不同框架的配置示例。需要验证模型响应时直接去模型对话页面发消息测试比在代码里反复调试快得多。最后给个实用建议记忆系统的namespace设计要提前想清楚。我见过有人把所有记忆都塞进一个 namespace结果用户 A 的偏好被用户 B 检索到了。正确的做法是按(memories, user_id)或(preferences, user_id)这种二元组隔离不同业务维度的记忆用不同前缀检索时各查各的。这个设计一旦定下来后面加功能就不用重构存储层。代码跑起来之后你会发现 Agent 突然“像个人了”——它记得你叫什么记得你上次说过什么甚至能根据历史偏好主动调整回答。这种体验的提升比换一个更强的模型来得更直接。