🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

====================

大家好,我是雙越。wangEditor 作者,前百度、滴滴 資深前端工程師,慕課網金牌講師,PMP,前端面試派 作者。

我正在致力於兩個專案的開發與升級,有興趣可以私訊我加入專案小組。

  • 划水AI Node 全端 AIGC 知識庫,包含 AI 寫作、多人協同編輯。複雜業務,真實上線。
  • 智語 AI Agent 智能體專案。一個智能面試官,可以優化履歷、模擬面試、解題等。

本文是一篇面向 Node.js 開發者的 RAG 學習筆記,涵蓋核心概念、技術細節、工程實作與 Agent 應用場景。

RAG 是什麼

RAG(Retrieval-Augmented Generation,檢索增強生成)是一種將資訊檢索與大型語言模型生成相結合的技術架構。

背景與動機

大型語言模型(LLM)在實際應用中存在幾個核心痛點:

  • 知識截止日期(Knowledge Cutoff):模型訓練資料有時效性,無法取得最新資訊
  • 無法訪問私有資料:企業內部文件、資料庫等私有知識對模型不可見
  • 幻覺問題(Hallucination):模型會自信地捏造不存在的資訊

RAG 的核心思路是:先查資料,再回答問題。就像員工在回答主管問題前先去查閱相關文件一樣。透過在生成答案前動態檢索相關知識,RAG 讓 LLM 的回答有據可查、可驗證。


核心技術架構

整體流程

使用者提問
   ↓
[Retrieval 檢索階段]
將問題向量化 → 在向量資料庫中搜尋 → 回傳相關文件片段
   ↓
[Augmentation 增強階段]
將檢索到的內容拼入 Prompt(作為上下文)
   ↓
[Generation 生成階段]
LLM 基於上下文生成最終答案

RAG 系統分為兩條流水線:索引流水線(離線處理文件)和查詢流水線(線上回應使用者)。

處理階段(Indexing Pipeline)

這是離線階段,負責將原始文件轉換為可檢索的向量索引:

原始文件(PDF / Word / 網頁 / 資料庫)
   ↓ 解析(抽取純文字)
純文字
   ↓ Chunking(分塊)
文本片段(通常 200~1000 tokens)
   ↓ Embedding 模型(如 text-embedding-3-small)
向量(float[] 陣列)
   ↓ 儲存
向量資料庫(Pinecone / Weaviate / pgvector / Chroma)

查詢階段(Query Pipeline)

這是線上階段,負責即時回應使用者提問:

async function ragQuery(userQuestion) {
  // Step 1: 將問題轉為向量
  const queryEmbedding = await embeddings.embed(userQuestion);

  // Step 2: 向量相似度搜尋
  const relevantChunks = await vectorDB.similaritySearch(queryEmbedding, topK = 5);

  // Step 3: 建構增強 Prompt
  const context = relevantChunks.map(c => c.text).join('\n\n');
  const prompt = `
    根據以下資料回答問題,如果資料中沒有相關資訊請說明。

    【參考資料】
    ${context}

    【問題】
    ${userQuestion}
  `;

  // Step 4: LLM 生成答案
  return await llm.generate(prompt);
}

深入技術細節

Chunking 策略

分塊方式直接影響檢索品質,是 RAG 系統中最容易被忽略、卻影響最大的環節之一。

策略說明與適用場景:

  • Fixed-size(固定大小):按固定 token 數切割 —— 簡單場景、快速驗證
  • Sliding Window(滑動窗口):帶重疊的滑動窗口 —— 防止關鍵資訊在分塊邊界斷裂
  • Semantic(語義邊界):按語義邊界切割(句子/段落) —— 通用場景,建議預設選擇
  • Recursive(遞歸):按標題層級切割 —— 結構化文件(Markdown / HTML)
  • Document-specific(文件專用):針對特定格式專門處理程式碼檔、表格等特殊內容

核心原則:Chunk 不可太大(引入雜訊)也不可太小(缺失上下文),通常 512 tokens 左右是比較合適的起點,需根據實際效果調整。

檢索增強主流程

生產級 RAG 系統的完整檢索流程如下:

使用者輸入模糊問題
   ↓ Query Rewriting    (擴展查詢,提升召回率)
多個查詢並行檢索
   ↓ Hybrid Search      (混合檢索,粗篩 Top 50)
Top 50 候選文件
   ↓ Re-ranking         (精準排序,篩出 Top 5)
Top 5 最相關文件
   ↓
注入 Prompt → LLM 回答

三個技術各司其職,組合使用才能達到生產級效果。


Hybrid Search 混合檢索

為什麼需要混合檢索

單一檢索方式各有缺陷:

向量檢索(Vector Search)將文字轉成數值向量,語義相近的內容向量距離相近。它能理解同義詞、近義詞,但對精確關鍵字不敏感 —— 搜尋 "GPT-4o" 可能找不到含有 "GPT-4o" 字樣的文件。

關鍵字檢索(BM25)基於詞頻統計打分,是傳統搜尋引擎(Elasticsearch)的核心演算法。它精準命中專有名詞、程式碼、型號,但完全不懂語意 —— 搜 "汽車" 找不到只寫了 "轎車" 的文件。

範例:

使用者搜尋: "蘋果手機拍照虛化效果怎麼弄"

  • 向量檢索:能找到 "iPhone 人像模式使用教學"(語意相關)✅,但可能漏掉含 "f1.8 光圈" 的專業文件
  • BM25:精準命中含 "虛化" 關鍵字的文件 ✅,但找不到只寫 "Bokeh 效果" 的文件
  • 混合檢索:兩者結果都要,然後合併排序 🎯

RRF 融合演算法

兩個檢索系統各自回傳一個排序列表,透過 RRF(Reciprocal Rank Fusion)合併:

RRF 得分 = Σ 1 / (k + rank) (k 通常取 60)

舉例說明:

向量檢索結果: BM25 檢索結果:
第1名: 文件 A 第1名: 文件 C
第2名: 文件 B 第2名: 文件 A
第3名: 文件 C 第3名: 文件 D

RRF 計算:
文件 A: 1/(60+1) + 1/(60+2) = 0.03252 ← 綜合最高
文件 C: 1/(60+3) + 1/(60+1) = 0.03226
文件 B: 1/(60+2) + 1/(60+4) = 0.03176

最終排序:A → C → B → D

RRF 的精髓是獎勵在多個系統中都表現好的文件。

BM25 的輸入注意事項

BM25 基於詞頻統計,並不適合輸入長句子,否則會適得其反:

  • 關鍵字被「在」、「中」、「通常」等無意義詞稀釋
  • 停用詞干擾打分
  • 詞越多,結果越發散

兩種檢索的最佳輸入策略截然相反:

  • 向量檢索最佳輸入:完整的句子/段落 —— Embedding 能壓縮整體語意
  • BM25 最佳輸入:精練的關鍵字 —— 詞越精準越好(類比 Ctrl+F 全文搜尋)

Node.js 實作

import { EnsembleRetriever } from "langchain/retrievers/ensemble";
import { BM25Retriever } from "@langchain/community/retrievers/bm25";

const vectorRetriever = vectorStore.asRetriever({ k: 10 });
const bm25Retriever = BM25Retriever.fromDocuments(docs, { k: 10 });

const hybridRetriever = new EnsembleRetriever({
  retrievers: [vectorRetriever, bm25Retriever],
  weights: [0.6, 0.4],  // 向量檢索權重較高
});

const results = await hybridRetriever.invoke("Node.js fs 模組怎麼用");

Re-ranking 重排序

核心原理:Bi-encoder vs Cross-encoder

初始檢索用 Bi-encoder(雙塔模型):Query 和文件各自獨立編碼,提前存好向量,查詢時只計算距離,速度極快,但兩者從未「見過彼此」,理解不夠深。

Re-ranking 用 Cross-encoder(交叉模型):Query 和文件拼在一起讓模型讀,能看到兩者完整關係,理解深度遠超 Bi-encoder,但無法提前計算,只能即時處理少量文件。

為什麼兩階段缺一不可

假設有 100 萬份文件:

  • Cross-encoder 直接檢索:100 萬次模型推理 → 慢到無法接受 ❌
  • Bi-encoder 檢索:毫秒級返回,但理解粗糙 ✅(用於粗篩)

最優方案:粗篩縮小範圍 → 精排提升品質

完整兩階段流程

100 萬文件
   ↓ 向量/混合檢索(毫秒級,粗篩)
Top 20~50 候選文件
   ↓ Cross-encoder Re-ranking(精排,只處理少量文件)
Top 5 高品質文件
   ↓ 注入 Prompt → LLM 生成答案

Node.js 實作

import { CohereRerank } from "@langchain/cohere";
import { ContextualCompressionRetriever } from "langchain/retrievers/contextual_compression";

const baseRetriever = vectorStore.asRetriever({ k: 20 });

const reranker = new CohereRerank({
  apiKey: process.env.COHERE_API_KEY,
  topN: 5,
  model: "rerank-multilingual-v3.0",  // 支援中文
});

const retriever = new ContextualCompressionRetriever({
  base_compressor: reranker,
  base_retriever: baseRetriever,
});

const results = await retriever.invoke("Node.js 如何處理檔案上傳");
// 自動流程:粗篩 20 條 → rerank 精排 → 回傳最相關的 5 條

類比:Re-ranking 就像招聘時的兩輪篩選——簡歷篩選(檢索)快速從 1000 份中選出 20 個,再安排面試(Re-ranking)深度評估,最終選出最合適的 5 個。


Query Rewriting 查詢改寫

為什麼需要改寫

使用者輸入的問題往往口語化、模糊、資訊量不足,直接檢索效果很差:

使用者輸入: "nodejs怎麼連資料庫"

可能會漏掉這些相關文件:
❌ "使用 Prisma ORM 操作 PostgreSQL"
❌ "Sequelize 設定連線池最佳實踐"
❌ "mysql2 驅動安裝與初始化"

根本原因:使用者問的方式和文件寫的方式之間存在表達鴻溝。

三種常見做法

做法一:同義擴展(生成多個查詢)

// 讓 LLM 將一個問題改寫成多個不同角度的查詢
const queries = [
  "Node.js 資料庫連線方法",
  "Prisma Sequelize 使用教學",
  "mysql2 pg 驅動設定",
  "Node.js connection pool 連線池"
];
// 用這四個查詢分別檢索,合併結果 → 召回率大幅提升

做法二:HyDE(假設性文件生成)

不改寫問題,而是讓 LLM 先假裝回答一遍,再拿這個「假答案」去檢索:

原始問題: "nodejs怎麼連資料庫"
   ↓ LLM 生成假設答案
"在 Node.js 中連接資料庫通常使用 mysql2 或 pg 這類驅動,
也可以使用 Prisma、Sequelize 等 ORM 框架..."
   ↓
拿這段話做向量檢索(效果更好,因為更接近真實文件的表達方式)

原理:假答案的向量比短問題的向量更接近真實文件的向量。

注意:HyDE 生成的長文本適合用於向量檢索,若用於 BM25 時需要先提取關鍵字:

// 向量檢索:直接用假設答案的完整語意
const vectorResults = await vectorStore.similaritySearch(hypotheticalAnswer, 20);

// BM25 檢索:先提取關鍵字再檢索
const keywords = ["mysql2", "Prisma", "連線池", "ORM", "pg"];
const bm25Results = await bm25.search(keywords.join(" "), 20);

做法三:問題分解(複雜問題)

原始問題: "Prisma 和 Sequelize 哪個更適合 Node.js 新專案"
   ↓ LLM 拆解
[
  "Prisma ORM 的優缺點",
  "Sequelize ORM 的優缺點",
  "Prisma 和 Sequelize 效能比較",
  "2024 年 Node.js ORM 選型建議"
]
每個子問題單獨檢索 → 匯總結果 → LLM 綜合回答

在 AI Agent 中的應用場景

在 Agent 架構中,RAG 不再是簡單的「查一次」,而是作為 Tool(工具)被 Agent 按需呼叫,賦予 Agent 動態存取外部知識的能力。

場景一:企業知識庫問答

最經典的場景,RAG 作為 Agent 的知識外挂:

使用者: "我們公司的年假政策是什麼?"
Agent: 呼叫 search_knowledge_base("年假政策")
    → 檢索 HR 內部文件
    → 生成準確答案(而非 LLM 瞎猜)

場景二:程式碼庫智能助理

程式碼向量化後,Agent 能理解整個程式碼倉庫的語意:

使用者: "幫我找到處理使用者登入的函式"
Agent: 呼叫 search_codebase("使用者登入驗證")
    → 回傳相關程式碼片段及檔案位置
    → 分析並解釋程式碼邏輯

場景三:Multi-step Research Agent

Agent 自主決定多次檢索,整合多個來源:

使用者: "幫我分析競爭對手的產品策略"
Agent:
  1. search_web("competitor A product 2024") → 檢索網頁內容
  2. search_internal_reports("市場分析") → 檢索內部報告
  3. 綜合兩次檢索結果 → 生成完整分析報告

場景四:長期記憶(Long-term Memory)

Agent 將歷史對話存入向量庫,實現真正的「記住使用者」:

// 儲存使用者偏好
await memoryStore.save({
  userId: "user_123",
  content: "使用者偏好用 TypeScript,不喜歡 callback 風格",
  embedding: await embed("使用者偏好 TypeScript...")
});

// 下次對話時檢索相關記憶,實現個性化回應
const memories = await memoryStore.recall(userId, currentQuestion);

代表框架:mem0。

場景五:動態工具文件檢索(Tool RAG)

當 Agent 有數百個可用工具時,全部塞進 Prompt 會超出上下文長度限制:

使用者意圖
   ↓ RAG 檢索最相關的 10 個工具定義
注入 Prompt
   ↓ LLM 選擇呼叫哪個工具

技術實作

框架(Node.js 生態)

LangChain.js

目前 Node.js 生態中最成熟的 RAG/Agent 框架,提供完整工具鏈:

import { ChatOpenAI } from "@langchain/openai";
import { OpenAIEmbeddings } from "@langchain/openai";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { EnsembleRetriever } from "langchain/retrievers/ensemble";
  • 優點:文件豐富、社群活躍、整合全面(向量庫、LLM、工具全覆蓋)
  • 缺點:抽象層較多,除錯較複雜,學習曲線較陡

LlamaIndex.TS

專注於資料索引與檢索的框架,對 RAG 場景優化更深:

  • 優點:RAG 專用設計,對複雜檢索場景支援良好
  • 缺點:社群比 LangChain 小,文件較少

入門建議:先用 LangChain.js,生態最完整,遇到問題最容易找到答案。


向量資料庫比較

Chroma — 本地開發專用

import { Chroma } from "@langchain/community/vectorstores/chroma";

// 零配置,像 SQLite 一樣嵌入式運行
const vectorStore = await Chroma.fromDocuments(docs, embeddings, {
  collectionName: "my-docs",
  persistDirectory: "./chroma-data",
});
  • 定位:嵌入式資料庫,直接跑在程序裡,資料存本地檔案
  • 優點:零配置,npm install 即用
  • 缺點:無叢集、無高可用,不適合生產
  • 適合:學習階段、本地原型驗證

pgvector — 已有 PostgreSQL 時的最佳方案

-- 開啟擴充
CREATE EXTENSION vector;

-- 普通 PG 表,多了一個向量欄位
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  metadata JSONB,
  embedding vector(1536)
);

-- 向量查詢和業務查詢混用,支援 JOIN
SELECT d.content, u.username
FROM documents d
JOIN users u ON d.author_id = u.id
WHERE u.plan = 'premium'
ORDER BY d.embedding <-> $1
LIMIT 5;
  • 定位:PostgreSQL 外掛,不是獨立資料庫
  • 優點:向量查詢與業務資料同庫,事務/JOIN/權限可復用,運維成本低
  • 缺點:沒有原生 BM25,混合檢索需自行實作(可用 ts_rank 等近似替代)
  • 適合:團隊已使用 PG、資料量中等

Weaviate — 功能最完整的開源方案

// 原生混合檢索,一行搞定
const results = await weaviate.graphql.get()
  .withClassName("Document")
  .withHybrid({
    query: "Node.js 連接資料庫",
    alpha: 0.6,  // 0=純BM25, 1=純向量
  })
  .withLimit(5)
  .do();
  • 定位:專為 RAG/AI 場景設計的向量資料庫
  • 內建能力:向量檢索 + BM25 + 混合檢索 + RRF 融合 + Re-ranking + 多租戶 + 多模態
  • 優點:功能最全,原生混合檢索開箱即用,省大量自建代碼
  • 缺點:需自託管或使用 Weaviate Cloud,有一定運維成本
  • 適合:需要混合檢索、對功能要求高、能接受自託管

Pinecone — 托管雲服務,最省心

// 配置最簡單,沒有任何伺服器要管
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pinecone.index("my-index");

await index.upsert(vectors);
const results = await index.query({ vector: queryEmbedding, topK: 10 });
  • 定位:純雲端托管 Serverless 向量資料庫
  • 優點:零運維、自動擴容、按量付費
  • 缺點:不支援原生混合檢索(需外掛 Elasticsearch)、資料存境外
  • 適合:不想管運維、快速上線、預算充足

選型決策

  • 學習 / 做原型 → Chroma(零成本零配置)
  • 專案已用 PostgreSQL → pgvector(復用既有基礎設施)
  • 需要混合檢索且能自託管 → Weaviate(功能最全)
  • 不想管運維且預算充足 → Pinecone(最省心)

比較概要(簡化):

  • Chroma:部署方式 本地嵌入,原生混合檢索 ❌,運維成本 零,適合 學習/原型
  • pgvector:部署方式 PG 外掛,原生混合檢索 ❌,運維成本 低,適合 復用 PG
  • Weaviate:部署方式 自託管/雲,原生混合檢索 ✅,運維成本 中,適合 生產
  • Pinecone:部署方式 純雲托管,原生混合檢索 ❌,運維成本 零,適合 生產/快速上線

Embedding 模型

Embedding 模型負責將文字轉為向量,是 RAG 品質的基礎。

模型提供方與特性:

  • text-embedding-3-small(OpenAI):性價比高、速度快,推薦預設選擇
  • text-embedding-3-large(OpenAI):精度更高,價格較貴
  • embed-multilingual-v3.0(Cohere):多語言支援佳,中文效果良好
  • BGE 系列(BAAI 智源)開源,中文效果極佳,可本地部署(via Ollama)—完全免費,資料不出本地,適合隱私要求高的場景

選型建議:

  • 快速上手:OpenAI text-embedding-3-small(API 簡單,效果穩定)
  • 中文場景:優先考慮 Cohere 多語言模型或 BGE 中文模型
  • 資料隱私要求高:本地部署 BGE(via Ollama)

總結

技術全景圖

使用者提問
   ↓
Query Rewriting     將模糊問題擴展為多個精準查詢
   ↓
Hybrid Search       向量檢索(語意) + BM25(關鍵字)→ RRF 融合
   ↓
Re-ranking          Cross-encoder 精準排序,淘汰低品質結果
   ↓
Prompt 增強         將 Top K 文件注入上下文
   ↓
LLM 生成            基於真實資料生成有據可查的答案

各技術作用一覽

  • 基礎 RAG:解決 LLM 知識截止/幻覺問題 —— 專案起步時導入
  • Chunking:優化檢索到的內容品質 —— 發現答案不準時引入
  • Hybrid Search:專有名詞/精確詞匹配差時 —— 有程式碼、型號等精確詞時引入
  • Re-ranking:檢索結果排序不夠精準時 —— 需要提升答案品質時引入
  • Query Rewriting:使用者問題模糊導致漏檢時 —— 提升召回率
  • RAGAS 評估:無法量化系統好壞時 —— 持續優化時使用

Node.js 開發者學習路徑

  1. 跑通最小 Demo:LangChain.js + Chroma + OpenAI,在本地搭一個知識庫問答
  2. 理解 Chunking:同一份文件用不同分塊策略,觀察檢索品質差異
  3. 引入混合檢索:用 Weaviate 體驗原生 Hybrid Search
  4. 加入 Re-ranking:接入 Cohere Rerank API,對比前後效果
  5. Agentic RAG:將 RAG 封裝成 Tool,讓 Agent 自主決定何時檢索
  6. 評估與迭代:建立測試集,用 RAGAS 量化每次改動的效果

原文出處:https://juejin.cn/post/7615882380273664035


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝23   💬8   ❤️1
584
🥈
我愛JS
📝1   💬6  
37
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付