前端转agent-【python】-06 长期记忆(向量数据库 + 嵌入)

给 AI Agent 装上"长期记忆":Ollama + Qwen3 实现跨会话记忆

这次来解决更棘手的问题:重启聊天、关掉终端后,Agent 还能记得你是谁。

如果你用过数据库或 localStorage,那长期记忆的本质你早就懂了。

短期记忆 vs 长期记忆

记忆类型 存储位置 生命周期 JS/TS 类比
短期记忆 内存中的 messages 列表 进程存活期间 useState, Redux store
长期记忆 外部存储(数据库、文件) 跨进程/跨会话 localStorage, IndexedDB, SQLite

短期记忆就是每次 API 请求时带上的聊天记录,刷新就没了。

长期记忆是把重要的信息(用户偏好、事实、过往对话摘要)持久化,下次对话时再检索出来塞进上下文。

目标:做一个带长期记忆的命令行聊天机器人,关掉脚本再开,它依然记得你之前说过的事。

技术方案:Ollama + Qwen3 + SQLite

我们用 SQLite 当记忆库(轻量、零配置,就像浏览器里的 IndexedDB)。

记忆流程:

  1. 用户说话 → 从 SQLite 中检索相关记忆
  2. 把检索到的记忆拼接到系统提示中
  3. 调用模型,得到回复
  4. 让模型从当前对话中提取值得记住的事实
  5. 把新事实存入 SQLite,方便下次检索

为什么用 SQLite 而不是向量数据库?

向量数据库更适合语义检索,但对这个 0.6B 小模型来说,搭建嵌入服务太重了。

我们用关键词匹配 + 最近的记忆优先,足够演示原理,也更容易理解。

环境准备:略

模型就绪:略

第一步:长期记忆存储器 LongTermMemory

这个类负责:

  • 初始化 SQLite 表
  • 添加记忆(自动记录时间戳)
  • 根据查询关键词检索相关记忆
  • 清理旧记忆
python 复制代码
# long_term_memory.py
import sqlite3
import datetime
from typing import List, Tuple

class LongTermMemory:
    def __init__(self, db_path: str = "agent_memory.db"):
        self.conn = sqlite3.connect(db_path)
        self.conn.row_factory = sqlite3.Row
        self._init_table()

    def _init_table(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS memories (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                content TEXT NOT NULL,
                source TEXT DEFAULT 'user',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
        self.conn.commit()

    def add_memory(self, content: str, source: str = "user"):
        """将一条记忆写入数据库"""
        self.conn.execute(
            "INSERT INTO memories (content, source) VALUES (?, ?)",
            (content.strip(), source)
        )
        self.conn.commit()

    def search_memories(self, query: str, limit: int = 5) -> List[str]:
        """
        简单的关键词检索:
        查找包含查询中任一关键词的记忆,按创建时间倒序,返回最近的 limit 条。
        """
        keywords = query.lower().split()
        if not keywords:
            return []

        # 构建 LIKE 条件
        conditions = " OR ".join(["content LIKE ?"] * len(keywords))
        params = [f"%{kw}%" for kw in keywords]
        sql = f"SELECT content FROM memories WHERE {conditions} ORDER BY created_at DESC LIMIT ?"
        params.append(limit)

        rows = self.conn.execute(sql, params).fetchall()
        return [row["content"] for row in rows]

    def clear_all(self):
        self.conn.execute("DELETE FROM memories")
        self.conn.commit()

    def close(self):
        self.conn.close()

JS/TS 对比 :这就相当于用 localStorage 封装了一个记忆仓库,只不过 SQLite 可以支持更复杂的查询。在 Node.js 里你可能会用 better-sqlite3 做同样的事。

第二步:事实提取器

每次对话结束,让模型把本轮对话里值得长期记住的信息提取出来,比如:

  • 用户的名字
  • 宠物名字
  • 偏好
  • 重要事件

我们不要求 100% 准确,小模型偶尔犯错可以接受,但整体思路能跑通。

python 复制代码
# fact_extractor.py
import ollama

MODEL = "qwen3:0.6b"

EXTRACTION_PROMPT = """你是一个事实提取器。请从以下对话中提取出需要长期记住的关键事实。
只输出事实,每条一行,不要添加任何解释。
如果没有任何值得记住的事实,输出 "无"。

对话:
{conversation}

提取的事实:"""

def extract_facts(conversation: str) -> list[str]:
    """
    调用模型提取事实,返回事实列表。
    """
    prompt = EXTRACTION_PROMPT.format(conversation=conversation)
    response = ollama.generate(model=MODEL, prompt=prompt)
    text = response["response"].strip()

    if text == "无":
        return []
    
    # 按行分割,过滤空行
    facts = [line.strip() for line in text.split("\n") if line.strip()]
    return facts

第三步:带长期记忆的聊天主程序

把上面的组件串起来:

python 复制代码
# long_term_chat.py
import ollama
from long_term_memory import LongTermMemory
from fact_extractor import extract_facts

MODEL = "qwen3:0.6b"
SYSTEM_PROMPT = """你是一个友善的助手,你的名字是"小记"。
你有长期记忆,能记住用户过去告诉你的重要信息。
请在回答时体现出你记得这些信息,用短句友好地回复。"""

# 初始化长期记忆
memory = LongTermMemory()

# 短期记忆(当前对话的上下文)
messages = [{"role": "system", "content": SYSTEM_PROMPT}]

print("🧠 带长期记忆的聊天机器人 (输入 /clear 清空长短期记忆, /bye 退出)")
print("💡 试试告诉我你的名字、宠物名字或爱好,然后退出重进看看效果")

while True:
    user_input = input("\n🧑 你: ")
    if user_input == "/bye":
        break
    if user_input == "/clear":
        messages = [{"role": "system", "content": SYSTEM_PROMPT}]
        memory.clear_all()
        print("✅ 长短期记忆已清空")
        continue

    # 1. 检索相关长期记忆
    retrieved = memory.search_memories(user_input, limit=5)
    memory_context = ""
    if retrieved:
        memory_context = "【你记得以下关于用户的信息】:\n- " + "\n- ".join(retrieved)
        print(f"🔍 检索到记忆: {retrieved}")

    # 2. 构建完整上下文(短期 + 注入的长期记忆)
    # 在用户消息前,把长期记忆作为系统附加信息插入(也可以直接修改 system prompt)
    # 为了简单,我们动态拼接 system 消息
    dynamic_system = SYSTEM_PROMPT
    if memory_context:
        dynamic_system += "\n\n" + memory_context
    
    messages[0] = {"role": "system", "content": dynamic_system}
    messages.append({"role": "user", "content": user_input})

    # 3. 调用模型
    response = ollama.chat(model=MODEL, messages=messages)
    assistant_msg = response["message"]["content"]
    messages.append({"role": "assistant", "content": assistant_msg})

    print(f"🤖 助手: {assistant_msg}")

    # 4. 提取本轮新事实,存入长期记忆
    # 用最近两条消息(用户+助手)作为提取素材
    recent_turn = f"用户: {user_input}\n助手: {assistant_msg}"
    facts = extract_facts(recent_turn)
    for fact in facts:
        memory.add_memory(fact, source="extracted")
        print(f"💾 新记忆: {fact}")

测试流程

第一次运行:

bash 复制代码
🧠 带长期记忆的聊天机器人
🧑 你: 我叫小明,我有一只猫叫花花
🤖 助手: 好的小明,我记住了,你有一只叫花花的猫。
💾 新记忆: 用户叫小明。
💾 新记忆: 用户有一只猫叫花花。

🧑 你: /bye

第二次运行:

less 复制代码
🧑 你: 我的猫叫什么?
🔍 检索到记忆: ['用户有一只猫叫花花。', '用户叫小明。']
🤖 助手: 你的猫叫花花呀,小明!

即便进程重启,记忆仍然保留在 agent_memory.db 中,长期记忆生效了。

关系总结

scss 复制代码
long_term_chat.py          (主程序 - 对话循环)
  ├── long_term_memory.py  (存储层 - SQLite 读写)
  └── fact_extractor.py    (提取层 - LLM 事实抽取)
         ↓ 调用
      Ollama API  (qwen3:0.6b 本地模型)

JS/TS 视角的横向对比

如果你用 TypeScript 写类似逻辑,核心结构一模一样:

typescript 复制代码
// 长期记忆存储(类比)
class LongTermMemory {
  private db: Database; // better-sqlite3

  addMemory(content: string, source: string): void { ... }
  searchMemories(query: string, limit: number): string[] { ... }
}

// 事实提取函数
async function extractFacts(conversation: string): Promise<string[]> {
  const prompt = `...`;
  const response = await ollama.generate({ model, prompt });
  return parseFacts(response.response);
}

// 聊天循环
const messages: Message[] = [{ role: 'system', content: SYSTEM_PROMPT }];
const memory = new LongTermMemory();

while (true) {
  const userInput = await readline();
  const retrieved = memory.searchMemories(userInput, 5);
  // 动态系统提示
  messages[0].content = SYSTEM_PROMPT + formatMemories(retrieved);
  messages.push({ role: 'user', content: userInput });
  const res = await ollama.chat({ model, messages });
  messages.push(res.message);
  const facts = await extractFacts(`用户: ${userInput}\n助手: ${res.message.content}`);
  facts.forEach(f => memory.addMemory(f, 'extracted'));
}

差异 只不过是把 Python 的 sqlite3 换成 better-sqlite3,把 ollama 库换成 ollama npm 包(同名),逻辑完全可移植。

还能怎么强化?

  1. 语义检索 :用 sentence-transformers 做嵌入,用向量相似度检索(相当于后端的 pineconepgvector)。
  2. 记忆衰减:给记忆加权重,越久远越不重要。
  3. 记忆冲突解决:如果用户说"其实我的猫叫蛋蛋",更新旧记忆而不是重复存储。
  4. 用户隔离 :给记忆表加 user_id 字段,支持多用户。

对于 0.6B 小模型,事实提取可能偶尔不靠谱,这时可以在代码里加一些正则清理,或者用更结构化提示词引导。

总结

  • 长期记忆 = 数据库,把对话中的关键信息持久化,下次再检索出来注入上下文。
  • Python 用 sqlite3,JS/TS 用 better-sqlite3localStorage,本质无差。
  • Ollama + Qwen3:0.6b 虽小,但足以演示完整的记忆循环,0 成本本地跑。
  • 把"提取-存储-检索-注入"这个循环玩透,你就掌握了 Agent 记忆系统的核心。
相关推荐
说了很好1 小时前
PyTorch从零搭建DDPM:时间嵌入+UNet网络+扩散调度完整复现
人工智能
小林ixn1 小时前
别再手写Prompt了!用AI Loop实现自动化自我迭代,效率提升10倍
人工智能·自动化运维
说了很好1 小时前
逐行注释DDPM源码:正向加噪、逆向去噪、MSE损失全流程复现
人工智能
Dilee1 小时前
Spring AI 1.1.7 接入 MCP:Filesystem Server 最小 Demo
人工智能·后端
Token炼金师1 小时前
大模型推理超参数原理详解
人工智能
Token炼金师1 小时前
大模型训练超参数:从Loss曲面到收敛策略的底层逻辑
人工智能
后端小肥肠1 小时前
Skill 囤了一堆却用不起来?我用 Codex 写了个整理神器
人工智能·agent
魏祖潇1 小时前
从"会聊天"到"能干活":用 OpenCode 给自己找个 AI 搭子
人工智能
子兮曰1 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端