feat(ws6): Hermes observability — latency logging + dispatch audit table
- nl_gateway.py: time.monotonic() 測量 SDK call 耗時 hermes_nl_dispatch log 加 latency_ms + success 欄位 - migrations/adr094_hermes_dispatch_log.sql hermes_dispatch_log(bigserial + chat_id/user_id/agent/latency_ms/success) 已部署至 prod awoooi_prod ADR-094 P95 latency 監控 + 幻覺追蹤用 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
26
apps/api/migrations/adr094_hermes_dispatch_log.sql
Normal file
26
apps/api/migrations/adr094_hermes_dispatch_log.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- ADR-094: Hermes NL Dispatch Audit Log
|
||||
-- 每次 @mention 觸發 → 記錄派發決策供 P95 latency 監控與幻覺追蹤
|
||||
-- 2026-04-25 ogt + Claude Sonnet 4.6
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hermes_dispatch_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
chat_id VARCHAR(32) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
username VARCHAR(100),
|
||||
agent_name VARCHAR(64) NOT NULL,
|
||||
input_preview VARCHAR(200), -- 前 200 字,不存完整輸入(隱私)
|
||||
latency_ms INTEGER,
|
||||
success BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
error_type VARCHAR(64),
|
||||
budget_usd NUMERIC(8, 5)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_hermes_dispatch_created ON hermes_dispatch_log(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_hermes_dispatch_agent ON hermes_dispatch_log(agent_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_hermes_dispatch_user ON hermes_dispatch_log(user_id);
|
||||
|
||||
GRANT SELECT, INSERT ON hermes_dispatch_log TO awoooi;
|
||||
GRANT USAGE, SELECT ON SEQUENCE hermes_dispatch_log_id_seq TO awoooi;
|
||||
|
||||
COMMENT ON TABLE hermes_dispatch_log IS 'ADR-094: Hermes NL 派發審計日誌(P95 latency 監控 + 幻覺追蹤)';
|
||||
@@ -6,6 +6,7 @@ Layer 1 意圖路由(關鍵字正則)→ Claude Agent SDK 呼叫 → Telegra
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import re
|
||||
import time
|
||||
|
||||
import structlog
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||||
@@ -93,15 +94,10 @@ async def process_nl_message(
|
||||
agent_name = DEFAULT_AGENT
|
||||
system_prompt = get_agent_system_prompt(agent_name) or ""
|
||||
|
||||
logger.info(
|
||||
"hermes_nl_dispatch",
|
||||
agent=agent_name,
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
username=username,
|
||||
)
|
||||
t0 = time.monotonic()
|
||||
|
||||
# 呼叫 Claude Agent SDK
|
||||
success = False
|
||||
try:
|
||||
options = ClaudeAgentOptions(
|
||||
system_prompt=system_prompt,
|
||||
@@ -117,6 +113,7 @@ async def process_nl_message(
|
||||
|
||||
if not result_text:
|
||||
result_text = "_Agent 回應為空,請稍後再試。_"
|
||||
success = True
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
@@ -127,6 +124,17 @@ async def process_nl_message(
|
||||
)
|
||||
result_text = f"_Hermes 暫時無法連線({type(exc).__name__}),請稍後再試。_"
|
||||
|
||||
latency_ms = int((time.monotonic() - t0) * 1000)
|
||||
logger.info(
|
||||
"hermes_nl_dispatch",
|
||||
agent=agent_name,
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
username=username,
|
||||
latency_ms=latency_ms,
|
||||
success=success,
|
||||
)
|
||||
|
||||
header = format_response_header(agent_name)
|
||||
# Telegram 訊息上限 4096 字元,超過截斷
|
||||
body = result_text[:3800]
|
||||
|
||||
Reference in New Issue
Block a user