iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
自我挑戰組

和AI學習gcp - 建立對話代理系列 第 12

Day12 :Metadata 分類設計

  • 分享至 

  • xImage
  •  

背景:為什麼需要 Metadata?

目前你的查詢流程是純向量搜尋:

問題 → embedding → 找最近的 chunks

問題是:如果用戶問「前端要怎麼防 XSS?」,向量搜尋可能回傳後端相關的 chunk。

加入 metadata 後可以做 預過濾(pre-filter)

-- 先過濾 stack=frontend,再做向量相似度排序
SELECT * FROM chunks
WHERE stack = 'frontend'
ORDER BY embedding <=> $1
LIMIT 5;

這是 RAG 系統中最重要的優化模式之一(D20 會實作完整版,D12 先把資料打好標籤)。


重點 1:Schema 設計 — chunks 表加欄位

目前你的 chunks 表只有 chunk_text,沒有 metadata。需要在 init_db() 裡加:

ALTER TABLE chunks ADD COLUMN IF NOT EXISTS category TEXT;     -- A01~A10
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS stack TEXT;        -- frontend / backend / both

documents 表已有 category 欄位(D11 你已做好),但 chunks 要繼承下來,每個 chunk 都要帶著它父 document 的 metadata,這樣過濾才有意義


重點 2:Category 標籤 — 從 crawler 資料直接取得

你的 main.py 已經爬出 category: "A01" 這個欄位了,ingestion 也在 upsert_document() 裡存入 documents.category。

D12 要做的是:在 upsert_chunk() 時,把 document 的 category 一起寫入 chunk。這個部分相對簡單,主要是學習「父子資料繼承」的設計概念。


重點 3:Stack 標籤 — Rule-based 自動分類

這是 D12 最有趣的地方。你需要設計一個函數,根據 chunk 的文字內容自動判斷是 frontend / backend / both

方法:關鍵字規則(不用 LLM,快又便宜)

FRONTEND_KEYWORDS = ["javascript", "browser", "dom", "html", "react", "csrf token", "csp", "cookie", "xss"]
BACKEND_KEYWORDS  = ["server", "api", "sql", "database", "injection", "authentication", "session", "jwt", "python", "java"]

def classify_stack(text: str) -> str:
    t = text.lower()
    is_fe = any(kw in t for kw in FRONTEND_KEYWORDS)
    is_be = any(kw in t for kw in BACKEND_KEYWORDS)
    if is_fe and is_be: return "both"
    if is_fe:           return "frontend"
    if is_be:           return "backend"
    return "both"  # 預設:兩者都適用

學習點:Rule-based vs LLM-based 的取捨

  • Rule-based:快、可控、免費,但維護關鍵字清單有成本
  • LLM-based:精準但每次 embed 都多一次 API 呼叫,成本高
  • POC 階段用 rule-based 完全合理

重點 4:OWASP 特定的分類邏輯

你的資料來源是 A01–A10,可以直接用 category 建立對應表增加語意:

Category 主要 Stack
A01 Broken Access Control both
A02 Cryptographic Failures both
A03 Injection (SQL/XSS) both
A07 XSS frontend
A08 Insecure Deserialization backend

這讓你認識到:一個知識庫的品質取決於資料打標的細緻度,不只是 embedding 好不好。


重點 5:幂等性 (Idempotency) 不能破壞

你現在的 upsert_chunk() 用 chunk_hash 做 ON CONFLICT DO NOTHING,這設計很好。D12 加 metadata 時要注意:如果 hash 相同但 stack 標籤邏輯改了,舊資料不會自動更新

解法選項:

  1. 改變 hash 計算方式(把 metadata 一起 hash)
  2. 用 ON CONFLICT DO UPDATE SET stack = EXCLUDED.stack
  3. 加一個 make reindex-force 指令,清除後重建

D12 完成後的效果

你的 chunks 表會長這樣:

chunk_id document_id chunk_text category stack
1 1 "Use parameterized queries..." A03 backend
2 1 "Validate all input on client side..." A03 frontend
3 5 "Ensure HTTPS is enforced..." A02 both

今天建議的工作範圍

  1. 修改 main.py:
    • init_db() 加 category / stack 欄位到 chunks 表
    • 新增 classify_stack(text) 函數
    • upsert_chunk() 加入這兩個參數
  2. 用現有的 crawler/data/raw/*.json local 資料跑 make pipeline-local 驗證

不需要修改 crawler,因為 category 資料已在 raw JSON 裡了。

本日實作

ingestion/main.py 修改建立chunks table時加上category和stack欄位

之前就有定義category了(A1 - A10)

主要的更新是在新增chunk時透過關鍵字判斷stack是 前端或後端 或兩者都是

FRONTEND_KEYWORDS = [
    "javascript", "browser", "dom", "html", "react", "angular", "vue",
    "csrf token", "csp", "content security policy", "cookie", "xss",
    "client-side", "client side", "front-end", "frontend",
]
BACKEND_KEYWORDS = [
    "server", "api", "sql", "database", "injection", "authentication",
    "session", "jwt", "python", "java", "node", "back-end", "backend",
    "query", "parameterized", "stored procedure", "orm",
]

def classify_stack(text: str) -> str:
    t = text.lower()
    is_fe = any(kw in t for kw in FRONTEND_KEYWORDS)
    is_be = any(kw in t for kw in BACKEND_KEYWORDS)
    if is_fe and is_be:
        return "both"
    if is_fe:
        return "frontend"
    if is_be:
        return "backend"
    return "both"  # 預設:無明確關鍵字時視為兩者皆適用

主要更新commit:

因為向量化的job有更新 需要取代舊有的image

gcloud builds submit ingestion --tag gcr.io/chat-bot-484615/bank-vectorize:latest --project=chat-bot-484615

驗證 - chunks table有新增category 和 stack欄位

gcp去到 cloud sql 選擇建立的instance 以我的專案來說是 'bank-ai-db-instance’

從左側欄選擇 cloud sql studio

https://ithelp.ithome.com.tw/upload/images/20260812/201543596gzT6r6RU2.png

會經過user, password詢問 以專案的設定來說可以參考 terraform state裡的

DB_USER, DB_PASSWORD

登入後選擇可以輸入query的介面

使用query 查詢chunks的欄位

SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'chunks'
ORDER BY ordinal_position;

https://ithelp.ithome.com.tw/upload/images/20260812/20154359FHFH8ff01H.png

可以看到category, stack欄位出現了

這裡使用gcp的cloud sql studio是因為 cloud sql是private ip

無法直接透過本地terminal的cloud sdk操作 所以進入gcp裡使用studio介面

額外心得

今天在分類知識時是透過keyword, 而不用經過LLM判斷, 會比較省費用, 不過假如內容是動態的, 那可能keyword判斷效果會不如LLM判斷

ps. 如果忘記先更新job檔而沒看到新增欄位 也可以在studio用query刪除table

更新job後

然後在本地再跑一次 make reindex 重建table


上一篇
Day11 Data Ingestion Pipeline
下一篇
Day13: Artifact Registry 遷移
系列文
和AI學習gcp - 建立對話代理14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言