使用Nodejs基于DeepSeek加chromadb实现RAG检索增强生成 本地知识库
定义
检索增强生成(RAG)的基本定义
检索增强生成(Retrieval-Augmented Generation,简称RAG)是一种结合了信息检索技术与语言生成模型的人工智能技术。RAG通过从外部知识库中检索相关信息,并将其作为提示(Prompt)输入给大型语言模型(LLMs),以增强模型处理知识密集型任务的能力。这种技术能够有效提升模型在问答、文本摘要、内容生成等自然语言处理任务中的表现。
RAG的工作机制
RAG的工作机制主要包括三个核心部分:检索(Retrieval)、增强(Augmentation)和生成(Generation)。
检索(Retrieval):RAG流程的第一步是从预先建立的知识库中检索与问题相关的信息。这一步骤的目的是为后续的生成过程提供有用的上下文信息和知识支撑。检索模块通常依赖于向量检索技术,将查询转换为向量表示,然后与知识库中的向量进行比对,找到最相似的内容。
增强(Augmentation):在RAG中增强是将检索到的信息用作生成模型(即大语言模型)的上下文输入,以增强模型对特定问题的理解和回答能力。这一步的目的是将外部知识融入生成过程中,使生成的文本内容更加丰富、准确和符合用户需求。
生成(Generation):生成是RAG流程的最后一步。这一步的目的是结合LLM生成符合用户需求的回答。生成器会利用检索到的信息作为上下文输入,并结合大语言模型来生成文本内容,最终输出准确、有用的回答。
实现
实现思路
文件上传 读取 语句的分割还有向量化 然后对用户的提问在向量库中进行检索
通过langchain文档工具进行文档的分割
通过chromadb进行向量转换和语义检索
其实完全可以通过python实现但是我看官方有提供的js的插件包 于是在用node写了一套 之后又用python写了一套
怎么说呢 看你喜欢用哪一种吧 反正离不开python环境
第一步安装python环境
有更多教你安装的帖子链接在下面 可以去看看
https://blog.csdn.net/qq_45502336/article/details/109531599?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522d97c47b484bd158f0638dbc85d5e8b2c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=d97c47b484bd158f0638dbc85d5e8b2c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-109531599-null-null.14 2
第二步安装chromadb
chromadb 官网地址
pip install chromadb
然后肯定要持久化客户端运行
官网有几种启动方式 既然我们要用node实现 那其他的都不用看了 cmd 命令行启动客户端
搞个空的文件夹就行 (注意 安装完环境之后 把py环境变量搞上 要不然可能chroma run 无法运行)
chroma run --path C:\Users\Administrator\Desktop\database
# 本地数据库地址
注意 这里有坑 因为我用的win系统 chroma-hnswlib 最新版本有问题所以每个集合只能插入99条坑爹 然后客户端莫名就报错崩溃
我以为是官方提供的node包有问题 后来我用python实现了一遍 不报错但就是插不进去🙂 一查只有99条
所以在启动之前把chroma-hnswlib版本降下来!!! 可能会有提示版本不兼容 无需理会
pip install chroma-hnswlib==0.7.3 -i https://pypi.tuna.tsinghua.edu.cn/simple
看一下版本降下来没有
pip show chroma-hnswlib
开始安装node插件包
安装chromadb 和 chromadb-default-embed
npm install --save chromadb chromadb-default-embed
插入示例
import { ChromaClient } from "chromadb";
//path 是chroma启动的客户端地址默认是8000
const client = new ChromaClient({path: "http://localhost:8000"});
//name 是集合名 getOrCreateCollection 查询集合没有就创建集合
const collection = await client.getOrCreateCollection({ name: "test-8" });
//根据id进行更新插入 有就更新没有就添加 这里如果说没有对 chroma-hnswlib进行降级每个集合插入99条之后客户端就会崩溃
await collection.upsert({
ids: ["id1", "id2", "id3"],
metadatas: [
{ chapter: "3", verse: "16" },
{ chapter: "3", verse: "5" },
{ chapter: "29", verse: "11" },
],
documents: ["i am ggbond", "she is cat", "he is dog"],
});
查询示例
当然还有其他查询参数可以查看官网
await collection.query({
queryTexts: ["Who is a dog"],
nResults: 10, //返回前10条
})
然后就可以拿着这些数据作为提示词给DeepSeek做参考回答用户问题了
以上示例是最基础的默认的embeddings模型是 all-MiniLM-L6-v2 所以不用传embeddings也是可以的虽然说可能对中文的调教不是很好 当然也可以自定义embeddings模型 下面有示例
综合示例 结合 langchain 文件分割插入数据库
我这里用了langchain提供的包方便一些
文档地址 langchainChroma
npm i langchain @langchain/community @langchain/core chromadb pdf-parse
const { Chroma } = require("@langchain/community/vectorstores/chroma");
const { HuggingFaceTransformersEmbeddings } = require("@langchain/community/embeddings/huggingface_transformers");
const { PDFLoader } = require("@langchain/community/document_loaders/fs/pdf");
const { TextLoader } = require("langchain/document_loaders/fs/text");
const { DocxLoader } = require("@langchain/community/document_loaders/fs/docx");
const { EPubLoader } = require("@langchain/community/document_loaders/fs/epub");
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
//加载模型 其实不用这一步也可以 有默认的模型的 主要是提示一下可以通过model使用自定义模型
const allMiniLML6V2 = new HuggingFaceTransformersEmbeddings({
model: "Xenova/all-MiniLM-L6-v2",
});
//文档分割
async function documentReading(filepath, filename) {
let mimeType = filepath.split(".")[1];
console.log("文件类型", mimeType);
//加载分块
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500, //500字符一分割 这个参数可能影响最终查询的质量 根据自己实际情况来
});
// 加载
const loader = this.documentClassification(filepath, mimeType);
console.log("文档读取");
const docs = await loader.load();
// 分割
const splitterDocs = await splitter.splitDocuments(docs);
console.log(splitterDocs, "文档分割完成");
return { splitterDocs };
}
//文件分类加载
function documentClassification(filepath, mimeType) {
if (!filepath) throw new Error(`路径出现问题:${filepath}`);
let loader = null;
switch (mimeType) {
case "pdf":
loader = new PDFLoader(filepath);
break;
case "epub":
loader = new EPubLoader(filepath);
break;
case "txt":
loader = new TextLoader(filepath);
break;
case "docx":
loader = new DocxLoader(filepath);
break;
default:
break;
}
if (!loader) throw new Error(`无法解析的类型:${mimeType}`);
return loader;
}
// 查询
async function query(collectionName, queryText) {
const vectorStore = new Chroma(allMiniLML6V2, {
url: "http://localhost:8000", //客户端地址
collectionName, // 注意 集合名 一般都是文件名的英文 filename 我这里随便写的
});
return await vectorStore.similaritySearch(queryText, 2);
}
// 上传示例
documentReading(filepath, filename).then(({ splitterDocs }) => {
const vectorStore = new Chroma(allMiniLML6V2, {
url: "http://localhost:8000", //客户端地址
collectionName: "test", // 注意 集合名 一般都是文件名的英文 filename 我这里随便写的
});
//二次分割 文件比较大的话插入会很卡
const chunkSize = 20;
const chunkedDocs = Array.from(
{
length: Math.ceil(splitterDocs.length / chunkSize),
},
(_, index) => splitterDocs.slice(index * chunkSize, (index + 1) * chunkSize)
);
// 开始插入
chunkedDocs.forEach(async (docs, index) => {
await vectorStore
.addDocuments(docs)
.then((res) => {
console.log(index, "插入成功");
})
.catch((err) => {
console.log(index, "插入失败");
});
});
});
补充关于自定义模型
const allMiniLML6V2 = new HuggingFaceTransformersEmbeddings({ model: "Xenova/all-MiniLM-L6-v2", });
再进行这一步的时候可能因为下载的地址在国内访问会导致很慢
可以在node_models 下 @xenova/transformers/src/env 文件下remoteHost 地址更换为国内镜像地址 https://hf-mirror.com/
或则直接在.cache文件下加你下好的模型 Xenova/all-MiniLM-L6-v2 就是 .cache/Xenova/all-MiniLM-L6-v2
结束
剩下和DeepSeek对接需要根据每个人的项目来
chromadb只能作为检索工具来作为提示词为AI提供额外的定制化数据 来增强 AI