Spring AI 企业级实战|智能记忆摘要+自动遗忘机制落地,彻底解决上下文爆炸与Token冗余

发布时间:2026/9/29 8:19:26

Spring AI 企业级实战|智能记忆摘要+自动遗忘机制落地,彻底解决上下文爆炸与Token冗余 1. 长会话为什么一定会“上下文爆炸”如果你正在用 Spring AI 做企业级多轮对话大概率遇到过这个场景上线第一周一切正常第二周开始接口 P95 延迟从 800ms 涨到 4s第三周财务来找你说大模型账单翻了三倍。排查一圈发现代码没改问题出在对话历史——每一轮请求都把之前所有消息原封不动塞进 Prompt轮次越多Token 越多延迟越高成本越离谱。这就是典型的上下文爆炸。Spring AI 原生给的MessageWindowChatMemory只做一件事设定一个最大消息条数超了就从头删。逻辑简单但生产上很致命。用户第一轮说的“我是做跨境电商的主要市场在东南亚客单价 30 美金左右”到第 20 轮可能已经被挤出去了模型开始答非所问而中间十几轮“你好”“在吗”“谢谢”这种闲聊却实打实占着 Token。所以企业级方案要解决的不是“存不存记忆”而是“怎么聪明地取舍记忆”。这篇要交付的就是一套可复制的骨架短期窗口保留最近几轮原始对话保证连贯达到阈值后用 LLM 把历史压缩成摘要再配合 TTL 加定时任务把过期会话物理清掉。目标很明确——把单次请求的 Token 控制在一个稳定区间同时核心信息不丢。整套方案基于 Spring AI 1.0 官方原生 API不引入任何灰色组件模型调用走标准 OpenAI 兼容接口。下面从依赖配置一路写到验证步骤你可以直接照着改。2. 前置准备模型接入与 Key 获取在写记忆逻辑之前得先有一个能稳定调用的 ChatModel。Spring AI 默认对接 OpenAI 协议我们只需要把 base-url 和 api-key 指向兼容服务即可。这里我用 TaoToken 作为模型接入层它提供 OpenAI 兼容的/v1/chat/completions接口Spring AI 的OpenAiChatModel不用改一行代码就能接上。操作路径很直接打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台创建一个 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建时建议按环境分 Key比如spring-ai-dev、spring-ai-prod方便后续做用量隔离和吊销。拿到 Key 之后接口地址填https://taotoken.net/api注意这个地址不带任何查询参数。如果你用的是 Spring AI 的 OpenAI starter配置里base-url写https://taotoken.net/apiSpring AI 会自动拼上/v1/chat/completions。模型名按你实际开通的填比如gpt-4o-mini或claude-3-5-sonnet这类兼容名称。注意Key 不要硬编码进代码或提交到 Git。用环境变量TAOTOKEN_API_KEY注入Spring 配置里写${TAOTOKEN_API_KEY}占位。如果你还没决定用哪个模型可以先去模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 手动试几轮确认响应格式和延迟符合预期再落到代码里。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的参数说明和错误码对照。3. 可复制配置依赖、YAML 与记忆 Bean3.1 Maven 依赖核心是 JDBC 记忆持久化加定时任务不需要额外中间件dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-chat-memory-jdbc/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version1.0.0/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-quartz/artifactId /dependency3.2 application.yml这里把模型接入、数据库、记忆 TTL 三块配置集中管理spring: datasource: url: jdbc:mysql://127.0.0.1:3306/spring_ai_memory?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: ${DB_PASSWORD} ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 chat: memory: jdbc: initialize-schema: true table-prefix: ai_chat_ time-to-live: 7dtime-to-live: 7d表示登录用户会话记忆 7 天有效游客场景可以单独配 1h。initialize-schema: true会在启动时自动建表生产环境建议改成false并用 Flyway 管理。3.3 智能记忆 Bean这是整套方案的核心。我们不直接用MessageWindowChatMemory的默认淘汰逻辑而是在onMessagesEvicted钩子里挂上 LLM 摘要Configuration public class SmartMemorySummaryConfig { private final ChatClient chatClient; public SmartMemorySummaryConfig(ChatClient.Builder builder) { this.chatClient builder.build(); } Bean public ChatMemory smartChatMemory(JdbcChatMemoryRepository repository) { return MessageWindowChatMemory.builder() .chatMemoryRepository(repository) .maxMessages(5) .onMessagesEvicted(this::autoSummaryConversation) .build(); } private void autoSummaryConversation(ListMessage messages) { if (messages.size() 15) { return; } String content messages.stream() .map(Message::getContent) .collect(Collectors.joining(\n)); String summary chatClient.prompt() .user(精简以下对话仅保留用户核心需求、业务配置、技术偏好删除闲聊200字以内\n content) .call() .getContent(); log.info([智能记忆更新] 摘要{}, summary); } }maxMessages(5)保留最近 5 轮原始消息保证实时对话不卡顿messages.size() 15是摘要触发阈值低于 15 条不调 LLM避免频繁压缩把成本又拉上去。摘要结果建议单独存一张ai_chat_summary表和原始消息物理隔离方便溯源。3.4 自动遗忘定时任务TTL 只做逻辑标记不物理删除数据表会越积越大。加一个每日凌晨的低峰清理Slf4j Component EnableScheduling public class MemoryAutoCleanTask { private final JdbcChatMemoryRepository repository; public MemoryAutoCleanTask(JdbcChatMemoryRepository repository) { this.repository repository; } Scheduled(cron 0 0 2 * * ?) public void cleanExpiredChatMemory() { try { repository.deleteAllExpired(); log.info([AI记忆运维] 过期会话清理完成); } catch (Exception e) { log.error([AI记忆运维] 清理异常, e); } } }3.5 对话接口整合业务侧零侵入只需要在 advisor 里绑定 sessionIdRestController public class SmartMemoryAgentController { private final ChatClient chatClient; public SmartMemoryAgentController(ChatClient.Builder builder, ChatMemory smartChatMemory) { this.chatClient builder .defaultAdvisors(MessageChatMemoryAdvisor.builder(smartChatMemory).build()) .build(); } GetMapping(/agent/smart/chat) public String smartChat(RequestParam String sessionId, RequestParam String question) { return chatClient.prompt() .user(question) .advisors(a - a.param( MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID, sessionId)) .call() .getContent(); } }sessionId是会话隔离的关键不同用户传不同值记忆互不串扰。4. 验证请求与成功结果配置写完后按下面四步验证每一步都有明确的观察点。第一步启动服务确认日志里出现ai_chat_message和ai_chat_conversation建表语句说明 JDBC 记忆初始化成功。如果报Table already exists把initialize-schema改成false即可。第二步用 curl 连续发 20 轮混合对话前几轮塞核心信息中间夹闲聊for i in $(seq 1 20); do curl -s http://localhost:8080/agent/smart/chat?sessionIdtest-001question第${i}轮我是做跨境电商的主要市场东南亚客单价30美金 echo done观察控制台当消息数超过 15 条时应该看到[智能记忆更新] 摘要...日志摘要里保留了“跨境电商、东南亚、30 美金”这些关键词闲聊被过滤掉。第三步重启服务用同一个sessionIdtest-001提问“我之前说的客单价是多少”模型应该能答出 30 美金。这一步验证的是摘要记忆跨重启留存。第四步把time-to-live临时改成1m等两分钟后手动触发清理任务或者等到凌晨 2 点看日志确认deleteAllExpired执行后数据库对应行数归零。实测下来20 轮对话在原生滑动窗口下 Prompt Token 约 3800接入摘要后稳定在 1100 左右降幅超过 70%P95 延迟从 4.2s 回到 900ms 区间。5. 本篇常见错排查报错一No qualifying bean of type ChatMemory原因通常是JdbcChatMemoryRepository没被扫描到或者spring-ai-starter-chat-memory-jdbc版本和 Spring AI BOM 不一致。检查pom.xml里是否引入了spring-ai-bom并统一版本号。另外确认Configuration类在启动类的同级或子包下。报错二摘要日志一直不触发先看messages.size()是否真的到了 15。onMessagesEvicted只在消息被淘汰时回调如果maxMessages设得比阈值还大永远不会触发。确保maxMessages(5)小于摘要阈值 15。还有一种情况是ChatMemoryBean 被覆盖了检查是否有其他地方也定义了ChatMemory。报错三deleteAllExpired报 SQL 语法错误不同数据库的 TTL 字段类型不一样。MySQL 下time_to_live是timestamp如果手动改过表结构导致类型不匹配就会报错。建议直接用initialize-schema: true生成的表结构不要手改。生产环境用 Flyway 时把官方 DDL 拷过去执行。报错四模型调用返回 401 或 404先确认base-url写的是https://taotoken.net/api不要多加/v1Spring AI 会自己拼。401 一般是 Key 无效或没带Bearer前缀检查环境变量TAOTOKEN_API_KEY是否注入成功。404 多半是模型名写错去模型对话页确认可用模型列表。如果还是不通直接看接入文档里的 curl 示例对照排查。报错五摘要内容把关键信息也删了这是 Prompt 写得太粗。把摘要指令改得更具体比如“必须保留用户身份、业务领域、数值参数、技术栈偏好可以删除问候、确认、重复提问”。另外把摘要阈值从 15 调到 20给 LLM 更多上下文判断。6. 下一步把记忆能力接到生产链路到这里短期窗口加 LLM 摘要加自动遗忘的三层骨架已经跑通了。你可以先把这套配置落到测试环境用真实业务对话跑一周观察摘要质量和 Token 曲线。如果发现摘要调用本身成本偏高可以把摘要模型换成更便宜的型号主对话用强模型两者分开配置。需要长期做编码或 Agent 场景的话建议直接上 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它针对高频代码补全和长上下文做了额度优化比按量计费更可控。接入过程中如果遇到 Key 权限或模型路由问题先查接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 再对照 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 确认 Key 状态。整套方案不依赖任何非标准组件换模型只需改application.yml里的 model 字段记忆逻辑完全复用。
延伸阅读

更多相关文章

2026/9/29 8:14:26

模2运算详解:从异或到CRC校验的底层原理与实操

做通信协议和底层软件这行,几乎天天要和模2运算打交道。奇偶校验、CRC校验、LFSR伪随机序列、汉明码纠错,这些看似各不相同的技术,剥开外壳后核心全是模2加法、模2减法、模2乘法、模2除法这一套四则运算。很多人一开始觉得简单,不…

2026/9/29 9:14:30

测试用例设计万能思路:六种核心方法助你打造高质量用例

做了这么多年测试,我见过太多人写用例时盯着需求文档发呆,然后凭感觉刷刷刷列出一堆用例,评审会上被追问两句就底虚。测试用例这东西,看起来就是“前置条件 操作步骤 预期结果”,可为什么不同人写出来的用例质量和数…

2026/9/29 9:14:30

Codex配置本地自定义Agent:TOML、AGENTS.md与优先级实战

如果你想让 Codex 成为真正服务于自己项目的本地自定义 Agent,那 TOML、AGENTS.md 和优先级这三个词会是你绕不开的关卡。我最早以为把配置里的模型名改成 DeepSeek 就能直接跑,结果命令行反复报错,最后才明白,接入点、项目指令、…

2026/9/29 9:14:30

C++异常处理最佳实践:从错误模型到RAII与安全设计

1. 为什么异常处理的“最佳实践”首先是取舍问题1.1 异常不是 bug,而是一种错误上报机制但凡用 C 写过一段时间的人,都会遇到这种争论:异常到底该不该用?C 异常处理从语言诞生之初就带着争议,一部分老派开发者坚持 “异…

2026/9/29 9:14:30

AI写代码、不画帧:Opus 5.5+Python+FFmpeg生成30秒粒子动画全解析

事情是这样的。我想让 Opus 5.5 帮我做一条 30 秒的视频,但最后成品里它一帧都没「生成」——所有画面,没有一帧是 AI 直接画出来的。你可能觉得这很怪,但恰恰是这次尝试,让我彻底理解了 AI 在视频创作里真正该站的位置。它不是替…

2026/9/29 9:09:29

CFBench 评测实战:用 TaoToken 统一 Key 跑通 LLM 约束遵循基准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/28 3:03:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/28 6:05:15

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 7:00:49

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 0:04:04

AI Evals实战指南:从零搭建LLM应用评估体系与CI/CD集成

1. 为什么AI Evals值得你花时间搞明白做LLM应用的人,迟早会撞上同一堵墙:模型输出飘忽不定,今天答得好好的,明天换个问法就胡说八道。你改了一版提示词,感觉好像好了点,但到底好了多少?说不清。…

2026/9/29 0:04:04

Java采购管理系统实战:从数据库设计到事务一致性

简介:这是一套面向Java Web初学者与课程设计者的采购管理系统完整源码,采用JSP技术搭建,配合MySQL数据库,用于解决企业采购信息的管理问题,适合作为毕业设计、课程大作业或进销存类项目的参考模板。系统实现了用户登录…

2026/9/29 3:53:39

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/26 19:58:38

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/29 6:36:14

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑