### 前言
接近 8.9 年的資深前端工程師,34 歲,雙非普本,座標廣州。25 年底被裁員,這三個月內也有投履歷、也有面試,有些推進到二面後就沒有下文,不禁感嘆現在的大環境實在不太好,而且在 AI 的衝擊下前端是受影響最大的職類之一。除了音視訊、圖形化方面還能有一些發揮空間外,AI 已經能完成 80–90% 的前端工作。在學歷及就業背景都不是特別強的情況下,一般前端即便技術不錯,也很缺乏競爭力。在失業的這三個月裡,經歷了持續學習、迷茫到看到曙光,決定轉型自學成為 AI agent;
很久一段時間沒有更新了,並不是斷更了,而是慢慢地進入狀態,最近都是每天早上起來學習直到晚上。
一般早上 8 點起來學習到中午 12 點,休息 2–3 個小時繼續學習到晚上 8 點,然後開始整理文件發到部落格。
不知不覺間,已經整理了很多相關文件,對這個賽道的知識體系有一個粗略的認知並且有了一定的基礎。

今天把今天學到的知識體系整理一下:
想像一下:
你有一個非常聰明但有點健忘的朋友(大型語言模型,LLM)。他知道很多常識,但如果你問他:「我們上週三開會時說了什麼?」 他就傻眼了,因為他沒有那天的記憶。
RAG 就是給這個朋友配了一本筆記。每次你問問題,他先快速翻筆記(檢索),找到相關記錄,然後結合自己的理解來回答你(生成)。
RAG(Retrieval-Augmented Generation,檢索增強生成)是一種將資訊檢索與大型語言模型生成能力相結合的技術架構。
核心公式:RAG = 檢索(Retrieval) + 增強(Augmented) + 生成(Generation)
環節作用:檢索從知識庫中找到與問題相關的資訊片段;增強把這些片段作為「上下文」注入到提示詞中;生成 LLM 基於增強後的提示詞生成最終答案。
| 對比維度 | RAG | 微調 |
|---|---|---|
| 知識更新 | 只需更新知識庫,無需重新訓練 | 需要重新訓練模型 |
| 可解釋性 | 答案可追溯到原始文件 | 難以追溯知識來源 |
| 實現成本 | 低,無需 GPU 訓練 | 高,需要訓練算力 |
| 即時性 | 秒級生效 | 小時/天級 |
| 適用場景 | 知識頻繁更新、私有文件問答 | 改變模型風格、行為或學習特定格式 |
建議一句話:想讓模型知道新事實 → 用 RAG;想改變模型行為方式 → 考慮微調。
| 問題說明 | RAG 如何解決 |
|---|---|
| 知識截止日期 | GPT-4 知識截止於 2023 年 10 月 → 注入最新文件(例如今天的新聞) |
| 模型幻覺 | LLM 會編造不存在的「事實」 → 強制基於檢索到的上下文回答 |
| 私有領域知識 | 公司內部文件、產品手冊、法律條文 → 將這些文件作為知識庫 |
| 動態更新 | 知識每天變化(如股價、政策) → 只需更新向量庫,秒級生效 |
| 答案可溯源 | 使用者想知道「你從哪裡知道的」 → 回傳答案時可附帶來源文件 |
RAG 分為兩大階段:
原始文件 → 文件載入 → 文本拆分 → 文本塊 → 向量化 → 向量資料庫
│ │ │ │ │ │
PDF/Word 讀取 切分成塊 小片段 轉成向量 存儲檢索
這個階段不需要使用者等待,可以在背景定期執行(例如每晚更新一次)。
使用者問題 → 向量化 → 問題向量 → 向量資料庫相似度搜尋 → Top-K 相關文本塊
↓
最終答案 ← 大型語言模型 ← 構建 Prompt(上下文 + 問題) ← ────────┘
假設你上傳了一份《2024 年公司休假政策》文件:
| 步驟 | 階段發生了什麼 |
|---|---|
| 1 | 索引:文件被切分成塊 → 向量化 → 存入向量庫 |
| 2 | 檢索:你問:「春節放假幾天?」 → 問題被向量化 |
| 3 | 檢索:向量庫找到最相關的文本塊(含「春節假期 7 天」) |
| 4 | 生成 Prompt =「根據上下文回答:春節放假幾天? 上下文:春節假期7天...」 |
| 5 | 生成:LLM 回答:「根據公司政策,春節放假 7 天。」 |
核心概念一句話總結:
本章只講原理,不涉及任何程式碼或框架。所有 LangChain 實作放在第四章。
目標:將各種格式的原始文件讀取為程式可處理的純文字。
挑戰:不同格式有不同複雜度
| 格式 | 挑戰 | 原理說明 |
|---|---|---|
| 表格、影像、多欄版面需要解析器提取文字流 | ||
| Word | 複雜格式、內嵌物件需要解壓並提取文字 | |
| Markdown | 標題層級需保留,標題可作為結構資訊 | |
| HTML | 標籤噪音需移除標籤,保留正文 | |
| 純文字 | 最簡單直接讀取 |
為什麼需要拆分?
核心概念:
| 概念 | 含義 | 示例 |
|---|---|---|
| chunk_size | 單個文本塊的最大長度 | 500 字元 或 200 tokens |
| chunk_overlap | 相鄰塊之間的重疊長度 | 50 字元,保留上下文連續性 |
| separators | 優先切割的位置 | 段落 > 句子 > 詞語 > 字元 |
重疊的作用:
文檔: [A 段開頭...中間部分...B 段結尾]
↓
塊1: [A 段開頭...中間部分]
塊2: [中間部分...B 段結尾] ← 重疊部分防止資訊被切斷
什麼是向量化?
將文本轉換為固定維度的浮點數陣列(向量),語意相似的文本在向量空間中距離更近。
示例:
"蘋果很好吃" → [0.12, -0.34, 0.56, ..., 0.78](1536 維)
"水果很美味" → [0.11, -0.33, 0.55, ..., 0.79](距離很近,語意相似)
"汽車很快" → [-0.45, 0.23, -0.67, ..., 0.12](距離很遠,語意不同)
關鍵原則:索引階段和檢索階段必須使用同一個 Embedding 模型,否則向量空間不匹配,無法正確比較。
存儲的內容結構:
┌─────────────────────────────────────────────┐
│ 向量資料庫中的一條紀錄 │
├─────────────────────────────────────────────┤
│ 向量:[0.12, -0.34, 0.56, ..., 0.78] │
│ 原始文本:"春節假期共7天" │
│ 元資料:{"source": "holiday.pdf", "page": 3}│
└─────────────────────────────────────────────┘
將使用者問題用與索引階段相同的 Embedding 模型轉換為向量。
常用相似度演算法:
| 演算法 | 直觀理解 | 公式 | |||
|---|---|---|---|---|---|
| 餘弦相似度 | 關注向量方向是否一致(最常用) | cos(θ) = (A·B) / ( | A | B | ) |
| 歐氏距離 | 關注絕對距離遠近 | d = √Σ(Ai - Bi)^2 | |||
| 點積 | 向量已歸一化時等同於餘弦 | A·B |
Top-K 檢索:返回與問題向量最相似的 K 個文本塊。
核心思想:將檢索到的文本塊作為「上下文」注入到提示詞中。
標準 RAG Prompt 範本結構:
你是一个基於以下上下文回答問題的助手。
上下文:
{這裡放檢索到的相關文本塊}
問題:{使用者的問題}
請基於以上上下文回答。如果上下文中沒有相關資訊,請說「我不知道」。
大型語言模型接收包含「上下文 + 問題」的 Prompt,基於上下文生成答案,而不是依賴自己的訓練記憶。
概念一句話解釋:
傳統資料庫(如 MySQL)無法高效進行向量相似度搜尋:
| 能力 | 傳統資料庫 | 向量資料庫 |
|---|---|---|
| 精確匹配 | ✅ 快 | ❌ 不支援 |
| 模糊搜尋 | ⚠️ 慢 | ❌ 不支援 |
| 向量相似度 | ❌ 不支援 | ✅ 支援(快) |
| 標量過濾 | ✅ 支援 | ✅ 支援(多數向量資料庫) |
向量資料庫專為向量搜尋設計,提供:
| 資料庫 | 類型 | 性能 | 易用性 | 擴展性 | 最適場景 |
|---|---|---|---|---|---|
| Chroma | 嵌入式 | 中等 | ⭐⭐⭐⭐⭐ | 低 | 學習原型、小專案 |
| FAISS | 函式庫 | 高 | ⭐⭐⭐ | 中 | 本地研究、無需持久化 |
| Pgvector | PostgreSQL 擴充 | 中高 | ⭐⭐⭐⭐ | 高 | 已有 PostgreSQL 棧 |
| Milvus | 雲原生 | 極高 | ⭐⭐ | 極高 | 十億級向量生產環境 |
| Redis | 內存資料庫 | 極高 | ⭐⭐⭐⭐ | 高 | 超低延遲場景 |
| Elasticsearch | 搜尋引擎 | 高 | ⭐⭐⭐ | 高 | 需要關鍵字+向量混合搜尋 |
Chroma
FAISS
Pgvector
Milvus
Redis
Elasticsearch
開始
│
├─ 只是學習/原型 → Chroma
│
├─ 已有 PostgreSQL → Pgvector
│
├─ 十億級向量 / 雲原生 → Milvus
│
├─ 需要超低延遲 → Redis
│
├─ 需要關鍵字+向量混合 → Elasticsearch
│
└─ 本地研究/高效能 → FAISS
本章只講核心常用程式碼,次要內容簡要帶過。
pip install langchain langchain-community chromadb openai tiktoken
# 按需安裝:unstructured pypdf docx2txt jq redis dashscope
| 元件 | 作用 | 常用類別 |
|---|---|---|
| 文件載入器 | 讀取各種格式文件 | TextLoader, PyPDFLoader, CSVLoader, Docx2txtLoader, JSONLoader |
| 文本分割器 | 切分長文件 | RecursiveCharacterTextSplitter(首選) |
| Embedding 模型 | 文本向量化 | OpenAIEmbeddings, HuggingFaceEmbeddings, DashScopeEmbeddings |
| 向量資料庫 | 存儲與檢索 | Chroma(學習), Redis(生產), FAISS(本地) |
| 檢索器 | 查詢相關文件 | as_retriever(k=4) |
| LLM 生成 | 生成答案 | ChatOpenAI, init_chat_model(阿里千問) |
| Prompt 模板 | 組裝提示詞 | PromptTemplate, ChatPromptTemplate |
# 純文字
from langchain_community.document_loaders import TextLoader
loader = TextLoader("file.txt", encoding="utf-8")
docs = loader.load()
# PDF
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("file.pdf", extraction_mode="plain")
docs = loader.load()
# Word
from langchain_community.document_loaders import Docx2txtLoader
loader = Docx2txtLoader("file.docx")
docs = loader.load()
# CSV
from langchain_community.document_loaders.csv_loader import CSVLoader
loader = CSVLoader(file_path="file.csv")
docs = loader.load()
# JSON
from langchain_community.document_loaders import JSONLoader
loader = JSONLoader(file_path="file.json", jq_schema=".", text_content=False)
docs = loader.load()
其他載入器(Markdown、HTML、目錄批次等)用法類似,按需查閱文件。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每塊最大字元數
chunk_overlap=50, # 塊間重疊
)
chunks = splitter.split_documents(docs)
# OpenAI
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# HuggingFace 本地(中文推薦)
from langchain_community.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh")
# 阿里千問
from langchain_community.embeddings import DashScopeEmbeddings
embeddings = DashScopeEmbeddings(model="text-embedding-v3", dashscope_api_key=api_key)
# Chroma(學習推薦)
from langchain_community.vectorstores import Chroma
vector_store = Chroma.from_documents(chunks, embeddings, persist_directory="./db")
vector_store.persist()
# Redis(生產推薦)
from langchain_community.vectorstores import Redis
vector_store = Redis.from_documents(docs, embeddings, redis_url="redis://localhost:6379", index_name="my_index")
# FAISS(本地快速)
from langchain_community.vectorstores import FAISS
vector_store = FAISS.from_documents(chunks, embeddings)
vector_store.save_local("./faiss_index")
retriever = vector_store.as_retriever(search_kwargs={"k": 4}) # 返回 Top-4
# OpenAI
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 阿里千問
from langchain.chat_models import init_chat_model
llm = init_chat_model(model="qwen-plus", model_provider="openai", api_key=api_key, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
from langchain_core.prompts import PromptTemplate
template = """基於以下上下文回答問題:
上下文:{context}
問題:{question}"""
prompt = PromptTemplate(template=template, input_variables=["context", "question"])
from langchain_core.runnables import RunnablePassthrough
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
)
result = rag_chain.invoke("你的問題")
print(result.content)
# complete_rag_example.py
import os
from langchain.chat_models import init_chat_model
from langchain_community.document_loaders import Docx2txtLoader
from langchain_core.prompts import PromptTemplate
from langchain_classic.text_splitter import CharacterTextSplitter
from langchain_core.runnables import RunnablePassthrough
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import Redis
# 1. 初始化 LLM
llm = init_chat_model(
model="qwen-plus",
model_provider="openai",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 2. Prompt 模板
prompt_template = """
請使用以下提供的文本內容來回答問題。僅使用提供的文本資訊,
如果文本中沒有相關資訊,請回答 "抱歉,提供的文本中沒有這個資訊"。
文本內容:{context}
問題:{question}
回答:
"""
prompt = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
# 3. Embedding
embeddings = DashScopeEmbeddings(model="text-embedding-v3", dashscope_api_key=os.getenv("aliQwen-api"))
# 4. 載入文件
loader = Docx2txtLoader("alibaba-java.docx")
documents = loader.load()
# 5. 分割文件
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 6. 建立 Redis 向量庫
vector_store = Redis.from_documents(
documents=documents,
embedding=embeddings,
redis_url="redis://localhost:6379",
index_name="my_index",
)
# 7. 檢索器
retriever = vector_store.as_retriever(search_kwargs={"k": 2})
# 8. RAG Chain
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
)
# 9. 提問
result = rag_chain.invoke("00000和A0001分別是什麼意思")
print(result.content)
| 文檔類型 | 推薦值 | 原因 |
|---|---|---|
| 產品問答 | 200–300 字元 | 每個問答短小精悍 |
| 技術文件 | 500–800 字元 | 段落通常較長 |
| 法律條文 | 300–500 字元 | 條款需保持獨立 |
| 長篇文章 | 1000–1500 字元 | 保持上下文連貫 |
chunk_overlap = chunk_size × (10% ~ 20%)
| K 值 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 3 | 答案集中在少數段落 | 精準 | 可能遺漏資訊 |
| 5 | 通用推薦 | 平衡 | — |
| 10 | 需要廣泛上下文信息 | 全面 | 噪音增多、成本增加 |
| 技術 | 一句話說明 |
|---|---|
| 多查詢檢索 | 將問題改寫成多個角度,分別檢索後合併 |
| 父文檔檢索 | 存小塊(精準匹配),返回大塊(完整上下文) |
| 自查詢檢索 | 從問題中提取語意條件 + 元資料過濾條件 |
| 重排序 | 檢索更多結果,用更強模型重新排序取 Top-K |
| 問題 | 可能原因 | 解決方案 |
|---|---|---|
| 答案不相關 | chunk 太大含噪音 | 減小 chunk_size |
| 丟失關鍵資訊 | chunk 太小切斷上下文 | 增大 chunk_size 或 overlap |
| 檢索不到問題 | 表述與文件不匹配 | 使用多查詢檢索 |
| 回答「不知道」但有文件 | Embedding 模型不適合中文 | 改用 BAAI/bge-large-zh |
| 速度慢 | 向量庫太大 | 添加索引、使用 GPU |
| 成本高 | Top-K 或 chunk 太大 | 減小 K 和 chunk_size |
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
retriever = vector_store.as_retriever(search_kwargs={"k": 4})
成為 AI agent 工程師並且就業
需要大家的關注跟按讚,你們的關注按讚就是對我最大的鼓勵。或許等我技術成熟時,你們當中的大佬還可以幫我一把,感謝。