modelcourt / core /llm.py
existcode's picture
reupload
f983375 verified
Raw
History Blame Contribute Delete
9.97 kB
from __future__ import annotations
import json
from abc import ABC, abstractmethod
from core.config import MODEL_BACKEND
from core.risks import HIGH_JARGON, NO_PRODUCT_VISUAL, PRICING_UNCLEAR
from core.schemas import (
CTA,
AggregatedReport,
Archetype,
ContentSection,
HeuristicReport,
Persona,
RedesignSuggestion,
UIAnalysis,
VisualAssessment,
)
class ModelClient(ABC):
@abstractmethod
def vision_analyze(self, image_path: str | None, app_description: str) -> UIAnalysis: ...
@abstractmethod
def customize_personas(
self,
archetypes: list[Archetype],
app_description: str,
target_audience: str,
) -> list[Persona]: ...
@abstractmethod
def run_swarm(
self,
personas: list[Persona],
ui: UIAnalysis,
heuristics: HeuristicReport,
flow_steps: list[str],
) -> list[tuple[str, str]]: ...
@abstractmethod
def generate_redesign(
self,
report: AggregatedReport,
ui: UIAnalysis,
heuristics: HeuristicReport,
) -> RedesignSuggestion: ...
class MockClient(ModelClient):
def vision_analyze(self, image_path: str | None, app_description: str) -> UIAnalysis:
text = app_description.lower()
good = any(term in text for term in ["clear", "good", "simple", "trusted"])
if good:
return UIAnalysis(
headline="Ship customer onboarding in minutes",
subheadline=(
"Create guided product tours with templates, analytics, and a free trial."
),
primary_cta=CTA(
text="Start free trial", position="above_fold", visibility="prominent"
),
secondary_ctas=["Watch demo"],
navigation=["Product", "Pricing", "Customers", "Docs"],
content_sections=[
ContentSection(
type="product_visual", summary="Large dashboard screenshot above the fold"
),
ContentSection(type="pricing", summary="Free trial and transparent paid tiers"),
ContentSection(
type="testimonials", summary="Customer logos and two short reviews"
),
],
visual_assessment=VisualAssessment(
clutter_level="low",
mobile_friendliness="good",
text_density="moderate",
contrast_issues="none",
),
overall_first_impression="Clear value proposition with a visible free-trial CTA.",
)
return UIAnalysis(
headline="Revolutionary agentic synergy for scalable workflow transformation",
subheadline=(
"Leverage our holistic ecosystem to optimize enterprise productivity instantly."
),
primary_cta=CTA(text="Get Started", position="above_fold", visibility="subtle"),
secondary_ctas=["Learn More", "Contact Sales"],
navigation=["Platform", "Solutions", "Company"],
content_sections=[
ContentSection(type="hero", summary="Text-heavy hero with abstract gradient art"),
ContentSection(type="features", summary="Dense feature claims without screenshots"),
],
visual_assessment=VisualAssessment(
clutter_level="high",
mobile_friendliness="poor",
text_density="dense",
contrast_issues="minor",
),
overall_first_impression=(
"Ambitious but vague; the page does not quickly explain the product."
),
)
def customize_personas(
self,
archetypes: list[Archetype],
app_description: str,
target_audience: str,
) -> list[Persona]:
personas: list[Persona] = []
for idx, archetype in enumerate(archetypes, start=1):
personas.append(
Persona(
persona_id=f"P{idx:02d}",
archetype=archetype.archetype,
name=f"Persona {idx}",
backstory=f"Evaluating this product for {target_audience or 'their team'}.",
goal="Understand the product and decide whether to continue onboarding.",
device="mobile_chrome" if "mobile" in archetype.archetype else "desktop_chrome",
tolerance=archetype.key_trait,
constraints=[archetype.key_trait],
brutal_quote_style="direct, specific, first-person",
)
)
return personas
def run_swarm(
self,
personas: list[Persona],
ui: UIAnalysis,
heuristics: HeuristicReport,
flow_steps: list[str],
) -> list[tuple[str, str]]:
from agents.personas import (
archetype_is_vulnerable,
issues_for_archetype,
quote_for_archetype,
)
total_steps = max(len(flow_steps), 3)
raw_results: list[tuple[str, str]] = []
severe = bool(
set(heuristics.risk_flags) & {HIGH_JARGON, PRICING_UNCLEAR, NO_PRODUCT_VISUAL}
)
for persona in personas:
fails = severe and archetype_is_vulnerable(persona.archetype, heuristics)
final_step = 2 if fails else total_steps
issues = issues_for_archetype(persona.archetype, heuristics)
result = {
"persona_id": persona.persona_id,
"archetype": persona.archetype,
"completed_task": not fails,
"final_step_reached": final_step,
"total_steps": total_steps,
"step_log": [
{
"step": 1,
"action": "Read the landing page headline",
"understanding": f"Saw headline: {ui.headline}",
"confusion_level": 0.75 if "HIGH_JARGON" in heuristics.risk_flags else 0.2,
"would_continue": True,
"issues": [
issue
for issue in issues
if "jargon" in issue.lower() or "headline" in issue.lower()
],
},
{
"step": 2,
"action": "Looked for the next onboarding action",
"understanding": f"Primary CTA says '{ui.primary_cta.text}'.",
"confusion_level": 0.85 if fails else 0.25,
"would_continue": not fails,
"issues": issues,
},
],
"overall_confusion_score": 0.78 if fails else 0.24,
"overall_trust_score": 0.34 if fails else 0.76,
"brutal_quote": quote_for_archetype(persona.archetype, ui.primary_cta.text, fails),
"top_issues": issues,
"suggested_fix": (
"Use plain copy, show the product, and clarify pricing near the CTA."
)
if fails
else "Keep the clear CTA and supporting proof visible above the fold.",
}
raw_results.append((persona.persona_id, json.dumps(result)))
return raw_results
def generate_redesign(
self,
report: AggregatedReport,
ui: UIAnalysis,
heuristics: HeuristicReport,
) -> RedesignSuggestion:
return RedesignSuggestion.model_validate(
{
"priority_fixes": [
{
"issue": report.top_issues[0].issue
if report.top_issues
else "Clarify value proposition",
"severity": "high",
"affected_segments": [
segment.archetype for segment in report.worst_segments[:3]
],
"current": ui.headline,
"proposed_alternatives": [
"Understand your onboarding failures before launch",
"See where new users get confused in 60 seconds",
],
"reasoning": (
"The weakest segments need immediate clarity before "
"they will trust the CTA."
),
}
],
"onboarding_flow_fix": {
"current": [step.label for step in report.failure_funnel],
"proposed": [
"Show concrete example",
"Let user try",
"Ask for signup after value",
],
"reasoning": "Move proof and value before commitment.",
},
"quick_wins": [
"Rewrite the headline in plain language under 8 words",
"Add pricing or 'Free trial' copy next to the primary CTA",
"Show a real product screenshot above the fold",
"Reduce competing CTAs to one primary and one secondary action",
],
}
)
class VLLMClient(MockClient):
"""Placeholder preserving the backend boundary until AMD/vLLM infra is available."""
class HFInferenceClient(MockClient):
"""Placeholder preserving the backend boundary until HF Inference is wired."""
def get_model_client() -> ModelClient:
if MODEL_BACKEND == "vllm":
return VLLMClient()
if MODEL_BACKEND == "hf_api":
return HFInferenceClient()
return MockClient()