feat(ml): multi-agent context framework + v4 orchestrator prompt
Adds ml/agents/ — five specialised sub-agents (overdue_task, momentum, time_of_day, recent_patterns, focus_area) each producing a prompt snippet from user signals. A registry wires them up; the orchestrator prompt in ml/serving/prompts.py synthesises their outputs into one tip via LiteLLM. Also wires /api/agents route in the API and updates the Dockerfile to copy the full ml/ tree with PYTHONPATH=/app so agent imports resolve correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
53
ml/agents/base.py
Normal file
53
ml/agents/base.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Base class and shared data structures for all recommendation sub-agents."""
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentInput:
|
||||
"""Everything an agent may need to produce its prompt snippet."""
|
||||
user_id: str
|
||||
tasks: list[dict] # task signal dicts (content, priority, is_overdue, …)
|
||||
profile: dict[str, float | None] # profile feature values keyed by feature name
|
||||
feedback_history: list[dict] = field(default_factory=list) # [{action, dwell_ms, created_at}, …]
|
||||
now: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentOutput:
|
||||
"""Result produced by an agent; persisted to agent_outputs table."""
|
||||
user_id: str
|
||||
agent_id: str
|
||||
prompt_text: str # snippet passed to the orchestrator
|
||||
signals_snapshot: dict # inputs consumed (for explainability / debugging)
|
||||
computed_at: str # ISO 8601
|
||||
expires_at: str # ISO 8601
|
||||
agent_version: str
|
||||
|
||||
|
||||
class BaseAgent(ABC):
|
||||
agent_id: ClassVar[str]
|
||||
ttl_seconds: ClassVar[int]
|
||||
version: ClassVar[str]
|
||||
|
||||
@abstractmethod
|
||||
def compute(self, inp: AgentInput) -> AgentOutput:
|
||||
"""Analyse inp and return a prompt snippet describing what was found."""
|
||||
...
|
||||
|
||||
def _make_output(self, inp: AgentInput, prompt_text: str, snapshot: dict) -> AgentOutput:
|
||||
computed_at = inp.now.astimezone(timezone.utc).isoformat()
|
||||
expires_at = (inp.now.astimezone(timezone.utc) + timedelta(seconds=self.ttl_seconds)).isoformat()
|
||||
return AgentOutput(
|
||||
user_id=inp.user_id,
|
||||
agent_id=self.agent_id,
|
||||
prompt_text=prompt_text,
|
||||
signals_snapshot=snapshot,
|
||||
computed_at=computed_at,
|
||||
expires_at=expires_at,
|
||||
agent_version=self.version,
|
||||
)
|
||||
Reference in New Issue
Block a user