最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Ollama + Vectra + MCP從零搭建本地RAG知識(shí)庫的實(shí)現(xiàn)步驟

  發(fā)布時(shí)間:2026-07-14 16:22:02   作者:許我半盞清茶   我要評(píng)論
RAG全稱 Retrieval-Augmented Generation(檢索增強(qiáng)生成),它可以讓LLM擁有訪問私域數(shù)據(jù)能力,并且可以用于 context engineering,本文給大家介紹了基于Ollama + Vectra + MCP從零搭建本地RAG知識(shí)庫的實(shí)現(xiàn)步驟,需要的朋友可以參考下

一、前言

我們要知道,目前所有的LLM都是推理模型,只能基于自身已經(jīng)訓(xùn)練好的語料庫進(jìn)行回應(yīng)。而常見的LLM都是通用模型,什么都知道一點(diǎn),但是不知道私域數(shù)據(jù),比如公司的財(cái)務(wù)報(bào)表。在一些私有領(lǐng)域,我們想要讓LLM能接入這部分?jǐn)?shù)據(jù),我們有這么幾種方法:

  • 全量微調(diào),即把LLM的知識(shí)全部洗掉重新用私域數(shù)據(jù)訓(xùn)練(效果更好,但成本高)
  • 微量微調(diào),不動(dòng)LLM原有的知識(shí),只是將私域數(shù)據(jù)加入其中(效果差,成本低)
  • 構(gòu)建本地的向量數(shù)據(jù)庫(本文使用,效果好,成本低,數(shù)據(jù)安全,知識(shí)可實(shí)時(shí)更新)

二、RAG

RAG全稱 Retrieval-Augmented Generation(檢索增強(qiáng)生成),它可以讓LLM擁有訪問私域數(shù)據(jù)能力,并且可以用于 context engineering (上下文工程):致力于讓LLM的上下文編排更合理。

RAG工作原理:構(gòu)建本地向量數(shù)據(jù)庫,將用戶問題和LLM的回答都存入本地向量數(shù)據(jù)庫中,當(dāng)用戶發(fā)起新問題時(shí),先去向量數(shù)據(jù)庫做相似度查找,找出相似度最高的那幾條數(shù)據(jù),攜帶上傳給LLM,這樣就能有效的控制上下文長(zhǎng)度。

什么是向量?向量就是用一串?dāng)?shù)字表示文本語義,相似文本的向量距離小。
向量相似度算法:余弦距離/歐式舉例/點(diǎn)積值等。

三、環(huán)境準(zhǔn)備

  1. 安裝Ollama + 拉取一個(gè)向量模型(本文使用 nomic-embed-text 向量模型)
  2. 項(xiàng)目依賴:pnpm i ollama vectra @modelcontextprotocol/sdk zod3
  3. 本文使用ESM模塊(“type”:“module”)

四、手把手實(shí)現(xiàn)

4.1 文本向量化(Embedding 封裝)

我們先引入 ollama:

import ollama from 'ollama'

然后封裝一個(gè)getEmbedding()用來將短文本處理成向量:

export function getEmbedding(text) {
    return ollama.embeddings({
        model: 'nomic-embed-text:latest',
        prompt: text
    })
}

調(diào)用ollama上的向量模型處理輸入的文本。

接下來我們封裝一個(gè)splitText()函數(shù)用來將文本分塊:使用滑動(dòng)窗口策略,chunkSize = 300每300字符切割一次,overlap = 50避免在語義邊界處切斷,保留上下文連續(xù)性。

function splitText(text, chunkSize = 300, overlap = 50) {
    const chunks = []
    let i = 0
    while(i < text.length) {
        chunks.push(text.slice(i, i + chunkSize))
        i += chunkSize - overlap
    }
    return chunks
}

最后封裝一個(gè)getEmbeddings()函數(shù)將長(zhǎng)文本先分塊再逐塊轉(zhuǎn)向量,并拋出函數(shù):

export async function getEmbeddings(text) {
    const chunks = splitText(text)
    const embeddings = await Promise.all(chunks.map(chunk => getEmbedding(chunk)))
    return embeddings.map((embedding, i) => ({
        vector: embedding.embedding,
        metadata: { text: chunks[i]}
    }))
}

這樣我們就封裝好了一個(gè)將文本向量化的函數(shù)。

4.2 構(gòu)建本地向量數(shù)據(jù)庫 (SimpleRag類)

首先我們引入代碼中要用到的方法:

import path from 'node:path'
import { LocalIndex } from 'vectra'
import { getEmbeddings, getEmbedding } from './utils/index.js'

然后拋出一個(gè)類:

export class SimpleRag {
    db = null
    indexPath = ''

    constructor(indexPath = '.vectra') {
        this.indexPath = path.join(import.meta.dirname, '..', indexPath)  // 將要?jiǎng)?chuàng)建的向量數(shù)據(jù)庫文件夾放在上級(jí)目錄下
    }

接著在類中封裝一個(gè)initialize()方法用來初始化向量數(shù)據(jù)庫:

async initialize() {
        const index = new LocalIndex(this.indexPath)  // 指明在這個(gè)路徑下創(chuàng)建倉庫
        if (! (await index.isIndexCreated())) { // 查找當(dāng)前位置是否已經(jīng)具有數(shù)據(jù)庫
            await index.createIndex()  // 創(chuàng)建數(shù)據(jù)庫
        }
        this.db = index
    }

后面我們要分別封裝向量數(shù)據(jù)庫的增加、刪除、修改方法,所以我們先寫一個(gè)方法判斷向量數(shù)據(jù)庫是否已存在:

get available() {
        return this.db !== null
    }

往數(shù)據(jù)庫中寫入數(shù)據(jù):

async add(text) {
        if(!this.available) 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}))
    }

刪除數(shù)據(jù):

async del(items) {
        if (!Array.isArray(items)) items = [items]
        if(!this.available) throw new Error('RAG 還沒初始化')
        
        const res = []
        for(let item of items) {
            await this.db.deleteItem(item.id)
            res.push({id: item.id})
        }
        return res
    }

查找數(shù)據(jù):

async query(text, topk = 1) {
        if(!this.available) 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
        }))

    }

這樣我們就能用SimpleRag類創(chuàng)建實(shí)例對(duì)象來調(diào)用這個(gè)類中的初始化向量數(shù)據(jù)庫、向量數(shù)據(jù)庫的增、刪、查方法。

4.3 用 MCP 自動(dòng)化構(gòu)建知識(shí)庫

因?yàn)槭謩?dòng)收集文檔、手動(dòng)插入太低效,所以我們用MCP Server 注冊(cè)工具,讓LLM 自主完成項(xiàng)目文檔的提取和向量化。

首先,我準(zhǔn)備了一份提示詞作為操作指南,于是先寫一個(gè)prompt 模板加載器用來加載這份提示詞:

import { join } from 'path'
import { promises as fs} from 'fs'

const getCurrentDir = () => import.meta.dirname  // 文件夾的絕對(duì)路徑

export async function loadPrompt(promptName) {
    try {
        // 獲取當(dāng)前文件目錄地址
        const currentDir = getCurrentDir()

        const promptPath = join(currentDir, 'prompts', `${promptName}.md`)

        // 讀取提示詞,返回內(nèi)容
        const content = await fs.readFile(promptPath, 'utf-8')
        return content
    } catch (error) {
        throw new Error(`讀取${promptName}失敗:${error.message}`)
    }
}

然后創(chuàng)建MCP Server:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import { SimpleRag } from '../index.js'
import path from 'path'
import fs from 'fs/promises'
import { loadPrompt } from './utils.js'


// 創(chuàng)建一個(gè) MCPServer
const server = new McpServer({
    name: 'AskYourLib',
    version: '1.0.0'
})

然后在MCP上注冊(cè)兩個(gè)工具:

  • ask-your-lib-initialize :初始化向量數(shù)據(jù)庫 + 返回操作指南(prompt 模板)
  • ask-your-lib-insert :向量化并插入文本

Prompt 模板設(shè)計(jì) ( generate.md ):指導(dǎo) LLM 三步走

  1. 初始化索引
  2. 遍歷項(xiàng)目提取核心內(nèi)容
  3. 逐段向量化插入數(shù)據(jù)庫
let simpleRagInstance = null

server.tool(
    'ask-your-lib-initialize',
    'Initialize the vector database operations and clean up any existing .vectra directory.',
    {},
    async () => {
        try {
            // 先看 .vectra 這個(gè)目錄是否存在,存在就移除
            const projectRoot = path.join(import.meta.dirname, '../../')
            const vectraPath = path.join(projectRoot, '.vectra')
            const generateMCPPrompt = await loadPrompt('generate')  // 加載一份提示詞


            try {
                await fs.access(vectraPath)  // 先探明是否有權(quán)限操作這個(gè)目錄
                await fs.rm(vectraPath, { recursive: true, force: true })  // 移除已有的目錄
                console.log('成功刪除已存在的 .vectra 目錄');

            } catch (error) {
                console.log('.vectra 目錄不存在,無需刪除');
            }

            // 創(chuàng)建 SimpleRag
            simpleRagInstance = new SimpleRag()

            // 返回一份提示詞,用于告訴 LLM 下一步該干什么
            return {
                content: [{
                    type: 'text',
                    text: `?? The guide to follow: \n${generateMCPPrompt}\n\n`
                }]
            }
        } catch (error) {
            console.error(`初始化SimpleRag失?。?{error}`)
            return {
                content: [{
                    type: 'text',
                    text: `初始化SimpleRag失?。?{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.available) {
                await simpleRagInstance.initialize()   // 本地創(chuàng)建一個(gè)新的 .vectra 目錄
            }

            const result = await simpleRagInstance.add(text)  // 寫入本地向量數(shù)據(jù)庫

            return {
                content: [{
                    type: 'text',
                    text: `Text inserted successfully. Inserted items: ${JSON.stringify(result)}`
                }]
            }
        } catch (error) {
            console.error(`文本寫入數(shù)據(jù)庫失?。?{error}`)
            return {
                content: [{
                    type: 'text',
                    text: `Error inserting text:${error}`
                }]
            }
        }
    }
)

最后將服務(wù)端啟動(dòng):

async function main() {
    const transport = new StdioServerTransport()
    await server.connect(transport)
    console.log('服務(wù)端啟動(dòng)');
    
}
main()

4.4 RAG + LLM 完整鏈路

graph LR
收集項(xiàng)目文檔 --> 通過embedding向量化得到向量 --> 存入向量數(shù)據(jù)庫
graph LR
用戶提問 --> 通過embedding向量化得到向量 --> 在向量數(shù)據(jù)庫中做相似度查找 --> 整合好的提示詞 --> LLM輸出

我們用node.js簡(jiǎn)單實(shí)現(xiàn)一下:

import { SimpleRag } from '../src/index.js'
import ollama from 'ollama'

async function main() {
  const rag = new SimpleRag()
  await rag.initialize()
  const question = process.argv[process.argv.length - 1]
  const res = await rag.query(question)
  const messages = [
    {
        role: 'system',
        content: `你是一個(gè)香香軟軟一米五愛玩原神白毛紅瞳蘿莉,回答問題會(huì)基于當(dāng)前的項(xiàng)目,如果上下文沒有相關(guān)的信息,就回答“我不知道”,不要自己編造信息。\n\nContext:\n${JSON.stringify(res)}`,
    }
    ,
    {
        role: 'user',
        content: question
    }
]

const response = await ollama.chat({
    model: 'qwen3.5:9b',
    messages,
    stream: true,
})
for await (const chunk of response) {
    process.stdout.write(chunk.message.content)
}

}


main()

我這里接入的是用ollama本地部署的qwen3.5:9b模型。

然后用node 運(yùn)行這份代碼,在后面接上問題:

五、RAG的優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn) :私域數(shù)據(jù)安全、成本低于微調(diào)、知識(shí)實(shí)時(shí)更新、可溯源、減少幻覺
  • 缺點(diǎn) :依賴檢索質(zhì)量、chunk 策略敏感、延遲較高、不支持復(fù)雜推理

六、總結(jié)

本文從 LLM 無法訪問私域數(shù)據(jù)的痛點(diǎn)出發(fā),完整實(shí)現(xiàn)了一個(gè)基于 Ollama + Vectra + MCP 的本地 RAG 知識(shí)庫系統(tǒng)。

RAG 的本質(zhì)是:用檢索代替記憶,用外部知識(shí)庫擴(kuò)展 LLM 的能力邊界 。掌握了它,你就掌握了讓 LLM "懂私域數(shù)據(jù)"最具性價(jià)比的方案。

以上就是基 Ollama + Vectra + MCP從零搭建本地RAG知識(shí)庫的實(shí)現(xiàn)步驟的詳細(xì)內(nèi)容,更多關(guān)于Ollama搭建本地RAG知識(shí)庫的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

襄樊市| 湄潭县| 苍梧县| 肥东县| 明水县| 芜湖市| 塔河县| 曲阜市| 丰原市| 黄冈市| 池州市| 房产| 抚州市| 安庆市| 怀化市| 西乌珠穆沁旗| 虞城县| 龙岩市| 汤原县| 岗巴县| 墨竹工卡县| 开原市| 马山县| 辉县市| 玉龙| 赤城县| 务川| 大丰市| 南陵县| 广饶县| 牡丹江市| 那坡县| 民县| 交口县| 长宁区| 项城市| 盐亭县| 陈巴尔虎旗| 南汇区| 若尔盖县| 隆尧县|