2026年7月9日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

用JavaScript快速实现低成本RAG知识库

发布日期:2024-08-21 20:01:03 浏览次数: 2713
作者:唐霜

微信搜一搜,关注“唐霜”

很久没有写文章了,过去两个多月我研发了一套系统,以辅助开发者们更便捷的获得AI服务。在过程中,我想提供给用户一个FAQ,但又不想做的像传统的问答编辑系统一样,于是想借助RAG来实现问答系统。初步想法是把站内所有的wiki向量化,作为知识库的一部分,另外,当用户提出自己的问题时,运营同学可以在后端进行答复,把这些答复的内容也向量化作为知识库的一部分。通过RAG来做,可以防止类似github issue沉底的问题,避免已经出现过的问题和答复无法实时的让新用户在有疑问时获得了解。
同时,在这个过程中,我浏览了各个云平台,以及各家大模型服务商,试图找到一个可以直接用于实现RAG的后端服务,但没有找到。对于FAQ这一需求而言,我认为一个精简的产品形态就是几个接口即可:
  • 用于提交知识(文本)的接口,该接口用于把网站或客户端的知识集中到后端服务中向量化存储

  • 用于提问/查询的接口,该接口用于从知识库中获得结果

  • 用于列出所有提问的接口

  • 用于对单一提问进行人工回答的接口
通过4个接口,屏蔽了有关向量化、向量存储、检索增强等一系列的技术性问题,变成了无论是前端还是后端同学都可以使用的纯粹接口。当然,我们可以在云服务的后台,对所使用的大模型、向量模型、向量存储等更底层的服务进行配置,不同的配置所需的花费不同,从而让不同资金需求的用户都可以获得各自想要的RAG服务效果。
抱着这样的心态,我开始自己实现这一需求。

什么是RAG知识库?

相信你已经对RAG已经有所了解了,为了使得文章更充实(凑字数),我还是从我个人的角度来聊一聊什么是RAG知识库。
作为本科和研究生阶段都对信息管理有深入研究的从业者,脱离学术,我们讲知识管理,本质上是在讲人类利用信息的高效通道。传统知识库我们称为“图书馆”,通过类目方式对知识进行索引,并提供类目检索工具让我们快速找到对应的图书,从而可以获得知识,但当我们要找粒度很细的知识时,犹如大海捞针。现代知识库我们称之为“搜索引擎”,传统知识库获取知识的通道效率太低,想要精准的获取某个知识点,可能还需要借助对应的人才能完成,而搜索引擎时代,通过新的索引方式,向人们提供了寻找知识的捷径。当代知识库我们我们称之为“智能助手”,搜索引擎时代虽然相较于传统图书馆已经有了极大的提升,但是随着信息泛滥,搜索引擎只能提供获取知识的“线索”,而要精准获得知识,仍然有赖于搜索者对搜索结果的总结,而借助于AI的智能助手,则更近一步,不仅主动完成用户对信息的搜索,还对信息进行提炼,将人们对知识的需求,以最直接的方式呈现在面前,也就是“所问既所答”。
RAG(检索增强生成)知识库就是当代AI背景下的知识助手,LLM-Based已经成为AI的新范式,可以说没有LLM也就没有RAG知识库。
知识库本身存在知识范围的限制,人类知识过于浩瀚,而真正在企业中发挥经济价值的知识,往往限定在与企业生产生活相关的范畴中。因此,从知识范围的角度,RAG知识库可以分为宏观行业知识库、企业单位生产知识库、自然个人学习生活知识库。对于使用RAG知识库的用户而言,应该明确自己所需的知识范围,避免因为知识范围的不同导致往知识库中加入错误的知识来源。
对于AI行业而言,RAG又是有效的应用形态,弥补了LLM训练数据局限的问题。
总而言之,RAG知识库是当下AI技术发展到LLM-Based背景下,可以提供给人类最先进的知识获取的方式。

RAG知识库通用技术架构

不同企业在实现RAG知识库时,技术细节上各有优化,但是总体而言,RAG有着通用的技术架构。
从我个人的角度,我把RAG的技术分为两大部分:1.向量化检索;2.增强生成。
什么是向量化检索呢?举个例子,我们规定了3个维度,分别是:高度、体积、颜色。现在任何一个物体,我们都能推算出它在这三个维度上的值。我们把这个物体存入到仓库中时,标注了它的这3个维度。现在,我们的仓库中充满了物体,我们看到一个新物体,想要找出仓库中与之最为接近的物体,就只需要找出这3个维度的值与新物体最接近的哪些物体即可。这种方法可以大大提高检索的效率。这就是向量化检索,其中这个物体在3个维度上的表示,就是一个向量,而通过向量坐标,在数学上进行相似性运算是很方便的。同时,在进行向量化时,我们还会做一个隐藏技能,就是对原本文档进行分片,通过分片,既让知识向量化,又让知识体积变小,这样对后面把知识交给LLM去整合又有帮助。
增强生成,简单讲就是让LLM结合向量化检索的结果,直接返回知识点的内容(而非参考条目)给用户。在我的博客 www.tangshuang.net 中,我提供了有关LLM应用的更多内容,你可以通过博客阅读更多。
如果对技术的发展做不恰当的比方,RAG非常简单,就是在上一代知识技术(搜索引擎)的基础上,用LLM对搜索结果进行总结生成。但是,由于技术的局限,我们无法真的在现实中,直接通过LLM对搜索引擎搜索结果进行处理,这里面涉及到LLM本身的上下文长度限制、搜索引擎结果的质量等等问题。在企业内,我们有可能会自己构建一套搜索引擎,例如基于ES来实现权重查询。限定在企业内的知识,搜索结果少、质量高,理论上应该是可以实现的,但是,现实中,企业知识文件体积大,要从巨大的文件中只挖掘小的知识点,有点大刀小用。况且,搜索结果的质量还依赖于搜索词。
带着以上总总的问题思考,我们来看看当代典型的RAG知识库的技术架构是怎么设计的,它是怎么解决上述的这些问题的。
RAG知识库通用技术架构
如图,在数据准备阶段,我们将不同的文件内容,进行embedding,此时,我们需要依赖一个Embedding Model(嵌入模型),目前很多大模型的服务商或者云服务商都提供了独立的Embedding Model服务,它的作用是将文件内容进行向量化。经过embedding之后,我们便得到了一堆向量,这里我们称之为Semantic Vector(语义向量),它们可存储可计算,对AI友好。之后我们将这些向量存储到一个向量数据库中,一般而言,向量数据库支持检索功能。不同的向量数据库特性和能力不同,因此,市面上有许多付费的向量数据库。
从这里可以看出,嵌入模型、向量数据库、大模型,这些底层服务对于厂商而言都是利润,而对于开发者而言,则是成本。当然,我们可以用自己的技术,在自己的服务器部署这些底层服务,从而降低成本。
数据检索阶段,用户输入一个问题,这个问题,搜索词经过嵌入模型向量化,称为Query Vector(查询向量),再该向量去向量数据库进行检索,获得结果。这里得到的结果就是我们需要的知识点,它们来自各种原始数据中,但此处它们并不是原始数据,而是原始数据的向量碎片,虽然它们关联了某些文件,但是它们现在的状态就是一个个的碎片,不利阅读。接下来,我们要把它们交给大模型去处理,我们需要构造一个合适的prompt,并把它们嵌入到该prompt中,由LLM来对该prompt进行响应。当LLM完成该prompt的响应后,我们就获得了较为精炼的知识内容。
以上是RAG的通用技术架构,我们可以在该架构基础上进行优化,例如对prompt进行优化,对向量数据库的召回率进行优化,对大模型的结果进行更深一层的智能逻辑处理(可以利用Agent技术)等等。但是,无论怎么变体,RAG的技术本质可以通过该架构体现。

基于LangChain在NodeJS中实现RAG

我的技术栈是JS,因此,我更多的是在nodejs中实现各种想法。langchain官方提供了RAG的引导文档,你几乎可以在理解了架构和langchain的设计基础上,无需辅助,照着文档完成。但作为开发者,我们希望更低成本的实现它,因此,我们要找到免费的技术选型。
通过上面技术架构我们可以知道,实际上,要构建最小的RAG,我们所依赖的服务,就3个:LLM、Embedding Model、Vector Database。有没有免费的替代呢?当然有,LLM和Embedding Model我们都可以通过Ollama来获得,向量数据库就使用本地化的faiss。
首先看下ollama中的嵌入模型
还不错,目前为止,有3个可以选。
接下来,就让我们一步一步的完成我们的代码。
ChatModel
import { ChatOllama } from "@langchain/ollama";
export function initOllamaChatModel() {const llm = new ChatOllama({model: "llama3",temperature: 0,maxRetries: 2,// other params...});return llm;}
当然,这里,你需要先用ollama把llama3跑起来。
Embedding Model
import { OllamaEmbeddings } from "@langchain/ollama";
epxort function initOllamaEmbeddings() {const embeddings = new OllamaEmbeddings({model: "mxbai-embed-large", // Default valuebaseUrl: "http://localhost:11434", // Default value});return embeddings;}
同样的道理,ollama把对应的模型跑起来。
TextLoader
我打算把所有的内容,都以纯文本的形式进行载入。你也可以使用其他的loader载入如txt、pdf、docx等文件或网页。
import { Document } from "@langchain/core/documents";
export async function loadPureText(text, metadata) {const docs = [new Document({pageContent: text,metadata,}),];return docs;}
TextSplitter
对大文本进行分片。
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
export async function splitTextDocuments(docs) {const textSplitter = new RecursiveCharacterTextSplitter({chunkSize: 1000,chunkOverlap: 200,});return await textSplitter.splitDocuments(docs);}
Vector Store
使用faiss作为向量数据库,节省成本,可本地存储,方便部署和迁移。
import { FaissStore } from "@langchain/community/vectorstores/faiss";
export function createFaissVectorStore(embeddings) {return new FaissStore(embeddings, {});}
export async function searchFromFaissVectorStore(vectorStore, searchText, count) {const results = await vectorStore.similaritySearch(searchText, count);return results;}
export async function addDocumentsToFaissVectorStore(vectorStore, docs, ids) {return await vectorStore.addDocuments(docs, { ids });}
export async function deleteFromFaissVectorStore(vectorStore, ids) {return await vectorStore.delete({ ids });}
/** 备份 */export async function saveFaissVectorStoreToDir(vectorStore, dir) {await vectorStore.save(dir);}
/** 恢复 */export async function loadFaissVectorStoreFromDir(dir, embeddings) {const vectorStore = await FaissStore.load(dir, embeddings);return vectorStore;}
Prompt
构建常用的RAG prompt模板。
import { ChatPromptTemplate } from "@langchain/core/prompts";
export function createRagPromptTemplate() {const promptTemplate = ChatPromptTemplate.fromTemplate(`You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.Question: {question}Context: {context}Answer:`);return promptTemplate;}
RAGApplication
最后,是把上面的这些部分组合起来。
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";import { StringOutputParser } from "@langchain/core/output_parsers";import path from 'path';import fs from 'fs';
export async function createRagApplication() {const llm = initOllamaChatModel();const embeddings = initOllamaEmbeddings();
const faissStoragePath = path.join(__dirname, 'faiss');const isFaissExist = fs.existsSync(faissStoragePath);const vectorStore = isFaissExist ? await loadFaissVectorStoreFromDir(faissStoragePath, embeddings) : createFaissVectorStore(embeddings);const saveTo = () => {if (!isFaissExist) {fs.mkdirSync(faissStoragePath, { recursive: true });}return saveFaissVectorStoreToDir(vectorStore, faissStoragePath);};
const retriever = vectorStore.asRetriever(options.retriever);const prompt = createRagPromptTemplate();
const chain = await createStuffDocumentsChain({llm,prompt,outputParser: new StringOutputParser(),});
/** * * @param {string} text * @param {{ * type: string; // 类型 * id: string | number; // 标识 * }} meta */const addText = async (text, meta) => {const docs = await loadPureText(text, meta);const docsToAdd = await splitTextDocuments(docs);const { type, id } = meta;const ids = docsToAdd.map((_, i) => `${type}_${id}_${i}`);await addDocumentsToFaissVectorStore(vectorStore, docsToAdd, ids);await saveTo(); // 每次新增向量之后,自动保存到目录中return ids;};
/** * @param {string[]} ids */const remove = async (ids) => {await deleteFromFaissVectorStore(vectorStore, ids);await saveTo();};
const query = async (question) => {const context = await retriever.invoke(question);const results = await chain.invoke({question,context,})return results;};
const stream = async (question) => {const context = await retriever.invoke(question);const results = await chain.stream({question,context,})return results;};
return {addText,remove,query,stream,};}
当然,这里你需要把前面的所有函数都引入进来。
Example
最后,我们来写一个例子,用以测试它是否正常工作:
const rag = await createRagApplication();
await rag.addText(`RAG 检索的底座:向量数据库,我的博客 www.tangshuang.net 中有专门的内容对向量数据库做介绍。在业界实践中,RAG 检索通常与向量数据库密切结合,也催生了基于 ChatGPT + Vector Database + Prompt 的 RAG 解决方案,简称为 CVP 技术栈。这一解决方案依赖于向量数据库高效检索相关信息以增强大型语言模型(LLMs),通过将 LLMs 生成的查询转换为向量,使得 RAG 系统能在向量数据库中迅速定位到相应的知识条目。这种检索机制使 LLMs 在面对具体问题时,能够利用存储在向量数据库中的最新信息,有效解决 LLMs 固有的知识更新延迟和幻觉的问题。尽管信息检索领域也存在选择众多的存储与检索技术,包括搜索引擎、关系型数据库和文档数据库等,向量数据库在 RAG 场景下却成为了业界首选。这一选择的背后,是向量数据库在高效地存储和检索大量嵌入向量方面的出色能力。这些嵌入向量由机器学习模型生成,不仅能够表征文本和图像等多种数据类型,还能够捕获它们深层的语义信息。在 RAG 系统中,检索的任务是快速且精确地找出与输入查询语义上最匹配的信息,而向量数据库正因其在处理高维向量数据和进行快速相似性搜索方面的显著优势而脱颖而出。以下是对以向量检索为代表的向量数据库与其他技术选项的横向比较,以及它在 RAG 场景中成为主流选择的关键因素分析:`, { type'text'id0 });
const results = await rag.stream('RAG检索的底座是什么?');for await (const chunk of results) {process.stdout.write(chunk);}

在命令行中运行上面这个js,就可以看到效果。如果运行正常,说明我们的编码没有问题。

需要注意的点
上面实现中,只提供了addText纯文本写到向量库中,你可以根据你自己的实际需求,利用langchain提供的loader,实现各种形式的载入。
另外,上面的实现中,只从RAG的通用架构角度进行了技术实现,而没有从应用出发,去进行缓存、去重等一系列的应用层设计。它相当于是一个通用的代码,任何nodejs项目都可以拿去用,再在它的上层进行更深度的业务封装。我自己就是在这样的基础上,封装了FAQ系统,当用户发起提问的时候,从知识库中捞取知识进行回答,同时,在业务上,我又增加了运营人员手工回答来覆盖AI回答,同时,又把人工回答再载入到知识库中,从而使得将来类似问题可以被更好的回答。

结语

虽然我在去年就在腾讯内网发布了如何用nodejs来实现RAG的文章,但是随着langchain的进步,以及市场上各类底层服务的完善,现在构建RAG已经变得轻而易举。本文算是我闲暇时,保持博客更新的一篇总结吧,毕竟对于不少朋友来说,还是需要有一篇类似的文章引导的。我在翻看langchain文档时,发现不少工具是nodejs独享的,python没有,这说明js社区的强大。而随着AI技术的不断普及化,以及LLM上下文长度的提升,在不考虑tokens的费用的情况下,或许我们真的可以做到不需要向量化来实现RAG知识库。对了,文章开头说的FAQ接口服务,我会在近期放出,你只需要持续关注我,适当的时候,它就会出现。


53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅