fix(awooop): cache heavy operator summaries
Some checks failed
CD Pipeline / tests (push) Successful in 1m28s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-01 09:20:18 +08:00
parent 0e30171858
commit 159f514f55
9 changed files with 381 additions and 30 deletions

View File

@@ -9,6 +9,7 @@ ADR-106AwoooP Agent Platform
from __future__ import annotations
import asyncio
import os
import re
import time
import uuid
@@ -59,6 +60,10 @@ from src.services.ollama_failover_manager import (
)
from src.services.ollama_health_monitor import HealthReport, HealthStatus
from src.services.operator_outcome import build_operator_outcome
from src.services.operator_summary_cache import (
get_cached_operator_summary,
store_operator_summary,
)
from src.services.run_state_machine import transition
logger = structlog.get_logger(__name__)
@@ -74,6 +79,9 @@ _MAX_STEP_SUMMARY_CHARS = 128
_AI_ROUTE_STATUS_SELECT_TIMEOUT_SECONDS = 12.0
_AI_ROUTE_STATUS_CONNECTIVITY_TIMEOUT_SECONDS = 2.5
_REMEDIATION_HISTORY_LIMIT = 20
_CALLBACK_REPLY_CACHE_TTL_SECONDS = int(
os.getenv("AWOOOP_CALLBACK_REPLY_CACHE_TTL_SECONDS", "20")
)
_INCIDENT_ID_RE = re.compile(r"\bINC-\d{8}-[A-Z0-9]{4,}\b")
_REMEDIATION_STATUS_FILTERS = {
"mcp_observed",
@@ -304,11 +312,13 @@ async def list_callback_replies(
incident_id: str | None,
page: int,
per_page: int,
refresh: bool = False,
) -> dict[str, Any]:
"""列出 Telegram detail/history callback reply evidence不改 runtime 狀態。"""
_validate_callback_reply_status_filter(callback_reply_status)
callback_action = _validate_callback_reply_action_filter(action)
_validate_incident_id_filter(incident_id)
normalized_project_id = project_id or "awoooi"
if callback_reply_status == "no_callback":
return {
@@ -318,6 +328,33 @@ async def list_callback_replies(
"per_page": per_page,
}
cache_key = {
"project_id": project_id or "__all__",
"callback_reply_status": callback_reply_status or "",
"action": callback_action or "",
"incident_id": incident_id or "",
"page": page,
"per_page": per_page,
}
if not refresh:
cached_response = get_cached_operator_summary(
"callback_replies",
cache_key,
ttl_seconds=_CALLBACK_REPLY_CACHE_TTL_SECONDS,
)
if cached_response is not None:
logger.info(
"operator_callback_replies_cache_hit",
project_id=normalized_project_id,
callback_reply_status=callback_reply_status,
action=callback_action,
incident_id=incident_id,
page=page,
per_page=per_page,
ttl_seconds=_CALLBACK_REPLY_CACHE_TTL_SECONDS,
)
return cached_response
where_clauses = [
"m.source_envelope ? 'callback_reply'",
]
@@ -399,14 +436,14 @@ async def list_callback_replies(
LIMIT :limit OFFSET :offset
""")
async with get_db_context(project_id or "awoooi") as db:
async with get_db_context(normalized_project_id) as db:
count_result = await db.execute(count_sql, params)
total = count_result.scalar_one()
rows_result = await db.execute(list_sql, params)
rows = list(rows_result.mappings().all())
audit_summary = await _fetch_callback_reply_audit_summary(
db,
project_id=project_id or "awoooi",
project_id=normalized_project_id,
)
items = [_callback_reply_event_item(row) for row in rows]
@@ -459,13 +496,31 @@ async def list_callback_replies(
km_completion_summary_cache[summary_cache_key] = km_summary
item["km_stale_completion_summary"] = km_summary
return {
response = {
"items": items,
"total": total,
"page": page,
"per_page": per_page,
"summary": audit_summary,
}
logger.info(
"operator_callback_replies_fetched",
project_id=normalized_project_id,
callback_reply_status=callback_reply_status,
action=callback_action,
incident_id=incident_id,
page=page,
per_page=per_page,
total=total,
cache_status="miss",
cache_ttl_seconds=_CALLBACK_REPLY_CACHE_TTL_SECONDS,
)
return store_operator_summary(
"callback_replies",
cache_key,
response,
ttl_seconds=_CALLBACK_REPLY_CACHE_TTL_SECONDS,
)
async def _fetch_callback_reply_audit_summary(