给 AI Agent 装上"长期记忆":Ollama + Qwen3 实现跨会话记忆
这次来解决更棘手的问题:重启聊天、关掉终端后,Agent 还能记得你是谁。
如果你用过数据库或
localStorage,那长期记忆的本质你早就懂了。
短期记忆 vs 长期记忆
| 记忆类型 | 存储位置 | 生命周期 | JS/TS 类比 |
|---|---|---|---|
| 短期记忆 | 内存中的 messages 列表 |
进程存活期间 | useState, Redux store |
| 长期记忆 | 外部存储(数据库、文件) | 跨进程/跨会话 | localStorage, IndexedDB, SQLite |
短期记忆就是每次 API 请求时带上的聊天记录,刷新就没了。
长期记忆是把重要的信息(用户偏好、事实、过往对话摘要)持久化,下次对话时再检索出来塞进上下文。
目标:做一个带长期记忆的命令行聊天机器人,关掉脚本再开,它依然记得你之前说过的事。
技术方案:Ollama + Qwen3 + SQLite
我们用 SQLite 当记忆库(轻量、零配置,就像浏览器里的 IndexedDB)。
记忆流程:
- 用户说话 → 从 SQLite 中检索相关记忆
- 把检索到的记忆拼接到系统提示中
- 调用模型,得到回复
- 让模型从当前对话中提取值得记住的事实
- 把新事实存入 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 包(同名),逻辑完全可移植。
还能怎么强化?
- 语义检索 :用
sentence-transformers做嵌入,用向量相似度检索(相当于后端的pinecone或pgvector)。 - 记忆衰减:给记忆加权重,越久远越不重要。
- 记忆冲突解决:如果用户说"其实我的猫叫蛋蛋",更新旧记忆而不是重复存储。
- 用户隔离 :给记忆表加
user_id字段,支持多用户。
对于 0.6B 小模型,事实提取可能偶尔不靠谱,这时可以在代码里加一些正则清理,或者用更结构化提示词引导。
总结
- 长期记忆 = 数据库,把对话中的关键信息持久化,下次再检索出来注入上下文。
- Python 用
sqlite3,JS/TS 用better-sqlite3或localStorage,本质无差。 - Ollama + Qwen3:0.6b 虽小,但足以演示完整的记忆循环,0 成本本地跑。
- 把"提取-存储-检索-注入"这个循环玩透,你就掌握了 Agent 记忆系统的核心。