AI engineering · Python · AWS
Prumo
The real accuracy of an AI in production: a few human labels correct the LLM judge's bias, and the prompt rolls back on its own when quality drops.
- Client
- Personal open-source project
- Role
- Software Engineer — independent project
- Year
- 2026
- Technologies
- Python
- FastAPI
- Amazon Bedrock
- AWS Lambda
- AWS CDK
- Step Functions
- DynamoDB
- AppConfig
- CloudWatch
- Pydantic
- NumPy
- pytest
- GitHub Actions































Context
Open-source Python 3.12 service that measures the real accuracy of an AI in production. The AI (Amazon Nova Lite on Bedrock) classifies leads as QUENTE, MORNO or FRIO (hot, warm or cold), an LLM judge checks 100% of the decisions, a random sample goes to human review and statistics correct the judge's bias. All data is synthetic. The project picks up where Easy Control leaves off: there, the AI pre-qualifies leads and the CEO reviews every hot or warm one before the first contact, because the volume still allows it.
Challenge
At 50 times the volume, reviewing everything is no longer possible. Trusting the LLM judge alone is not enough either: its errors are systematic, and when a new prompt makes the AI wrong more often, nothing breaks, no alert fires and the judge's score barely moves. The challenge was to keep the automation under control with few human labels, know the real accuracy with an error margin that stays honest even when checked every hour, and roll the prompt back without waiting for someone to notice.
Results
- simulations in which the drop from 92% to 76% was detected and rolled back while the judge stayed around 91% (prumo bench benchmark)
- 10/10
- wrongful rollbacks of v3, the version that improves the AI, across every benchmark setting
- 0/10
- real accuracy of Nova Lite in the AWS test with 30 synthetic leads, against 70% approved by the Nova Pro judge, which let 4 wrong classifications through
- 60%
- automated tests: 205 in the app, with 94% coverage, and 56 for the CDK infrastructure
- 261
Key deliverables
- 01Implemented Prediction-Powered Inference (PPI and PPI++) in pure Python, with the standard library's math and statistics modules, using a random sample of human labels to remove the LLM judge's bias and report the real accuracy with a confidence band, plus an anytime-valid confidence sequence (Waudby-Smith et al., 2024) for hourly readings.
- 02Architected the full flow on AWS with Python CDK: a FastAPI service on Lambda (through Mangum), the classifier and the judge on Bedrock through the Converse API with forced tool use, a single-table DynamoDB design with atomic counters and a 180-day TTL, and human review on Step Functions with waitForTaskToken.
- 03Automated the prompt rollback: the hourly estimator publishes the metric as EMF to CloudWatch, and the alarm for 2 consecutive readings below target (85% SLO) triggers, through EventBridge, a Lambda that reverts the version in AppConfig with compare-and-swap, with the estimator itself as a safety net.
- 04Built the FastAPI web dashboard with a simulator that runs at no AWS cost and the prumo bench benchmark, which reproduces a prompt version that drops the AI from 92% to 76% while the judge stays around 91%, comparing methods and human-label volumes per hour.
- 05Created 261 automated tests (205 in the app, with 94% coverage, and 56 for the infrastructure), including Hypothesis property tests, Monte Carlo simulations in NumPy that validate interval coverage and AWS adapters tested with Stubber and moto, under strict mypy, ruff and CI on GitHub Actions.
- 06Secured operations with an admin token generated by Secrets Manager and read at cold start, deploys from GitHub Actions through OIDC with no stored access key, SNS alarms with an SQS dead-letter queue for the rollback, and synthetic data only.
Screens and components
The judge and the real rate
After the incident, the judge's score barely moves, the estimated real accuracy drops with v2 and the prompt rolls back to v1 on its own. The dashboard comes from the simulator, with fictitious data.

The incident
During the incident, the judge stays near 91% while v2's estimated real accuracy falls below the 85% target, and the status turns to Below target. Simulator with fictitious data.

Indicators on mobile
On mobile, the indicators separate what the judge says, 90.8%, from the estimated real accuracy, 77.2%, with the confidence band and the budget burn rate right next to them. Fictitious data.
Human review and events
In the review queue, the reviewer says whether the AI was right and sees the simulator's answer key; the events log deploys, alarms and rollbacks with date and time. Fictitious data.
Design system foundations
Ten color tokens in light and dark, type, radii and spacing support the Jinja2 and framework-free JavaScript dashboard, from buttons in every state to the SLO statuses.

Domain components
The domain components put the judge next to the real rate, show the SLO status, the 48 readings and the deploy, alarm and rollback events, and organize the human review, in both themes. Fictitious data.

How the math works
In a 20-decision example, the judge approves 90%, the human sample shows 75% and a band below target triggers the rollback. The confidence sequence stays valid even when checked every hour.

AWS architecture
The board follows the lead from 1 to 12: API Gateway, Lambda with FastAPI, Bedrock with the judge, DynamoDB, human review in Step Functions, the hourly estimator, the CloudWatch alarm and the prompt rollback on AppConfig by compare-and-swap, all in Python CDK.

Documented API
The FastAPI service documents with OpenAPI the routes for quality, prompts, decisions and human review, including prompt deploy and rollback.

Results
The benchmark runs 10 simulations per row, and the real AWS test used 30 synthetic leads: the AI was right 60% of the time, and the judge approved 70%.

Features and the pains they solve
Every feature was born from a real operational problem.
01
Real accuracy with a confidence band
- Pain
- The LLM judge checks 100% of the decisions, but it always errs in the same direction: its score is not the real accuracy, and reviewing everything by hand does not scale.
- Solution
- A random sample labeled by people measures the judge's error, and Prediction-Powered Inference (PPI and PPI++) removes that error from the judge's score, reporting the real accuracy with a confidence band. The estimate only uses sums and sums of squares, so reading the quality costs O(1).
Impact: The 95% interval contains the truth 92% to 98% of the time (400 Monte Carlo simulations)
02
Confidence sequence
- Pain
- A regular confidence interval is valid for a single look; read every hour, the chance of a false alarm grows with each reading.
- Solution
- The default method (ppi_cs) uses an asymptotic confidence sequence (Waudby-Smith et al., 2024), which stays valid even when checked forever, and requires at least 30 labels before any decision. It detects more slowly than the regular interval, in exchange for a real guarantee against false alarms.
Impact: Checked every hour for 30 hours, it errs at most α + 2%
03
Automatic prompt rollback
- Pain
- A prompt version that makes the AI worse raises no exception and no alert: without measurement, the regression stays live until someone notices.
- Solution
- If the whole band falls below target (85% SLO) for 2 consecutive readings, the CloudWatch alarm triggers, through EventBridge, a Lambda that rolls the prompt back to the previous version in AppConfig, with no new code deploy.
Impact: Rollback in 28, 17 and 11 h (median) with 10, 20 and 40 labels per hour; v2 detected 10/10
04
Two triggers, one atomic swap
- Pain
- EventBridge only fires on the transition to ALARM: if that attempt fails, the alarm sits still without a new event, and a redelivered alarm could roll back two versions.
- Solution
- The hourly estimator applies the same rule as a safety net, and the swap is compare-and-swap on the measured version: a redelivered alarm, a simultaneous manual click or both triggers together never roll back two versions. A failed event goes to an SQS dead-letter queue, with an alarm.
Impact: 0/10 wrongful rollbacks of v3 across every benchmark setting
05
Human review on Step Functions
- Pain
- If only the doubtful cases are reviewed, the correction of the judge's bias becomes biased itself, and a label written twice would count double in the estimate.
- Solution
- Sampled decisions enter a Step Functions state machine that waits for the reviewer with waitForTaskToken, for up to 72 h. The API rejects labels on decisions that were not sampled, and the DynamoDB write is conditional, so the same decision can never be labeled twice.
06
Classification with Bedrock
- Pain
- Asking the model for JSON and hoping for the right shape breaks the flow on the first off-format answer, and the lead's text may carry injected instructions.
- Solution
- The classifier and the judge call Bedrock through the Converse API with a forced tool (tool use) and temperature 0, and the output is still validated with Pydantic. The prompt tells the model to ignore instructions inside the lead, and Bedrock Guardrails can be turned on through configuration.
Impact: Real test with 30 synthetic leads: 0 HTTP errors and about 2.6 s per lead
07
Dashboard and simulator
- Pain
- A silent quality drop is hard to explain without seeing the judge and the real accuracy side by side, and testing the idea straight on AWS costs money.
- Solution
- The dashboard shows the judge's score, the estimated real accuracy with its band, the error-budget burn rate and the events. In simulated mode, with no AWS and no cost, you can publish v2, change the labels per hour, turn the rollback on or off and label decisions yourself.
Impact: The same logic runs in the simulator and on AWS, through ports and adapters
08
Method benchmark
- Pain
- Without comparing methods, there is no way to know how many labels per hour are enough or how many hours each statistical guarantee costs.
- Solution
- The prumo bench command runs 10 simulations per row: v1 live, v2 dropping the AI from 92% to 76% and v3 improving it to 93%, measuring detection, hours to rollback and wrongful rollbacks for ppi_cs and ppi_ci with 10, 20 and 40 labels per hour.
Impact: ppi_ci rolls back in 14, 5 and 4 h; a judge-only monitor never alarms
09
Infrastructure as code
- Pain
- Setting up Lambda, Step Functions, alarms and AppConfig by hand in the console is not reproducible, and an access key stored in CI is a leak waiting to happen.
- Solution
- A single AWS CDK (Python) stack creates every application resource, with tests that lock the template's invariants. GitHub Actions reruns lint, type checks and tests before assuming, through OIDC, a role bound to the production environment, and deploys only run when triggered manually.
Impact: Stack deploy in about 3.5 min
10
Personal data and secrets
- Pain
- The lead's text is personal data under Brazil's LGPD, and a token in an environment variable is visible to anyone who can read the Lambda configuration.
- Solution
- The decision text expires after 180 days through DynamoDB TTL, while the estimate's sums remain. The admin token is generated by Secrets Manager and read at cold start; without it, the protected routes refuse the request.
Impact: No customer data: every lead is synthetic




















