iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
AI & Data

30 天從 0 至 1 建立一個自已的 AI 學習工具人系列 第 20

30-20: [知識] Retrieval-Augmented Generation 之 核心概念與總覽

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20251004/200893589u44956JLL.png

🚀 什麼是 RAG 呢 ? 為什麼要它呢 ?

RAG 檢索增強生成,全名叫 Retrieval Augmented Generation,真的當初看到它時我在想他在供啥小。然後看了一下 openai 的文件後,我覺得他這段話說的很好讓我懂。

Retrieval Augmented Generation (RAG) is a technique that improves a model’s responses by injecting external context into its prompt at runtime. Instead of relying solely on the model’s pre-trained knowledge, RAG retrieves relevant information from connected data sources and uses it to generate a more accurate and context-aware response.
檢索增強生成(RAG)是一種技術,能夠在執行時將外部資料注入模型的提示(prompt),讓模型產生更精準、更具上下文的回應。RAG 不僅依賴模型的預訓練知識,還會從連接的資料來源中檢索相關資訊,補充生成過程中的背景。

當時我看到時第一時間在想,好像聽起來就是根據『 用戶的問題,然後先產生出這個問題的相關 Context,然後再喂給 LLM 』。

但那時我就在想,那這樣我就在收到問題時,然後自已產生一段 Context 在一起加到 prompt 中,最後再丟給 LLM 不就好 ? 那這樣幹麻還說的得高大上的感覺呢 ?

例如文件有提到說 RAG 的價值,就有說到一個範例,大約如下 :

假設你在開發一個幫助客服團隊回答產品問題的 GPT。基礎模型雖然有廣泛的通用知識,但並不了解你產品的最新更新記錄或幫助中心的內容。然後透過 RAG 就可以做到讓 GPT 去取得到你的產品知識與最新訊息。

然後我那時就在想,這樣不就只要當用戶訊息我們公司相關資訊時,然後用 Function Calling 回傳需要的公司資料就好 ?

後來想想有幾個問題 :

  • 你是想將全公司的資訊都丟到 Prompt 中嗎 ?
  • 而且真可以的話 AI 要從那麼多資料找到答案,然後回應,好像也會花不少 token。

所以我自已覺得 RAG 真正想要做到的是 :

進行 Retrieval 流程,整理 ( Indexing ) 與找出 ( Query ) 『 好 』的 Context,然後可以讓 LLM ( Augmented、Generation ) 答出更好的答案。

🚀 RAG 的基本流程

RAG 整個流程可以分兩大部份 :

  • Indexing : 將你要當上下文的資料,建立索引。
  • Query : 從索引中,找出你要的東西當上下文,加到 Prompt 中。

整個 RAG 流程事實上就是分這兩個,但是這兩個裡面會有很多詳細,再下二篇將會詳細的說明。

https://ithelp.ithome.com.tw/upload/images/20251004/20089358svanTUAUX7.png

🤔 是否一定要用向量資料庫
事實上不一定要,因為我們的目的一直以來都是 :

找到最適合的上下文加入到 Prompt

所以如果你用其它資料庫,又或是用 Elasticsearch 就可以找到你要的上下文,那就不用向量資料庫。

🚀 簡單的 RAG 範例

這裡用 LangChain 寫個最簡單的 RAG 範例,然後就是如上面一樣分成兩部份 Indxing 與 Retrieval。

🤔 Step 1. Indexing

import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { ChatOpenAI } from "@langchain/openai";

async function indexing() {
  // 1. 資料
  const documents = [
    "馬克是個好人",
    "馬克的年齡是20歲",
    "馬克的身高是180cm",
    "馬克的體重是70kg",
    "馬克的職業是工程師",
    "馬克的愛好是打電動",
    "馬克的興趣是看書",
    "馬克的興趣是切東西"
  ];

  // 2. Chunking
  const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 100,
    chunkOverlap: 20,
  });

  const docs = await textSplitter.createDocuments(documents);

  const embeddings = new OpenAIEmbeddings({
    modelName: "text-embedding-3-small",
  });

  // 3. Embedding
  const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings);

  return vectorStore;
}

🤔 Step 2. Query

async function query(vectorStore: MemoryVectorStore) {

  // 1. 從向量資料庫中找出相關的 Context
  const retriever = vectorStore.asRetriever({
    k: 2, // 返回最相關的 2 個文檔
  });

  const contexts = await retriever.invoke("馬克喜歡什麼?");
  console.log("contexts", contexts);

  // 2. 將 Context 加到 Prompt 中
   
  const llm = new ChatOpenAI({
    modelName: "gpt-5-mini",
  });
   
  const answer = await llm.invoke([
    {
      role: "system",
      content: `Context: ${contexts.map((doc) => doc.pageContent).join("\n")}`,
    },
    {
      role: "user",
      content: "馬克喜歡什麼?",
    },
  ]);
  console.log("answer", answer);

  return context;
}

🤔 執行與結果

概念簡單吧,就是先 indexing 後,再來查詢資料,然後再加到 Prompt 上。

(async () => {
  const vectorStore = await indexing();
  await retrieval(vectorStore);
})();

// Console Log 的結果---------------------------------------------------
context [
  Document {
    pageContent: '馬克的興趣是看書',
    metadata: { loc: [Object] },
    id: undefined
  },
  Document {
    pageContent: '馬克是個好人',
    metadata: { loc: [Object] },
    id: undefined
  }
]
answer AIMessage {
  "id": "chatcmpl-CMtJ5be3Z9jnBeB6N0jiUKhxymjff",
  "content": "馬克喜歡看書。",
  "additional_kwargs": {},
  "response_metadata": {
    "tokenUsage": {
      "promptTokens": 35,
      "completionTokens": 144,
      "totalTokens": 179
    },
    "finish_reason": "stop",
    "model_provider": "openai",
    "model_name": "gpt-5-mini-2025-08-07"
  },
  "tool_calls": [],
  "invalid_tool_calls": [],
  "usage_metadata": {
    "output_tokens": 144,
    "input_tokens": 35,
    "total_tokens": 179,
    "input_token_details": {
      "audio": 0,
      "cache_read": 0
    },
    "output_token_details": {
      "audio": 0,
      "reasoning": 128
    }
  }
}

🚀 LangChain 有提供的東西

https://docs.langchain.com/oss/javascript/integrations/providers

LangChain 在 RAG 這塊事實上有整合了不少東西,處了基本的功能,還有串接了其它第三方服務相關的東西,主要分這三大塊 :

  • Retrievers : 它就是有提到一些現成整套的 Retrievers,例如 ArxivRetriever,我們就可以用他只接抓到我們想要的 Context,然後再帶到我們 Prompt 中後,就可以用了。
  • Embeddings : 有提到與其它 Embedding 供應商的整合,例如 OpenAI,如我們上面的範例就有用到。
  • Vector stores : 他有提到一些和其它向量資料庫的整合,例如我們之後會用到的 MongoDB Atlas
  • Document loaders : 它可以幫我們讀取一些資來來源,或是 PDF 之類的,然後進行 Chunking 的工作。

然後以 Retrievers 為例,它就是可以直接去 Arxiv 這個地方取得到論文的資訊來。順到說一下 Arxiv 就是很多論文作者在正式發表論文前或審核的同時會上傳的公開地方,它是一個免費的知識庫,目前我很多 AI 相關的論文也都是在這裡看到的。

https://arxiv.org/

然後下面是範例程式碼,我們在這裡就不用做 indexing 的過程,因為已經處理好了,然後我們這裡就是可以直接用以下的方法取得到 Context 來將下一步會帶入到我們的 Prompt 中,但我們這裡就只寫到這就好。

所以如果你是用 LangChain 來做 RAG 可以先看看整合的部份有沒有已經有你要用的東西了,這樣就可以省掉一些時間

import { ArxivRetriever } from "@langchain/community/retrievers/arxiv";

async function setupRetriever() {
  console.log("📚 設定 Arxiv Retriever...\n");

  const retriever = new ArxivRetriever({
    getFullDocuments: false, // false = 只取摘要, true = 下載完整 PDF
    maxSearchResults: 3, // 最多返回 3 篇論文
  });

  return retriever;
}

(async () => {
  const retriever = await setupRetriever();
  const contexts = await retriever.invoke("RAG");

  for (const context of contexts) {
    console.log(context.metadata.title);
    console.log(context.metadata.published);
    console.log(context.metadata.url);
    console.log("--------------------------------");
  }
})();

// =======================================================================

Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks
2024-07-26T03:45:30Z
http://arxiv.org/abs/2407.21059v1
--------------------------------
RAG-Instruct: Boosting LLMs with Diverse Retrieval-Augmented Instructions
2024-12-31T09:00:51Z
http://arxiv.org/abs/2501.00353v1
--------------------------------
Evaluating the Performance of RAG Methods for Conversational AI in the Airport Domain
2025-05-19T11:46:30Z
http://arxiv.org/abs/2505.13006v1
--------------------------------

🚀 RAG 的種類

RAG 事實上在出來後到現在,已經有很多類型了,這裡根據以下幾篇論文簡單的說一下幾個比較有名的,然後詳細的內容,之後文章會一個一個討論,然後先說一下還不只這些,這裡只列幾個現在比較常見的。

首先是第一篇論中中,它提到將 RAG 分為三種類型 :

https://ithelp.ithome.com.tw/upload/images/20251006/20089358yxJI24UjAS.png
圖片來源: Retrieval-Augmented Generation for Large Language Models: A Survey

  • Naive RAG : 這個應該就是我們現在看到的最基本的版本。
  • Advanced RAG : 它是為了解決 Naive RAG 不太準確的問題,它加入了兩個階段 Pre-Retrieval 與 Post-Retrieval 。
  • Modular RAG : 它事實上有點不像是我們上面說的傳統 RAG,它比較像是為了應付不同情境,讓每個部份可以模組化,可以讓整個過程更加的彈性。( 可以想成 Naive RAG 與 Advanced RAG 都是 Modular RAG 的一種 Pattern,如上圖 )

接下來又出了兩個 :

  • Graph RAG : 這個是將知識圖譜 ( Knowledge Graph )整合到 RAG 中的概念,利用實體之間的關係和層級結構來增強傳統 RAG 的能力。簡單的說就是如果要回答一個問題,需要理解整個知識中的關係後,才能推論與回答的情境,就適合用它。例如你丟三國志進去,然後問曹操為什麼打袁紹,這種情況下你覺得在不知道他倆的關係有辦法答嗎 ?

https://ithelp.ithome.com.tw/upload/images/20251004/20089358Ex02JehaEK.png
圖片來原: Graph Retrieval-Augmented Generation: A Survey

  • Agentic RAG : 主要就是在 RAG 流程中,導入 Agent,讓整個流程可以自主決策與優化,這裡先說個大概之後會開篇來專門說。

https://ithelp.ithome.com.tw/upload/images/20251004/20089358qnnw5qeYGZ.png
圖片來源: Agentic Retrieval-Augmented Generation: A Survey on Agentic RAG

🚀 RAG 會死掉嗎 ?

最近在網路上看到的議題,然後會有這個問題的核心原因應該在於 :

LLM 支援越來越多的 Token

但這裡我自已的看法應該是不會,主要有幾個問題 :

🤔 1. 成本的問題
只要 LLM 供應商還是用 token 來計價,我是覺得這個就是個繞不開的問題,就算有 Input Cache 便宜個 1/10 ( 如 OpenAI ),但長期算下來還是錢。

🤔 2. 長上下文導致 LLM 的效果降低
例如最常看到的這篇,主要在說如果上下文太長,會讓 LLM 在理解中間區段,會完全變笨蛋。

Lost in the Middle: How Language Models Use Long Context :

還有這篇文章總結出的 4 個問題 :

How Long Contexts Fail

  • Context Poisoning ( 上下文毒藥 ) : 當幻覺或其他錯誤進入上下文後,被反覆引用,例如如果你的目的是要找到一間百貨公司,然後上下文中有一句有問題的話『 百貨公司在北方 』,但實際上在南方的話,那就會完全找錯方向。
  • Context Distraction ( 上下文分心 ) : 就是如果上下文太長,LLM 會可能會專注在歷史上下文上,而導致沒有做到原本要的目標。這個很常發生在我們維護上下文的情況,如果你一個上下文有很多句『 我要做 xxx 』,那這樣就可能會讓 LLM 以為你要去做那些,而忽略你本來要做的。
  • Context Confusion ( 上下文混亂 ) : 就是如果你整個上下文有多餘的內容的話,那這個可能影響 LLM 的判斷,以這篇文章來說它是以 tools 來當說明,如果給 LLM 越多的 tools 選項,那他成功處理任務的機率會越來越低。
  • Context Clash ( 上下文衝突 ) : 當你在上下文中累積與其他資訊衝突,那就會導致 LLM 做到錯誤的回應。

所以目前我自已是覺得,只要這些問題還在,RAG 應該不太可能掛掉。

🚀 RAG Ecosystem ( 學習圖 )

這個是從這篇文章中看到的,我覺得給了我很多學習的方向可以建議看看。

Retrieval-Augmented Generation for Large Language Models: A Survey ( 2023/12 )

https://ithelp.ithome.com.tw/upload/images/20251006/200893588NNgDaWIF5.png

🚀 小總結

這篇文章我們將整個 RAG 體系,簡單且基本的理解過,包含了 :

  1. 基本的 RAG 流程
  2. 用 LangChain 實作的 RAG 簡單範例
  3. LangChain 中的 RAG 功能。
  4. 還有從論文中理解現有比較有名的 RAG 類型。
  5. 最後還有簡單想一下 RAG 會不會死這個問題。

接下來明天我們將要來更深入的理解 RAG 的每個部份。

🚀 參考資料


上一篇
30-19: [實作-9] 讓我們的 AI 工具人可以與其它 Agent 溝通 - A2A 的實作
下一篇
30-21: [實作-10] 用字幕檔實作 AI 課程問答功能 - Native RAG + MongoDB Atlas
系列文
30 天從 0 至 1 建立一個自已的 AI 學習工具人24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言