implemenation on the ai agents and registry
This commit is contained in:
66
core/decisions.py
Normal file
66
core/decisions.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user