Agent Harness系列(三):记忆层的3种持久化架构——从SQLite到向量库

系列第一篇拆了 5 层总览,第二篇深入了上下文管理。这篇聚焦第 3 层------记忆。上下文管理处理的是"当前 session 内的信息",记忆层处理的是"跨 session 的知识持久化"。这一层决定了你的 Agent 是一个"每次见面都忘了你是谁的陌生人",还是一个"记得你偏好和历史的助手"。

记忆层和上下文管理层的区别

很多人混淆这两层。一张表说清楚:

维度 上下文管理(第 2 层) 记忆(第 3 层)
作用范围 当前 session 跨 session
生命周期 session 结束即消失 永久(或按策略过期)
存储位置 内存中的消息数组 数据库 / 文件系统
注入方式 直接拼进 prompt 检索后注入
典型内容 最近几轮对话 用户偏好、项目知识、历史决策

一句话区分:上下文管理管"这次对话记得什么",记忆管"下次对话还记得什么"。

3 种持久化架构

架构 1:键值对存储(Key-Value Memory)

最简单的记忆方案。把关键信息存成 key-value 对,下次 session 开始时按 key 检索注入。

python 复制代码
import sqlite3

class KeyValueMemory:
    def __init__(self, db_path="memory.db"):
        self.db = sqlite3.connect(db_path)
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS memories (
                key TEXT PRIMARY KEY,
                value TEXT,
                category TEXT,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
    
    def remember(self, key: str, value: str, category: str = "general"):
        self.db.execute(
            "INSERT OR REPLACE INTO memories (key, value, category, updated_at) VALUES (?, ?, ?, datetime('now'))",
            (key, value, category)
        )
        self.db.commit()
    
    def recall(self, category: str = None, limit: int = 20) -> list:
        if category:
            rows = self.db.execute(
                "SELECT key, value FROM memories WHERE category = ? ORDER BY updated_at DESC LIMIT ?",
                (category, limit)
            ).fetchall()
        else:
            rows = self.db.execute(
                "SELECT key, value FROM memories ORDER BY updated_at DESC LIMIT ?",
                (limit,)
            ).fetchall()
        return [{"key": r[0], "value": r[1]} for r in rows]
    
    def inject_into_context(self, category: str = None) -> str:
        """把记忆格式化成可注入上下文的文本"""
        memories = self.recall(category)
        if not memories:
            return ""
        lines = [f"- {m['key']}: {m['value']}" for m in memories]
        return "[已知信息]\n" + "\n".join(lines)

使用场景:

python 复制代码
# Agent 在对话中发现了需要记住的信息
memory.remember("project_name", "Phoenix", category="project")
memory.remember("preferred_language", "Python", category="user_pref")
memory.remember("db_type", "PostgreSQL 16", category="tech_stack")

# 下次 session 开始时注入
context = memory.inject_into_context(category="project")
# 输出:
# [已知信息]
# - project_name: Phoenix
# - db_type: PostgreSQL 16
优点 缺点
实现极简(SQLite 就够) 只能精确匹配 key,不能语义检索
读写性能高 需要显式调用 remember(),不能自动提取
占用空间小 不适合存长文本(如完整对话记录)
零依赖 记忆量大了之后全部注入会撑爆上下文

适用场景:Agent 需要记住的信息量较小(<100 条)、结构明确(用户偏好、项目配置、固定事实)。大部分个人 Agent 用这种方案就够了。

OpenClaw 的 memory-core 插件底层就是这个思路------用 SQLite 存 slot 式的记忆。

架构 2:对话日志 + 摘要归档(Log + Digest)

把完整对话历史存下来,定期做摘要归档。下次 session 加载摘要,不加载原始对话。

python 复制代码
class DigestMemory:
    def __init__(self, db_path="memory.db"):
        self.db = sqlite3.connect(db_path)
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS conversation_logs (
                id INTEGER PRIMARY KEY,
                session_id TEXT,
                role TEXT,
                content TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS digests (
                id INTEGER PRIMARY KEY,
                period TEXT,
                summary TEXT,
                key_decisions TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
    
    def log_message(self, session_id: str, role: str, content: str):
        """记录每条消息"""
        self.db.execute(
            "INSERT INTO conversation_logs (session_id, role, content) VALUES (?, ?, ?)",
            (session_id, role, content)
        )
        self.db.commit()
    
    def create_digest(self, period: str = "daily"):
        """把最近的对话日志压缩成摘要"""
        # 拉最近 24 小时的对话
        logs = self.db.execute("""
            SELECT role, content FROM conversation_logs 
            WHERE created_at > datetime('now', '-1 day')
            ORDER BY created_at
        """).fetchall()
        
        if not logs:
            return
        
        # 用轻量模型做摘要
        conversation_text = "\n".join([f"{r}: {c}" for r, c in logs])
        summary = summarize_with_llm(conversation_text, 
            prompt="提取这段对话中的关键决策、用户偏好和待办事项。用简洁的列表格式。")
        
        self.db.execute(
            "INSERT INTO digests (period, summary) VALUES (?, ?)",
            (period, summary)
        )
        self.db.commit()
    
    def get_recent_digests(self, days: int = 7, limit: int = 5) -> str:
        """获取最近的摘要,用于注入上下文"""
        digests = self.db.execute("""
            SELECT period, summary FROM digests 
            WHERE created_at > datetime('now', ?) 
            ORDER BY created_at DESC LIMIT ?
        """, (f'-{days} day', limit)).fetchall()
        
        if not digests:
            return ""
        
        lines = [f"[{d[0]}] {d[1]}" for d in digests]
        return "[历史摘要]\n" + "\n".join(lines)

核心设计:原始对话存日志(用于审计和回放),摘要存归档(用于注入上下文)。两者分开存。

优点 缺点
保留完整历史(可回放任何 session) 摘要有信息损失
注入量可控(只注入摘要) 定期归档需要额外的定时任务
自然支持时间维度("上周讨论了什么") 不支持语义检索
审计和 debug 方便 日志量大了 SQLite 会变慢

适用场景:需要审计能力的团队 Agent("回看上周的对话决策"),或者需要时间维度记忆("上周做了什么决定")的项目管理类 Agent。

架构 3:向量记忆(Vector Memory)

把每条消息/知识做 embedding,存入向量数据库。每次 session 按语义相关性检索最相关的记忆注入上下文。

python 复制代码
from openai import OpenAI
import numpy as np

class VectorMemory:
    def __init__(self, db_path="vector_memory.db"):
        self.client = OpenAI(base_url="https://your-api-gateway.com/v1")
        self.db = sqlite3.connect(db_path)
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS memories (
                id INTEGER PRIMARY KEY,
                content TEXT,
                embedding BLOB,
                category TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
    
    def store(self, content: str, category: str = "general"):
        """存储一条记忆(自动做 embedding)"""
        response = self.client.embeddings.create(
            model="text-embedding-3-small",
            input=content
        )
        embedding = np.array(response.data[0].embedding, dtype=np.float32)
        
        self.db.execute(
            "INSERT INTO memories (content, embedding, category) VALUES (?, ?, ?)",
            (content, embedding.tobytes(), category)
        )
        self.db.commit()
    
    def search(self, query: str, top_k: int = 5) -> list:
        """按语义相关性检索记忆"""
        # query 做 embedding
        response = self.client.embeddings.create(
            model="text-embedding-3-small",
            input=query
        )
        query_emb = np.array(response.data[0].embedding, dtype=np.float32)
        
        # 全量检索(小规模用 numpy 就够)
        rows = self.db.execute("SELECT id, content, embedding FROM memories").fetchall()
        scores = []
        for row in rows:
            mem_emb = np.frombuffer(row[2], dtype=np.float32)
            similarity = np.dot(query_emb, mem_emb) / (np.linalg.norm(query_emb) * np.linalg.norm(mem_emb))
            scores.append((row[1], float(similarity)))
        
        scores.sort(key=lambda x: x[1], reverse=True)
        return [{"content": s[0], "score": s[1]} for s in scores[:top_k]]
    
    def inject_into_context(self, current_query: str, top_k: int = 5) -> str:
        """按当前问题的语义检索相关记忆,格式化注入"""
        results = self.search(current_query, top_k)
        if not results:
            return ""
        lines = [f"- {r['content']} (相关度: {r['score']:.2f})" for r in results]
        return "[相关历史信息]\n" + "\n".join(lines)
优点 缺点
不受时间顺序限制,100 轮前的信息也能检索到 需要 Embedding 模型(额外成本)
按相关性注入,上下文利用率高 检索准确率不是 100%,可能漏掉关键信息
记忆量可以很大(万级) 实现复杂度高
天然支持跨渠道(飞书说的话 Discord 也能检索到) 丢失时间顺序信息

适用场景:长期运行的个人助手(跑了几个月,积累了大量知识),需要从海量历史中按需召回特定信息。知识库类 Agent(FAQ、文档助手)。

3 种架构的决策矩阵

维度 KV 存储 日志+摘要 向量记忆
实现复杂度 ⭐⭐ ⭐⭐⭐⭐
记忆容量 <100 条 千级 万级
检索方式 精确 key 时间范围 语义相关
信息损失 摘要有损 检索可能漏
额外成本 摘要调用 Embedding 调用
跨 session
审计能力
适用规模 个人 团队 个人/知识库

我的建议:从 KV 存储开始。 不要上来就搞向量库------90% 的 Agent 记忆需求用 SQLite 的 KV 存储就够了("记住用户叫什么"、"项目用什么技术栈"、"上次做了什么决定")。等你发现 KV 存不下或者精确匹配不够用了,再升级。

记忆的写入时机:自动提取 vs 显式存储

一个容易忽视的设计问题:什么时候往记忆里写?

方式 做法 优点 缺点
显式存储 用户说"记住这个",Agent 才存 精确,不存垃圾 依赖用户主动触发
自动提取 每轮对话后模型自动判断是否有值得记住的信息 不需要用户操心 可能存入噪音
混合 重要信息自动提取 + 用户可以手动纠正 兼顾 实现稍复杂

自动提取的实现:

python 复制代码
def auto_extract_memories(conversation_turn: str, memory: KeyValueMemory):
    """让模型判断这轮对话是否有值得记住的信息"""
    response = client.chat.completions.create(
        model="qwen/qwen3.5-9b",  # 用便宜模型做提取
        messages=[{
            "role": "user",
            "content": f"""分析以下对话内容,提取需要长期记住的信息。
只提取以下类型:
1. 用户偏好(语言、代码风格、工作习惯)
2. 项目信息(名称、技术栈、架构决策)
3. 重要决定(选了什么方案、为什么)

如果没有值得记住的信息,返回空 JSON 数组。

对话内容:
{conversation_turn}

返回格式:[{{"key": "xxx", "value": "xxx", "category": "xxx"}}]"""
        }],
        response_format={"type": "json_object"}
    )
    
    memories = json.loads(response.choices[0].message.content)
    for m in memories:
        memory.remember(m["key"], m["value"], m.get("category", "auto"))

关键点:自动提取用便宜模型。 它是一个"信息分类"任务,不需要高级推理。Qwen 3.5 9B 或类似的轻量模型够用了。

记忆衰减与整合

记忆不是越多越好。存了 1000 条记忆,全部注入上下文是不可能的------那是几万 token。

记忆的生命周期管理

python 复制代码
def maintain_memories(memory: KeyValueMemory, max_memories: int = 100):
    """定期维护记忆:合并重复、清理过期"""
    
    all_memories = memory.recall(limit=9999)
    
    if len(all_memories) <= max_memories:
        return  # 还没超限
    
    # 策略1:按更新时间淘汰最旧的
    # 策略2:让模型合并重复/相似的记忆
    # 策略3:让模型判断哪些记忆已经不再相关
    
    merge_prompt = f"""以下是 Agent 的记忆列表({len(all_memories)} 条):
{json.dumps(all_memories, ensure_ascii=False, indent=2)}

请执行以下操作:
1. 合并重复或高度相似的记忆
2. 删除明显过期或不再相关的记忆
3. 保留最多 {max_memories} 条

返回精简后的记忆列表(JSON 格式)。"""
    
    # 用模型做记忆整合
    result = client.chat.completions.create(
        model="qwen/qwen3.5-plus",
        messages=[{"role": "user", "content": merge_prompt}],
        response_format={"type": "json_object"}
    )
    # ... 用整合后的结果替换原有记忆

定期整合比无限堆积重要。 建议每周或每 100 条新记忆做一次整合。

常见问题

Q: 在内存里保存完整对话历史和持久化记忆有什么区别?

A: 内存里的历史是给上下文管理层用的(当前 session 内的滚动窗口)。持久化记忆是给记忆层用的(跨 session 的知识)。两者独立存储。之前出过 OOM 就是因为内存里的历史无限增长------内存里的要定期清理或归档到磁盘。

Q: 向量记忆需要专门的向量数据库吗?

A: 记忆量 <1 万条时,SQLite + numpy 余弦相似度就够了(上面代码的方案)。超过 1 万条再考虑 Chroma、Milvus 这类专用向量库。不要过早引入复杂依赖。

Q: 多渠道(飞书+Discord)的记忆怎么统一?

A: 所有渠道的记忆写入同一个数据库。记忆本身不区分来源渠道------"用户的项目叫 Phoenix"这条信息不管是在飞书说的还是 Discord 说的,都是同一条记忆。OpenClaw 的做法就是一个 SQLite 存所有渠道的记忆。

相关推荐
一切皆是因缘际会1 小时前
从概率生成到内生心智:2026大模型瓶颈与下一代AI演进方向
人工智能·安全·ai·架构
X54先生(人文科技)1 小时前
《元创力》纪实录·心田记釉下新声:当《纪·念》成为可聆听的星轨
人工智能·开源·ai写作·开源协议
CeshirenTester1 小时前
字节面试官追问:“你的Agent调了三个工具就死循环了,异常处理在哪写的?”我:啊?还要写这个?
人工智能
小程故事多_802 小时前
[大模型面试系列] RAG系统检索失效全链路排查指南,从根源定位到落地优化方法
人工智能·智能体
圣殿骑士-Khtangc2 小时前
AI Agent Skills 数量爆炸治理方案:从混沌到有序的系统性实践
人工智能
Slow菜鸟2 小时前
单体架构的三种形态
架构
汽车仪器仪表相关领域2 小时前
Kvaser Memorator Professional 5xHS CB:五通道CAN FD裸板记录仪,赋能多总线系统集成测试的旗舰级核心装备
大数据·网络·人工智能·单元测试·汽车·集成测试
淡海水2 小时前
【AI模型】模型量化技术详解
人工智能·算法·机器学习
Zik----2 小时前
CILP模型讲解
人工智能·python·多模态