agent面试必备40-AI Agent 核心架构:会话摘要与压缩(Memory Compression)
️ AI Agent 核心架构会话摘要与压缩Memory Compression全解析与面试通关指南在开发 AI Agent 时如果你做的是一个“虚拟伴侣”或者“长期私人助理”你会遇到一个极其头疼的问题用户可能会跟你聊上几个月甚至几年。如果用“全量记忆”大模型的 Token 费用会让你倾家荡产如果用“滑动窗口记忆”直接丢弃老对话AI 就会变成“渣男/渣女”上一周发生的事情它全忘了。为了打破这个僵局工业界引入了记忆系统中最优雅的折中方案会话摘要与压缩Conversation Summarization Compression。在高级 AI 研发面试中面试官非常喜欢考察候选人对长文本上下文的管理能力。这篇博客将用大白话带你搞懂记忆压缩的核心策略并附带面试级别的核心代码实现 一、 为什么需要会话摘要与压缩大白话秒懂通俗概念和 AI 聊天就像背着石头爬山。每一句话都是一块石头Token聊得越久背包越重。全量记忆石头全背着最后把你压垮Token 超出限制API 报错。滑动窗口边走边扔石头直接丢弃老对话最后到了山顶你忘了自己怎么上来的硬遗忘。摘要与压缩每当你背了 10 块石头觉得累了你就停下来用炼金术把这 10 块石头炼成一颗小钻石总结提炼核心信息。你扔掉石头只带着钻石继续走。这样既减轻了重量又保留了最核心的记忆。核心收益控制 Token 成本将几万字的聊天记录压缩成几百字的摘要大幅降低每一次 API 调用的花费。避免“硬遗忘”即使是几个月前聊过的重要设定比如“用户有一个女儿叫小芳”也能通过摘要一直保留在当前对话中。⚙️ 二、 主流的两种压缩机制面试必背1. 滚动摘要机制 (Rolling Summary)原理维护一个current_summary当前摘要字符串。当新增的聊天记录达到一定长度比如 10 轮对话时触发一次后台 LLM 调用让它把【旧摘要】【新聊的 10 轮对话】重新总结成一段【新摘要】。优点实现简单上下文始终保持极其精简的状态。缺点每次压缩都可能丢失一些细节。压缩次数多了就像传话游戏一样可能会产生幻觉或信息模糊。2. 实体与画像抽取 (Entity/Profile Extraction)原理不总结大段的流水账对话而是让模型像填表一样专门提取对话中的“关键实体”。比如提取出{user_name: 张三, job: 程序员, hobbies: [打游戏, 看科幻小说]}。优点信息极其结构化不仅能作为当前记忆还能直接落库到关系型数据库MySQL中长期保存。缺点只能记住“设定”记不住具体的“聊天语境”或“情绪”。 工业界标准做法真实的业务线通常是“短期原话滑动窗口” “中长期滚动摘要” “永久用户画像抽取”三管齐下。 三、 高频面试 QA 实战演练Q1做会话摘要时什么时候触发压缩最合适标准答案通常有两种触发机制按阈值触发Threshold-based当短期对话轮数如 10 轮或 Token 数量如达到 2000 Tokens达到上限时触发一次压缩。适合常规问答。异步定时触发Asynchronous/Cron-based不阻塞用户的正常聊天在服务器闲时比如用户离线 5 分钟后由后台队列Celery/Kafka发起压缩任务。适合对 C 端延迟Latency要求极高的闲聊产品。Q2滚动摘要Rolling Summary会带来“信息磨损”聊得越久早期的记忆就越模糊如何解决标准答案摘要必然会带来信息的有损压缩。为了缓解这个问题我们在工程上通常结合RAG检索增强生成在丢弃原始聊天记录将其压缩之前先将被丢弃的原话做 Embedding 向量化存入本地的长期向量数据库Vector DB。这样摘要作为“主线剧情”一直带在 Prompt 里而那些被磨损的“支线细节”当用户重新提及时系统还能通过 RAG 瞬间从长期记忆库里捞回来。Q3调用大模型做摘要也是要花钱和时间的这难道不是增加了系统的负担吗标准答案从单次看确实增加了负担但从长期算是一笔极其划算的经济账。假设你带了 10K Token 的上下文每次用户跟你聊一句你都要为这 10K Token 付费。聊 10 句就是 100K 的计费。如果花 1K 的 Token 成本把它压缩成 500 Token 的摘要。接下来的几十轮对话你都只需要为这 500 Token 付费。长线来看不仅极大地节省了成本还降低了主模型大段阅读带来的延迟TTFT。此外后台摘要完全可以采用更便宜、更快速的小模型如 GPT-4o-mini 或 Qwen-7B来完成。 四、 面试加分代码手写“滚动摘要记忆管理器”在面试白板环节如果让你手写记忆压缩逻辑面试官最想看的是你何时触发压缩、如何拼接 Prompt、以及如何替换旧状态。以下是一段保姆级注释的滚动摘要实现代码importjson# # 1. 模拟环境大模型 API# classMockLLM:模拟大模型接口专门处理摘要任务defsummarize(self,text_to_summarize:str)-str:# 在真实环境中这里应该是一个调用 OpenAI/大模型的 Prompt 链# 比如请将以下【旧摘要】和【新对话】合并提炼保留用户的核心信息、偏好和未完成的任务...print(⚙️ [后台服务] 正在调用小模型执行长文本压缩压缩...)returnf【核心摘要】用户张三是一名程序员之前探讨了 Python 的基础语法当前正在研究如何写并发代码。# # 2. 核心大管家会话压缩记忆类# classSummarizationMemory: 带滚动摘要机制的短期记忆管理器 def__init__(self,token_limit:int100):# 1. 保存极少量的高清近期对话滑动窗口self.recent_messages[]# 2. 保存中长期的模糊记忆滚动摘要self.current_summary# 3. 触发压缩的阈值为了方便演示这里把长度设得很小self.token_limittoken_limit self.llmMockLLM()def_estimate_length(self)-int:简单估算当前近期对话的长度returnsum(len(msg[content])formsginself.recent_messages)defadd_message(self,role:str,content:str):添加新消息并检查是否需要触发压缩self.recent_messages.append({role:role,content:content})print(f [存入原话]{role}:{content[:15]}...)# 检查如果近期对话太长了立刻触发压缩机制ifself._estimate_length()self.token_limit:self._trigger_compression()def_trigger_compression(self):核心逻辑滚动摘要的炼金术print(\n⚠️ 警告近期记忆太长触发【会话压缩】机制)# 1. 将当前的旧摘要和需要被压缩的近期对话拼接起来chat_text\n.join([f{msg[role]}:{msg[content]}formsginself.recent_messages])text_to_compressf【旧的摘要】\n{self.current_summary}\n\n【需要补充的新对话】\n{chat_text}# 2. 调用模型生成新摘要new_summaryself.llm.summarize(text_to_compress)# 3. 状态更新极其重要self.current_summarynew_summary# 用新摘要替换旧摘要self.recent_messages[]# ⚠️ 清空被压缩过的近期原话释放出宝贵的上下文空间print(f✅ [压缩完成] 历史对话已清空当前记忆已更新为\n{self.current_summary}\n)defget_prompt_context(self)-str:每次向大模型发请求前获取当前的最优上下文contextifself.current_summary:contextfSystem 背景信息 (历史摘要):\n{self.current_summary}\n\ncontext近期对话原话:\nformsginself.recent_messages:contextf{msg[role]}:{msg[content]}\nreturncontext# # 3. 测试运行与面试讲解# if__name____main__:memorySummarizationMemory(token_limit50)# 设积极小的阈值以触发压缩# 回合 1闲聊字数不多正常存入memory.add_message(user,你好我是张三是个程序员。)memory.add_message(assistant,你好张三请问在研究什么技术)print(\n--- 第一轮聊完后的内存状态 ---)print(memory.get_prompt_context())print(-*40)# 回合 2突然发了一大堆话导致总长度超过 50触发大压缩long_text我之前一直在用 Python 写基础脚本但是现在业务量上来了我必须要学习一下 Python 的异步编程和多线程并发控制你能教教我吗memory.add_message(user,long_text)print(--- 触发压缩后的内存状态 ---)print(memory.get_prompt_context())# 面试讲解要点# 向面试官解释“在真正的工程落地中_trigger_compression 通常是被推送到 MQ消息队列中异步执行的。# 通过维护 current_summary 这个字符串变量我们把大模型看做是一个‘状态机’的更新函数# 新状态 Update(旧状态, 新事件)。# 采用这种滚动压缩的策略即使用户和我们的 Agent 聊了 3 年# Prompt 的长度也永远会稳定在一个安全的范围内实现了成本和体验的完美平衡。”