• 正文
  • 相关推荐
申请入驻 产业图谱

大模型根本没有记忆,那AI到底是怎么"记住"你的?

17小时前
241
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

01、“你不是说过吗?”一次让人血压升高的对话

你上午跟一个智能体客服聊天,告诉它:“我叫小林,对海鲜过敏,麻烦帮我订餐的时候都避开海鲜。”它痛快答应了。你俩又聊了十几轮,敲定了各种细节。

下午你回来接着聊,问它:“对了,帮我加一份今晚的宵夜。”它兴高采烈地说:“好的小林,为您推荐鲜虾粥,鲜美滋补!”

你人都麻了,我早上刚说过对海鲜过敏啊!

这不是它笨,而是它压根不记得你说过这句话。因为对大模型来说,“记忆”这个词本身就是个精心营造的幻觉。我们今天就来彻底聊清楚:智能体的记忆到底是怎么一回事,为什么会遗忘,以及怎么亲手给它装一套靠谱的记忆系统。

02、拆穿真相:模型压根没有“记忆”这回事

先说一个可能颠覆你认知的事实:大模型本身是彻底无状态(stateless)的

它不像人一样脑子里有个记忆区,随着对话推移不断往里存东西。每一次你调用模型的接口,它都是一张白纸,它之所以看起来“记得”你之前说的话,纯粹是因为你把完整的历史对话,一字不落地重新打包发给了它

这就好比你每次跟一个刚认识的陌生人聊天,都得先把你俩之前聊过的所有内容,从头到尾给他念一遍,念完了他才能接着往下聊。他表现得“记得”,其实只是因为你每次都把“记忆”原原本本地塞回他脑子里而已。

代码层面,这个“塞记忆”的动作长这样,你在前几篇里应该也见过:

messages = [

    {"role": "user", "content": "我叫小林,对海鲜过敏"},

    {"role": "assistant", "content": "好的小林,我记住啦,避开海鲜"},

    {"role": "user", "content": "帮我订个午餐吧"},



    # ... 中间可能还有几十条历史消息



    # 最新一条消息

    {"role": "user", "content": "帮我加一份今晚的宵夜"},

]

只要“我叫小林,对海鲜过敏”这句话,一直老老实实地待在 messages 列表里,被完整传给模型,模型就“记得”。一旦这条消息因为某种原因被漏掉、被截断、被删掉,模型立刻失忆,态度诚恳地给你推荐一碗虾粥。

问题来了:为什么会漏掉?答案是:上下文窗口是有限的,对话不可能无限往里塞。这就引出了第一个核心问题:短期记忆该怎么管理。

03、短期记忆:对话历史膨胀之后怎么办

SHORT-TERM · 滑窗与摘要

假设用户和智能体聊了一整个下午,几百轮对话,messages 列表已经长得吓人。这时候会出现两个实打实的问题:

1、成本爆炸:每次调用都要把几百轮历史重新发一遍给模型,Token 消耗蹭蹭涨,你的 API 账单会很难看。

2、“迷失在中间”问题:学术界有研究发现,当上下文特别长时,模型对塞在中间部分的信息,注意力和准确率会明显下降,它更容易记住最近说的话和最开始说的话,中间那一大坨反而容易被“模糊处理”掉。

那怎么办?业界常见的几种思路,我按“简单粗暴”到“精细讲究”的顺序给你排一下:

方案一

滑动窗口(Sliding Window):最简单粗暴,只保留最近 N 轮对话,更早的直接砍掉。好处是实现简单、成本可控;坏处是砍掉的内容彻底丢失。

方案二

摘要压缩(Summarization):让模型自己把“要被挤出窗口”的旧对话,浓缩成一段简短摘要,替代掉那一大坨原始历史记录。坏处是压缩本身也要调用一次模型,有额外开销。

方案三

滑动窗口+摘要混合:实际项目里最常见的做法:永远保留最近 K 轮原始对话,K 轮之前的所有内容滚动压缩进一条摘要。既控制成本,又不至于把关键信息彻底丢掉。

我们直接手写一版第三种方案,用 deepseek-v4-flash 来实现:

from openai import OpenAI


client = OpenAI(
api_key="sk-xxxx",
base_url="https://api.deepseek.com",
)


class ConversationMemory:
"""短期记忆管理:保留最近 K 轮原始对话,更早的滚动压缩成摘要"""

def __init__(self, keep_recent_turns: int = 6):
self.keep_recent_turns = keep_recent_turns
self.summary = ""  # 滚动摘要,一直挂在最前面
self.recent_messages = []  # 最近的原始对话,保留细节

def add(self, role: str, content: str):
self.recent_messages.append(
            {
"role": role,
"content": content,
}
        )

# 超出保留轮数时,把最老的一轮压缩进摘要
if len(self.recent_messages) > self.keep_recent_turns * 2:
self._compress_oldest()

def _compress_oldest(self):
# 把最老的 2 条消息(一问一答算一轮)拿出来准备压缩
old_turn = self.recent_messages[:2]
self.recent_messages = self.recent_messages[2:]

        old_text = "\n".join(
f"{message['role']}: {message['content']}"
for message in old_turn
        )

        prompt = f"""已有的历史摘要:{self.summary or '(无)'}

新增的一轮对话:
{old_text}

请把"已有摘要"和"新增对话"融合成一段新的、简洁的摘要,
只保留对后续对话有用的关键事实(比如用户偏好、已确认的结论),
去掉寒暄和无关细节,控制在 100 字以内。"""

response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
                {
"role": "user",
"content": prompt,
}
            ],
)

self.summary = response.choices[0].message.content

def build_messages(self, system_prompt: str = ""):
"""组装最终要发给模型的完整消息列表"""

messages = []

if system_prompt:
            messages.append(
                {
"role": "system",
"content": system_prompt,
}
            )

if self.summary:
            messages.append(
                {
"role": "system",
"content": f"以下是本次对话此前的关键信息摘要:{self.summary}",
}
            )

        messages.extend(self.recent_messages)

return messages


# ------- 使用示例 -------

memory = ConversationMemory(keep_recent_turns=3)

memory.add(
"user",
"我叫小林,对海鲜过敏,麻烦订餐都避开海鲜",
)

memory.add(
"assistant",
"好的小林,我记住了,会全程避开海鲜类菜品",
)

# ……假设中间又聊了很多轮,触发了自动压缩……

memory.add(
"user",
"帮我加一份今晚的宵夜",
)

messages = memory.build_messages(
system_prompt="你是一个贴心的订餐助手",
)

response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
)

print(response.choices[0].message.content)

有了这套机制,就算原始对话被挤出了滑动窗口,“用户叫小林,对海鲜过敏”这个关键事实,也会被沉淀在滚动摘要里,一直挂在每次请求的最前面,模型就不会闹出推荐虾粥这种笑话了。

04、长期记忆:跨越“这一次对话”的边界

短期记忆解决的是“这一次对话别遗忘”,但还有一个更大的问题:用户下次打开一个全新的对话窗口,智能体还能记得他对海鲜过敏吗?

答案是:如果你只做了上面那套短期记忆,那不能!因为一旦对话窗口关闭,recent_messages 和 summary 全都灰飞烟灭了。要跨越“这一次对话”的边界,你需要一套持久化的长期记忆,把关键信息真正存到硬盘或数据库里,供未来任意一次新对话调用。

长期记忆最常见的实现思路,是“存文本 + 存向量,需要时按相似度检索”,业界管这个叫 RAG(检索增强生成)的一种简化应用。别被“向量数据库”这种听起来很唬人的词吓到,我们不依赖任何框架,纯手写一个极简版本,你就能彻底看懂它的原理:

import math
from collections import Counter


class LongTermMemory:
"""极简长期记忆:手写 RAG 的核心——存文本+向量,检索时按相似度取回。

    不依赖任何向量数据库/框架,"向量"就是一个普通的词频字典,
    "检索"就是算余弦相似度后排序取 top_k。原理和真实系统一模一样,
    只是把 embedding 模型换成了最朴素的词频统计。
    """

def __init__(self):
self.records = []  # 每条记忆:{"text": 原文, "vector": 词频向量}

def _vectorize(self, text: str) -> Counter:
"""把文本变成向量。真实系统这里调用 embedding 模型,
        返回一个几百/上千维的浮点数组;这里简化成"每个字出现几次"。"""
return Counter(text)

def _cosine_similarity(self, vec_a: Counter, vec_b: Counter) -> float:
"""两个向量的夹角余弦值,衡量"有多相似",范围 [0, 1]。"""
common = set(vec_a) & set(vec_b)
        dot = sum(vec_a[k] * vec_b[k] for k in common)
        norm_a = math.sqrt(sum(v ** 2 for v in vec_a.values()))
        norm_b = math.sqrt(sum(v ** 2 for v in vec_b.values()))
if norm_a == 0 or norm_b == 0:
return 0.0
return dot / (norm_a * norm_b)

def store(self, text: str):
"""存:文本 + 向量,一起存进去。"""
self.records.append({"text": text, "vector": self._vectorize(text)})

def retrieve(self, query: str, top_k: int = 2) -> list:
"""检索:把 query 也变成向量,跟库里每条记忆算相似度,
        取分数最高的 top_k 条返回。"""
query_vec = self._vectorize(query)
        scored = [
            (self._cosine_similarity(query_vec, r["vector"]), r["text"])
for r in self.records
        ]
        scored.sort(key=lambda x: x[0], reverse=True)
return [text for score, text in scored[:top_k] if score > 0]


# ------- 使用示例 -------
if __name__ == "__main__":
    memory = LongTermMemory()
    memory.store("用户小林对海鲜过敏")
    memory.store("用户小林喜欢清淡口味,不吃辣")
    memory.store("用户小林的收货地址是望京SOHO")

    query = "用户对什么食物过敏"
print("检索结果:", memory.retrieve(query))

真实项目里,这一步通常会换成正经的 embedding 模型(比如把文本转换成一个几百维的语义向量),检索效果会比这种朴素的“字频统计”精准得多。但核心思路完全一致:把过去的关键信息存起来,等新对话开始时,用当前问题去检索最相关的几条,塞进给模型的上下文里,让它“回忆”起相关的往事。

05、记忆不是越多越好,有时候它是个负担

PITFALLS · 三个反直觉的坑

讲到这里,你可能觉得“那我把所有对话都存进长期记忆不就万事大吉了?”千万别这么干。记忆这东西,用不好反而会拖累智能体,有三个具体的坑:

!坑一:噪音干扰

如果把用户随口一提的闲聊、无关紧要的寒暄也一股脑存进长期记忆,检索的时候大概率会捞出一堆不相关的垃圾信息,反而让模型的回答变得答非所问。记忆系统应该有意识地只存“关键事实”(用户偏好、已确认的结论、重要的约定),而不是有闻必录。

!坑二:幻觉放大

如果检索出来的历史记忆本身就是错的(比如很久以前模型自己说错了一句话,被当成“事实”存了下来),后续对话会一直被这个错误信息误导,越陷越深,形成“错误的记忆→错误的回答→再次巩固错误记忆”的恶性循环。

!坑三:成本与延迟

检索这个动作本身也需要时间和计算资源,塞进上下文的每一条记忆都占用 Token 预算。不是记得越多就越“智能”,很多时候克制地遗忘,才是让智能体保持敏捷和聚焦的关键。人类大脑其实也是这样,我们的大脑每天都在主动“删除”大量不重要的信息,这不是缺陷,而是一种进化出来的智慧。

下一步:光会记,还不够

这一篇我们解决了“记不住”的问题和短期靠滑动窗口+摘要压缩,长期靠检索式记忆存取。但你可能已经想到了:如果任务本身特别复杂,需要提前规划好几个步骤,记忆再好也没用,因为问题根本不在于“记不记得住”,而在于“一开始就没想清楚该怎么拆解这个任务”。

比如用户说:“帮我调研一下今年新能源汽车电池技术的最新进展,写一份两千字的报告。”这种任务,光靠 ReAct 那种“走一步看一步”的循环,很容易越走越偏,缺乏一个全局的执行蓝图。

下一篇要讲的是任务规划(Planning):如何让智能体在动手之前先想清楚步骤

如何让智能体在动手之前,先把复杂任务拆解成一份清晰的步骤清单,按图索骥地执行,必要时还能根据执行中发现的新情况,动态调整这份计划。下一篇见。

 

我是写代码的中年人,长期专注于 AI 算法、智能体、以及 AI 工程化落地。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录