前端转agent-【python】-07 长期记忆进阶:用 ChromaDB + 语义搜索给 Agent 装上真正的长期记忆

进阶:用 ChromaDB + 语义搜索给 Agent 装上真正的长期记忆

上次我们用 SQLite + 关键词匹配实现了"跨会话记忆"。

但"我的猫叫什么"和"宠物的名字是?"在关键词匹配下可能找不到交集。

这次我们引入向量数据库语义嵌入,让记忆检索真正理解含义。

为什么要从 SQLite 升级到 ChromaDB?

方案 检索方式 能理解语义? 适用场景
SQLite + LIKE 关键词包含 ❌ 只能字面匹配 小规模、确定性查询
ChromaDB + 嵌入 向量相似度 ✅ "宠物"与"猫"会被归为近义 模糊回忆、开放式对话

JS/TS 视角 :这就相当于你从 Array.filter() 升级到了 Pineconepgvector------数据存在专门的向量数据库里,通过计算向量距离来找"最相似"的记忆。

技术栈

  • Ollama + Qwen3:0.6b:本地对话与事实提取(脑子)
  • ChromaDB:向量数据库,负责记忆的存储与语义检索(海马体)
  • sentence-transformers:将文本转换成向量,让 ChromaDB 能算相似度(嵌入引擎)

ChromaDB 自带 SentenceTransformerEmbeddingFunction,我们可以直接用,省去手动调 sentence-transformers

环境准备

bash 复制代码
pip install ollama chromadb sentence-transformers

如果下载慢,可先设置镜像:pip install chromadb -i https://pypi.tuna.tsinghua.edu.cn/simple

模型就绪(若未拉取):

bash 复制代码
ollama pull qwen3:0.6b

第一步:初始化 ChromaDB 记忆库

ChromaDB 可以持久化到本地目录,数据不会丢。

python 复制代码
# chroma_memory.py
import chromadb
from chromadb.utils import embedding_functions

# 使用轻量嵌入模型,所有计算在本地,无需联网
EMBEDDING_MODEL = "all-MiniLM-L6-v2"
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name=EMBEDDING_MODEL
)

class ChromaLongTermMemory:
    def __init__(self, collection_name: str = "agent_memory", persist_dir: str = "./chroma_db"):
        self.client = chromadb.PersistentClient(path=persist_dir)
        # 获取或创建集合,指定嵌入函数
        self.collection = self.client.get_or_create_collection(
            name=collection_name,
            embedding_function=embedding_fn,
            metadata={"hnsw:space": "cosine"}  # 使用余弦相似度
        )

    def add_memory(self, content: str, source: str = "user"):
        """添加一条记忆,自动生成嵌入并存储"""
        # 用当前时间戳作为唯一 ID(简单起见)
        import time
        mem_id = str(int(time.time() * 1000000))  # 微秒级时间戳
        self.collection.add(
            documents=[content],
            metadatas=[{"source": source}],
            ids=[mem_id]
        )

    def search_memories(self, query: str, n_results: int = 5) -> list[str]:
        """语义检索:返回最相关的 n 条记忆"""
        if self.collection.count() == 0:
            return []
        results = self.collection.query(
            query_texts=[query],
            n_results=min(n_results, self.collection.count())
        )
        # 提取文档列表
        documents = results.get("documents", [[]])[0]
        return documents

    def clear_all(self):
        # 删除集合并重建
        self.client.delete_collection(self.collection.name)
        self.collection = self.client.get_or_create_collection(
            name=self.collection.name,
            embedding_function=embedding_fn
        )

JS/TS 类比

typescript 复制代码
// 如果用 Node.js 和 chromadb 包
import { ChromaClient } from "chromadb";
const client = new ChromaClient({ path: "./chroma_db" });
const collection = await client.getOrCreateCollection({
  name: "agent_memory",
  embeddingFunction: new SentenceTransformerEmbeddingFunction({ model: "all-MiniLM-L6-v2" })
});
// 添加文档时自动生成向量
await collection.add({ ids: [id], documents: [text] });
// 查询
const results = await collection.query({ queryTexts: [query], nResults: 5 });

第二步:事实提取器(与之前类似,但可以复用)

python 复制代码
# fact_extractor.py
import ollama

MODEL = "qwen3:0.6b"
EXTRACTION_PROMPT = """从以下对话中提取值得长期记住的关键事实。
只输出事实,每条一行,不要解释。若无事实输出"无"。

对话:
{conversation}

提取的事实:"""

def extract_facts(conversation: str) -> list[str]:
    response = ollama.generate(model=MODEL, prompt=EXTRACTION_PROMPT.format(conversation=conversation))
    text = response["response"].strip()
    if text == "无":
        return []
    return [line.strip() for line in text.split("\n") if line.strip()]

第三步:带语义记忆的聊天主程序

python 复制代码
# semantic_chat.py
import ollama
from chroma_memory import ChromaLongTermMemory
from fact_extractor import extract_facts

MODEL = "qwen3:0.6b"
SYSTEM_PROMPT = """你是小记,一个有长期记忆的助手。
下面是你记得的关于用户的信息,请在回答时自然地体现出这些记忆。"""

# 初始化记忆库
memory = ChromaLongTermMemory()

# 短期上下文
messages = [{"role": "system", "content": SYSTEM_PROMPT}]

print("🧠 语义长期记忆聊天机器人 (ChromaDB 版本)")
print("💡 告诉我你的爱好、宠物、名字,然后退出重启看看它记不记得!")
print("输入 /bye 退出,/clear 清空记忆")

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

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

    # 2. 动态拼接系统提示
    dynamic_system = SYSTEM_PROMPT + "\n\n" + memory_context if memory_context else SYSTEM_PROMPT
    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. 提取新事实并存入 ChromaDB
    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

第二次启动后:

复制代码
🧑 你: 我有哪些爱好?
🔍 检索到 3 条记忆
🤖 小记: 小明,我记得你喜欢打篮球和弹吉他。

注意:即使你的问题是"我有哪些爱好",它也能通过语义相似性找到"喜欢打篮球"、"喜欢弹吉他"------这就是向量检索的威力。

与 SQLite 方案的对比

查询内容 SQLite (关键词) ChromaDB (语义)
"我的猫叫什么" 找到含"猫"的记忆 可能找到"宠物叫花花"
"我爱好什么" 需要关键词"爱好" 找到"喜欢打篮球"
"上次说的那个朋友" 几乎不可能 有一定概率,取决于上下文

JS/TS 开发者的完全对应版

如果你用 TypeScript 实现同样的功能,会是这样的结构:

typescript 复制代码
import { ChromaClient } from "chromadb";
import ollama from "ollama";

// 嵌入函数直接用 chromadb 默认的(或 Transformer.js 的)
const client = new ChromaClient({ path: "./chroma_db" });
const collection = await client.getOrCreateCollection({
  name: "agent_memory",
  embeddingFunction: "all-MiniLM-L6-v2"  // 伪代码示意
});

// 添加记忆
await collection.add({
  ids: [id],
  documents: [content],
  metadatas: [{ source: "extracted" }]
});

// 检索
const results = await collection.query({ queryTexts: [query], nResults: 5 });
const facts = results.documents[0];

核心逻辑完全一致:存储时把文档转成向量,查询时用查询向量找最近邻。Python 版和 TS 版只是 API 语法的区别。

进阶优化方向

  1. 混合检索:结合关键词过滤 + 语义检索,适合需要精确匹配的场景(如日期、金额)。
  2. 记忆更新:当用户说"我的猫不叫花花了,叫蛋蛋",可以删除旧记忆或覆盖更新。
  3. 记忆权重:根据时间衰减,越新的记忆相似度越高。
  4. 用户隔离 :给记忆加 user_id 字段,支持多用户。

总结

  • ChromaDB 是本地可用的向量数据库,对接 sentence-transformers 后就能实现语义记忆。
  • 相比 SQLite 方案,升级代价很小(几行代码),检索质量提升显著。
  • 整个流程依然是:提取事实 → 生成向量 → 存库 → 查询时语义检索 → 注入上下文。
  • 对 JS/TS 开发者来说,这就是把 localStorage 换成向量数据库,前端有 transformers.js,后端有 chromadb,完全对应。
相关推荐
阿黎梨梨2 小时前
AI Loop:告别“人肉写提示词”,让代码替你“鞭策”AI
javascript·人工智能
甲维斯3 小时前
坦克大战测试全翻车了!豆包,DeepSeek,Qwen,GPT,Claude
前端·人工智能·游戏开发
若丶相见3 小时前
AI 大模型零基础知识扫盲
人工智能
猿人谷5 小时前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
说了很好6 小时前
PyTorch从零搭建DDPM:时间嵌入+UNet网络+扩散调度完整复现
人工智能
Bigfish_coding6 小时前
前端转agent-【python】-06 长期记忆(向量数据库 + 嵌入)
人工智能
小林ixn6 小时前
别再手写Prompt了!用AI Loop实现自动化自我迭代,效率提升10倍
人工智能·自动化运维
说了很好6 小时前
逐行注释DDPM源码:正向加噪、逆向去噪、MSE损失全流程复现
人工智能
Dilee6 小时前
Spring AI 1.1.7 接入 MCP:Filesystem Server 最小 Demo
人工智能·后端