File size: 5,570 Bytes
e6fb135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98ee05e
e6fb135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98ee05e
e6fb135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98ee05e
e6fb135
 
 
98ee05e
e6fb135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""CLI to trigger an agent run or a regression eval and print results nicely.

Usage:
    python scripts/run_eval.py run                # run the agent on the example brief
    python scripts/run_eval.py run --brief brief.json
    python scripts/run_eval.py regression         # run golden regression eval
    python scripts/run_eval.py status             # show golden / flagged counts

Uses Rich for clean terminal output.
"""

import argparse
import json
import os
import sys

# Make the project root importable when run as `python scripts/run_eval.py`.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from dotenv import load_dotenv  # noqa: E402

load_dotenv()

from rich.console import Console  # noqa: E402
from rich.panel import Panel  # noqa: E402
from rich.table import Table  # noqa: E402

console = Console()

EXAMPLE_BRIEF = {
    "brand": "FitFuel",
    "product": "High-protein meal replacement shake",
    "audience": "Busy professionals aged 25-40",
    "tone": "Energetic and no-nonsense",
    "goal": "Drive trial purchases",
}


def _load_brief(path: str | None) -> dict:
    if not path:
        return EXAMPLE_BRIEF
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def cmd_run(args: argparse.Namespace) -> int:
    from agent.core import Agent

    brief = _load_brief(args.brief)
    console.print(Panel.fit(json.dumps(brief, indent=2), title="Brand Brief", border_style="cyan"))

    with console.status("[bold green]Running agent loop (retrieve -> generate -> evaluate -> feedback)..."):
        agent = Agent()
        result = agent.run(brief)

    console.print(
        f"\n[bold]Run #{result['run_id']}[/bold]  "
        f"prompt=[magenta]{result['prompt_version']}[/magenta]  "
        f"retrieved few-shot examples=[yellow]{result['retrieved_examples']}[/yellow]\n"
    )

    table = Table(title="Generated Variants & Scores", show_lines=True)
    table.add_column("Variant", style="cyan", no_wrap=True)
    table.add_column("Copy", style="white", max_width=50)
    table.add_column("Hook", justify="right")
    table.add_column("Brand", justify="right")
    table.add_column("Clarity", justify="right")
    table.add_column("Conv", justify="right")
    table.add_column("Weighted", justify="right", style="bold")

    for o in result["outputs"]:
        s = o["scores"]
        table.add_row(
            o["variant_type"],
            o["content"],
            str(s["hook_strength"]),
            str(s["brand_alignment"]),
            str(s["clarity"]),
            str(s["conversion_intent"]),
            f"{s['weighted_average']:.2f}",
        )
    console.print(table)

    fb = result["feedback"]
    console.print(
        f"\n[green]Promoted to golden:[/green] {fb['promoted_to_golden'] or 'none'}   "
        f"[red]Flagged for review:[/red] {fb['flagged_for_review'] or 'none'}"
    )
    return 0


def cmd_regression(args: argparse.Namespace) -> int:
    from evals.runner import format_report, run_golden_eval

    with console.status("[bold green]Running golden regression eval..."):
        report = run_golden_eval()

    table = Table(title=f"Regression Eval - {report.prompt_version}", show_lines=True)
    table.add_column("#", justify="right")
    table.add_column("Variant", style="cyan")
    table.add_column("Baseline", justify="right")
    table.add_column("New", justify="right")
    table.add_column("Delta", justify="right")
    table.add_column("Status", justify="center")

    for i, r in enumerate(report.results, 1):
        status = "[red]REGRESSED[/red]" if r.regressed else "[green]ok[/green]"
        table.add_row(
            str(i), r.variant_type,
            f"{r.baseline_score:.2f}", f"{r.new_score:.2f}",
            f"{r.delta:+.2f}", status,
        )

    if report.count:
        console.print(table)
    console.print()
    if report.passed:
        console.print(Panel.fit("PASS - no entry regressed beyond tolerance.", border_style="green"))
    else:
        console.print(
            Panel.fit(
                f"FAIL - {len(report.regressions)} entry(ies) regressed. "
                "Do not promote this prompt version.",
                border_style="red",
            )
        )
    # Non-zero exit on failure so it can gate CI / a prompt swap.
    return 0 if report.passed else 1


def cmd_status(args: argparse.Namespace) -> int:
    from db.store import Store
    from evals.golden import GoldenDataset

    store = Store()
    golden = GoldenDataset(store=store)
    flagged = store.get_flagged()

    console.print(
        Panel.fit(
            f"Golden entries: [green]{golden.size()}[/green]\n"
            f"Flagged outputs: [red]{len(flagged)}[/red]",
            title="System Status",
            border_style="cyan",
        )
    )
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(description="Self-improving ad copy agent CLI")
    sub = parser.add_subparsers(dest="command", required=True)

    p_run = sub.add_parser("run", help="Run the agent loop on a brief")
    p_run.add_argument("--brief", help="Path to a brief JSON file (defaults to FitFuel example)")
    p_run.set_defaults(func=cmd_run)

    p_reg = sub.add_parser("regression", help="Run the golden-dataset regression eval")
    p_reg.set_defaults(func=cmd_regression)

    p_status = sub.add_parser("status", help="Show golden / flagged counts")
    p_status.set_defaults(func=cmd_status)

    args = parser.parse_args()
    return args.func(args)


if __name__ == "__main__":
    raise SystemExit(main())