1. 项目概述:当AI学会“遗忘”

最近在折腾AI智能体(Agent)项目时,我遇到了一个几乎所有开发者都会头疼的问题:随着智能体运行时间增长,它的“记忆”越来越臃肿。每次调用大模型(LLM)时,都要把过去所有的对话历史、观察结果一股脑塞进上下文窗口,不仅成本飙升,响应速度变慢,更糟糕的是,模型开始“迷失”在冗长的信息里,抓不住重点,表现反而下降。

这让我开始思考一个反直觉的方向: 一个真正智能的“记忆”系统,不应该只是无限膨胀的数据库,而应该像人脑一样,懂得“遗忘”和“提炼”。 于是,我动手构建了一个名为“Experience Engine”的模块。它的核心目标不是存储更多,而是存储更“精”。随着智能体不断学习,它的记忆库会主动压缩、提炼、重构,体积反而可能缩小,但信息的“密度”和“效用”却大幅提升。

简单来说,这是一个 会自主进化的AI记忆系统 。它让智能体摆脱了“上下文窗口”的粗暴限制,通过动态的知识管理,实现更高效、更稳定、更接近人类认知方式的长期学习。对于任何涉及长期交互、持续学习的AI应用场景——无论是游戏NPC、客服助手、个人学习伴侣还是自动化工作流——这或许都是一个值得深入探索的架构思路。

2. 核心设计思路:从“记录本”到“知识图谱”

传统的AI记忆,无论是简单的对话历史列表,还是基于向量数据库的检索增强生成(RAG),本质上都是一个 只增不减的“记录本” 。RAG虽然通过检索相关片段缓解了上下文长度压力,但存储的依然是原始经验的“副本”。当经验数量爆炸时,检索质量会下降,存储和计算成本也会线性增长。

Experience Engine的设计哲学完全不同。它受启发于人类记忆的“意义化”和“图式化”过程。我们不会记住每一天每一顿饭的细节,但会形成“早餐通常吃什么”、“某家餐厅的特色菜”这样的概括性知识。基于此,我设计了系统的三个核心层级:

2.1 记忆的原子化与向量化

所有智能体的原始交互经验(如用户提问、环境反馈、执行结果),首先被切分为独立的“经验单元”。每个单元包含一个完整的“事件”或“观察”。例如,在客服场景中,一次完整的用户咨询和解决过程就是一个单元。

每个经验单元会通过嵌入模型(如 text-embedding-3-small )转化为高维向量。这一步是基础,为后续的聚类和比较提供了数学基础。关键在于,这里存储的向量并非最终记忆,而是 待处理的原材料

2.2 动态聚类与模式提取

这是系统的“学习”核心。系统会定期(例如每积累N个新经验单元后)运行一个在线聚类算法(如流式K-Means或基于密度的聚类)。算法会自动将相似的经验单元归为一类。

这里的“相似”不是简单的文本相似,而是语义和结果层面的相似。 例如,“用户询问退货政策”和“用户询问换货流程”可能被聚为一类,因为它们都指向“售后政策”这个高层概念。聚类后,系统会分析这一类内的所有经验单元,尝试提取一个共通的“模式”或“模板”。

注意 :聚类阈值不是固定的。我引入了一个“新颖性检测”机制。如果一个新经验单元与所有现有聚类中心的距离都超过动态阈值,它可能代表一个全新的知识类别,系统会为其创建一个新的聚类。这个阈值会根据历史经验的离散程度自适应调整。

2.3 记忆的提炼与重构:生成“知识节点”

对于每一个聚类,系统会调用大模型(如GPT-4或Claude 3),执行一次“记忆提炼”任务。输入是该聚类下的所有原始经验文本,指令是:“请分析以下一组相似经历,总结出一条通用、简洁、可操作的知识或原则。请忽略具体细节和身份信息,聚焦于可复用的模式。”

例如,输入几十条关于“解决登录失败”的对话记录,大模型可能输出:“知识节点:用户登录失败时,应优先引导其检查网络连接、验证账号密码是否正确、并提示可尝试密码重置。避免直接建议清除缓存,除非用户主动提及浏览器问题。”

这个生成的“知识节点”,就是经过压缩和提炼后的 核心记忆 。它比任何一条原始记录都更抽象、更通用、更节省空间。原始的经验单元不会被立即删除,而是被标记为“已提炼”,其向量表示将被这个“知识节点”的向量所替代(或建立强关联)。于是,记忆库的“实体”数量可能减少,但信息价值提升了。

2.4 遗忘与衰减机制

并非所有知识都永远重要。我为每个“知识节点”设计了两个核心属性: 激活强度 关联度

  • 激活强度 :每次该知识被成功检索并用于解决问题时,其强度增加。长时间未被使用的知识节点,其强度会随时间缓慢衰减。
  • 关联度 :知识节点之间会形成连接。例如,“登录失败处理”可能与“密码重置流程”、“账户锁定规则”等节点相连。连接边的权重代表了它们之间的相关性强弱。

系统定期进行“记忆整理”。激活强度低于某个阈值的、孤立的(关联度弱的)知识节点,会被标记为“低优先级”,在需要腾出资源时,可以被归档或移除(移至一个廉价的长期存储,仅保留元数据)。这模拟了人类的“自然遗忘”,确保了记忆库始终聚焦于当前最有用的知识。

3. 系统架构与核心组件实现

将上述思路工程化,我构建了以下核心模块。整个系统采用松耦合设计,方便各个组件独立升级。

3.1 经验采集与预处理模块

这个模块负责从智能体的运行环境中捕获原始数据。关键在于定义什么是“一个经验”。

class Experience:
    def __init__(self, observation, action, result, timestamp, metadata=None):
        self.observation = observation  # 智能体感知到的输入
        self.action = action            # 智能体采取的行动
        self.result = result            # 行动导致的结果/反馈
        self.timestamp = timestamp
        self.metadata = metadata or {}  # 如会话ID、环境状态等
        self.embedding = None           # 后续生成的向量
        self.is_consumed = False        # 是否已被提炼

    def to_text(self):
        """将经验转化为用于向量化和提炼的文本描述"""
        # 这是一个简化示例,实际中可以更复杂,例如包含结果的重要性加权
        return f"Observation: {self.observation}. Action: {self.action}. Result: {self.result}."

采集器以事件驱动的方式工作,在智能体完成一个关键动作循环后,自动生成一个 Experience 对象,并送入待处理队列。

3.2 向量化与聚类引擎

这是系统的计算核心。我选择了 sentence-transformers 库生成嵌入向量,因为它平衡了速度和效果。对于聚类,我采用了 scikit-learn MiniBatchKMeans ,它支持在线学习,适合流式数据。

from sentence_transformers import SentenceTransformer
from sklearn.cluster import MiniBatchKMeans
import numpy as np

class ClusteringEngine:
    def __init__(self, embedding_model_name='all-MiniLM-L6-v2', n_clusters=50, batch_size=100):
        self.embedder = SentenceTransformer(embedding_model_name)
        # 初始聚类数可以较小,随着新类别出现而增长
        self.clusterer = MiniBatchKMeans(n_clusters=n_clusters, batch_size=batch_size, reassignment_ratio=0.1)
        self.experience_buffer = [] # 临时缓存经验
        self.cluster_centers_history = [] # 跟踪中心点漂移

    def add_experience(self, experience):
        """添加经验到缓冲区,并触发增量学习"""
        text = experience.to_text()
        embedding = self.embedder.encode(text, convert_to_numpy=True)
        experience.embedding = embedding
        self.experience_buffer.append(experience)

        if len(self.experience_buffer) >= self.batch_size:
            self._incremental_cluster()

    def _incremental_cluster(self):
        """执行增量聚类"""
        embeddings = np.array([exp.embedding for exp in self.experience_buffer])
        # 部分拟合,更新聚类中心
        self.clusterer.partial_fit(embeddings)

        # 为每个经验分配聚类标签
        labels = self.clusterer.predict(embeddings)
        for exp, label in zip(self.experience_buffer, labels):
            exp.cluster_label = label

        # 清空缓冲区,将经验送入下一阶段(提炼)
        processed_exps = self.experience_buffer.copy()
        self.experience_buffer.clear()
        return processed_exps

实操心得 MiniBatchKMeans reassignment_ratio 参数很重要,它控制着每次迭代中低质量聚类中心被重新初始化的比例,对于处理不断变化的经验流、避免聚类中心“僵化”非常有效。我通常设置为0.1到0.2。

3.3 知识提炼器(LLM Orchestrator)

这是系统的“灵魂”,负责将原始经验升华成知识。我设计了一个提示词模板,以稳定地引导大模型完成提炼工作。

class KnowledgeRefiner:
    def __init__(self, llm_client):
        self.llm = llm_client

    def refine_cluster_to_knowledge(self, cluster_experiences):
        """将一个聚类内的经验提炼成一个知识节点"""
        # 1. 准备输入文本
        experiences_text = "\n---\n".join([exp.to_text() for exp in cluster_experiences])

        # 2. 构建系统提示
        system_prompt = """你是一个经验提炼专家。你的任务是从一组具体的交互经历中,抽取出普适、简洁、可操作的核心知识或原则。
        请完全忽略对话中的具体人名、日期、一次性细节。聚焦于可重复使用的模式、因果关系和最佳实践。
        输出格式为:`【知识标题】\n核心原则:\n- 要点1\n- 要点2\n...`"""

        user_prompt = f"""请分析以下一组相似的交互经历,并提炼出一条核心知识:

{experiences_text}

请输出提炼后的知识:"""

        # 3. 调用LLM
        response = self.llm.chat.completions.create(
            model="gpt-4-turbo",
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            temperature=0.2, # 低温度保证输出稳定、聚焦
            max_tokens=500
        )

        raw_knowledge = response.choices[0].message.content

        # 4. 解析并结构化输出
        knowledge_node = self._parse_knowledge_output(raw_knowledge, cluster_experiences)
        return knowledge_node

    def _parse_knowledge_output(self, raw_text, source_experiences):
        # 解析LLM返回的文本,提取标题、原则要点等
        # 同时,根据源经验计算该知识的初始“置信度”和“适用范围”
        # ...
        pass

避坑指南 :直接让LLM总结大量文本可能因上下文过长而失败或成本过高。我的做法是,在将经验文本发送给LLM前,先使用嵌入向量进行 聚类内再筛选 ,只选取最接近聚类中心的3-5条最具代表性的经验作为提炼的输入样本。这既保证了输入质量,又极大降低了token消耗。

3.4 知识图谱与记忆存储

提炼出的知识节点,被存储在一个图形数据库(如Neo4j)或一个自定义的内存结构中,形成一张动态的知识图谱。

class KnowledgeNode:
    def __init__(self, node_id, title, principles, embedding, source_experience_ids):
        self.id = node_id
        self.title = title  # 如“处理登录失败”
        self.principles = principles # 原则列表
        self.embedding = embedding # 知识节点本身的向量(由标题和原则生成)
        self.source_exps = source_experience_ids # 溯源,指向原始经验
        self.activation_strength = 1.0 # 初始激活强度
        self.last_activated = datetime.now()
        self.connections = {} # key: 关联节点ID, value: 关联权重

class KnowledgeGraph:
    def __init__(self):
        self.nodes = {} # node_id -> KnowledgeNode
        # 使用向量索引(如FAISS)快速检索
        self.vector_index = faiss.IndexFlatL2(embedding_dimension)

    def add_node(self, knowledge_node):
        # 添加节点
        # 更新向量索引
        # 尝试与现有节点建立关联(计算向量相似度)
        pass

    def retrieve_relevant_knowledge(self, query_embedding, top_k=3):
        """根据查询向量检索最相关的知识节点"""
        # 1. 从向量索引搜索
        distances, indices = self.vector_index.search(query_embedding, top_k*2) # 多查一些
        candidate_nodes = [self.nodes[idx] for idx in indices[0]]

        # 2. 根据激活强度进行加权排序 (相关性 + 活跃度)
        scored_nodes = []
        for node, dist in zip(candidate_nodes, distances[0]):
            # 综合得分 = 相似度得分 * 激活强度衰减因子
            similarity_score = 1 / (1 + dist)
            recency_factor = self._calculate_recency_factor(node.last_activated)
            final_score = similarity_score * node.activation_strength * recency_factor
            scored_nodes.append((node, final_score))

        scored_nodes.sort(key=lambda x: x[1], reverse=True)
        return [node for node, _ in scored_nodes[:top_k]]

    def _calculate_recency_factor(self, last_activated_time):
        # 计算时间衰减因子,例如过去7天内使用过则因子为1,超过30天则衰减到0.5
        # ...
        pass

当智能体需要决策时,它将当前情境向量化,然后从 KnowledgeGraph 中检索最相关的几个知识节点,将这些节点的原则(而非原始对话历史)作为上下文提供给LLM进行推理。这极大地减少了提示词的长度,并注入了经过提炼的“智慧”。

4. 工作流程与迭代循环

整个Experience Engine作为一个后台服务,与主智能体异步运行,形成一个持续优化的闭环:

  1. 经验收集 :智能体在环境中运行,产生原始经验,送入引擎。
  2. 缓冲与聚类 :经验在缓冲区积累,达到阈值后触发增量聚类,将经验分门别类。
  3. 触发提炼 :对于达到一定规模(如包含5条以上经验)的聚类,启动知识提炼任务,生成知识节点。
  4. 更新图谱 :新知识节点加入图谱,与现有节点建立关联。原始经验被标记,其存储形式可转为低成本归档。
  5. 服务检索 :智能体在需要时,向知识图谱查询相关知识,获得精炼后的原则指导。
  6. 反馈强化 :如果某个知识节点被使用并成功帮助智能体完成任务,则该节点的“激活强度”增加。反之,长期未被使用的节点强度衰减。
  7. 定期整理 :系统周期性地评估所有节点,将强度过低、过于陈旧的节点进行归档或删除,实现记忆的“新陈代谢”。

这个循环使得记忆系统不再是静态的存储,而是一个 动态的、自组织的、不断演化的知识体 。智能体学得越多,它的记忆就越精炼、越结构化,而不是越庞杂。

5. 关键参数调优与实战心得

要让Experience Engine良好运行,以下几个参数的调优至关重要,它们直接影响了“学习效率”和“记忆质量”。

5.1 聚类相关参数

  • 初始聚类数量 ( n_clusters ) :不宜过大或过小。一开始可以设置为一个中等值(如50)。系统应具备动态增加聚类的能力。我实现了一个规则:如果连续多个新经验都被判定为“离群点”(距离所有聚类中心超过2倍平均距离),则自动增加一个新的聚类中心。
  • 聚类批次大小 ( batch_size ) :这决定了“学习”的粒度。太小会导致聚类中心频繁剧烈波动;太大则学习滞后。我建议根据经验产生的速度来定。对于高频交互场景(如聊天机器人),可以设置每100-200条经验聚类一次;对于低频决策场景(如自动化交易Agent),可以按时间周期(如每小时)触发。
  • 相似度阈值 :这是判断“是否属于同一类”的关键。我采用动态阈值法,基于历史经验向量两两之间的距离分布,取一个百分位数(如75%分位数)作为阈值。这个阈值会定期更新。

5.2 提炼触发条件

  • 最小聚类规模 :一个聚类需要积累多少条经验才值得提炼?太少(如2-3条)提炼出的知识可能过拟合、不普适;太多则学习延迟高。我通常设置为5-10条。同时,对于规模超大(如>50条)的聚类,可以考虑进行 二次细分 ,将其拆分成更精细的知识节点。
  • 提炼采样策略 :发送给LLM的经验样本必须具有代表性。我采用的方法是:选择聚类中距离中心点最近的N条经验,以及随机选择M条稍远的经验(以增加多样性)。通常N=3, M=2。

5.3 记忆衰减与遗忘策略

  • 衰减函数 :激活强度的衰减不应是线性的。我使用指数衰减: strength = initial_strength * exp(-decay_rate * time_interval) decay_rate 是一个关键参数,需要根据领域调整。在知识快速迭代的领域(如热点新闻分析),衰减率应较高;在知识稳定的领域(如软件API文档),衰减率应很低。
  • 归档/删除阈值 :当节点强度低于 archive_threshold (如0.2)时,将其元数据(标题、关键标签)保留在一个SQLite数据库中,但将其详细的“原则”文本和向量从内存和FAISS索引中移除。只有当存储空间紧张或节点强度为0时,才考虑彻底删除。这相当于人脑的“模糊记忆”和“彻底遗忘”。

5.4 成本与性能权衡

  • LLM调用成本 :知识提炼是主要的成本来源。为了控制成本,我设置了两个阀门:1) 提炼冷却期 :同一个聚类在一次提炼后,24小时内不再触发第二次,除非有大量新经验涌入。2) 重要性过滤 :不是所有经验都值得提炼。我为经验定义了“重要性”分数(基于结果的成功程度、用户的反馈强度等),只有重要性高于平均值的经验才会被送入提炼流程。
  • 检索速度 :使用FAISS等向量索引库是实现毫秒级检索的关键。定期对FAISS索引进行重建(例如每天一次),以整合新节点、移除旧节点,可以保持检索效率。

6. 效果评估与常见问题排查

部署Experience Engine后,如何评估其效果?我主要从以下几个维度进行监控:

1. 智能体性能指标:

  • 任务成功率 :使用精炼知识后,智能体完成核心任务的准确率或成功率是否有提升?
  • 响应延迟 :由于输入上下文的长度大幅缩短,单次调用LLM的响应时间应明显下降。
  • 运营成本 :在相同任务量下,消耗的Token总数应呈现下降趋势,或增速远低于经验积累的增速。

2. 记忆系统自身指标:

  • 知识节点数量 vs 原始经验数量 :理想情况下,节点数增长曲线应远低于经验数增长曲线,甚至在某些阶段趋于平稳或下降。
  • 知识节点平均激活强度 :整体强度应保持在一个健康水平,说明系统保留的是有用知识。
  • 检索命中率与相关性 :智能体的查询,有多少比例能检索到相关(人工评估)知识节点?

在实际运行中,我遇到了几个典型问题及解决方案:

问题一:提炼出的知识过于空泛或抽象,无法指导具体行动。

  • 现象 :LLM总结出了像“要提供优质服务”这样的废话,对智能体决策毫无帮助。
  • 排查与解决
    1. 检查提示词 :在系统提示中强调“可操作”、“具体步骤”、“条件判断”。例如,改为“请总结出像‘如果遇到A情况,优先尝试B方法,如果无效则转向C方法’这样的具体指导原则。”
    2. 检查输入样本 :确保送入提炼的经验样本本身是高质量、有明确因果关系的。可以预先过滤掉那些结果模糊或无效的经验。
    3. 调整LLM温度 :将 temperature 调低(如0.1),让输出更确定、更贴近输入文本的模式。

问题二:聚类效果差,不相关的经验被混在一起。

  • 现象 :导致提炼出的知识节点逻辑混乱,包含矛盾信息。
  • 排查与解决
    1. 审视向量化模型 :嵌入模型是否适合你的领域?对于专业领域(如医疗、法律),可能需要使用在该领域语料上微调过的嵌入模型,或换用更强大的模型(如 text-embedding-3-large )。
    2. 调整经验文本化方法 Experience.to_text() 方法可能丢失了关键信息。尝试包含更多结构化信息,例如将结果的成功/失败标签、关键实体单独列出。
    3. 引入多维度聚类 :除了语义向量,是否可以加入基于元数据(如任务类型、结果代码)的硬规则进行预分类?

问题三:知识图谱膨胀,检索速度变慢。

  • 现象 :即使节点总数不多,但检索延迟增加。
  • 排查与解决
    1. 检查FAISS索引类型 IndexFlatL2 是精确搜索但速度慢。对于大规模节点(>10万),应切换到 IndexIVFFlat IndexHNSW 等近似搜索索引,在精度可接受的前提下大幅提升速度。
    2. 实施分层检索 :先根据元数据(如知识领域标签)进行粗筛,减少需要做向量相似度计算的候选集。
    3. 定期重建索引 :碎片化的增量添加会导致索引效率下降。设定在低峰期定期全量重建索引。

问题四:智能体过于依赖旧知识,无法适应新变化。

  • 现象 :环境规则改变了,但智能体仍沿用旧知识节点,导致错误。
  • 排查与解决
    1. 强化“新颖性检测” :降低新聚类创建的阈值,让新模式更容易被识别为独立类别。
    2. 引入知识冲突与更新机制 :当新提炼出的知识节点与旧节点在原则上冲突时,触发一个“知识仲裁”流程。可以用新节点覆盖旧节点,或者记录两者各自的适用条件(形成更复杂的知识结构)。
    3. 手动注入“种子知识”或“规则” :对于非常重要的、确定性的规则,可以不经过经验学习,直接以高权重注入知识图谱,确保其优先被采用。

构建Experience Engine的过程,是一个不断在“记住”与“遗忘”、“具体”与“抽象”、“稳定”与“适应”之间寻找平衡点的过程。它不是一个一劳永逸的解决方案,而是一个需要根据具体智能体任务形态持续观察和调优的复杂系统。但它的价值是显而易见的:它让AI智能体拥有了更接近人类的学习和成长轨迹——不是简单地堆积数据,而是通过理解、归纳和遗忘,形成真正属于自己的、可驾驭的“智慧”。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐