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,并在检索后过滤掉低于该阈值的结果
    relevant = [(doc, score) for doc, score in results if score >= COSINE_THRESHOLD]
    
    避免返回语义上“不相关”的记忆(如相似度 0.05 的噪声匹配)。
  • 上一篇的程序:直接返回 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() 获取真实相似度

  • 本文里面的程序:调用
    vectorstore.similarity_search_with_relevance_scores(user_input, k=3)
    
    显式获取余弦相似度分数(因为 DashScope 向量已 L2 归一化,FAISS 内积 = 余弦相似度)。
  • 上一篇的程序:使用 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]
这个向量同时隐含了:

  • 饮品属性(靠近“茶”“果汁”)
  • 咖啡子类(靠近“浓缩”“黑咖”,远离“拿铁”)
  • 情感倾向(正向,远离“讨厌”“反感”)

向量记忆的优势

  • 无需人工标注:自动从语言中学习语义;
  • 支持模糊匹配:即使措辞不同,只要意思近,就能召回;
  • 天然支持层次推理
    “美式咖啡” → “咖啡” → “热饮” → “饮品”,在向量空间中是连续过渡的。
posted @ 2026-01-31 12:59  船山薪火  阅读(0)  评论(0)    收藏  举报
![image](https://img2024.cnblogs.com/blog/3174785/202601/3174785-20260125205854513-941832118.jpg)