Files

76 lines
3.5 KiB
Python

"""Turn the local scan + app picks into a personalized greeting and starters.
One cheap aux call on whatever lane the user just connected; every failure path
returns the static fallback so the reveal can never be an error card.
"""
import json
import re
from typing import List, Optional
from typeguard import typechecked
from backend.apps.agents.core.aux_llm import aux_max_tokens_for, safe_resp_text
from backend.apps.onboarding.models import PrepRequest, PrepResponse
from backend.apps.settings.models import AppSettings, PersonalizedStarter
FALLBACK_STARTERS: List[PersonalizedStarter] = [
PersonalizedStarter(title="Clean up Downloads", prompt="Sort my Downloads folder into tidy subfolders. Show me the plan before moving anything."),
PersonalizedStarter(title="Research something", prompt="Research the best noise-cancelling headphones under $300 and give me a comparison table."),
PersonalizedStarter(title="Build a tiny app", prompt="Build me a simple habit tracker app I can use right now."),
PersonalizedStarter(title="Plan a trip", prompt="Plan a 3-day weekend trip itinerary and turn it into a printable page."),
]
P_SYSTEM = (
"You write first-run starter tasks for OpenSwarm, a desktop AI agent platform that can "
"organize local files, browse the web in a real browser, build small apps, and run agents in parallel. "
"Given facts about the user's machine and the apps they picked, respond with STRICT JSON only: "
'{"greeting": string, "starters": [{"title": string, "prompt": string}]}. '
"Exactly 4 starters. Each title is 2-5 words. Each prompt is a concrete, safe, immediately runnable task "
"referencing the user's real folders, file counts, or picked apps when possible; never invent facts, never "
"propose deleting anything without review. The greeting is one warm sentence that names 2-3 specific things "
"found on the machine. No markdown, no commentary, JSON only."
)
@typechecked
def parse_prep(text: str) -> Optional[PrepResponse]:
match = re.search(r"\{.*\}", text, re.DOTALL)
if not match:
return None
try:
data = json.loads(match.group(0))
starters = [
PersonalizedStarter(title=str(s.get("title", "")).strip(), prompt=str(s.get("prompt", "")).strip())
for s in data.get("starters", [])
if isinstance(s, dict) and str(s.get("title", "")).strip() and str(s.get("prompt", "")).strip()
]
if not starters:
return None
return PrepResponse(greeting=str(data.get("greeting", "")).strip(), starters=starters[:4])
except Exception:
return None
@typechecked
async def build_prep(settings: AppSettings, request: PrepRequest) -> PrepResponse:
facts = request.model_dump()
try:
from backend.apps.agents.providers.registry import resolve_aux_model
from backend.apps.settings.credentials import get_anthropic_client_for_model
aux_model, _ = await resolve_aux_model(settings, preferred_tier="haiku")
client = get_anthropic_client_for_model(settings, aux_model)
resp = await client.messages.create(
model=aux_model,
max_tokens=aux_max_tokens_for(aux_model, base=700),
system=P_SYSTEM,
messages=[{"role": "user", "content": json.dumps(facts)}],
)
parsed = parse_prep(safe_resp_text(resp))
if parsed is not None:
return parsed
except Exception:
pass
return PrepResponse(greeting="", starters=list(FALLBACK_STARTERS))