67 lines
2.6 KiB
Python
67 lines
2.6 KiB
Python
"""
|
|
Records the engine's model decisions in the backend's decision log.
|
|
|
|
Until Phase 5 of the plan (krow_talent_app/docs/agent-platform-plan.md) the
|
|
stall and assignment-failure decisions existed only as log lines, so the
|
|
console's Insights tab could not show them. Each decision is now sent to
|
|
``POST /api/v1/internal/agent-decisions`` — the same log routemate's
|
|
assignment decisions go to.
|
|
|
|
Fire-and-forget: the post runs as a background task, so a slow or down
|
|
backend never delays an agent's reaction to a stall. A failure is logged by
|
|
the HTTP client and otherwise ignored — the decision was still made and acted
|
|
on; only its record is missing.
|
|
"""
|
|
import asyncio
|
|
from typing import Any, Dict, Optional, Set
|
|
|
|
from config.system_config import GO_API_BASE_URL, INTERNAL_API_KEY
|
|
from core.http_client import api_post
|
|
|
|
_pending: Set[asyncio.Task] = set() # keep references so tasks are not garbage-collected
|
|
|
|
|
|
def _as_booking_id(value: Any) -> Optional[int]:
|
|
"""The backend's booking_id is an unsigned integer; anything else is omitted."""
|
|
try:
|
|
n = int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
return n if n > 0 else None
|
|
|
|
|
|
def build_payload(decision_type: str, booking_id: Any, facts: Optional[Dict[str, Any]],
|
|
action: str, confidence: float, reasoning: str, model: str) -> Dict[str, Any]:
|
|
"""The request body. Pure, so its shape is tested without a network."""
|
|
return {
|
|
"decision_type": decision_type,
|
|
"booking_id": _as_booking_id(booking_id),
|
|
"context": {"facts": facts or {}, "model": model},
|
|
"decision": {"action": action, "confidence": round(float(confidence), 3)},
|
|
"reasoning": reasoning or "",
|
|
}
|
|
|
|
|
|
async def _post(payload: Dict[str, Any]) -> None:
|
|
await api_post(
|
|
f"{GO_API_BASE_URL}/api/v1/internal/agent-decisions",
|
|
json=payload,
|
|
headers={"X-Internal-Key": INTERNAL_API_KEY},
|
|
)
|
|
|
|
|
|
def record_decision(decision_type: str, booking_id: Any, facts: Optional[Dict[str, Any]],
|
|
action: str, confidence: float, reasoning: str, model: str) -> Optional[asyncio.Task]:
|
|
"""Schedule the record and return at once. Returns the task (for tests), or
|
|
None when there is no running loop or no key to authenticate with."""
|
|
if not INTERNAL_API_KEY:
|
|
return None
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
except RuntimeError:
|
|
return None
|
|
task = loop.create_task(_post(build_payload(decision_type, booking_id, facts, action, confidence, reasoning, model)))
|
|
_pending.add(task)
|
|
task.add_done_callback(_pending.discard)
|
|
return task
|