migrations 024/025/026 — 統一 LLM 遙測 + 預算告警 + RAG 一致性護欄 - 024: ai_calls 表 + 5 索引 + 6 CHECK constraint(H1/H2/M3/L3) - 025: mcp_calls + ai_call_budgets + 10 種子預算(含 ollama_secondary) - 026: ai_insights.embedding_signature + pgcrypto + CONCURRENTLY index A11 critic 三輪審查記錄完整保留: - Phase 1 schema review: 2 BLOCKER + 4 HIGH + 6 MEDIUM 全處理 - Phase 1 final sign-off: 0 BLOCKER + 2 HIGH + 4 MEDIUM - Phase 6 ADR review: 5 BLOCKER + 6 HIGH 全修 Operation Ollama-First v5.0 / Phase 0+1+6 護欄 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
205 lines
9.5 KiB
SQL
205 lines
9.5 KiB
SQL
-- =============================================================================
|
||
-- Migration 025: mcp_calls + ai_call_budgets
|
||
-- Operation Ollama-First v5.0 — Phase 1 (Phase 10 MCP / Phase 9 預算告警 預備)
|
||
-- 日期: 2026-05-03 台北
|
||
-- =============================================================================
|
||
-- 說明:
|
||
-- mcp_calls — Phase 10 引入 5 個 MCP server 後的遙測表,Schema 先到位。
|
||
-- 與 ai_calls 分表,因 MCP 沒有 token 概念、計費邏輯不同。
|
||
-- ai_call_budgets — Phase 9 預算告警表;種子資料即立即可用。
|
||
--
|
||
-- 設計決策:
|
||
-- 1. mcp_calls.insight_id 不加 FK:避免 cascade(Phase 11 ai_insights 會頻繁 archive)
|
||
-- 改用「軟連結」+ 應用層 join,保留可被 NULL 化的彈性。
|
||
-- 2. ai_call_budgets.provider NULL = 全供應商總額(UNIQUE constraint 用 (period, provider)
|
||
-- 若 NULL 行為不一致需保護,由應用層強制單例)
|
||
-- 註: PostgreSQL 預設 NULL != NULL,所以同 period 多筆 provider=NULL 會通過 UNIQUE,
|
||
-- 需應用層自律或改用部分索引(見下方)。
|
||
--
|
||
-- 回滾腳本:
|
||
-- DROP INDEX IF EXISTS idx_mcp_calls_called_at;
|
||
-- DROP INDEX IF EXISTS idx_mcp_calls_caller_called_at;
|
||
-- DROP INDEX IF EXISTS idx_mcp_calls_server_tool;
|
||
-- DROP INDEX IF EXISTS idx_mcp_calls_status_called_at;
|
||
-- DROP INDEX IF EXISTS uq_ai_call_budgets_period_null_provider;
|
||
-- DROP TABLE IF EXISTS mcp_calls;
|
||
-- DROP TABLE IF EXISTS ai_call_budgets;
|
||
-- =============================================================================
|
||
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
-- mcp_calls — MCP Server 呼叫遙測(Phase 10 預備)
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
CREATE TABLE IF NOT EXISTS mcp_calls (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
called_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
-- 與 ai_calls.caller 同一張白名單,便於跨表 trace
|
||
caller VARCHAR(64) NOT NULL,
|
||
|
||
-- omnisearch / firecrawl / postgres / playwright / filesystem / git
|
||
server VARCHAR(64) NOT NULL,
|
||
|
||
-- search / scrape / query / read_file / git_log / ...
|
||
tool VARCHAR(128) NOT NULL,
|
||
|
||
input_args JSONB,
|
||
output_size INTEGER, -- bytes,異常巨大可警示
|
||
duration_ms INTEGER,
|
||
-- M1: NOT NULL 對齊 ai_calls
|
||
status VARCHAR(16) NOT NULL,
|
||
error TEXT,
|
||
cost_usd NUMERIC(10,6) NOT NULL DEFAULT 0,
|
||
cache_hit BOOLEAN NOT NULL DEFAULT FALSE,
|
||
|
||
-- M6: 跨 ai_calls/mcp_calls 串鏈用(Phase 10 後 LLM→MCP→LLM 鏈不可斷)
|
||
request_id VARCHAR(64),
|
||
|
||
-- 軟連結:若 MCP 結果被 embed 寫入 ai_insights,記錄 insight_id 但不加 FK
|
||
insight_id BIGINT,
|
||
|
||
-- ─────── critic-A11 修補:白名單 + PII/膨脹護欄 ───────
|
||
-- M1: status 白名單
|
||
CONSTRAINT chk_mcp_calls_status CHECK (
|
||
status IN ('ok','error','timeout','rate_limited','cache_only')
|
||
),
|
||
-- L3: duration 範圍
|
||
CONSTRAINT chk_mcp_calls_duration_range CHECK (
|
||
duration_ms IS NULL OR (duration_ms >= 0 AND duration_ms <= 600000)
|
||
),
|
||
-- H2: input_args / error 大小護欄(postgres-mcp 可能含 SQL,含 PII 風險)
|
||
CONSTRAINT chk_mcp_calls_args_size CHECK (
|
||
input_args IS NULL OR octet_length(input_args::text) <= 16384
|
||
),
|
||
CONSTRAINT chk_mcp_calls_error_size CHECK (
|
||
error IS NULL OR octet_length(error) <= 4096
|
||
)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_calls_called_at
|
||
ON mcp_calls (called_at DESC);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_calls_caller_called_at
|
||
ON mcp_calls (caller, called_at DESC);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_calls_server_tool
|
||
ON mcp_calls (server, tool, called_at DESC);
|
||
|
||
-- M5: 異常監控 partial 精確列舉
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_calls_status_called_at
|
||
ON mcp_calls (status, called_at DESC)
|
||
WHERE status IN ('error','timeout','rate_limited');
|
||
|
||
-- M6: request_id 串鏈(部分索引,sparse 不全建)
|
||
CREATE INDEX IF NOT EXISTS idx_mcp_calls_request_id
|
||
ON mcp_calls (request_id)
|
||
WHERE request_id IS NOT NULL;
|
||
|
||
GRANT ALL PRIVILEGES ON mcp_calls TO momo;
|
||
GRANT USAGE, SELECT ON SEQUENCE mcp_calls_id_seq TO momo;
|
||
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
-- ai_call_budgets — 預算與告警閾值(Phase 9 預算守門)
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
CREATE TABLE IF NOT EXISTS ai_call_budgets (
|
||
id SERIAL PRIMARY KEY,
|
||
period VARCHAR(16) NOT NULL, -- daily / weekly / monthly
|
||
provider VARCHAR(32), -- NULL = 全供應商總額
|
||
budget_usd NUMERIC(10,2) NOT NULL,
|
||
alert_pct INTEGER NOT NULL DEFAULT 80, -- 達此百分比觸發 Telegram 告警
|
||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||
|
||
CONSTRAINT chk_ai_budget_period
|
||
CHECK (period IN ('daily', 'weekly', 'monthly')),
|
||
CONSTRAINT chk_ai_budget_alert_pct
|
||
CHECK (alert_pct BETWEEN 1 AND 100),
|
||
CONSTRAINT chk_ai_budget_amount
|
||
CHECK (budget_usd > 0)
|
||
);
|
||
|
||
-- 部分唯一索引:分別處理 provider IS NULL 與 NOT NULL,避免 NULL != NULL 漏洞
|
||
CREATE UNIQUE INDEX IF NOT EXISTS uq_ai_call_budgets_period_provider
|
||
ON ai_call_budgets (period, provider)
|
||
WHERE provider IS NOT NULL;
|
||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS uq_ai_call_budgets_period_null_provider
|
||
ON ai_call_budgets (period)
|
||
WHERE provider IS NULL;
|
||
|
||
GRANT ALL PRIVILEGES ON ai_call_budgets TO momo;
|
||
GRANT USAGE, SELECT ON SEQUENCE ai_call_budgets_id_seq TO momo;
|
||
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
-- 種子資料(戰役 v5.0 規格 + critic-A11 H3 補 nim/nim_via_elephant/ollama)
|
||
-- M2: ON CONFLICT 配 partial unique index 會炸;改用 WHERE NOT EXISTS 確保冪等
|
||
-- ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
-- 全供應商總額(period, provider=NULL)
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'daily', NULL, 1.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='daily' AND provider IS NULL
|
||
);
|
||
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'weekly', NULL, 5.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='weekly' AND provider IS NULL
|
||
);
|
||
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', NULL, 20.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider IS NULL
|
||
);
|
||
|
||
-- 個別供應商(含 H3 修補:補 nim / nim_via_elephant / ollama 雙線)
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'claude', 10.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='claude'
|
||
);
|
||
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'gemini', 8.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='gemini'
|
||
);
|
||
|
||
-- H3: NIM 兩條獨立計費鏈(NemoTron 配額 + ElephantAlpha 49B),各設預算
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'nim', 5.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='nim'
|
||
);
|
||
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'nim_via_elephant', 5.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='nim_via_elephant'
|
||
);
|
||
|
||
-- H3: OpenRouter(PPT deepseek-v3.2)
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'openrouter', 3.00, 80
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='openrouter'
|
||
);
|
||
|
||
-- Ollama 雙線(免費,但設極低預算 + alert=100% 統一告警邏輯,異常激增可警示)
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'gcp_ollama', 0.01, 100
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='gcp_ollama'
|
||
);
|
||
|
||
INSERT INTO ai_call_budgets (period, provider, budget_usd, alert_pct)
|
||
SELECT 'monthly', 'ollama_111', 0.01, 100
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM ai_call_budgets WHERE period='monthly' AND provider='ollama_111'
|
||
);
|
||
|
||
DO $$
|
||
BEGIN
|
||
RAISE NOTICE 'Migration 025 done: mcp_calls + ai_call_budgets + 10 seed budgets (Operation Ollama-First v5.0 P1, critic-A11 fixes B2/H1/H2/H3/M1/M2/M5/M6/L3 applied)';
|
||
END $$;
|