GoodCheng

GoodCheng

要开心哟!Be Happy!
twitter
github

初探AI開發之RAG

前言#

在開始講解什麼是 RAG 之前,先討論討論現在我們用的這些模型或者說 LLM 有哪些缺點:

  • 在沒有答案的情況下提供虛假信息。(會胡說八道)
  • 當用戶需要特定的當前響應時,提供過時或通用的信息。(因為模型有訓練的截止日期)
  • 從非權威來源創建響應。(因為訓練的內容來自於互聯網,所以可能會有一些不準確的內容)
  • 由於術語混淆,不同的培訓來源使用相同的術語來談論不同的事情,因此會產生不準確的響應。

還有一個重要的也比較現實的原因 —— 我們自己有一些私人的數據,這些數據市面上的一些模型是肯定沒有對我們的數據進行訓練,所以單我們需要對自己的數據集進行詢問就是一個不可能的。

所以 RAG(檢索增強生成) 就出現了 —— 它通過修改與大型語言模型(LLM)的互動,使模型在回應用戶查詢時參考指定的文檔集,優先使用這些信息,而不是從自身龐大的靜態訓練數據中提取信息。這樣,LLMs 就可以使用特定領域和 / 最新信息。 用例包括讓聊天機器人訪問公司內部數據,或只提供來自權威來源的事實信息。

流程#

  1. 首先,需要將我們的一些知識數據使用向量模型轉化為向量,然後保存在向量數據庫當中

image

  1. 剩下的就是用戶輸入,然後我們會將用戶的數據轉化為向量,使用用戶輸入的向量對現在的向量數據庫進行檢索(Retrieval),按照相似度匹配找出最接近的幾條數據,使用大模型進行將檢索出的數據源和用戶需求的問題組合(Augmented)一起輸入給大模型進行生成(Generation),這樣 —— 完整的 RAG 就出來啦。

image

代碼實現#

作為一個前端開發來說,最熟悉的也就是 JavaScript,剛好市面上比較出名的langchain有 js 的版本,使得前端開發進入 AI 開發變得更加的容易,廢話不多說現在就開始吧!

簡單使用#

接下來,我們通過代碼展示如何使用 RAG。為了簡單起見,我們採用 LangChain 的 JavaScript 版本,並在本地使用 llama3:3b 模型。你可以根據需要替換其他模型。

import { ChatOllama } from "@langchain/ollama";
import { StringOutputParser } from '@langchain/core/output_parsers'

const llm = new ChatOllama({
  model: 'llama3.2:3b-instruct-fp16',
  temperature: 0, // 控制生成內容的隨機性
});

const res = await llm.invoke('你好');

// 對輸出文本進行格式化
const parser = new StringOutputParser();
const parsed = await parser.invoke(res);

console.log(parsed); 
// 輸出:您好!我是你的AI助手,歡迎來到我們的對話中。有什么需要幫助的嗎?

加載文檔#

因為我們的目的是對自己的數據進行增強搜索,那麼第一步就是需要加載文檔,langchain 提供了許多加載不同文檔的方法(文檔加載),基本上包含了市面上所有的文檔,有興趣的可以去查看一下。本次我們採用的是 Web 遠程加載文檔。

import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";

const loader = new CheerioWebBaseLoader("https://goodcheng.wang/web3", {
  selector: 'article' // 指定加載內容的區域
});

const doc = await loader.load();

對文檔進行分割#

為什麼要分割文檔呢?直接對整個文檔進行嵌入(embedding)看似簡單,但實際上這樣做並不高效。原因如下:

  • 內存與計算限制:大規模文檔(如整本書、長篇報告等)通常非常龐大,如果直接將整個文檔進行 embedding,計算資源和內存消耗會非常高。有一個顯而易見的問題 —— 我們所用到的模型不支持這麼龐大的輸入。
  • 上下文窗口限制:現有的大多數自然語言處理(NLP)模型在處理文本時有一定的上下文窗口大小限制,即模型只能處理一定數量的 token。如果文本超出了該窗口大小,超出部分就會被截斷或忽略,導致丟失大量信息。因此,將文檔分割成小塊,每塊的長度適應模型的上下文限制,可以確保模型在生成 embedding 時能夠充分利用輸入的上下文信息。
  • 提升檢索與匹配精度: 對文檔進行分割還可以提高信息檢索的精度。將整個文檔 embedding 後,查詢特定問題時,embedding 可能會涵蓋太多不相關的信息,導致匹配不精確。相反,如果對文檔進行分割並對每個分段進行 embedding,查詢時可以只檢索到與問題最相關的段落,結果更加精準。
  • and so on

在這裡我們將文本每 500 個字符進行分割,相鄰 100 個字符作為一個 “塊”。感興趣的可以在這個網站上自己摸索Text-splitter demo

image

import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";

const splitter = new RecursiveCharacterTextSplitter({
  chunkOverlap: 100, // 相鄰塊的重疊字符數,增強連續性
  chunkSize: 500, // 每塊的字符數
});

const allSplits = await splitter.splitDocuments(doc);
console.log(allSplits[0]); // 打印分割後的第一個塊

將內容 embedding 存儲到向量數據庫當中#

接下來,將分割後的文檔內容進行嵌入,並存儲在向量數據庫中。在這個示例中,我們使用內存中的向量數據庫,實際應用中可以替換為其他存儲方案。。VectorStore

import { OllamaEmbeddings } from "@langchain/ollama";
import { MemoryVectorStore } from "langchain/vectorstores/memory";

const embeddings = new OllamaEmbeddings({
  model: "mxbai-embed-large", // Default value
  baseUrl: "http://localhost:11434", // Default value
});

const vectorStore = await MemoryVectorStore.fromDocuments(allSplits, embeddings)
const vectorStoreRetriever = vectorStore.asRetriever({
  k: 5,
  searchType: 'similarity',
})
const find = await vectorStoreRetriever.invoke("什麼是web3.0")
console.log(find)

整合成 RAG 鏈#

最後,我們把上述所有步驟整合成一個完整的處理鏈。首先為生成的鏈編寫提示詞模板:


import {ChatPromptTemplate} from '@langchain/core/prompts'

const ragPrompt = ChatPromptTemplate.fromMessages([
  [
    'human',
    `你是問題解答任務的助手。使用以下檢索到的上下文回答問題。如果不知道答案,就說不知道。最多使用三句話,答案要簡明扼要。
    問題: {question}
    上下文: {context}
    答案:`
  ]
])

然後,使用 RunnableSequence 對流程進行鏈接:

import { formatDocumentsAsString } from "langchain/util/document";
import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables";

const runnableRagChain = RunnableSequence.from([
  {
    context: vectorStoreRetriever.pipe(formatDocumentsAsString), // 檢索到的上下文
    question: new RunnablePassthrough(), // 用戶問題
  },
  ragPrompt, // 生成的提示詞
  llm, // 大語言模型
  new StringOutputParser(), // 解析生成的結果
]);

const res = await runnableRagChain.invoke('web2.0的代表網站有哪些?');
console.log(res); 
// 輸出:Facebook和Twitter是Web2.0的著名網站。

效果還不錯~

總結#

通過 RAG(檢索增強生成),我們能夠提升現有大語言模型的回答準確性,尤其是在需要最新或特定領域數據的場景中。本文通過 LangChain 的 JavaScript 實現展示了如何從文檔加載、分割、嵌入到最終生成的整個流程。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。