最近在一個教育 AI 的專案裡深度用到 RAG,順便把 RAGAS 評估也整進 pipeline
記錄一下實作的過程,以及一些不太直覺的地方
RAG 是什麼,為什麼教育場景特別需要它
RAG(Retrieval Augmented Generation)做的事情很簡單:
回答問題之前,先去知識庫搜一把相關段落,再把這些段落連同問題一起給模型
為什麼不直接問模型就好?
模型的訓練資料有截止日,也無法包含你的私有知識。教育場景更明顯——教材、課綱、解題步驟都是需要版本控制的東西,不能讓模型亂編
架構選型
這個專案的 RAG stack 選了這幾個:
| 層 | 技術 | 為什麼選它 |
|---|---|---|
| Vector DB | pgvector | PostgreSQL 原生擴充,不需要額外維運一個向量資料庫 |
| Embedding | Vertex AI text-embedding-004 | GCP ADC 驗證,不需要額外 API key;768 維 |
| Retriever | LangChain BaseRetriever | 跟 LangGraph + RAGAS 天然整合 |
| LLM | LiteLLM 包一層 | 一行換模型,同時支援 Google、OpenAI、開源模型 |
| Evaluation | RAGAS | 業界標準,四個核心指標 |
pgvector vs 獨立向量資料庫
很多人第一反應是 Pinecone 或 Weaviate,但對這個專案來說,沒必要多維護一個服務。pgvector 直接裝在 PostgreSQL 上,cosine similarity 查詢夠快,schema 管理跟其他 table 一致
當然如果資料量到億級、需要 ANN 特殊調優,再換不遲
踩到的最大坑:AsyncSession 跨 greenlet
LangGraph 是 async 的,RAGAS 評估也需要跑 retriever,但如果你的 retriever 用了 SQLAlchemy AsyncSession,會踩到一個很隱密的問題
sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called
根本原因是:SQLAlchemy async session 不能跨 greenlet 傳遞,但 RAGAS 在跑評估時會開新的 greenlet
解法:Retriever 用 sync psycopg2 自管連線,不用 AsyncSession
class ExpertScopedRetriever(BaseRetriever):
"""用 sync psycopg2,與 LangGraph/RAGAS 天然相容"""
def _get_relevant_documents(self, query: str) -> list[Document]:
embedding = self.embeddings.embed_query(query)
# 用 CTE + pgvector cosine similarity
# 自己管 psycopg2 connection,不走 AsyncSession
這個坑官方文件沒有明確說,是 debug 很久才找到的
RAGAS 四個指標
RAGAS 把 RAG 的品質拆成四個可量化的數字:
Faithfulness(忠實度)
模型的回答有多少是真的來自 retrieved context?
舉例:如果 RAG 拿到三段課文,模型回答時說了一句根本不在課文裡的話,Faithfulness 就會低
這是最重要的指標,特別是教育場景——你不能讓 AI 自己編答案
Answer Relevancy(回答相關性)
回答有沒有直接回答問題?模型有時候會答非所問,這個指標抓這件事
Context Recall(上下文召回率)
Ground truth 裡的資訊,有多少被 retrieved context 覆蓋到?
如果分數低,通常是 chunk 策略或 embedding 品質的問題,不一定是模型的問題
Context Precision(上下文精確率)
Retrieved 的段落裡,有多少是真正有用的?有沒有撈到很多不相關的雜訊?
為什麼 Faithfulness 低不一定是模型問題
這是實作之後最重要的發現
很多人看到 Faithfulness 0.6 就開始換模型、調 prompt,但其實問題可能在別的地方
我們設計了一個控制變因的測試:同一組測試題,跑四種組合
| 有 RAG | 無 RAG | |
|---|---|---|
| 強模型 | 版本 A | 版本 B |
| 弱模型 | 版本 C | 版本 D |
比較 A vs C:如果差距小,問題在 RAG(chunk 品質、retrieval 策略) 比較 A vs B:如果差距大,RAG 確實有幫助
這個設計讓你把「RAG 的問題」和「模型的問題」分開看,不會一直改錯方向
版本化是讓評估有意義的前提
RAGAS 跑出數字之後,你要能回答:「相比上一個版本改善了多少?」
這需要把 Expert 的設定做版本化
每次改 prompt、換 chunk 策略、調 top_k,都存成一個新版本,附上 SHA256 hash
class ExpertVersion(Base):
version = Column(Integer)
config = Column(JSON) # chunk_strategy, prompt_template, temperature, top_k...
instruction_hash = Column(String) # SHA256 of prompt
這樣 RAGAS 的評估結果才能對應到具體的設定,不然改了什麼不知道,數字好了也不知道為什麼好
目前這樣的設定效果如何
還在跑評估資料,不過有幾個初步觀察:
- pgvector + 768 dim embedding 夠用:搜尋速度在幾百毫秒以內,對 RAG use case 問題不大
- chunk 大小影響很大:數學題和文章的最佳 chunk 策略不一樣,沒有萬用值
- Faithfulness 對 prompt 非常敏感:instruction 裡有沒有明確說「只能根據提供的內容回答」,分數差距很大
小結
RAG + RAGAS 這套組合的最大價值,是把原本靠感覺的「這個 AI 回答得好不好」,變成四個可以追蹤的數字
但數字本身不會告訴你怎麼改。Faithfulness 低,要去查 prompt。Context Recall 低,要去查 chunk 策略。把問題對應到正確的層,才是這套評估框架真正節省時間的地方
