fix(incidents): batch decision token lookup
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m5s
CD Pipeline / build-and-deploy (push) Successful in 3m20s
CD Pipeline / post-deploy-checks (push) Successful in 1m19s

This commit is contained in:
Your Name
2026-05-06 21:27:35 +08:00
parent 780a742110
commit edef1aa4c7
4 changed files with 60 additions and 1 deletions

View File

@@ -206,11 +206,14 @@ async def list_incidents(
responses = []
background_tasks = []
existing_tokens = await decision_manager._find_existing_tokens_for_incidents(
[incident.incident_id for incident in incidents]
)
for incident in incidents:
try:
# 只查已快取的決策 (不等待 AI立即返回)
existing = await decision_manager._find_existing_token(incident.incident_id)
existing = existing_tokens.get(incident.incident_id)
if existing:
decision_info = DecisionInfo(
token=existing.token,

View File

@@ -2953,6 +2953,52 @@ class DecisionManager:
return None
async def _find_existing_tokens_for_incidents(
self,
incident_ids: list[str],
) -> dict[str, DecisionToken]:
"""
批次查找現有決策令牌。
2026-05-06 Codex: GET /api/v1/incidents 是前端輪詢路徑,不可對每個
incident 都掃描一次 decision:*。這裡只掃一次 Redis keyspace避免
200+ incidents 時形成 O(N×M) 延遲與前端控制台卡死。
"""
wanted = set(incident_ids)
if not wanted:
return {}
import json
redis_client = get_redis()
found: dict[str, DecisionToken] = {}
cursor = 0
while True:
cursor, keys = await redis_client.scan(
cursor=cursor,
match=f"{DECISION_TOKEN_PREFIX}*",
count=500,
)
for key in keys:
try:
data = await redis_client.get(key)
if not data:
continue
token_data = json.loads(data)
incident_id = token_data.get("incident_id")
if incident_id in wanted and incident_id not in found:
found[incident_id] = DecisionToken.from_dict(token_data)
if len(found) == len(wanted):
return found
except Exception:
continue
if cursor == 0:
break
return found
async def _persist_decision_to_db(
self, incident_id: str, proposal_data: dict
) -> None:

View File

@@ -30,8 +30,15 @@ class _IncidentService:
class _DecisionManager:
def __init__(self) -> None:
self.created = 0
self.single_token_lookups = 0
self.batch_token_lookups = 0
async def _find_existing_tokens_for_incidents(self, incident_ids: list[str]):
self.batch_token_lookups += 1
return {}
async def _find_existing_token(self, incident_id: str):
self.single_token_lookups += 1
return None
async def get_or_create_decision(self, *args, **kwargs):
@@ -51,3 +58,5 @@ async def test_list_incidents_does_not_trigger_ai_decision_by_default(monkeypatc
assert result.incidents[0].incident_id == "INC-20260506-PURE01"
assert result.incidents[0].decision is None
assert decision_manager.created == 0
assert decision_manager.batch_token_lookups == 1
assert decision_manager.single_token_lookups == 0

View File

@@ -10,6 +10,7 @@
- `GET /api/v1/incidents` 新增 `generate_missing_decisions=false` 預設參數。
- 預設只讀取既有 decision token缺少 token 時回傳 `decision=null`,不再背景觸發 Ollama / OpenClaw / Gemini。
- 若維運人員明確需要舊行為,可用 `generate_missing_decisions=true` 觸發背景生成;正式修復建議仍應走 `POST /api/v1/incidents/{incident_id}/proposal` 或 AwoooP Operator Run。
- `DecisionManager` 新增批次 token 查詢;列表路徑只掃一次 Redis `decision:*`,避免 200+ incidents 時逐筆掃描造成 O(N×M) 延遲。
- 新增 regression test鎖定列表查詢預設不會呼叫 `get_or_create_decision()`
**驗證**