Files
oO/ml/agents/inference/framework.py
alvis ad6747c242 feat(profile): /api/profile + eligibility filter + inference framework (ADR-0014 steps 4-6)
Step 4 — /api/profile read-through API:
  GET  /api/profile          → { user, prefs, consents, contexts }
  PATCH /api/profile/prefs/:scope  upsert user_preferences (source='user')
  PATCH /api/profile/consents      grant / revoke consent keys
  PATCH /api/profile/contexts      create / activate / deactivate contexts
  Legacy consentGiven bit folded in as data:core fallback.

Step 5 — registry-driven eligibility filter:
  fetchRegistry() exported from agent-registry.ts.
  profile/eligibility.ts: getEligibleAgentIds(userId) — filters by required
  consents, silenced_in_contexts, and user_preferences[enabled=false].
  fetchOrchestratorTip filters agent_outputs to eligible set before calling
  ml/serving /recommend. Fail-closed: registry unavailable → empty set.

Step 6 — shared context-inference framework (#111) + time-of-day proof (#112):
  ml/agents/inference/: UserHistory, FeedbackEvent, run_inference().
  Framework: cold-start, min_history gating, error fallback, structured logs.
  TimeOfDayAgent v1.1.0: inferred_params=[preferred_hour]; also reads
  quiet_start/quiet_end from agent_prefs. agent_prefs injected by TS caller.
  AgentInput gains agent_prefs field.
  ml/serving: POST /agents/{agent_id}/infer endpoint.
  agent-outputs.ts computeAndStore: loads prefs before compute, calls /infer
  after, persists results (source='inferred'); user overrides never touched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 11:14:25 +00:00

60 lines
2.0 KiB
Python

"""run_inference — core of the context-inference framework (ADR-0014 §3).
Contract:
run_inference(manifest, history) → dict[key, value]
Semantics:
- For each InferredParam in manifest.inferred_params:
- If len(history.events) < param.min_history → emit cold_start_default.
- Otherwise → call param.infer(history) and emit the result.
- Returns {key: value} ready for the caller to persist to user_preferences
with source='inferred'.
- User overrides (source='user') are handled by the caller's upsert logic;
this function has no DB access.
"""
from __future__ import annotations
import logging
import time
from typing import Any
from ..manifest import AgentManifest
from .history import UserHistory
log = logging.getLogger(__name__)
def run_inference(manifest: AgentManifest, history: UserHistory) -> dict[str, Any]:
"""Evaluate all InferredParams for an agent and return {key: inferred_value}."""
result: dict[str, Any] = {}
n = len(history.events)
for param in manifest.inferred_params:
t0 = time.monotonic()
if param.infer is None:
result[param.key] = param.cold_start_default
continue
if n < param.min_history:
value = param.cold_start_default
source = "cold_start"
else:
try:
value = param.infer(history)
source = "inferred"
except Exception as exc:
log.warning(
"inference_error agent=%s param=%s error=%s — using cold_start_default",
manifest.id, param.key, exc,
)
value = param.cold_start_default
source = "error_fallback"
latency_ms = round((time.monotonic() - t0) * 1000, 1)
log.info(
"inference_param agent=%s param=%s source=%s value=%r history_len=%d latency_ms=%s",
manifest.id, param.key, source, value, n, latency_ms,
)
result[param.key] = value
return result