恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
4篇4章1节:认识 AI 中的短期记忆与长期记忆——从 TaoToken 统一 Key 看上下文窗口与持久化存储
首页
资讯中心
/
4篇4章1节:认识 AI 中的短期记忆与长期记忆——从 TaoToken 统一 Key 看上下文窗口与持久化存储
4篇4章1节:认识 AI 中的短期记忆与长期记忆——从 TaoToken 统一 Key 看上下文窗口与持久化存储
发布时间:2026/10/9 5:03:13
1. 为什么你的 AI 聊到一半就“失忆”了先说一个我反复遇到的场景在 AI IDE 里跟模型聊了三十多轮前面刚交代过“这个项目用 Vue3 TypeScript禁止 any组合式 API”结果聊到后面让它改个表单校验它又开始用 Options API 写data()了。你以为是模型变笨了其实是你踩到了大语言模型最底层的特性——无状态。大语言模型本身没有记忆。它不像数据库那样把每次对话存起来下一次请求时再读出来。每一次 API 调用你发给它的所有内容——系统提示词、历史对话、当前问题——都是一次性的。模型看完这一批 token生成回复然后这批内容就从它的“脑子”里消失了。下一次你再发请求如果客户端没有把历史对话重新塞进去模型就真的什么都不记得。这就是短期记忆的本质它不是模型“记住”了什么而是你的客户端AI IDE、聊天工具、自己写的脚本在每次请求时把之前的对话历史重新打包发给模型。这个打包的内容有上限就是上下文窗口context window。一旦历史对话累积超过这个窗口最早的内容就会被截断丢弃模型自然就“忘了”你开头说的规则。而长期记忆是另一套机制把需要跨会话保留的信息项目规范、个人偏好、团队约定写到外部存储里——可以是一个 Markdown 文件、一个向量数据库、一张 SQLite 表——每次新会话开始时再把这些内容检索出来注入到系统提示词或上下文头部。短期记忆负责“这一轮对话别断片”长期记忆负责“换个窗口我还认识你”。这篇文章我会用TaoToken 统一 Key/API 通道作为接入示例把短期记忆的上下文截断配置、长期记忆的持久化验证步骤完整跑一遍。你会看到多轮对话中记忆是怎么读写的边界在哪里以及怎么用可复制的配置把这两套机制串起来。适合正在做 AI 对话应用、AI IDE 插件、或者自己写 Agent 的开发者。2. TaoToken 统一 Key 接入把上下文窗口和持久化存储串起来在讲具体配置之前先解决一个前置问题你用什么通道调模型。自己写对话应用时最烦的是每换一个模型就要改一套 SDK、换一个 Key、调一遍参数格式。TaoToken 的做法是提供一个统一的 API 入口你用同一个 Key、同一套 OpenAI 兼容格式就能切换不同模型。这对我们做记忆实验特别方便——因为短期记忆的截断行为、长期记忆的注入位置在不同模型上表现会有差异统一通道能让你快速对比。TaoToken 的 API 地址是https://taotoken.net/api兼容 OpenAI 的/v1/chat/completions接口。你需要在控制台创建一个 API Key然后就可以用标准 OpenAI SDK 调用。下面是我实际用的配置方式。先看环境变量这是最干净的写法避免 Key 硬编码进代码export TAOTOKEN_API_KEYsk-你的实际Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用 Python 的 openai 库客户端初始化长这样from openai import OpenAI import os client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) response client.chat.completions.create( modelclaude-sonnet-4-20250514, messages[ {role: system, content: 你是一个严谨的代码助手。}, {role: user, content: 用一句话说明什么是上下文窗口。}, ], ) print(response.choices[0].message.content)这里有个关键点base_url后面不要加/v1TaoToken 的路径已经处理好了。如果你用的是其他兼容 OpenAI 的客户端比如 LangChain、LlamaIndex配置项名字可能叫openai_api_base值同样是https://taotoken.net/api。模型 ID 这块TaoToken 支持多个主流模型你在请求里通过model参数指定。我实测下来做记忆实验时建议固定一个模型先把短期记忆的截断逻辑跑通再换模型对比长期记忆的召回效果。因为不同模型的上下文窗口大小不一样比如有的 128K、有的 200K截断阈值要跟着调。现在把短期记忆和长期记忆的分工画清楚。短期记忆的载体是messages数组每次请求你都要把历史对话按顺序放进去。长期记忆的载体是你自己的存储——可以是本地文件、Redis、向量库。每次新会话开始时你从长期存储里检索出相关片段拼到system消息里。这样模型看到的messages就同时包含了两类信息头部是长期记忆注入的规则后面是短期记忆的对话历史。我踩过的一个坑是把长期记忆直接塞进每一轮messages的末尾结果它被当成“最新用户输入”处理模型会试图回复这段规则而不是遵守它。正确做法是放在system角色里或者放在第一轮user消息之前。TaoToken 的接口对system角色支持是完整的你可以放心用。还有一个细节TaoToken 的响应里会返回usage字段包含prompt_tokens、completion_tokens、total_tokens。这个数据对短期记忆管理极其有用——你可以根据prompt_tokens判断当前上下文用了多少快接近窗口上限时主动触发截断或摘要。下面我会在截断配置里用到这个字段。3. 可复制的上下文截断配置与记忆持久化 JSON这一节直接给可复制的配置。我把它拆成两块短期记忆的截断策略和长期记忆的持久化结构。你可以直接拿去改。先说短期记忆截断。核心思路是维护一个messages列表每次请求前检查 token 数超过阈值就从最早的非 system 消息开始删或者把最早的一段对话做摘要压缩。下面是一个 Python 实现用 tiktoken 估算 token如果你用的模型不是 OpenAI 系可以用字符数粗略估算误差在可接受范围import tiktoken from openai import OpenAI import os, json client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) ENC tiktoken.get_encoding(cl100k_base) MAX_CONTEXT_TOKENS 8000 # 留出余量实际窗口可能更大 KEEP_RECENT_ROUNDS 6 # 至少保留最近6轮对话 def count_tokens(messages): total 0 for m in messages: total len(ENC.encode(m[content])) 4 return total def truncate_messages(messages, max_tokensMAX_CONTEXT_TOKENS): system_msgs [m for m in messages if m[role] system] dialog_msgs [m for m in messages if m[role] ! system] while count_tokens(system_msgs dialog_msgs) max_tokens \ and len(dialog_msgs) KEEP_RECENT_ROUNDS * 2: dialog_msgs.pop(0) return system_msgs dialog_msgs这段代码的逻辑是system 消息永远保留长期记忆注入的规则不能丢对话历史从最早开始删但至少保留最近 6 轮。你可以把MAX_CONTEXT_TOKENS调成模型窗口的 70% 左右留出生成回复的空间。长期记忆的持久化我用一个 JSON 文件做示例结构清晰、方便版本管理。实际生产可以换成 SQLite 或向量库但字段设计是一样的{ user_profile: { language: zh-CN, code_style: 组合式API禁止any小驼峰命名, comment_rule: 仅单行注释不写多行注释块 }, projects: { vue-admin: { tech_stack: [Vue3, TypeScript, Vite, Pinia], rules: [ 表单校验统一用 async-validator, API 请求封装在 src/api 目录, 组件文件名用 PascalCase ], last_updated: 2025-09-23 } }, memory_entries: [ { id: mem_001, scope: global, content: 用户偏好直接给代码不要冗长解释, hit_count: 12, created_at: 2025-09-01 }, { id: mem_002, scope: project:vue-admin, content: 权限模块用 RBAC 模型角色表 role_permissions, hit_count: 5, created_at: 2025-09-10 } ] }这个结构里user_profile和projects是相对固定的长期记忆memory_entries是动态增删的条目带hit_count用于淘汰低价值记忆。每次新会话开始时你根据当前项目名从projects里取规则从memory_entries里按scope过滤拼成 system 消息def build_system_prompt(project_name, memory_filememory.json): with open(memory_file, r, encodingutf-8) as f: mem json.load(f) parts [你是一个严谨的代码助手。] profile mem.get(user_profile, {}) if profile: parts.append(用户偏好 json.dumps(profile, ensure_asciiFalse)) proj mem.get(projects, {}).get(project_name) if proj: parts.append(f当前项目 {project_name} 规范 json.dumps(proj, ensure_asciiFalse)) entries [e for e in mem.get(memory_entries, []) if e[scope] in (global, fproject:{project_name})] if entries: parts.append(长期记忆条目 .join(e[content] for e in entries)) return \n.join(parts)把build_system_prompt的返回值作为第一条 system 消息后面接短期对话历史再走truncate_messages就完成了短期记忆和长期记忆的拼接。这套配置可以直接复制到你的项目里改一下文件路径和项目名就能跑。4. 验证请求多轮对话中记忆读写是否真的生效配置写完了怎么验证它真的在工作我设计了一个三步验证法每一步都有明确的预期结果。你跟着跑一遍就能确认短期记忆的截断和长期记忆的注入都生效了。第一步验证长期记忆注入。用上面的build_system_prompt生成 system 消息然后发一个和项目规范相关的问题看模型是否遵守了规则。比如我在memory.json里给vue-admin项目写了“禁止 any”然后发system_prompt build_system_prompt(vue-admin) messages [ {role: system, content: system_prompt}, {role: user, content: 写一个函数接收一个用户对象返回它的名字。}, ] resp client.chat.completions.create( modelclaude-sonnet-4-20250514, messagesmessages, ) print(resp.choices[0].message.content) print(prompt_tokens:, resp.usage.prompt_tokens)预期结果是模型输出的 TypeScript 代码里不会出现any并且会用组合式 API 的风格。如果它写了function getName(user: any)说明长期记忆没注入成功检查build_system_prompt的返回值里有没有包含“禁止 any”。我实测下来只要 system 消息里明确写了规则模型遵守的概率很高。第二步验证短期记忆的累积。连续发三轮对话每轮都把历史messages带上观察prompt_tokens是否递增。代码history [{role: system, content: system_prompt}] questions [ 项目里表单校验用什么库, 那这个库怎么自定义错误提示, 给我一个手机号校验的示例。, ] for q in questions: history.append({role: user, content: q}) resp client.chat.completions.create( modelclaude-sonnet-4-20250514, messageshistory, ) answer resp.choices[0].message.content history.append({role: assistant, content: answer}) print(fQ: {q}) print(fA: {answer[:80]}...) print(fprompt_tokens: {resp.usage.prompt_tokens}) print(---)预期结果是prompt_tokens从第一轮的几百涨到第三轮的几千。第三轮的回答应该能引用第一轮提到的库名说明短期记忆在起作用。如果第三轮模型说“我不知道你之前说的什么库”那说明历史messages没传对检查history.append的顺序。第三步验证截断。把MAX_CONTEXT_TOKENS临时调小比如设成 500然后跑一个长对话观察最早的消息是否被删掉。你可以在truncate_messages里加一行打印看删了多少条def truncate_messages(messages, max_tokensMAX_CONTEXT_TOKENS): system_msgs [m for m in messages if m[role] system] dialog_msgs [m for m in messages if m[role] ! system] removed 0 while count_tokens(system_msgs dialog_msgs) max_tokens \ and len(dialog_msgs) KEEP_RECENT_ROUNDS * 2: dialog_msgs.pop(0) removed 1 if removed: print(f[truncate] 删除了 {removed} 条最早的消息) return system_msgs dialog_msgs跑长对话时你会看到[truncate]打印说明截断逻辑被触发。这时候再问一个依赖早期对话的问题模型应该答不上来——这不是 bug是短期记忆的边界。要解决这个问题就得靠长期记忆把早期对话里重要的信息提取出来写进memory_entries下次会话就能召回。这三步跑完你对短期记忆和长期记忆的读写链路就有体感了。TaoToken 的usage字段在这里帮了大忙没有它你很难判断上下文到底用了多少。5. 常见报错排查401、local proxy failed、reading choices、OAuth接入过程中我遇到过几类典型报错这里按现象、原因、解决方式列出来你对照着查。401 Unauthorized。这个最常见通常是 Key 没传对。检查三处环境变量TAOTOKEN_API_KEY是否真的导出到了当前 shellecho $TAOTOKEN_API_KEY看有没有值代码里api_key参数是否读到了这个变量Key 本身是否在控制台被禁用或删除。还有一种情况是base_url写成了https://taotoken.net/api/v1多了一层/v1导致路径拼接错误。正确写法是https://taotoken.net/apiSDK 会自动补/v1/chat/completions。local proxy failed。这个报错通常出现在你本地配了 HTTP 代理但代理进程没启动或者端口不对。如果你在用某些网络工具先确认代理是否正常运行。不过更推荐的做法是在调用 API 的代码里显式禁用代理避免环境变量干扰。Python 里可以这样import os os.environ[HTTP_PROXY] os.environ[HTTPS_PROXY] os.environ[NO_PROXY] taotoken.net这样能确保请求直连 TaoToken不走本地代理。如果你确实需要代理确认代理地址和端口正确并且NO_PROXY里没有误伤taotoken.net。reading choices 相关报错。这个通常表现为KeyError: choices或者response.choices为空。原因一般是响应体不是预期的 JSON 结构可能是请求被拦截、返回了 HTML 错误页或者模型名写错了导致接口返回错误信息。排查方法先把原始响应打出来看resp client.chat.completions.create(...) print(resp.model_dump_json(indent2))如果看到error字段里面会有具体原因。模型名写错是高频问题比如把claude-sonnet-4-20250514写成claude-sonnet-4接口会返回模型不存在。确认模型 ID 拼写正确并且你的 TaoToken 账号有该模型的权限。OAuth 相关报错。如果你用的是某些 IDE 插件或 CLI 工具它们可能走 OAuth 流程而不是 API Key。这类工具报 OAuth 错误时通常是 token 过期或回调地址不匹配。解决方式是重新走一遍授权流程或者在工具配置里改成用 API Key 直连。以 Claude Code 这类工具为例如果你要接入 TaoToken需要配置三件套Base URL 填https://taotoken.net/apiKey 填你的 TaoToken API KeyModel ID 填你要用的模型。三个都填对OAuth 那套就不需要了。还有一个容易忽略的点如果你在 Cline、CC Switch 这类工具里配置 MCP 或自定义模型记得把 Base URL、Key、Model ID 三件套都写全。只填 Key 不填 Base URL工具会默认走官方地址自然连不上。我见过有人只改了 Key结果请求还是发到原来的地址报 401 之后查了半天。6. 把记忆体系跑起来从 TaoToken Key 到可复用的对话应用到这里短期记忆的截断配置、长期记忆的持久化结构、验证步骤和排错方法都齐了。你手上现在有一套可以直接跑的最小实现用 TaoToken 统一 Key 调模型用truncate_messages管短期上下文用memory.json管长期规则用build_system_prompt把两者拼起来。接下来你可以做几件事让它更实用。一是把memory.json换成 SQLite加一个hit_count自增逻辑每次记忆被召回就加一定期淘汰低命中条目。二是把长期记忆的检索从“按 scope 过滤”升级成向量检索用 embedding 算相似度这样即使用户没提项目名也能召回相关规则。三是把截断策略从“删最早消息”升级成“摘要压缩”用模型把早期对话总结成一段话保留信息密度。如果你还没开始接入先去 TaoToken 控制台创建一个 API Key然后拿本文第 2 节的 Python 代码跑通第一个请求。验证模型对话可以直接在模型对话页面试需要长期编码或 Agent 场景可以看 Coding Plan接入文档和 API Keys 管理在文档和 API Keys 页面。把 Key 拿到手把memory.json建起来你的 AI 对话应用就不再是“聊完就忘”的一次性工具了。