fix(decision-manager): AI 分析結果寫入 incidents.decision_chain (DB 長期保存)
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
修復 Gap: decision token 只有 Redis TTL 12min,AI 診斷歷史永久丟失 - 新增 _persist_decision_to_db() method - get_or_create_decision() 完成後 fire-and-forget 寫入 PG - 寫入: ts / confidence / risk_level / provider / source / diagnosis[:200] - try/except 吞錯不影響主流程,warning log 追蹤 DB/Cache 分層: PG (長期): incidents.decision_chain (歷史) + outcomes + KM entries Redis (短期): decision token dedup + working memory + playbook cache 2026-04-16 Claude Sonnet 4.6 Asia/Taipei Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1271,6 +1271,14 @@ class DecisionManager:
|
||||
# 4. 儲存最終結果
|
||||
await self._save_token(token)
|
||||
|
||||
# 4b. 2026-04-16 Claude Sonnet 4.6: 將 AI 分析結果寫入 incidents.decision_chain (DB 長期保存)
|
||||
# 修復 Gap: decision token 只有 Redis TTL ~12min,AI 分析結果歷史永久丟失
|
||||
# 寫入: diagnosis / confidence / risk_level / provider / source
|
||||
if token.proposal_data:
|
||||
_fire_and_forget(
|
||||
self._persist_decision_to_db(incident.incident_id, token.proposal_data)
|
||||
)
|
||||
|
||||
# 5. ADR-030 Phase 4: 自動執行判斷
|
||||
if token.state == DecisionState.READY and token.proposal_data:
|
||||
# 評估是否可以自動執行
|
||||
@@ -2290,6 +2298,68 @@ class DecisionManager:
|
||||
|
||||
return None
|
||||
|
||||
async def _persist_decision_to_db(
|
||||
self, incident_id: str, proposal_data: dict
|
||||
) -> None:
|
||||
"""
|
||||
2026-04-16 Claude Sonnet 4.6: 將 AI 分析結果寫入 incidents.decision_chain (PostgreSQL)
|
||||
修復 Gap: decision token 只有 Redis TTL,AI 分析歷史永久丟失
|
||||
|
||||
寫入格式: JSON array,每次分析追加一條記錄
|
||||
欄位: ts / confidence / risk_level / provider / source / diagnosis (前 200 字)
|
||||
"""
|
||||
try:
|
||||
from src.db.base import get_db_context
|
||||
from sqlalchemy import text as _sa_text
|
||||
import json as _json
|
||||
from src.utils.timezone import now_taipei
|
||||
|
||||
entry = {
|
||||
"ts": now_taipei().isoformat(),
|
||||
"confidence": proposal_data.get("confidence", 0.0),
|
||||
"risk_level": proposal_data.get("risk_level", "unknown"),
|
||||
"provider": proposal_data.get("provider", proposal_data.get("source", "")),
|
||||
"source": proposal_data.get("source", ""),
|
||||
"diagnosis": str(proposal_data.get("diagnosis", proposal_data.get("description", "")))[:200],
|
||||
}
|
||||
|
||||
async with get_db_context() as db:
|
||||
# 讀取現有 decision_chain (可能為 null 或 json array)
|
||||
r = await db.execute(
|
||||
_sa_text(
|
||||
"SELECT decision_chain FROM incidents WHERE incident_id = :iid"
|
||||
),
|
||||
{"iid": incident_id},
|
||||
)
|
||||
row = r.fetchone()
|
||||
if row is None:
|
||||
return
|
||||
existing = row[0] or []
|
||||
if isinstance(existing, str):
|
||||
try:
|
||||
existing = _json.loads(existing)
|
||||
except Exception:
|
||||
existing = []
|
||||
if not isinstance(existing, list):
|
||||
existing = []
|
||||
existing.append(entry)
|
||||
|
||||
await db.execute(
|
||||
_sa_text(
|
||||
"UPDATE incidents SET decision_chain = :dc::json WHERE incident_id = :iid"
|
||||
),
|
||||
{"dc": _json.dumps(existing), "iid": incident_id},
|
||||
)
|
||||
await db.commit()
|
||||
logger.debug(
|
||||
"decision_chain_persisted",
|
||||
incident_id=incident_id,
|
||||
confidence=entry["confidence"],
|
||||
provider=entry["provider"],
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("decision_chain_persist_failed", incident_id=incident_id, error=str(e))
|
||||
|
||||
async def _save_token(self, token: DecisionToken) -> None:
|
||||
"""儲存決策令牌到 Redis"""
|
||||
import json
|
||||
|
||||
Reference in New Issue
Block a user