← back to projects

2026 · Live

Juno.

Agentic copilot for Marketing Mix Model outputs — and a benchmark that measures whether its advice holds up.

Juno preview
PythonFastAPINext.jsClaudeRAGChromaDBSSELLM Eval

Demo sleeps when idle — first load may take ~50s to wake.

Marketers receive Marketing Mix Model results as a wall of coefficients and credible intervals. Juno parses that output deterministically, then a multi-agent LLM system explains what it means: a prioritized report, per-channel reads that carry explicit confidence, and a chat interface routing questions to specialized handlers. Every claim is grounded in either the parsed numbers or a cited methodology document. The backend is FastAPI streaming over SSE with ChromaDB for retrieval, Claude Sonnet 4.5 driving the agent and Opus 4.5 acting as an independent judge; the frontend is Next.js and TypeScript. The second half of the project is the real point: a benchmark suite scores the agent across six dimensions against 100 ground-truth scenarios, then validates the judge itself for reliability.

Benchmark results

Composite
0.897
Ranking accuracy
0.875
Calibration error (ECE)
0.093
Groundedness
0.903
Failure-mode recall
0.940
Judge test–retest κ
0.81

Scored across 100 ground-truth scenarios by an LLM-as-judge, with the judge itself validated for reliability. 58 backend tests in CI.

What I learned

The agent was under-confident, and the reliability diagram showed it

Plotting a reliability diagram surfaced something counterintuitive: Juno was labelling channels medium or low confidence while actually being correct about 88% of the time. The confidence signal was measuring the wrong thing. Redefining it as rank-certainty — how sure the model is about a channel's position in the ROI ordering, rather than raw uncertainty in the estimate — cut expected calibration error from 0.263 to 0.093.

A crash that looked like success

On a 512 MB host, loading the embedding model got the worker OOM-killed. Because that arrives as SIGKILL rather than an exception, try/except was structurally unable to catch it — and SSE reads a dropped connection as a normal end of stream, so users saw a report that stopped halfway with no error at all. The fix was to select the retrieval backend from the container's own memory limit and add a model-free BM25 retriever for constrained environments.

Juno is a solo portfolio project, not a product with real users. The public demo replays pre-computed answers; running the agent live on your own MMM upload requires your own Anthropic API key.