feat(ws6): Hermes observability — latency logging + dispatch audit table
Some checks failed
run-migration / migrate (push) Failing after 16s
CD Pipeline / build-and-deploy (push) Has been cancelled

- 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:
Your Name
2026-04-25 02:09:29 +08:00
parent 834a65c833
commit 00443370ba
2 changed files with 41 additions and 7 deletions

View 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 監控 + 幻覺追蹤)';

View File

@@ -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]