知识模块
🤖 Agent 知识模块
记忆检索

记忆检索策略

记忆检索是连接长期记忆和短期记忆的桥梁。高效的检索策略能够从海量历史信息中快速找到相关内容,为 Agent 提供精准的知识支持。


一、什么是记忆检索?

1.1 定义

记忆检索(Memory Retrieval) 是从长期记忆存储中查找与当前查询相关的信息,并将其注入到短期记忆(Context Window)的过程。

1.2 检索的核心挑战

┌─────────────────────────────────────────────────────────────┐
│                    记忆检索的核心挑战                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  挑战 1:查全率 vs 查准率                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 查全率:相关记忆被检索出来的比例                    │   │
│  │ • 查准率:检索结果中相关记忆的比例                    │   │
│  │ • 权衡:提高查全率可能降低查准率                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  挑战 2:语义鸿沟                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 用户查询:口语化、模糊                              │   │
│  │ • 存储记忆:正式、结构化                              │   │
│  │ • 例如:"怎么用那个?" vs "XX工具使用教程"            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  挑战 3:规模与效率                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 记忆数量可能达到百万级                              │   │
│  │ • 检索延迟需控制在毫秒级                              │   │
│  │ • 实时性要求                                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  挑战 4:上下文窗口限制                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 检索结果必须放入 Context Window                     │   │
│  │ • 结果数量和长度受限                                  │   │
│  │ • 需要精准筛选                                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.3 检索流程概览

┌─────────────────────────────────────────────────────────────┐
│                    记忆检索流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                    ┌─────────────┐                          │
│                    │  用户查询   │                          │
│                    └──────┬──────┘                          │
│                           │                                 │
│                           ↓                                 │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Step 1: 查询理解与改写                               │  │
│  │  • 意图识别                                           │  │
│  │  • 查询扩展                                           │  │
│  │  • Query 改写                                         │  │
│  └──────────────────────┬───────────────────────────────┘  │
│                         │                                  │
│                         ↓                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Step 2: 多路召回                                     │  │
│  │  • 向量检索(语义相似)                               │  │
│  │  • 关键词检索(精确匹配)                             │  │
│  │  • 元数据过滤                                         │  │
│  └──────────────────────┬───────────────────────────────┘  │
│                         │                                  │
│                         ↓                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Step 3: 结果融合与排序                               │  │
│  │  • 分数归一化                                         │  │
│  │  • 加权融合                                           │  │
│  │  • Rerank 重排序                                      │  │
│  └──────────────────────┬───────────────────────────────┘  │
│                         │                                  │
│                         ↓                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Step 4: 结果筛选与截断                               │  │
│  │  • Top-K 截断                                         │  │
│  │  • 相似度阈值过滤                                     │  │
│  │  • Token 预算控制                                     │  │
│  └──────────────────────┬───────────────────────────────┘  │
│                         │                                  │
│                         ↓                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Step 5: 上下文注入                                   │  │
│  │  • 格式化检索结果                                     │  │
│  │  • 注入到 Prompt                                      │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、检索方式分类

2.1 语义检索

┌─────────────────────────────────────────────────────────────┐
│                    语义检索原理                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原理:                                                     │
│  • 将文本转换为向量表示(Embedding)                        │
│  • 通过向量相似度计算语义相关性                              │
│  • 能理解同义词、概念关联                                    │
│                                                             │
│  示例:                                                     │
│  查询:"如何优化数据库性能?"                               │
│  检索到:                                                   │
│  • "SQL 索引优化技巧" ← 语义相关                            │
│  • "数据库查询调优方法" ← 语义相关                          │
│  • "MySQL 性能优化实践" ← 语义相关                          │
│                                                             │
│  优势:                                                     │
│  • 理解语义关联                                             │
│  • 无需精确关键词匹配                                       │
│  • 适合自然语言查询                                         │
│                                                             │
│  劣势:                                                     │
│  • 专有名词可能不准确                                       │
│  • 需要 Embedding 模型                                      │
│  • 计算开销较大                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Chroma 语义检索实现

import chromadb
from chromadb.utils import embedding_functions
 
# 初始化
client = chromadb.Client()
embedding_fn = embedding_functions.DefaultEmbeddingFunction()
 
collection = client.create_collection(
    name="agent_memory",
    embedding_function=embedding_fn
)
 
# 存储记忆
collection.add(
    documents=[
        "用户偏好使用中文进行技术讨论",
        "用户喜欢简洁的回答风格",
        "用户关注 Python 和机器学习领域",
        "用户是一名后端开发工程师"
    ],
    ids=["mem_1", "mem_2", "mem_3", "mem_4"]
)
 
# 语义检索
results = collection.query(
    query_texts=["用户的职业是什么?"],  # 自然语言查询
    n_results=2
)
 
print(results["documents"])
# 输出: [["用户是一名后端开发工程师", "用户关注 Python 和机器学习领域"]]

2.2 关键词检索

┌─────────────────────────────────────────────────────────────┐
│                    关键词检索原理                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原理:                                                     │
│  • 基于 BM25、TF-IDF 等算法                                 │
│  • 精确匹配关键词                                           │
│  • 基于词频和文档频率计算相关性                              │
│                                                             │
│  示例:                                                     │
│  查询:"LangChain Agent"                                    │
│  检索到:                                                   │
│  • "LangChain Agent 开发教程" ← 精确匹配                    │
│  • "使用 LangChain 构建 Agent" ← 精确匹配                   │
│  • "Agent 框架对比" ← 部分匹配                              │
│                                                             │
│  优势:                                                     │
│  • 精确匹配专有名词                                         │
│  • 检索速度快                                               │
│  • 不需要 Embedding 模型                                    │
│                                                             │
│  劣势:                                                     │
│  • 无法理解语义关联                                         │
│  • 对同义词无能为力                                         │
│  • 依赖查询词精确性                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

BM25 关键词检索实现

from rank_bm25 import BM25Okapi
from typing import List, Tuple
import jieba
 
class KeywordRetriever:
    """关键词检索器"""
    
    def __init__(self, documents: List[str]):
        self.documents = documents
        # 中文分词
        self.tokenized_docs = [list(jieba.cut(doc)) for doc in documents]
        self.bm25 = BM25Okapi(self.tokenized_docs)
    
    def search(self, query: str, top_k: int = 5) -> List[Tuple[str, float]]:
        """搜索"""
        tokenized_query = list(jieba.cut(query))
        scores = self.bm25.get_scores(tokenized_query)
        
        # 排序并返回 Top-K
        ranked = sorted(enumerate(scores), key=lambda x: x[1], reverse=True)
        results = [
            (self.documents[idx], score)
            for idx, score in ranked[:top_k]
            if score > 0
        ]
        return results
 
# 使用示例
documents = [
    "LangChain 是一个 LLM 应用开发框架",
    "Agent 是具有自主决策能力的 AI 系统",
    "LangChain Agent 开发实战教程",
    "向量数据库用于语义检索",
    "RAG 是检索增强生成技术"
]
 
retriever = KeywordRetriever(documents)
results = retriever.search("LangChain Agent", top_k=3)
 
for doc, score in results:
    print(f"[{score:.2f}] {doc}")

2.3 混合检索

┌─────────────────────────────────────────────────────────────┐
│                    混合检索原理                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原理:                                                     │
│  • 同时使用语义检索和关键词检索                              │
│  • 融合两种检索结果                                          │
│  • 兼顾语义理解和精确匹配                                    │
│                                                             │
│  流程:                                                     │
│                                                             │
│               ┌─────────────┐                               │
│               │  用户查询   │                               │
│               └──────┬──────┘                               │
│                      │                                      │
│          ┌───────────┴───────────┐                         │
│          ↓                       ↓                         │
│   ┌─────────────┐         ┌─────────────┐                 │
│   │ 语义检索    │         │ 关键词检索  │                 │
│   │ (Vector)    │         │ (BM25)      │                 │
│   └──────┬──────┘         └──────┬──────┘                 │
│          │                       │                         │
│          ↓                       ↓                         │
│   ┌─────────────┐         ┌─────────────┐                 │
│   │ 结果集 A    │         │ 结果集 B    │                 │
│   │ Top-K       │         │ Top-K       │                 │
│   └──────┬──────┘         └──────┬──────┘                 │
│          │                       │                         │
│          └───────────┬───────────┘                         │
│                      ↓                                      │
│               ┌─────────────┐                               │
│               │  结果融合   │                               │
│               │  Reciprocal │                               │
│               │  Rank Fusion│                               │
│               └──────┬──────┘                               │
│                      │                                      │
│                      ↓                                      │
│               ┌─────────────┐                               │
│               │  最终结果   │                               │
│               └─────────────┘                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

混合检索实现

from typing import List, Dict, Tuple
import chromadb
from rank_bm25 import BM25Okapi
import jieba
 
class HybridRetriever:
    """混合检索器"""
    
    def __init__(
        self,
        documents: List[str],
        vector_weight: float = 0.5,
        keyword_weight: float = 0.5
    ):
        self.documents = documents
        self.vector_weight = vector_weight
        self.keyword_weight = keyword_weight
        
        # 初始化向量检索
        self.vector_client = chromadb.Client()
        self.collection = self.vector_client.create_collection("hybrid_search")
        self.collection.add(
            documents=documents,
            ids=[f"doc_{i}" for i in range(len(documents))]
        )
        
        # 初始化关键词检索
        tokenized = [list(jieba.cut(doc)) for doc in documents]
        self.bm25 = BM25Okapi(tokenized)
    
    def search(self, query: str, top_k: int = 5) -> List[Dict]:
        """混合检索"""
        # 1. 向量检索
        vector_results = self.collection.query(
            query_texts=[query],
            n_results=top_k * 2  # 多召回一些
        )
        vector_docs = vector_results["documents"][0]
        vector_distances = vector_results["distances"][0]
        
        # 2. 关键词检索
        tokenized_query = list(jieba.cut(query))
        bm25_scores = self.bm25.get_scores(tokenized_query)
        
        # 3. 分数归一化
        vector_scores = self._normalize_scores(vector_distances, inverse=True)
        bm25_norm_scores = self._normalize_scores(bm25_scores)
        
        # 4. 构建文档分数映射
        doc_scores = {}
        
        # 向量检索结果
        for doc, score in zip(vector_docs, vector_scores):
            doc_scores[doc] = {
                "vector_score": score,
                "keyword_score": 0,
                "doc": doc
            }
        
        # 关键词检索结果
        for i, score in enumerate(bm25_norm_scores):
            doc = self.documents[i]
            if doc in doc_scores:
                doc_scores[doc]["keyword_score"] = score
            else:
                doc_scores[doc] = {
                    "vector_score": 0,
                    "keyword_score": score,
                    "doc": doc
                }
        
        # 5. 加权融合
        for doc_data in doc_scores.values():
            doc_data["final_score"] = (
                self.vector_weight * doc_data["vector_score"] +
                self.keyword_weight * doc_data["keyword_score"]
            )
        
        # 6. 排序返回
        sorted_results = sorted(
            doc_scores.values(),
            key=lambda x: x["final_score"],
            reverse=True
        )
        
        return sorted_results[:top_k]
    
    def _normalize_scores(self, scores: List[float], inverse: bool = False) -> List[float]:
        """分数归一化到 0-1"""
        if not scores:
            return []
        
        min_s, max_s = min(scores), max(scores)
        if max_s == min_s:
            return [0.5] * len(scores)
        
        if inverse:
            # 对于距离,越小越好
            return [1 - (s - min_s) / (max_s - min_s) for s in scores]
        else:
            return [(s - min_s) / (max_s - min_s) for s in scores]
 
# 使用示例
documents = [
    "LangChain 是一个 LLM 应用开发框架",
    "Agent 是具有自主决策能力的 AI 系统",
    "LangChain Agent 开发实战教程",
    "向量数据库用于语义检索",
    "RAG 是检索增强生成技术",
    "如何使用 LangChain 构建 Agent"
]
 
retriever = HybridRetriever(documents)
results = retriever.search("LangChain Agent 开发", top_k=3)
 
for r in results:
    print(f"[{r['final_score']:.3f}] {r['doc']}")

三、RAG 架构详解

3.1 RAG 定义

RAG(Retrieval-Augmented Generation,检索增强生成) 是将检索与生成相结合的技术架构,通过检索外部知识来增强 LLM 的生成能力。

3.2 RAG 架构图

┌─────────────────────────────────────────────────────────────────────┐
│                    RAG 架构完整流程                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  【索引阶段】                                                       │
│                                                                     │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│  │  文档    │ →  │  切分    │ →  │ Embedding │ →  │ 向量存储 │     │
│  │ Documents│    │ Chunking │    │          │    │ Vector DB│     │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘     │
│                                                                     │
│  【检索阶段】                                                       │
│                                                                     │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│  │  查询    │ →  │ Embedding │ →  │ 向量检索 │ →  │ Top-K    │     │
│  │  Query   │    │          │    │          │    │ Results  │     │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘     │
│                                                       │             │
│                                                       ↓             │
│  【生成阶段】                                         │             │
│                                                      │             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│  │ Prompt   │ ← │ 上下文    │ ← │ 格式化   │ ← │ 检索结果 │     │
│  │ 组装     │    │ Context  │    │          │    │          │     │
│  └────┬─────┘    └──────────┘    └──────────┘    └──────────┘     │
│       │                                                             │
│       ↓                                                             │
│  ┌──────────┐    ┌──────────┐                                      │
│  │   LLM    │ →  │  回答    │                                      │
│  │          │    │  Answer  │                                      │
│  └──────────┘    └──────────┘                                      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.3 LangChain RAG 实现

from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
 
class RAGSystem:
    """RAG 系统实现"""
    
    def __init__(self, documents, persist_directory: str = "./chroma_db"):
        self.documents = documents
        self.persist_directory = persist_directory
        
        # 初始化组件
        self.embeddings = OpenAIEmbeddings()
        self.llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,
            chunk_overlap=50
        )
        
        # 构建向量存储
        self._build_vector_store()
        
        # 构建 RAG Chain
        self._build_rag_chain()
    
    def _build_vector_store(self):
        """构建向量存储"""
        # 切分文档
        splits = self.text_splitter.split_documents(self.documents)
        
        # 存入向量数据库
        self.vectorstore = Chroma.from_documents(
            documents=splits,
            embedding=self.embeddings,
            persist_directory=self.persist_directory
        )
        
        self.retriever = self.vectorstore.as_retriever(
            search_type="similarity",
            search_kwargs={"k": 4}
        )
    
    def _build_rag_chain(self):
        """构建 RAG Chain"""
        # 自定义 Prompt
        prompt_template = """
        你是一个专业的问答助手。请根据以下检索到的上下文回答问题。
        如果上下文中没有相关信息,请明确说明"根据现有信息无法回答"。
        
        上下文:
        {context}
        
        问题:{question}
        
        回答:
        """
        
        prompt = PromptTemplate(
            template=prompt_template,
            input_variables=["context", "question"]
        )
        
        self.qa_chain = RetrievalQA.from_chain_type(
            llm=self.llm,
            chain_type="stuff",
            retriever=self.retriever,
            return_source_documents=True,
            chain_type_kwargs={"prompt": prompt}
        )
    
    def query(self, question: str) -> Dict:
        """查询"""
        result = self.qa_chain.invoke({"query": question})
        
        return {
            "answer": result["result"],
            "source_documents": result["source_documents"]
        }
 
# 使用示例
from langchain.schema import Document
 
documents = [
    Document(page_content="LangChain 是一个用于开发 LLM 应用的框架,提供了链式调用、记忆、工具等核心组件。"),
    Document(page_content="Agent 是具有自主决策能力的 AI 系统,能够根据目标选择合适的工具和行动。"),
    Document(page_content="RAG(检索增强生成)是将检索与生成相结合的技术,通过检索外部知识增强 LLM 能力。"),
    Document(page_content="向量数据库用于存储文本的向量表示,支持语义相似度检索。"),
]
 
rag = RAGSystem(documents)
 
result = rag.query("什么是 RAG?")
print(result["answer"])

四、检索优化技术

4.1 Query 改写

┌─────────────────────────────────────────────────────────────┐
│                    Query 改写技术                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  目的:解决用户查询与存储记忆之间的语义鸿沟                  │
│                                                             │
│  方法 1:查询扩展                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 原查询:"Python 文件操作"                             │   │
│  │ 扩展后:["Python 文件操作", "Python read write file", │   │
│  │         "Python 文件读写", "Python IO 操作"]         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  方法 2:查询改写                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 原查询:"那个怎么用?"                                 │   │
│  │ 改写后:"LangChain Agent 的使用方法"                  │   │
│  │ (结合上下文理解"那个"指代什么)                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  方法 3:假设性文档生成(HyDE)                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 原查询:"如何优化数据库查询?"                         │   │
│  │ 生成假设文档:                                        │   │
│  │ "数据库查询优化可以从以下几个方面入手:                │   │
│  │  1. 建立合适的索引                                    │   │
│  │  2. 优化 SQL 语句                                     │   │
│  │  3. 使用查询缓存..."                                  │   │
│  │ 然后用假设文档去检索相似的真实文档                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Query 改写实现

from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
 
class QueryRewriter:
    """查询改写器"""
    
    def __init__(self, llm=None):
        self.llm = llm or ChatOpenAI(temperature=0)
        
        self.rewrite_prompt = PromptTemplate(
            template="""
            请将以下用户查询改写为更适合检索的形式。
            要求:
            1. 保持原意
            2. 使用更正式的表述
            3. 添加可能的关键词
            
            原查询:{query}
            
            改写后的查询:
            """,
            input_variables=["query"]
        )
        
        self.expand_prompt = PromptTemplate(
            template="""
            请为以下查询生成3个语义相同但表述不同的变体,用于扩展检索范围。
            
            原查询:{query}
            
            变体(每行一个):
            """,
            input_variables=["query"]
        )
    
    def rewrite(self, query: str) -> str:
        """改写查询"""
        chain = self.rewrite_prompt | self.llm
        result = chain.invoke({"query": query})
        return result.content.strip()
    
    def expand(self, query: str) -> list:
        """扩展查询"""
        chain = self.expand_prompt | self.llm
        result = chain.invoke({"query": query})
        variants = result.content.strip().split("\n")
        return [v.strip() for v in variants if v.strip()]
 
# 使用示例
rewriter = QueryRewriter()
 
# 改写
print(rewriter.rewrite("那个怎么用?"))
# 输出:LangChain Agent 框架使用方法教程
 
# 扩展
print(rewriter.expand("Python 数据处理"))
# 输出:['Python 数据处理', 'Python pandas 数据分析', 'Python 数据清洗方法']

4.2 Rerank 重排序

┌─────────────────────────────────────────────────────────────┐
│                    Rerank 重排序技术                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原理:                                                     │
│  1. 使用粗检索快速召回大量候选(如 Top-100)                 │
│  2. 使用精细模型对候选重排序                                 │
│  3. 返回最相关的 Top-K                                      │
│                                                             │
│  优势:                                                     │
│  • 平衡检索效率和质量                                        │
│  • 精细模型考虑更多上下文                                    │
│  • 显著提升检索准确性                                        │
│                                                             │
│  常用 Rerank 模型:                                         │
│  • Cohere Rerank                                           │
│  • BGE Reranker                                            │
│  • Cross-Encoder 模型                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Rerank 实现

from typing import List, Tuple
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
 
class LLMReranker:
    """基于 LLM 的 Reranker"""
    
    def __init__(self, llm=None):
        self.llm = llm or ChatOpenAI(temperature=0)
        
        self.rerank_prompt = PromptTemplate(
            template="""
            请根据查询对以下文档进行相关性打分(0-10分)。
            
            查询:{query}
            
            文档:{document}
            
            只输出分数(0-10的整数):
            """,
            input_variables=["query", "document"]
        )
    
    def rerank(
        self, 
        query: str, 
        documents: List[str], 
        top_k: int = 5
    ) -> List[Tuple[str, float]]:
        """重排序"""
        scored_docs = []
        
        for doc in documents:
            chain = self.rerank_prompt | self.llm
            result = chain.invoke({"query": query, "document": doc})
            
            try:
                score = float(result.content.strip())
            except:
                score = 0
            
            scored_docs.append((doc, score))
        
        # 排序
        scored_docs.sort(key=lambda x: x[1], reverse=True)
        
        return scored_docs[:top_k]
 
# 使用示例
reranker = LLMReranker()
 
query = "LangChain Agent 开发"
candidates = [
    "LangChain 是一个 LLM 应用框架",
    "LangChain Agent 开发实战教程",
    "向量数据库的使用方法",
    "如何构建一个智能 Agent",
    "LangChain 记忆系统详解"
]
 
results = reranker.rerank(query, candidates, top_k=3)
for doc, score in results:
    print(f"[{score}] {doc}")

4.3 元数据过滤

from typing import Dict, List, Optional
 
class MetadataFilteredRetriever:
    """支持元数据过滤的检索器"""
    
    def __init__(self, vectorstore):
        self.vectorstore = vectorstore
    
    def search(
        self,
        query: str,
        filters: Optional[Dict] = None,
        top_k: int = 5
    ) -> List[Dict]:
        """
        带元数据过滤的检索
        
        filters 示例:
        {
            "user_id": "user_123",
            "memory_type": "preference",
            "created_at": {"$gt": "2024-01-01"}
        }
        """
        # 构建过滤条件
        where_clause = self._build_where_clause(filters)
        
        # 执行检索
        results = self.vectorstore.similarity_search(
            query,
            k=top_k,
            filter=where_clause
        )
        
        return results
    
    def _build_where_clause(self, filters: Optional[Dict]) -> Optional[Dict]:
        """构建过滤条件"""
        if not filters:
            return None
        
        conditions = []
        
        for key, value in filters.items():
            if isinstance(value, dict):
                # 复杂条件
                for op, val in value.items():
                    if op == "$gt":
                        conditions.append({key: {"$gt": val}})
                    elif op == "$lt":
                        conditions.append({key: {"$lt": val}})
                    elif op == "$in":
                        conditions.append({key: {"$in": val}})
            else:
                # 简单等值条件
                conditions.append({key: value})
        
        if len(conditions) == 1:
            return conditions[0]
        elif len(conditions) > 1:
            return {"$and": conditions}
        
        return None
 
# 使用示例
# retriever.search(
#     query="用户的偏好",
#     filters={
#         "user_id": "user_123",
#         "memory_type": "preference"
#     },
#     top_k=5
# )

五、面试高频问题

Q1: 向量数据库检索不准怎么办?

答案要点

解决方案(三个层次):

1. 数据层
   • 数据清洗:去除噪声、重复数据
   • Chunking 优化:合理切分,避免语义断裂
   • 元数据丰富:添加时间、类型、来源等标签

2. 检索层
   • 混合检索:语义 + 关键词
   • Rerank 重排序:精细化排序
   • Query 改写:优化用户查询

3. 应用层
   • Prompt 指示:告诉 LLM "检索内容无关时回答不知道"
   • 多轮检索:根据初步结果扩展查询
   • 用户反馈:收集反馈优化检索

Q2: 语义检索和关键词检索如何选择?

答案要点

场景推荐方式理由
专有名词查询关键词检索精确匹配
概念性问题语义检索理解语义关联
混合场景混合检索兼顾两者
生产环境混合检索 + Rerank最佳效果

最佳实践:生产环境推荐混合检索,权重可调。

Q3: RAG 和微调(Fine-tuning)有什么区别?

答案要点

维度RAGFine-tuning
知识更新实时更新检索库需重新训练
成本检索成本训练成本高
可控性高(可追溯来源)低(黑盒)
适用场景知识频繁变化特定任务优化
响应速度检索+生成直接生成

最佳实践:两者可结合使用,RAG 提供知识,Fine-tuning 优化任务表现。

Q4: 如何评估检索质量?

答案要点

评估指标:

1. 离线指标
   • Recall@K:Top-K 结果中相关文档的比例
   • MRR:平均倒数排名
   • NDCG:归一化折损累积增益

2. 在线指标
   • 点击率:用户点击检索结果的比例
   • 满意度:用户反馈评分
   • 任务完成率:基于检索完成任务的比率

评估方法:
• 人工标注测试集
• A/B 测试
• 用户反馈收集

Q5: 如何处理检索结果过多或过少的情况?

答案要点

检索结果过多:
• 提高相似度阈值
• 增加 Rerank 步骤
• 使用更严格的元数据过滤
• 限制返回数量

检索结果过少:
• 降低相似度阈值
• 扩展查询(Query Expansion)
• 放宽元数据过滤条件
• 使用同义词扩展
• 提示 LLM 说明信息不足

六、总结

核心概念回顾

概念定义关键要点
语义检索基于向量相似度的检索理解语义关联
关键词检索基于 BM25 等算法的检索精确匹配
混合检索语义 + 关键词融合生产环境首选
RAG检索增强生成检索 + LLM 生成
Rerank结果重排序提升检索精度

一句话总结

记忆检索是连接长期记忆和短期记忆的桥梁,通过语义检索、关键词检索、混合检索等多种策略,配合 Query 改写、Rerank 等优化技术,实现高效精准的知识召回。

设计口诀

记忆检索策略口诀:
语义理解向量查,关键词做精确抓
混合检索效果佳,Rerank 后排序它
Query 改写补语义,元数据过滤筛精华
RAG 架构增知识,检索生成两不差

最后更新:2026年3月18日