第 31 课: VectorStore 与 Retriever — 检索核心

4 阅读5分钟

课程目标

精读 VectorStore 抽象类、BaseRetriever 抽象类、VectorStoreRetriever 实现类以及 BaseStore<K,V> 通用存储抽象,理解 LangChain.js 检索层的完整设计。


31.1 检索层的设计哲学

LangChain.js 将检索拆分为三个独立关注点:

  1. VectorStore — 负责向量的存储与相似度搜索(数据层)
  2. BaseRetriever — 负责将"查询字符串 -> 文档列表"的能力统一为 Runnable(接口层)
  3. BaseStore<K,V> — 通用键值存储抽象,用于非向量场景(缓存、会话记录等)

关键洞察:Retriever 是 RunnableRunnable<string, Document[]>),这意味着它可以直接通过 pipe() 组合进任何链中。


31.2 VectorStore 抽象类

源码位置: libs/langchain-core/src/vectorstores.ts:553

export abstract class VectorStore extends Serializable implements VectorStoreInterface {
  declare FilterType: object | string;
  embeddings: EmbeddingsInterface;

  constructor(embeddings: EmbeddingsInterface, dbConfig: Record<string, any>) {
    super(dbConfig);
    this.embeddings = embeddings;
  }

  // 子类必须实现的三个抽象方法
  abstract _vectorstoreType(): string;
  abstract addVectors(vectors: number[][], documents: DocumentInterface[], options?: AddDocumentOptions): Promise<string[] | void>;
  abstract addDocuments(documents: DocumentInterface[], options?: AddDocumentOptions): Promise<string[] | void>;
  abstract similaritySearchVectorWithScore(query: number[], k: number, filter?: this["FilterType"]): Promise<[DocumentInterface, number][]>;

  // 有默认实现的方法
  async similaritySearch(query: string, k = 4, filter?, _callbacks?): Promise<DocumentInterface[]> {
    const results = await this.similaritySearchVectorWithScore(
      await this.embeddings.embedQuery(query),  // 先将文本转为向量
      k, filter
    );
    return results.map((result) => result[0]);  // 丢弃分数,只返回文档
  }

  async similaritySearchWithScore(query: string, k = 4, filter?, _callbacks?): Promise<[DocumentInterface, number][]> {
    return this.similaritySearchVectorWithScore(
      await this.embeddings.embedQuery(query), k, filter
    );
  }

  // 可选的 MMR 搜索 — 平衡相关性与多样性
  async maxMarginalRelevanceSearch?(query: string, options: MaxMarginalRelevanceSearchOptions<this["FilterType"]>, _callbacks): Promise<DocumentInterface[]>;
}

设计要点

方法必须实现说明
_vectorstoreType()返回标识字符串,如 "pinecone"
addVectors()存储预计算向量
addDocuments()存储文档(内部调用 embeddings 生成向量)
similaritySearchVectorWithScore()核心:向量相似度搜索
similaritySearch()默认实现:文本 -> embedQuery -> 向量搜索
maxMarginalRelevanceSearch()可选的 MMR 搜索
delete()默认抛 Not implemented

31.3 BaseRetriever — Runnable 化的检索器

源码位置: libs/langchain-core/src/retrievers/index.ts:65

export abstract class BaseRetriever<Metadata extends Record<string, any> = Record<string, any>>
  extends Runnable<string, DocumentInterface<Metadata>[]>
  implements BaseRetrieverInterface
{
  // 子类应实现此方法
  _getRelevantDocuments(
    _query: string,
    _callbacks?: CallbackManagerForRetrieverRun
  ): Promise<DocumentInterface<Metadata>[]> {
    throw new Error("Not implemented!");
  }

  // invoke 自动处理 callbacks 生命周期
  async invoke(input: string, options?: RunnableConfig): Promise<DocumentInterface<Metadata>[]> {
    const callbackManager_ = await CallbackManager.configure(/*...*/);
    const runManager = await callbackManager_?.handleRetrieverStart(/*...*/);
    try {
      const results = await this._getRelevantDocuments(input, runManager);
      await runManager?.handleRetrieverEnd(results);
      return results;
    } catch (error) {
      await runManager?.handleRetrieverError(error);
      throw error;
    }
  }
}

关键观察

  1. BaseRetriever 继承自 Runnable<string, DocumentInterface[]> — 输入是查询字符串,输出是文档数组
  2. invoke() 使用了模板方法模式,自动管理 callback 生命周期(handleRetrieverStart/End/Error
  3. 子类只需实现 _getRelevantDocuments() 即可

31.4 VectorStoreRetriever — 桥接 VectorStore 与 Retriever

源码位置: libs/langchain-core/src/vectorstores.ts:192

export class VectorStoreRetriever<V extends VectorStoreInterface = VectorStoreInterface>
  extends BaseRetriever
  implements VectorStoreRetrieverInterface
{
  vectorStore: V;
  k = 4;                         // 默认返回 4 个文档
  searchType = "similarity";     // "similarity" | "mmr"
  searchKwargs?: VectorStoreRetrieverMMRSearchKwargs;
  filter?: V["FilterType"];

  async _getRelevantDocuments(query: string, runManager?): Promise<DocumentInterface[]> {
    if (this.searchType === "mmr") {
      if (typeof this.vectorStore.maxMarginalRelevanceSearch !== "function") {
        throw new Error(`... does not support max marginal relevance search.`);
      }
      return this.vectorStore.maxMarginalRelevanceSearch(
        query,
        { k: this.k, filter: this.filter, ...this.searchKwargs },
        runManager?.getChild("vectorstore")
      );
    }
    return this.vectorStore.similaritySearch(
      query, this.k, this.filter, runManager?.getChild("vectorstore")
    );
  }
}

创建 Retriever 的便捷方式asRetriever()

// VectorStore.asRetriever() 工厂方法
const retriever = vectorStore.asRetriever(5);  // k=5

// 或者传入完整配置
const retriever = vectorStore.asRetriever({
  k: 10,
  searchType: "mmr",
  searchKwargs: { fetchK: 50, lambda: 0.5 },
  filter: { category: "tech" },
});

31.5 BaseStore<K,V> — 通用键值存储

源码位置: libs/langchain-core/src/stores.ts

BaseStore 与 VectorStore 的定位完全不同:

特性VectorStoreBaseStore<K,V>
用途向量相似度搜索通用键值存储
输入文档 + 向量任意键值对
查询语义相似度精确键匹配
典型场景RAG 检索缓存、会话历史、文档索引
export abstract class BaseStore<K, V> extends Serializable {
  abstract mget(keys: K[]): Promise<(V | undefined)[]>;
  abstract mset(keyValuePairs: [K, V][]): Promise<void>;
  abstract mdelete(keys: K[]): Promise<void>;
  abstract yieldKeys(prefix?: string): AsyncGenerator<K | string>;
}

核心设计:所有操作都是批量的(m 前缀 = multi),通过 yieldKeys() 异步生成器支持大量键的惰性遍历。

内置实现 InMemoryStore 直接使用 JavaScript 对象作为存储:

export class InMemoryStore<T = any> extends BaseStore<string, T> {
  protected store: Record<string, T> = {};

  async mget(keys: string[]) {
    return keys.map((key) => this.store[key]);
  }

  async mset(keyValuePairs: [string, T][]): Promise<void> {
    for (const [key, value] of keyValuePairs) {
      this.store[key] = value;
    }
  }

  async *yieldKeys(prefix?: string): AsyncGenerator<string> {
    for (const key of Object.keys(this.store)) {
      if (prefix === undefined || key.startsWith(prefix)) {
        yield key;
      }
    }
  }
}

31.6 完整 RAG 链构建

将检索器组合进 Runnable 链:

import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables";

// 假设已有 vectorStore 实例
const retriever = vectorStore.asRetriever(4);

const prompt = ChatPromptTemplate.fromMessages([
  ["system", "根据以下上下文回答问题:\n\n{context}"],
  ["human", "{question}"],
]);

// 格式化文档为文本
function formatDocs(docs: DocumentInterface[]): string {
  return docs.map((doc) => doc.pageContent).join("\n\n");
}

// 构建 RAG 链
const ragChain = RunnableSequence.from([
  {
    context: retriever.pipe(formatDocs),   // 检索并格式化
    question: new RunnablePassthrough(),    // 透传原始问题
  },
  prompt,
  model,
  new StringOutputParser(),
]);

// 使用
const answer = await ragChain.invoke("什么是 Runnable?");

数据流分析

  1. 输入 "什么是 Runnable?" 被广播给 RunnableParallel
  2. retriever.pipe(formatDocs) 执行相似度搜索 -> 返回格式化文本
  3. RunnablePassthrough 透传原始问题
  4. prompt{context, question} 组合为消息
  5. model 生成回答
  6. StringOutputParser 提取纯文本

31.7 SaveableVectorStore — 持久化扩展

export abstract class SaveableVectorStore extends VectorStore {
  abstract save(directory: string): Promise<void>;
  static load(_directory: string, _embeddings: EmbeddingsInterface): Promise<SaveableVectorStore> {
    throw new Error("Not implemented");
  }
}

为需要本地持久化的向量库(如 HNSWlib、FAISS)提供标准接口。


31.8 MMR 搜索详解

MMR(最大边际相关性)搜索在普通相似度搜索基础上增加了多样性控制:

export type MaxMarginalRelevanceSearchOptions<FilterType> = {
  k: number;          // 最终返回的文档数
  fetchK?: number;    // 初始检索的候选文档数(供 MMR 算法筛选)
  lambda?: number;    // 0 = 最大多样性, 1 = 最大相关性
  filter?: FilterType;
};
  • lambda = 1.0: 退化为普通相似度搜索
  • lambda = 0.5: 相关性与多样性各占一半
  • lambda = 0.0: 最大化结果多样性

31.9 源码精读路线

优先级文件关注点
P0langchain-core/src/vectorstores.tsVectorStore 抽象类、asRetriever() 工厂方法
P0langchain-core/src/retrievers/index.tsBaseRetriever 的 invoke()_getRelevantDocuments()
P1langchain-core/src/stores.tsBaseStore<K,V> 与 InMemoryStore
P2langchain-core/src/embeddings.tsEmbeddings 接口(embedQuery/embedDocuments)

31.10 实战练习

  1. 基础: 用 InMemoryStore 实现一个简单的文档缓存,存储和检索文档
  2. 进阶: 实现一个自定义 BaseRetriever 子类,从 JSON 文件中按关键词检索文档
  3. 高阶: 构建完整 RAG 链 — retriever.pipe(formatDocs) -> prompt -> model -> parser,用 FakeChatModel 测试

本课收获总结

级别你应该掌握的
🟢 基础理解向量检索的基本原理;掌握 similaritySearch() 的使用
🔵 中阶掌握 VectorStore 的核心接口(addDocuments/similaritySearch/asRetriever)
🟡 高阶理解 BaseRetriever 继承 Runnable 的设计,能将检索器 pipe 进链
🟠 资深分析 VectorStore vs BaseStore 的定位差异;理解 MMR 搜索的参数调优
🔴 架构能设计完整的 RAG pipeline:分块策略 + 检索 + 重排 + 生成

下一课预告

第 32 课将使用本课学到的检索核心组件,构建端到端的完整 RAG 应用:从文档加载、分割、嵌入、存储,到检索、生成的全流程实战。