27-Day 6 长期记忆 向量数据库 - 改进
27-Day 6 长期记忆 —— 向量数据库 - 改进
✅ 6 个方面做了优化和增强:
1. 抑制了 LangChain 的相关性分数警告
-
问题背景:DashScope 的
text-embedding-v2输出的是 L2 归一化的向量,因此余弦相似度 ∈ [-1, 1]。但 LangChain 的 FAISS 默认期望相关性分数 ∈ [0, 1],会抛出警告:Relevance scores must be between 0 and 1 -
本文里面的程序:显式添加了
warnings.filterwarnings("ignore", message="Relevance scores must be between 0 and 1")避免干扰用户。
-
上一篇的程序:未处理此警告,运行时可能频繁打印烦人的提示。
🔍 改进点:提升用户体验,避免无关日志污染输出。
2. 引入了可配置的余弦相似度阈值(COSINE_THRESHOLD)
- 本文里面的程序:定义了
COSINE_THRESHOLD = 0.2,并在检索后过滤掉低于该阈值的结果:
避免返回语义上“不相关”的记忆(如相似度 0.05 的噪声匹配)。relevant = [(doc, score) for doc, score in results if score >= COSINE_THRESHOLD] - 上一篇的程序:直接返回 top-k 结果,无论多不相关都展示,可能导致“幻觉式回忆”。
🔍 改进点:提高检索质量,避免低相关性结果误导用户。
3. 支持调试模式(DEBUG_MODE),显示所有检索分数
- 本文里面的程序:开启
DEBUG_MODE = True后,会打印所有 top-k 的原始相似度分数:
便于开发者或用户理解“为什么某条记忆被召回”。print(f" [{score:.3f}] {doc.page_content}") - 上一篇的程序:完全不显示分数,用户无法判断匹配强度。
🔍 改进点:增强系统透明度和可解释性,利于调试和调优。
4. 更严格的输入校验与格式规范
- 本文里面的程序:
- 要求记忆必须以“我”开头(
if not user_input.startswith("我")) - 拒绝过短内容(
if len(memory) < 4) - 自动跳过空文本(
[t.strip() for t in texts if t.strip()])
- 要求记忆必须以“我”开头(
- 上一篇的程序:
- 允许任意非问句输入保存为记忆(甚至可能保存“今天天气不错” → “用户今天天气不错”,语义混乱)
- 无长度校验,可能存入“我。”这类无效记忆
🔍 改进点:保证记忆库的数据质量和一致性,提升后续检索准确率。
5. 使用 similarity_search_with_relevance_scores() 获取真实相似度
- 本文里面的程序:调用
显式获取余弦相似度分数(因为 DashScope 向量已 L2 归一化,FAISS 内积 = 余弦相似度)。vectorstore.similarity_search_with_relevance_scores(user_input, k=3) - 上一篇的程序:使用
retriever.invoke(),底层虽也计算相似度,但不暴露分数,无法做阈值过滤或调试。
🔍 改进点:获得关键的相似度数值,支撑阈值过滤和调试功能。
6. 交互提示更友好、示例更清晰
- 本文里面的程序:
- 启动时明确显示当前阈值和调试状态
- 提供具体输入示例(含中英文标点、典型疑问词)
- 对不符合格式的输入给出明确提示(“请以「我...」开头”)
- 上一篇的程序:提示较简略,未说明格式要求细节。
🔍 改进点:降低用户学习成本,减少无效输入。
✅ 📌 本文里面的程序的核心改进
| 改进项 | 本文里面的程序 | 上一篇的程序 |
|---|---|---|
| 抑制无关警告 | ✅ | ❌ |
| 相似度阈值过滤 | ✅(可配置) | ❌(全返回) |
| 调试模式(显示分数) | ✅ | ❌ |
| 输入格式校验 | ✅(严格) | ⚠️(宽松) |
| 获取真实相似度分数 | ✅ | ❌(隐藏) |
| 用户交互体验 | ✅(清晰提示+示例) | ⚠️(基础提示) |
💡
本文里面的程序在鲁棒性、可用性、可解释性、数据质量控制等方面做了优化,
是一个更成熟、更适合实际使用的向量记忆系统原型。
✅ 程序 vector_memory_2.py
tee vector_memory_2.py << 'EOF'
# day06/vector_memory_2.py
# 使用 DashScope 原生 embedding(余弦相似度 ∈ [-1,1])
# 手动设定合理阈值,抑制无关警告
# 新增功能:输入 "show all" 可查看所有记忆及其向量摘要
import os
import numpy as np # 新增:用于向量范数计算
from dotenv import load_dotenv
import dashscope
from dashscope import TextEmbedding
from langchain_community.vectorstores import FAISS
from langchain_core.embeddings import Embeddings
from typing import List
import warnings
# 抑制 LangChain 的 relevance score 警告
warnings.filterwarnings("ignore", message="Relevance scores must be between 0 and 1")
# 加载环境变量
load_dotenv()
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
class DashScopeEmbeddings(Embeddings):
def __init__(self, model: str = "text-embedding-v2"):
self.model = model
def embed_documents(self, texts: List[str]) -> List[List[float]]:
texts = [t.strip() for t in texts if t.strip()]
if not texts:
return []
response = TextEmbedding.call(model=self.model, input=texts)
if response.status_code != 200:
raise RuntimeError(f"Embedding failed: {response}")
return [item["embedding"] for item in response.output["embeddings"]]
def embed_query(self, text: str) -> List[float]:
return self.embed_documents([text])[0]
# === 核心参数配置 ===
COSINE_THRESHOLD = 0.2
DEBUG_MODE = True
# 初始化
embeddings = DashScopeEmbeddings()
initial_memories = ["用户喜欢喝美式咖啡", "用户住在杭州"]
vectorstore = FAISS.from_texts(initial_memories, embeddings)
print("🧠 向量记忆系统(余弦相似度模式)")
print(f"📌 相似度阈值: {COSINE_THRESHOLD} | 调试模式: {'ON' if DEBUG_MODE else 'OFF'}")
print("\n✅ 输入示例:")
print(" - 我喜欢喝茶") # 保存记忆
print(" - 我平时喜欢做什么?") # 触发检索
print(" - show all") # 👈 新增:查看所有记忆及向量摘要
print(" - quit\n")
# 主交互循环
while True:
try:
user_input = input("🗨️ 你: ").strip()
except (KeyboardInterrupt, EOFError):
print("\n👋 再见!")
break
if not user_input:
continue
if user_input.lower() == "quit":
print("👋 再见!")
break
# 👇 新增:调试命令 - 显示所有记忆及向量摘要
if user_input.lower() == "show all":
total = len(vectorstore.index_to_docstore_id)
print(f"\n📚 当前所有记忆(共 {total} 条):")
if total == 0:
print(" (暂无记忆)")
else:
for i in range(total):
doc_id = vectorstore.index_to_docstore_id[i]
doc = vectorstore.docstore.search(doc_id)
vec = vectorstore.index.reconstruct(i) # 获取原始向量
norm = float(np.linalg.norm(vec))
preview = ", ".join(f"{x:.3f}" for x in vec[:5])
print(f"\n[{i}] 文本: {doc.page_content}")
print(f" 向量维度: {vec.shape[0]}")
print(f" 前5维: [{preview}, ...]")
print(f" L2范数: {norm:.4f} {'✅' if abs(norm - 1.0) < 1e-3 else '⚠️'}")
print()
continue
# 判断是否为问题
is_question = (
user_input.endswith('?') or
user_input.endswith('?') or
any(w in user_input for w in ["什么", "吗", "呢", "为什么", "怎么", "谁", "是否", "why", "how", "what", "when"])
)
if is_question:
print(f"\n🔍 检索: 「{user_input}」")
results = vectorstore.similarity_search_with_relevance_scores(user_input, k=3)
relevant = [(doc, score) for doc, score in results if score >= COSINE_THRESHOLD]
if DEBUG_MODE:
print("📊 所有结果(余弦相似度):")
for doc, score in results:
print(f" [{score:.3f}] {doc.page_content}")
if relevant:
print("✅ 回忆起:")
for i, (doc, _) in enumerate(relevant, 1):
print(f" {i}. {doc.page_content}")
else:
print("🤔 没有足够相关的记忆(可以说得更明确些)")
print()
else:
if not user_input.startswith("我"):
print("💡 提示:请以「我...」开头,例如「我喜欢音乐」\n")
continue
memory = "用户" + user_input[1:]
if len(memory) < 4:
print("⚠️ 内容太短,请说完整句子\n")
continue
vectorstore.add_texts([memory])
print(f"💾 记住啦: 「{memory}」\n")
EOF
$ python vector_memory.py
🧠 向量记忆系统(余弦相似度模式)
📌 相似度阈值: 0.2 | 调试模式: ON
✅ 输入示例:
- 我喜欢喝茶
- 我讨厌早起
- 我平时喜欢做什么?
- quit
🗨️ 你: 我喜欢喝茶
💾 记住啦: 「用户喜欢喝茶」
🗨️ 你: 我讨厌周一开会
💾 记住啦: 「用户讨厌周一开会」
🗨️ 你: 我对周一有什么看法?
🔍 检索: 「我对周一有什么看法?」
📊 所有结果(余弦相似度):
[0.217] 用户讨厌周一开会
[-0.286] 用户喜欢喝茶
[-0.300] 用户喜欢喝美式咖啡
✅ 回忆起:
1. 用户讨厌周一开会
✅ 回顾理论
向量记忆:自动学习的“连续语义表示
向量数据库中的长期记忆,通过嵌入模型自动提取语义特征。同一句话会生成一个高维向量,天然包含多重语义维度:
“我喜欢美式咖啡” → 向量 ≈ [0.82, -0.15, 0.91, ..., 0.33]
这个向量同时隐含了:
- 饮品属性(靠近“茶”“果汁”)
- 咖啡子类(靠近“浓缩”“黑咖”,远离“拿铁”)
- 情感倾向(正向,远离“讨厌”“反感”)
向量记忆的优势:
- 无需人工标注:自动从语言中学习语义;
- 支持模糊匹配:即使措辞不同,只要意思近,就能召回;
- 天然支持层次推理:
“美式咖啡” → “咖啡” → “热饮” → “饮品”,在向量空间中是连续过渡的。
浙公网安备 33010602011771号