GraphRAG vs RAG + A2A:後端工程師的請假流程 AI 實作

重點摘要
  • 一個後端/DBA 背景的人,一個下午從「完全不懂 RAG/LLM」到蓋出一個 HR 請假流程的 GraphRAG + A2A demo,本文鉅細靡遺記錄。
  • 用 RDB 的話講清楚:LLM 是一個 API、RAG 是「查 DB 把結果貼進 prompt」、GraphRAG 差在「查詢時有沒有沿著線走」、A2A 是有門禁的 microservice。
  • 核心紀律:LLM 只負責「聽懂話、回答」,執行/授權/卡控全是確定性 code。LLM 是嘴巴和耳朵,不是手,更不是印章。
  • 真實世界沒那麼乾淨:entity linking(打錯字)、混亂大圖走錯領域,加上 Cloudflare 逾時、ollama CPU 塞車等工程踩坑,全收錄。

我是後端 / DBA 出身,過去十幾年都在 table、column、SQL、JOIN 裡打滾。RAG、LLM、GraphRAG、A2A 這些詞我聽過無數次,但「它到底怎麼插進我熟的系統流程裡」一直是個黑盒。這篇是我用一個下午,逼自己從零搞懂、並親手蓋出一個能跑的 demo(HR 請假流程 → 知識圖譜 → AI 問答 + A2A 執行)的完整紀錄——包含我踩的每一個坑。

如果你也是後端,看過一堆 RAG 文章卻還是覺得隔靴搔癢,這篇用你熟的東西講。先記住一句話定錨:LLM 就是一個 API;RAG 就是呼叫這個 API 之前,先查 DB 把結果貼進去;「插進流程」就是你的 handler 裡多一個 service 呼叫——而且只放在邊緣,核心邏輯一行不用變。

1. 用後端的話定位:LLM、RAG、GraphRAG、A2A 是什麼

LLM = 一個你會呼叫的 API

你平常會 POST /api/xxx 打別人的服務。LLM 一模一樣:丟文字進去,吐文字出來。只有三個不同:輸入輸出是自然語言、同樣輸入可能不同輸出(非確定性)、它知道全世界常識但完全不知道你公司的事、偶爾會唬爛。心智模型:一個很會講話、讀過全世界文章,但不知道你公司任何事、偶爾唬爛的 API。

RAG = 呼叫 LLM 前,先查 DB 把答案貼進去

LLM 不知道你公司的事,你問「我們公司請假規定?」它會唬一個出來。RAG 的解法,用後端的話講就是字串拼接:

# 沒 RAG:直接問 → 它亂編
answer = llm("我們公司請假規定?")

# 有 RAG:先查你自己的資料,貼進問題,再問
rows   = db.query("SELECT * FROM 請假規定 ...")     # 查你的 DB
answer = llm("只能根據以下資料回答:\n" + rows +
             "\n問題:我們公司請假規定?")

RAG = Retrieval(查資料)+ Augmented(塞進 prompt)+ Generation(再叫 LLM)。沒有魔法,就是「先 SELECT,把結果 concat 進問題,再呼叫 LLM」。那「知識圖譜 / Neo4j」是什麼?就是這裡的「DB」,只是查出來的是「點跟線」不是 table rows。

A2A = 有門禁的 microservice

A2A agent 就是「一支只做一件事的小服務 + 一張自我介紹卡(Agent Card)」,概念跟 microservice 一樣。每支收到請求先過兩道關卡才執行:授權(你的 auth middleware)+ 卡控(你的 validation + 業務規則)。我之前寫過 A2A 是什麼:用一個 Python 把資料庫變成 AI 外掛 講更細。

2. RAG 跟 GraphRAG 差在哪?(都用 Neo4j,差在查詢)

這是我卡最久、也最值得講的一點:用了 graph DB ≠ 在做 GraphRAG。關鍵在「檢索時有沒有用到圖的結構」,不是「存在圖裡」。同一個 Neo4j,你可以把它當笨倉庫,也可以當圖引擎。

  Vector RAG(普通 RAG) GraphRAG
怎麼存文字切塊 → 轉向量 → 存向量庫抽成「點+有方向有類型的線」→ 存圖庫
怎麼查找「最像問題的幾塊文字」沿著線走:鄰居、A→B 路徑、樞紐、社群
後端比喻模糊版全文搜尋有外鍵的 schema,可 JOIN/traverse
擅長「文件裡關於 X 怎麼說」「A 跟 B 怎麼連」「整體主題」「跨文件綜合」
成本便宜、快貴、慢、要維護

我的 demo 一開始問答是「MATCH (e) RETURN e 把整張圖倒進 prompt」——這嚴格講還不算 GraphRAG,只是「用圖當資料源的普通 RAG」。真正的 GraphRAG 是用 shortestPath、變長路徑 -[:REL*]-、中心性、社群這些「沿著線走」的查詢。我拿一份真實的華新麗華 ESG 知識圖譜(42 實體、55 關係、8 社群)當 sample,跑出這條多跳路徑:

// 沿著線找 Walsin 到 CDP 怎麼連(-[:REL*..6]- 是變長路徑遍歷)
MATCH p=shortestPath((a {id:'Walsin Lihwa'})-[:REL*..6]-(b {id:'CDP'})) RETURN p
// → Walsin Lihwa → SASB → IFRS S2 → scenario analysis → CDP

這種「華新麗華要拿 CDP 高分,中間要經過哪些框架」的多跳答案,vector RAG 生不出來——它只會找「像問題的文字」,不會「走路徑」。一句話分辨:Vector RAG 找「長得像問題的段落」;GraphRAG 沿著「點和線」找關係和結構。

3. 這個「圖資料庫」長怎樣?(對照 RDB)

RDB(你熟的) Graph DB(Neo4j)
Table employeeLabel :Entity(點的分類≈表名)
Row(一筆記錄)Node(一個點)
ColumnProperty(但 schema-less)
Foreign Key + JOINRelationship/邊(第一級實體,沿線走)

最大差別:RDB 的「關係」藏在欄位(foreign key),查時用 JOIN 接出來;Graph 的「關係」是第一級實體——一條有方向、有類型的線實際連著兩個點,你不 JOIN,你沿著線走。而且每個點直接存了鄰居指標(index-free adjacency),走一格是 O(1),不是 RDB 那種越多跳越慢的 self-join。這就是為什麼「不固定跳數的關係鏈」graph 做得到、RDB 很痛苦。

4. LLM 怎麼「插進」流程?它只站門口,不進核心

看我本來會怎麼做請假功能,跟加上 LLM 之後的差別——注意後端核心完全沒變:

① 原本(純後端):
   [前端表單] 下拉假別+填天數 → POST /leave {type,days} → 驗證→查額度→寫DB→回覆

② 加 LLM 後:
   [打字] "我要請特休5天"
     → 後端 step1: llm("轉成JSON:"+text) → {type:"特休",days:5}   ★ LLM 只做這 ★
     → 後端 step2: 驗證→查額度→寫DB→回覆   ← 跟①一模一樣,沒變!

關鍵領悟:LLM 不是來取代你後端的,它只取代了「表單」——把「使用者要什麼」從填表單,換成打字 + LLM 幫你解析成表單欄位。解析完,接手的就是你平常那套後端,一行不用改。而且 LLM 翻出來的 {type,days} 一樣要跑你平常的 validation——你會像對待任何不可信輸入一樣,拿它的輸出但一定驗過才用。

一句話心法
LLM 是嘴巴和耳朵(看懂話、講出話),不是手(做事),更不是印章(做決定)。手跟印章還是你平常寫的後端。

5. A2A 的授權、卡控、人閘(以請假流程為例)

我把請假流程的每一步做成 A2A agent,跑一次「員工 alice 請 5 天特休」,每個操作背後實際跑這些步驟:

操作 背後步驟(哪一層)
員工打字申請LLM 翻成結構 → 授權(是員工?)→ 卡控(欄位齊?)→ 寫 DB
主管核准授權(是主管?)→ 卡控(>3天→升級部門主管)
部門主管核准(不可逆)授權 → 人閘(停下來等人按) → 卡控(額度夠?)→ 扣假寫 DB
員工查狀態查 DB → 回「已核准」

重點:授權跟卡控是寫死在每支 agent 裡的確定性規則,不是靠 AI 自律。就算 LLM 把意圖判錯,最壞只是「選錯一個 enum」;後端的權限檢查照樣擋。錯誤被關在「翻譯」這格,流不到「執行」。不可逆步驟(最終核准)交給人按——這跟我一直在講的 AI 不是答案販賣機 同一條神經。

6. 真實世界沒這麼乾淨(這才是會出包的地方)

上面是 happy path。真實的圖很大、很多領域混在一起、使用者還會打錯字。我把 HR 流程從 8 點擴到 32 點(請假+加班+出差+onboarding+離職交織),立刻體會到兩個真實難題——這也是 GraphRAG 最脆的兩環。

難題 A:使用者打「SAB」不是「SASB」,怎麼對到正確的點?

不是「它就知道」,是產候選 → 排序 → 不確定就回問:① 模糊比對(Neo4j 全文 fuzzy index SAB~,編輯距離最近)② 別名表(最穩,production 做法)③ embedding 語意比對 ④ 把候選+問題上下文丟 LLM 排序。信心不夠就回問「你是指 SASB 嗎?」,別硬猜——補錯起點,後面整條全錯。

難題 B:混亂大圖,從一個點出發怎麼不走錯到別的領域?

不是祈禱它走對,是帶著約束走:domain/label 鎖(每個點標領域,走時 -[r]-(x:ESG) 只走同領域鄰居)+ edge type 白名單 + hop 上限 + 相關性剪枝。而「要走哪條 edge」別硬挑——撈整個鄰居子圖讓 LLM 自己讀,或用查詢模板;千萬別讓弱模型自由寫 Cypher(會把方向寫反、3-hop 寫不出)。

誠實收斂:沒有 100% 魔法。
明確的東西(label/domain/別名表/hop)用確定性鎖死;模糊的才交給 LLM 排序;危險/不確定的就回問使用者。「混亂」是因為沒標 domain;標好 + 查詢時 scope 死,就不亂了。

7. 工程踩坑(鉅細靡遺)

把它掛上 neo4j.子網域(Cloudflare tunnel)給人玩的過程,踩了一串很實際的坑:

症狀 根因 解法
建圖回 Unexpected token '<'建圖要 60-120 秒,撞 Cloudflare ~100 秒逾時,回了 HTML 錯誤頁不是 JSON非同步:送出秒回 job_id,前端輪詢進度
逾時 / 一直轉單 CPU 上 ollama 排隊塞車,並發請求互相拖垮全域鎖(一次一個)+ keep_alive(模型不卸載)+ 拉長逾時
圖像一團「毛球」很亂真實領域圖本來就密(42 點 55 線)不看毛球,用查詢拉乾淨子圖;關係文字預設隱藏、按社群上色
DNS NXDOMAIN 連不上純粹網址打錯字(網域拼錯)不是系統問題,複製正確網址

那個「毛球」其實是最好的一課:整張圖很亂沒關係,那是倉庫;你不該盯著毛球看,而是用查詢從裡面拉出一條乾淨的路徑/鄰居。這正好證明「把整張圖倒給 LLM」在小圖看似沒事、大圖就爆——所以才需要 GraphRAG 的子圖檢索。本地 LLM 的選型與量測,我在 Token 砍 43% 那篇 也有相關心得。

8. 一個下午學到的核心

  • RAG 怎麼箝制 LLM:① system prompt 命令「只准用我給的內容」② 把答案來源塞進 context(真正的槓桿)③ 用 code 驗它的輸出。前兩層是軟的,真正的鎖是外面那層 code。
  • LLM 在流程裡的位置:只在「聽懂使用者打的字」和「用你的資料回答」兩個邊緣;執行/授權/卡控全是確定性後端。
  • RAG vs GraphRAG:差在查詢有沒有「沿著線走」,不在用不用 graph DB。
  • GraphRAG 真正的難:不在走線(那是確定性的),在「把人話對應到起點 + 要走的線」——entity linking 和 domain scoping 才是會出包的地方。
  • 總原則:能用確定性 code 鎖的就鎖死(label/domain/別名/hop),模糊的才給 LLM 排序,危險的就回問人。

我從「RAG/LLM 是黑盒」到「能畫出每個操作背後跑哪些步驟、哪一步是 LLM、哪一步是授權卡控」,核心就一句:把 LLM 當成一個會講話但不可信的外部 API,放在系統的嘴巴耳朵位置;真正的手和印章,還是你後端那套確定性、可稽核的 code。想看整套 agent 系統的全貌,可以對照我之前的 一張圖看懂 AI Agent 系統

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *