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:
49
ml/agents/momentum.py
Normal file
49
ml/agents/momentum.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
from typing import ClassVar
|
||||
from .base import BaseAgent, AgentInput, AgentOutput
|
||||
|
||||
|
||||
class MomentumAgent(BaseAgent):
|
||||
"""Characterises the user's recent engagement trend from profile features."""
|
||||
agent_id: ClassVar[str] = "momentum"
|
||||
ttl_seconds: ClassVar[int] = 21600 # 6h
|
||||
version: ClassVar[str] = "1.0.0"
|
||||
|
||||
def compute(self, inp: AgentInput) -> AgentOutput:
|
||||
completion = inp.profile.get("completion_rate_30d")
|
||||
dismiss = inp.profile.get("dismiss_rate_30d")
|
||||
volume = inp.profile.get("tip_volume_30d")
|
||||
|
||||
parts: list[str] = []
|
||||
|
||||
if completion is not None:
|
||||
pct = round(completion * 100)
|
||||
if pct >= 50:
|
||||
parts.append(f"The user completes {pct}% of tips (strong engagement).")
|
||||
elif pct >= 25:
|
||||
parts.append(f"The user completes {pct}% of tips (moderate engagement).")
|
||||
else:
|
||||
parts.append(
|
||||
f"The user completes {pct}% of tips "
|
||||
f"(low engagement — prefer simple, immediately actionable tips)."
|
||||
)
|
||||
else:
|
||||
parts.append("No completion-rate data yet (new user).")
|
||||
|
||||
if dismiss is not None:
|
||||
dpct = round(dismiss * 100)
|
||||
if dpct >= 40:
|
||||
parts.append(f"Dismiss rate is high ({dpct}%) — avoid repetitive or irrelevant tips.")
|
||||
elif dpct <= 10:
|
||||
parts.append(f"Dismiss rate is low ({dpct}%).")
|
||||
|
||||
if volume is not None and int(volume) < 5:
|
||||
parts.append("Very few tips served so far — this is an early-stage user.")
|
||||
|
||||
prompt = " ".join(parts) if parts else "No engagement data available yet."
|
||||
snapshot = {
|
||||
"completion_rate_30d": completion,
|
||||
"dismiss_rate_30d": dismiss,
|
||||
"tip_volume_30d": volume,
|
||||
}
|
||||
return self._make_output(inp, prompt, snapshot)
|
||||
Reference in New Issue
Block a user