告别微调成本!用 RAG 本地向量库解决大模型私有领域数据接入

0 阅读5分钟

引言

LLM都是推理模型,只能基于自身训练好的语料库进行回应;如果我们想要将一些私有领域的数据接入到LLM当中(就像一家企业想做一款独属于自己的agent, 就需要把公司里的数据喂给LLM,如财务报表),针对这种有三种方法来训练LLM:

  1. 全量微调 (效果好,成本高)
  2. 轻量微调 (效果差,成本低)
  3. 构建本地向量数据库(效果好, 成本低)

1. 什么是RAG

定义:全称Retrieval-Augmented Generation 生成式检索增强,给大模型外接专属知识库,回答前先查资料,再写答案。
RAG涉及到的领域有很多,常见的有:

  1. 在一些私有领域,让LLM接入这部分数据。
  2. context engineering(上下文工程):致力于如何让 LLM 的上下文编排更合理。

2. RAG下的context engineering

主要思路:先构建本地向量数据库,将用户问题和LLM的回答都存入到向量数据库中,当用户发起问题,先去向量数据库中进行相似度查找,找出相似度较高的几条数据。然后将用户问题并携带查找到的数据查找给LLM,而不是直接把所有的上下文全部传给LLM,这样就能有效的控制上下文长度。

2.1 实操RAG 1.0(针对上下文工程)

  1. ollama push nomic-embed-text:latest 安装一个专门用来生成向量的模型,并写一个函数将文本转为向量的函数:

需要注意:文本向量化阶段需严格控制单条文本片段的长度,因此需预先对原始长文本执行切分操作。在分段设计上采用重叠切片机制,后一段文本需复用前一段末尾的部分内容,以此衔接上下文语义,防止因文本割裂丢失关联信息,提升后续检索准确度。

image.png

  1. pnpm i ollama 在项目中安装 ollama SDK

  2. pnpm i vectra 安装一个 node 环境下的轻量级向量数据库

image.png
4. 初始化生成了一个本地数据库 (json文件)

image.png 5. 在SimpleRag类里封装 向量数据库的 增加、删除、查找(可进行相似度查找输出相似度较高的数据)的方法

    // 往数据库中写入数据
    async add(text) {
        if (!this.avaliable) throw new Error('RAG 还没初始化')

        const embeddings = await getEmbeddings(text)
        const res = []
        for (const embedding of embeddings) {
            const overResult = await this.db.insertItem(embedding)
            res.push(overResult)
        }
        return res.filter(item => item).map(item => ({ id: item.id }))  // [{id: xxxxxx}, {id: xxx}]

    }
    // 从数据库中删除数据
    async del(items) {
        if (!Array.isArray(items)) items = [items]
        if (!this.avaliable) throw new Error('RAG 还没初始化')

        const res = []
        for (let item of items) {

            await this.db.deleteItem(item.id)

            res.push({ id: item.id })
        }
        return res
    }
    // 从数据库中查找数据
    async query(text, topK = 5) {
        if (!this.avaliable) throw new Error('RAG 还没初始化')
        const vector = (await getEmbedding(text)).embedding   // 新问题被处理成向量
        const result = await this.db.queryItems(vector, text, topK)
        return result.map(({item, score}) => ({
            text: item.metadata.text,
            query: text,
            simularity: score,
            id: item.id
        }))
    }

2.2. RAG实操 2.0(针对向量数据库的实时更新)

新员工接手项目开发时,可调用企业 Agent 查找公司前辈们对同类需求写的案例,借鉴前人方案搭建项目基础框架。但是公司管老板会结合业务现状对原有框架优化调整,文档内容随之改动,这个时候便需要对向量数据库进行实时更新,保证检索内容与最新业务资料一致。

  1. 构建MCP服务

image.png 2. 注册更新向量数据库的工具

server.tool(
    'ask-your-lib-initialize',
    'Initialize the vector database operations and clean up any existing .vectra directory.',
    {},
    async () => {
        try {
            // 先看.vectra 这个目录是否存在,存在就移除
            const projectRoot = path.join(import.meta.dirname, '../../')
            const vectraPath = path.join(projectRoot, '.vectra')
            const generateMcpPrompt = await loadPrompt('generate') // 加载一份提示词 skill

            try {
                await fs.access(vectraPath)  // 判明是否有权限操作这个目录
                await fs.rm(vectraPath, { recursive: true, force: true })     // 移除该目录
                console.log('成功删除已存在的.vectra 目录');
            } catch (error) {
                console.log('.vectra 目录不存在, 无需删除')
            }
            simpleRagInstance = new SimpleRag()

            // 返回一份提示词, 用于告诉LLM下一步该干什么
            return {
                content: [{
                    type: 'text',
                    text: `☠ the guide to follow: \n ${generateMcpPrompt}\n\n`
                }]
            }
        } catch (error) {
            console.error(`初始化SimpleReg失败: ${error}`)
            return {
                content: [{
                    type: 'text',
                    text: `初始化SimpleReg失败:${error}`
                }]
            }
        }
    }
)

server.tool(
    'ask-your-lib-insert',
    `Insert and vectorize text content into the vector database.`,
    {
        text: z.string()
    },
    async ({ text }) => {
        try {
            if (!simpleRagInstance) {
                return {
                    content: [
                        {
                            type: 'text',
                            text: 'Database instance is not initialized. Please call ask-your-lib-initialize first.'
                        }
                    ],
                };
            }
            if (!simpleRagInstance.avaliable) {
                await simpleRagInstance.initialize()  // 本地创建一个新的 .vectra 目录
            }
            const result = await simpleRagInstance.add(text)  // 写入本地向量数据库中

            return {
                content: [{
                    type: 'text',
                    text: `Text inserted successfully. Inserted item: ${JSON.stringify(result)}`
                }]
            }
        } catch (error) {
            console.error(`文本写入数据库失败: ${error}`)
            return {
                content: [{
                    type: 'text',
                    text: `Error inserting text: ${error}`
                }]
            }
        }
    }
)
  1. 在MCPClient添加MCPServer(以Trae为例)

image.png

image.png

3. 面试官必问面试题:

RAG是什么? 有什么优缺点? 你用过什么向量数据库?实现过程是怎样的

1.RAG是什么? RAG(Retrieval-Augmented Generation,生成式检索增强)是让LLM接入私有领域数据的方案,通过构建向量数据库做相似度查找,将相关数据传给LLM并生成回答。

2.RAG有什么优缺点? 优点是效果好、成本低、上下文可控、数据更新灵活;缺点是检索质量依赖embedding模型、长文本切分复杂、本地存储性能受限、无实时更新机制。

3.用过什么向量数据库? 项目中使用的是 Vectra,这是一个 Node 环境下的轻量级向量数据库,以本地 JSON 文件的形式存储数据。

4.实现过程是怎样的?
首先安装环境依赖,包括通过 ollama push nomic-embed-text:latest 安装向量生成模型,以及 pnpm i ollama 和 pnpm i vectra 安装相关 SDK 和向量数据库;
然后实现向量生成模块,短文本通过 getEmbedding 直接生成向量,长文本通过 getEmbeddings 先切割再生成向量;
接着封装一个 SimpleRag 类,去提供基于 Vectra 的 add(向量化后插入)、del(根据 ID 删除)、query(向量化后相似度查找)三个核心方法;最后整合到完整的 RAG 流程中:用户提问 → 向量化 → 相似度查找 → 整合提示词 → LLM 输出。