Skip to content

内存型向量库

回忆 RAG 关键步骤:

  1. 文本切割
  2. 嵌入处理
  3. 存储向量数据库

向量数据库可以分为这几种类型:

  1. 内存型
  2. 本地自托管
  3. 云托管

LangChain 内置了 MemoryVectorStore,这就是一个内存型向量库,用于将文档向量存储到内存中,适合本地调试、快速演示,零依赖、即插即用。

MemoryVectorStore 整体工作流:

  1. 把要检索的文本包装成 Document
  2. 调用 vectorstore.addDocuments() 触发嵌入;
  3. similaritySearch(...)similaritySearchWithScore(...) 做相似度检索。

1. 实例化内存向量库

import { MemoryVectorStore } from 'langchain/vectorstores/memory'
const store = new MemoryVectorStore()
console.log(store)

效果:

MemoryVectorStore {
lc_serializable: false,
lc_kwargs: {},
lc_namespace: [ 'langchain', 'vectorstores', 'memory' ],
embeddings: undefined,
memoryVectors: [],
similarity: [Function: cosine]
}

2. 指定嵌入工具

自定义嵌入类:NomicEmbeddings

import { MemoryVectorStore } from 'langchain/vectorstores/memory'
import { NomicEmbeddings } from './utils/embed.js'
const embeddings = new NomicEmbeddings(4)
const store = new MemoryVectorStore(embeddings)
console.log(store)

效果:

MemoryVectorStore {
lc_serializable: false,
lc_kwargs: {},
lc_namespace: [ 'langchain', 'vectorstores', 'memory' ],
embeddings: NomicEmbeddings {
caller: AsyncCaller {
maxConcurrency: Infinity,
maxRetries: 6,
onFailedAttempt: [Function: defaultFailedAttemptHandler],
queue: [PQueue]
},
model: 'nomic-embed-text',
apiUrl: 'http://localhost:11434/api/embeddings',
concurrency: 4
},
memoryVectors: [],
similarity: [Function: cosine]
}

3. 添加文档

添加文档可以调用 vectorstore 实例的 addDocuments 方法,该方法接收一个数组,数组里面每一项是 Document 实例对象,之后会自动调用 embeddings.embedDocuments 生成向量并存入内存。

await vectorstore.addDocuments([
new Document({
pageContent: 'Vue 是一个渐进式前端框架,易于上手。',
metadata: { id: 'a1', tag: 'frontend' },
}),
new Document({
pageContent: 'React 通过 JSX 描述 UI,强调组件化与状态管理。',
metadata: { id: 'b2', tag: 'frontend' },
}),
new Document({
pageContent: 'LangChain 提供 RAG 组件与向量检索工具。',
metadata: { id: 'c3', tag: 'ai' },
}),
])

具体示例:

import { MemoryVectorStore } from 'langchain/vectorstores/memory'
import { NomicEmbeddings } from './utils/embed.js'
import { TextLoader } from 'langchain/document_loaders/fs/text'
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
const loader = new TextLoader('data/kong.txt')
const docs = await loader.load()
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 64,
chunkOverlap: 0,
})
const splittedDocs = await splitter.splitDocuments(docs)
const embeddings = new NomicEmbeddings(4)
const store = new MemoryVectorStore(embeddings)
await store.addDocuments(splittedDocs)
console.log(store.memoryVectors)

4. 检索操作

首先创建一个检索器:

const retriever = vectorstore.asRetriever(2)

vectorstore.asRetriever(2) 是一个简写,等价于 vectorstore.asRetriever({ k: 2 }),表示创建一个检索器,每次查询返回前 2 条最相似的 Document。返回值是一个 VectorStoreRetriever

快速上手示例:

import { MemoryVectorStore } from 'langchain/vectorstores/memory'
import { NomicEmbeddings } from './utils/embed.js'
import { TextLoader } from 'langchain/document_loaders/fs/text'
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
const loader = new TextLoader('data/kong.txt')
const docs = await loader.load()
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 64,
chunkOverlap: 0,
})
const splittedDocs = await splitter.splitDocuments(docs)
const embeddings = new NomicEmbeddings(4)
const store = new MemoryVectorStore(embeddings)
await store.addDocuments(splittedDocs)
// 创建一个检索器,现在仓库已经有值了
const retriever = store.asRetriever(1)
const res = await retriever.invoke('茴香豆是做什么用的')
console.log(res)
store.asRetriever(1)
store.asRetriever({
k: 1,
})

配置对象支持如下的参数:

const retriever = vectorstore.asRetriever({
k: 4,
searchType: 'mmr',
searchKwargs: { fetchK: 20, lambda: 0.5 },
filter: (doc) => doc.metadata?.source?.endsWith('data/kong.txt'),
tags: ['demo', 'kong'],
metadata: { lesson: 'RAG-intro' },
verbose: true,
})

另外配置项也支持数字简写,方法签名如下:

asRetriever(kOrFields?, filter?, callbacks?, tags?, metadata?, verbose?)

不过想跳过中间的参数再传后面的,就用 undefined 占位。例如:

const r3 = vectorstore.asRetriever(3, undefined, undefined, ['demo', 'kong'])

另外还有 similaritySearch 以及 similaritySearchWithScore 方法,用法也和上面类似。

平时在使用的时候更推荐使用 asRetriever 方法先创建一个检索器,后期进行检索的时候只需要传入检索文本即可。