课程目标
精读 VectorStore 抽象类、BaseRetriever 抽象类、VectorStoreRetriever 实现类以及 BaseStore<K,V> 通用存储抽象,理解 LangChain.js 检索层的完整设计。
31.1 检索层的设计哲学
LangChain.js 将检索拆分为三个独立关注点:
- VectorStore — 负责向量的存储与相似度搜索(数据层)
- BaseRetriever — 负责将"查询字符串 -> 文档列表"的能力统一为 Runnable(接口层)
- BaseStore<K,V> — 通用键值存储抽象,用于非向量场景(缓存、会话记录等)
关键洞察:Retriever 是 Runnable(Runnable<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;
}
}
}
关键观察:
- BaseRetriever 继承自
Runnable<string, DocumentInterface[]>— 输入是查询字符串,输出是文档数组 invoke()使用了模板方法模式,自动管理 callback 生命周期(handleRetrieverStart/End/Error)- 子类只需实现
_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 的定位完全不同:
| 特性 | VectorStore | BaseStore<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?");
数据流分析:
- 输入
"什么是 Runnable?"被广播给RunnableParallel retriever.pipe(formatDocs)执行相似度搜索 -> 返回格式化文本RunnablePassthrough透传原始问题prompt将{context, question}组合为消息model生成回答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 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | langchain-core/src/vectorstores.ts | VectorStore 抽象类、asRetriever() 工厂方法 |
| P0 | langchain-core/src/retrievers/index.ts | BaseRetriever 的 invoke() 与 _getRelevantDocuments() |
| P1 | langchain-core/src/stores.ts | BaseStore<K,V> 与 InMemoryStore |
| P2 | langchain-core/src/embeddings.ts | Embeddings 接口(embedQuery/embedDocuments) |
31.10 实战练习
- 基础: 用
InMemoryStore实现一个简单的文档缓存,存储和检索文档 - 进阶: 实现一个自定义
BaseRetriever子类,从 JSON 文件中按关键词检索文档 - 高阶: 构建完整 RAG 链 —
retriever.pipe(formatDocs)->prompt->model->parser,用 FakeChatModel 测试
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 理解向量检索的基本原理;掌握 similaritySearch() 的使用 |
| 🔵 中阶 | 掌握 VectorStore 的核心接口(addDocuments/similaritySearch/asRetriever) |
| 🟡 高阶 | 理解 BaseRetriever 继承 Runnable 的设计,能将检索器 pipe 进链 |
| 🟠 资深 | 分析 VectorStore vs BaseStore 的定位差异;理解 MMR 搜索的参数调优 |
| 🔴 架构 | 能设计完整的 RAG pipeline:分块策略 + 检索 + 重排 + 生成 |
下一课预告
第 32 课将使用本课学到的检索核心组件,构建端到端的完整 RAG 应用:从文档加载、分割、嵌入、存储,到检索、生成的全流程实战。