Files
ewoooc/migrations/027_create_rag_query_log.sql
OoO 2f20d8d7ba
All checks were successful
CD Pipeline / deploy (push) Successful in 3m30s
db(p11): rag_query_log + learning_episodes — RAG 自主學習迴圈基礎
Operation Ollama-First v5.0 / Phase 11 RAG + 自主學習

migrations/027 — rag_query_log(每次 RAG 查詢的 audit log)
- query_text 4KB CHECK + 90 天保留
- VECTOR(1024) bge-m3 embedding (與 ai_insights 一致簽名)
- ivfflat lists=100 索引
- saved_call 欄位追蹤「成功攔截 LLM 呼叫」次數
- feedback_score 1-5(NULL=未反饋)
- 6 條 CHECK 含 chk_rag_saved_consistent

migrations/028 — learning_episodes(蒸餾池 → ai_insights 前哨)
- 8 狀態機:pending/approved/awaiting_review/rejected_*4/expired
- weight 0-1(>=0.8 觸發 PromotionGate Stage 4 人工驗收)
- 9 條 CHECK 含 chk_le_approved_consistent / chk_le_review_consistent
- partial index idx_le_status WHERE in (pending, awaiting_review)
- distilled_text 16KB 上限

docs/phase11_db_design — 設計文檔
- 6 大決策(兩表分離 / ivfflat / partial index / 軟連結 / 90天保留 / 應用層白名單)
- 6 大風險評估(R1 PII / R2 蒸餾失誤 / R3 ivfflat 退化 / R4 dangling FK / R5/R6 trade-off)
- Phase 11 上線後驗收 SQL(EXPLAIN ANALYZE)

PromotionGate 4 階段(v5.0 護欄 #1, ADR-033):
  Stage 1: quality_score >= 0.7
  Stage 2: 無幻覺檢測(規則引擎,零 LLM)
  Stage 3: 與既有 insight 相似度 < 0.95
  Stage 4: weight >= 0.8 必經 Telegram 👍/👎(24h 無回應 → expired)

A4 fullstack-engineer 同時在寫 services/rag_service.py + learning_pipeline.py,
service 完成後一起部署啟用。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:39:47 +08:00

127 lines
6.2 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =============================================================================
-- Migration 027: rag_query_log — RAG 查詢遙測 (audit log)
-- Operation Ollama-First v5.0 — Phase 11
-- 日期: 2026-05-03 台北
-- 對應戰役: ADR-029Hermes-First 雙塔)+ Phase 11 RAG 自主學習迴圈
-- =============================================================================
-- 說明:
-- 每次 RAG 召回hermes_qa / openclaw_qa / etc.寫一筆append-only。
-- 核心指標:
-- - hit_count : top_k 召回實際命中數threshold 過濾後)
-- - saved_call : 命中且最終未升級到 LLM => 真實節省成本
-- - feedback_score : Telegram 👍/👎 後填回NULL = 尚未反饋)
-- 與 ai_calls / mcp_calls 透過 request_id 串鏈,跨表 trace 同一邏輯請求。
--
-- 設計決策:
-- 1. embedding 與 ai_insights 同維度 1024 (bge-m3),可跨表計 cosine
-- 2. ivfflat lists=100 對齊既有風格;資料量達 1M 後依 sqrt(N) 重建
-- 009 ai_insights 用 HNSW但本表寫入頻繁 + 不需即時最近鄰 query
-- 採 ivfflat 寫入便宜weekly 排程 REINDEX 即可,詳見 design doc
-- 3. used_results BIGINT[] 紀錄命中的 ai_insights.id方便事後召回率分析
-- (不加 FKai_insights 可能 archive避免 cascade
-- 4. query_text 限 4KBquery_text 可能含 PII用戶問題
-- 90 天保留 + 後續 PromotionGate 過濾後才允許進 ai_insights
-- 5. caller 與 ai_calls.caller 共白名單(不重複定義 CHECK避免雙寫漂移
-- 由 application logger 端強制DB 端僅檢長度)
--
-- 回滾腳本(緊急用):
-- DROP INDEX IF EXISTS idx_rag_query_log_embedding;
-- DROP INDEX IF EXISTS idx_rag_query_log_request_id;
-- DROP INDEX IF EXISTS idx_rag_query_log_caller;
-- DROP INDEX IF EXISTS idx_rag_query_log_queried_at;
-- DROP TABLE IF EXISTS rag_query_log;
-- =============================================================================
CREATE TABLE IF NOT EXISTS rag_query_log (
id BIGSERIAL PRIMARY KEY,
queried_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- 與 ai_calls.caller 同一張白名單hermes_qa / openclaw_qa / ...
caller VARCHAR(64) NOT NULL,
-- 用戶查詢PII 風險,限 4KB不 normalize 以保留原始查詢樣貌)
query_text TEXT NOT NULL,
-- bge-m3 embedding1024 維,與 ai_insights.embedding 同源;可 NULL = embedding 失敗仍記錄此次嘗試)
query_embedding VECTOR(1024),
-- 召回參數
top_k INTEGER NOT NULL DEFAULT 5,
threshold NUMERIC(4,3) NOT NULL DEFAULT 0.85,
-- 召回結果
hit_count INTEGER NOT NULL DEFAULT 0,
used_results BIGINT[], -- 命中的 ai_insights.id 陣列(軟連結,不加 FK
-- 是否成功避免 LLM 呼叫(核心成本指標)
saved_call BOOLEAN NOT NULL DEFAULT FALSE,
-- Telegram 👍/👎 反饋1-5NULL = 未反饋)
feedback_score INTEGER,
-- 與 ai_calls.request_id 串鏈
request_id VARCHAR(64),
-- ─────── 護欄 (對齊 critic-A11 風格) ───────
CONSTRAINT chk_rag_threshold CHECK (
threshold BETWEEN 0 AND 1
),
CONSTRAINT chk_rag_top_k CHECK (
top_k BETWEEN 1 AND 50
),
CONSTRAINT chk_rag_hit_count CHECK (
hit_count >= 0 AND hit_count <= top_k
),
CONSTRAINT chk_rag_query_size CHECK (
octet_length(query_text) <= 4096
),
CONSTRAINT chk_rag_feedback CHECK (
feedback_score IS NULL OR feedback_score BETWEEN 1 AND 5
),
-- saved_call=TRUE 必須有命中hit_count > 0才合理
CONSTRAINT chk_rag_saved_consistent CHECK (
(saved_call = FALSE) OR (hit_count > 0)
)
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 索引設計
-- ─────────────────────────────────────────────────────────────────────────────
-- (1) 時間範圍掃描(日報 / 命中率報表)
CREATE INDEX IF NOT EXISTS idx_rag_query_log_queried_at
ON rag_query_log (queried_at DESC);
-- (2) caller 分布(哪個入口 RAG 命中率高)
CREATE INDEX IF NOT EXISTS idx_rag_query_log_caller
ON rag_query_log (caller, queried_at DESC);
-- (3) request_id 串鏈部分索引sparse 不全建)
CREATE INDEX IF NOT EXISTS idx_rag_query_log_request_id
ON rag_query_log (request_id)
WHERE request_id IS NOT NULL;
-- (4) pgvector ivfflatcosine similarity只索引非 NULL embedding
-- 注意: ivfflat 須先有資料才能正確訓練 lists空表建索引會 fallback exact scan
-- Phase 11 灌入首批查詢後若效能退化REINDEX CONCURRENTLY 重訓
CREATE INDEX IF NOT EXISTS idx_rag_query_log_embedding
ON rag_query_log
USING ivfflat (query_embedding vector_cosine_ops)
WITH (lists = 100)
WHERE query_embedding IS NOT NULL;
-- ─────────────────────────────────────────────────────────────────────────────
-- 權限
-- ─────────────────────────────────────────────────────────────────────────────
GRANT ALL PRIVILEGES ON rag_query_log TO momo;
GRANT USAGE, SELECT ON SEQUENCE rag_query_log_id_seq TO momo;
-- 註: 90 天保留由 scheduler 任務執行(與 ai_calls 對齊):
-- DELETE FROM rag_query_log WHERE queried_at < NOW() - INTERVAL '90 days';
-- 建議 03:30 跑ai_calls 03:00 之後),避免 IO 尖峰
DO $$
BEGIN
RAISE NOTICE 'Migration 027 done: rag_query_log + 4 indexes (ivfflat 1024d) (Operation Ollama-First v5.0 P11)';
END $$;