steven0226/tw-labor-law-rag-demo
繁體中文 Hybrid RAG 知識問答系統
English | 繁體中文

3 分鐘看懂專案: 技術審閱導覽 | 面試展示腳本 | 架構 | 證據重算 | 限制
以台灣 15 部勞動法規(13 部法律、2 部命令)為目標知識庫的檢索增強生成(RAG)問答系統:BM25 + BGE-M3 向量檢索以 RRF 融合,經 bge-reranker-v2-m3 重排序後生成附條文引用的答案,回答附上法規、條號、法規來源連結與修正/生效日期,查無依據時誠實拒答而非瞎掰。設計決策由 40 題正式評估、8 組消融實驗與 60 題可靠性壓力集檢驗——見 EVAL_REPORT.md。
正式評估摘要
structure-aware + Hybrid + reranker 在 40 題評估集中的 30 題可答子集,檢索 Hit@5 為 0.967、MRR@10 為 0.906。10 題不可答題最終全數拒答,其中 9/10 由 threshold 直接擋下且不呼叫 LLM,另 1 題由 LLM 判定條文不足;同時有 1/30 可答題在 LLM 層被誤拒(threshold 層誤拒為 0/30)。上述 retrieval、answerability 與 refusal 算術可從 committed privacy-reduced traces 完整離線重算。
實際作答的 29 題平均 faithfulness 4.90/5、relevancy 5.00/5 則屬 archived provider evidence:repository 可離線重新聚合已提交的 judge 數字,但不含完整生成答案、judge 理由或 provider response,因此不能從公開 evidence 重新產生或獨立複判這些評分。完整方法與限制見 EVAL_REPORT.md,去識別化逐題 trace 見 `eval/official/`,claim 到 evidence 的映射見 claim matrix。
v0.3.1 reliability stress evidence 另以 40 題可答、20 題不可答的長句/中英夾雜壓力集,對 2026-08-29 稽核的 15 部/884 條 snapshot 重建隔離索引。主設定 Hit@5 0.950、MRR@10 0.908;0.03 門檻直接誤拒 1/40、直接攔下不可答 17/20。既有 40 題正式集 guard 同時重現 Hit@5 0.967、MRR@10 0.906、門檻誤拒 0/30 與直接攔截 9/10。門檻掃描沒有 Pareto-better 候選,因此保留 0.03,不以新壓力集改寫 v0.1.0 正式模型品質指標。
Gemini gemini-3.5-flash-lite/OpenAI gpt-5.6-luna 的 US$5 硬上限 safety cross-check 已完成並 fail closed:兩家各五筆請求;Gemini refusal accuracy 0.8、citation success 1.0、estimated cost US$0.0022620;OpenAI refusal accuracy 1.0、citation success 1.0、estimated cost US$0.0026414。公開 evidence 僅含去識別化、嚴格 content-free 的十筆 trace、可重算的 metrics 與每家 US$5 預算 ledger;trace 不含 question/answer text、provider payload、憑證或原始 run artifacts。這是 safety cross-check,不取代 v0.1.0 formal evidence baseline 的正式模型品質指標。
Release evidence boundary
uv run python scripts/verify_release.py 不載入模型、不呼叫 provider、不啟動 Qdrant/Docker,會核對 40 題正式集、60 題壓力集、10 題 portfolio regression、8×40 ablation grid、Hit@5/MRR、0.03 threshold sweep、15 部/884 條 law/source 與逐條文 content-free snapshots、設定一致性、OGDL samples、official trace schema、provider complete contract、完整 publication inventory、secret/privacy scan、人工審閱 binary hashes 與 GitHub Action pins。Git 歷史稽核涵蓋 heads、tags、remotes 的所有可公開 commits;GitHub Actions 暫時產生、不可發布的 refs/remotes/pull/* 合成 merge refs 除外,本機 refs/archive/* recovery evidence 也會保留在 publication graph 之外。0.03 reranker threshold 不是通用 answerability classifier;壓力集已量測到 1/40 直接誤拒,因此只保留現值而不宣稱問題已消失。
v0.3.5 Portfolio readiness
本版把私有 BYOK 展示整理成 reviewer-first 體驗:首頁先說明可驗證能力與費用邊界,再引導受邀者選擇 Gemini/OpenAI、於遮罩欄位輸入自己的專用 Key,並以逐步狀態、引用來源與可展開 debug 證據呈現結果。Space 保持 private、免費 cpu-basic,不持有站長的 LLM Key,也不做跨 provider fallback。
新增 10 題完全離線、content-free 的示範回歸:6/6 可答題來源契約通過,10/10 路由與檢索階段決策契約通過,provider calls 為 0。另以法務部官方來源建立 15 部/884 條逐條文 SHA-256 baseline;人工 audit 會同時報告 law/source 欄位與新增、移除、變更條號,不建立排程或自動 writer。這些證據不取代既有 40 題 formal baseline、60 題 reliability suite 或 archived provider judgments。
v0.3.4 欠薪/立即離職檢索強化
只有同時命中「欠薪」與「勞工立即離職」兩組已審閱 cue 的問題,檢索管線才會補上《勞動基準法》第 14 條的固定法規詞。BM25、向量檢索與 reranker 看到擴充查詢;生成模型仍收到使用者原始問題。
本版沒有新增 provider 呼叫、調整 0.03 門檻、重建 Qdrant 或改寫歷史指標。v0.1.0 formal baseline 與 v0.3.1 reliability evidence 保持原證據版本;v0.3.4 的公開主張只涵蓋可由單元測試驗證的決定論式路由契約。
v0.3.3 新舊制資遣費檢索強化
這是 v0.3.3 source-only runtime and deployment release。當問題同時包含資遣、新制、舊制與計算/比較語意時,檢索管線會以決定論式 query expansion 補上「勞工退休金條例、勞動基準法、工作年資、平均工資、六個月」等法規檢索詞。擴充內容只送往 BM25、向量檢索與 reranker;生成模型仍收到使用者的原始問題,避免檢索輔助詞改寫使用者意圖。
這項擴充必須同時命中四組 cue 才會啟用,因此一般資遣、退休或單純制度差異問題不會被廣泛改寫。v0.1.0 正式模型品質基準、v0.3.1 reliability evidence 與 v0.3.2 provider safety cross-check 仍維持原來的證據版本;本版沒有用新的 provider 呼叫改寫歷史指標。
v0.3.2 provider safety cross-check:可靠性、來源與雙模型 runtime
這是 v0.3.2 source-only runtime and deployment release。公開 API/UI 預設使用 Gemini gemini-3.5-flash-lite,若伺服器同時設定 OpenAI,使用者可逐次請求選擇 gpt-5.6-luna。這些型號可分別由 server-side GEMINI_GENERATION_MODEL 與 OPENAI_GENERATION_MODEL 覆寫;對應 key 已設定時,LLM_PROVIDER=gemini 決定省略請求選擇時的預設 provider,否則 API 會改用另一個已設定的公開 provider;LLM_FALLBACK_ENABLED=true 才允許備援。GEMINI_API_KEY 與 OPENAI_API_KEY 只存在 API 伺服器環境,前端不接收、保存或顯示 key。
備援邊界是固定的:只有主 provider 發生連線、限流、5xx 服務或空回應等 operational failure 時,才會最多嘗試另一個已設定的公開 provider 一次。檢索階段拒答不會呼叫生成模型;模型依據條文拒答、provider 安全擋下或政策拒絕也不會 fallback。正式評估路徑仍直接固定單一 generator/judge provider,不使用 runtime fallback,避免路由變動改寫評估設定。
Streamlit 側邊欄的「回答模型」只顯示 API /models 回傳的已設定 Gemini/OpenAI;送出問題時會將選擇的 provider 一併傳給 /query。回應中 requested_provider 保留指定 provider,provider 與 model 是實際生成結果的 metadata,fallback_used/fallback_from 說明是否改走備援,generation_called=false 表示在檢索層已拒答。UI 會分開顯示指定與實際作答模型,並在改走備援時警示。Live provider smoke test 需要伺服器端本機 secrets,不屬公開 offline CI。
v0.1.0 的正式模型品質指標仍是歷史結果,由 release/manifest.json 所列 generator 與 judge 模型產生;本版沒有取代或重新審計這些數值。本版已在不呼叫 provider 的情況下,以 60 題壓力集與既有 40 題正式集 guard 重跑 retrieval 與 threshold 行為。
私有 BYOK Docker Space(邀請制)
Demo 狀態: private Space 正常運行;僅限擁有者與受邀審閱者,不公開列出入口。
私有展示模式採 BYOK(Bring Your Own Key):受邀者選擇 Gemini gemini-3.5-flash-lite 或 OpenAI gpt-5.6-luna,並在遮罩欄位輸入自己的專用 API Key。Key 只存在目前 Streamlit 工作階段、送往同容器 loopback FastAPI 的單次內部 header,以及該次請求建立的 provider client;不寫入檔案、聊天紀錄、共用設定或跨請求快取。Space 不設定站長的 GEMINI_API_KEY/OPENAI_API_KEY,也不做跨 provider fallback,因此受邀者不會消耗站長的模型 token 額度。
Space 只持有 Qdrant 兩個法規 collections 的唯讀 Key;建索引使用的短期 write/manage Key 於本機完成後立即撤銷。啟動時只讀 scroll payload,在記憶體重建 structure/fixed 兩份 BM25,不把私有 data/raw/ 或 storage/bm25_*.json 放入 image。預設每個展示工作階段 20 題、全域同時 2 題、單題 timeout 60 秒,最多保留 1,000 個未過期的匿名工作階段。Key 隔離、唯讀權限與免費 cpu-basic 已完成驗收;完整操作與 rollback 見 BYOK Hugging Face runbook。
人工更新 Qdrant 法規索引
雲端法規索引只接受有人值守的 blue-green 更新。先用 scripts/rebuild_qdrant_blue_green.py dry-run 驗證本機 official archives、normalized corpus 與 committed snapshot 完全一致;execute mode 另要求 temporary writer key 與重複 candidate 名稱,且只建立新 pair,不覆寫、重建或刪除正式 collections。完整指令、private cutover 與 rollback 見 BYOK Hugging Face runbook;安全邊界與失敗模型見 blue-green Qdrant maintenance design。
架構
flowchart TB
subgraph Ingestion["攝取管線"]
A["法規 JSON / Markdown / txt / PDF"] --> B["Loader + Cleaner"]
B --> C{"Chunking 策略"}
C -->|"structure-aware<br/>按條文切"| D1["Chunks"]
C -->|"fixed-size<br/>400字+overlap"| D2["Chunks"]
end
subgraph Indexing["索引"]
D1 & D2 --> E["BGE-M3 Embedder<br/>(+ SQLite 內容快取)"]
D1 & D2 --> F["jieba 斷詞"]
E --> G[("Qdrant<br/>向量索引")]
F --> H[("BM25 索引")]
end
subgraph Retrieval["檢索(每題)"]
Q["使用者問題"] --> G
Q --> H
G --> R1["向量 top-20"]
H --> R2["BM25 top-20"]
R1 & R2 --> RRF["RRF 融合<br/>(k=60)"]
RRF --> RR["bge-reranker-v2-m3<br/>rerank → top-5"]
end
subgraph Generation["生成"]
RR -->|"top score < 0.03"| Refuse1["拒答<br/>(不呼叫 LLM)"]
RR -->|"top score ≥ 0.03"| LLM["LLM<br/>(Anthropic/OpenAI/Gemini/Ollama)"]
LLM -->|"條文不足以回答"| Refuse2["拒答"]
LLM -->|"可回答"| Answer["答案 + [1][2] 引用來源"]
end
Answer & Refuse1 & Refuse2 --> API["FastAPI /query"] --> UI["Streamlit 聊天介面"]Quickstart
需求:Python 3.11、uv。有 NVIDIA GPU 可大幅加速 embedding/rerank,純 CPU 也能跑(較慢)。
# 1. 安裝依賴
uv sync
# 2. 設定環境變數(公開 API/UI 至少在伺服器端填 Gemini / OpenAI 一組 key)
cp .env.example .env
# 編輯 .env,填入對應 API key;不要將 .env 提交到 Git
# 3. 下載語料(全國法規資料庫官方開放資料,約 30MB,首次執行)
uv run python scripts/download_corpus.py
# 4. 建索引(向量 + BM25,兩種 chunking 策略各一份;有 GPU 約 1 分鐘)
uv run python scripts/build_index.py
# 5. 命令列問答(開發用,免啟動伺服器)
uv run python scripts/ask.py "加班費怎麼算?"
# 6. 或啟動 API + 前端
uv run python scripts/run_api.py & # http://localhost:8000/docs
uv run streamlit run ui/app.py # http://localhost:8501用 Docker(Qdrant server mode)
docker compose up -d qdrant
# 將 .env 的 QDRANT_MODE 改為 server,QDRANT_URL 保持 http://localhost:6333
uv run python scripts/build_index.py --strategy all # 對 Qdrant 服務建兩種索引
docker compose up --build api ui跑測試與評估
測試與 release verifier 不依賴 GPU、模型權重、Qdrant 或真實 LLM API;heavy components 皆延遲載入,unit tests 使用純邏輯、fixture、cache 或明確的 test double。GitHub Actions 會在 main push、v* tag push 與所有 pull request 執行。
uv run python scripts/verify_release.py # committed evidence 離線重算與公開邊界稽核
uv run ruff check . # locked lint gate
uv run pytest # 單元、正式產物、privacy 與 package 測試
uv build # sdist + wheel;驗證 runtime dictionary 有打包重新執行 eval/ablation.py 需要既有索引與本機模型;eval/run_e2e_eval.py 還需要 provider,不屬於公開離線 reviewer path。可公開、去識別化的正式指標與逐題 trace 已收錄在 `eval/official/`;eval/runs/ 保留原始本機執行結果,不進版控。完整 clean reviewer 步驟見 REVIEWER_GUIDE.md。
Demo 截圖
技術棧
每個選擇的理由與 tradeoff 見 DESIGN.md。
專案文件
- DESIGN.md — 技術選型理由與 tradeoff
- EVAL_REPORT.md — 評估數據、消融實驗、失敗案例分析
- eval/official/README.md — 可公開的正式評估產物與重現方式
- eval/dataset/README.md — 評估集 schema 與出題原則
- README.en.md — calibrated English portfolio summary
- docs/release/ — claim matrix、OGDL attribution、publication/privacy boundary 與 reviewer path
資料來源與授權
完整知識庫語料為 15 部台灣勞動法規,來自法務部資訊處在政府資料開放平臺發布的「中文法規法律資料檔下載」與「中文法規命令資料檔下載」,由 scripts/download_corpus.py 於執行時下載;完整 dump 與其餘 13 部 normalized corpus 不隨 repository 散布(見 .gitignore)。
Repository 有散布兩份小型 OGDL 命令樣本供 loader/chunking smoke test:data/sample/勞工請假規則.json 與 data/sample/勞動基準法施行細則.json。兩者來源為法務部資訊處「中文法規_命令資料檔下載」,依政府資料開放授權條款第 1 版可重製、散布與改作,前提是保留顯名聲明。完整 attribution、snapshot hashes 與再散布結論見 OGDL_ATTRIBUTION.md。
本 repository 的原創程式碼以 MIT License 釋出。兩份 samples 與執行時下載的法規語料仍適用其原始 OGDL 條款,不因本專案採 MIT 而重新授權;Python 套件與模型等第三方元件亦各自適用其原始授權。
公開範圍
這是 v0.3.5 source-only runtime and deployment release。正式模型品質指標沿用未變更的 v0.1.0 formal evidence baseline;本版新增 reviewer-first 私有 BYOK 介面、10 題離線 portfolio regression 與 15 部/884 條 content-free 逐條文 freshness baseline,但不把示範回歸寫成新的模型品質基準。v0.3.2 Gemini/OpenAI safety cross-check 仍是 archived provider evidence,兩家各五筆請求均在 US$5 硬上限內:Gemini refusal accuracy 0.8、citation success 1.0、estimated cost US$0.0022620;OpenAI refusal accuracy 1.0、citation success 1.0、estimated cost US$0.0026414。公開 trace 嚴格不含 question/answer text、provider payload 或憑證;此 cross-check 不取代正式模型品質基準。它是 evidence-backed software portfolio artifact,不是法律意見,也不是 production legal service。完整 corpus、模型權重、私有索引與 provider raw artifacts 仍不在本次 source release 範圍。
