Inspiration
Most AI media tools only keep the outputs that pass. Everything that fails gets deleted. But failures carry the most signal about what is not working and why. B2 is cheap enough to store everything, so we built a pipeline that treats rejections as data, not waste.
What it does
Providence takes a creative brief and runs it through a multi-provider generation pipeline. Images come from HuggingFace, audio from Edge TTS. Every output gets scored by Gemini Flash Lite against the original brief. Passing assets go to B2 /outputs/. Failing assets go to B2 /rejections/ with structured metadata: score, provider, and specific failure reasons. A failure intelligence layer mines those rejections and updates routing weights so the pipeline gets smarter over time.
Four B2 bucket paths power the whole thing:
/outputs/for delivered assets/rejections/for failed generations with failure reasons/pipeline-state/for step checkpoints so a failed pipeline can resume mid-run instead of starting over/analytics/for per-provider performance logs
How we built it
FastAPI backend with a pipeline orchestrator that fans tasks out to multiple providers in sequence. Gemini Flash Lite acts as the quality evaluator, scoring each output against the campaign brief, style keywords, and TTS readability criteria. Backblaze B2 via boto3 handles all four storage paths. The React frontend has a live pipeline tracker showing step-by-step progress and an intelligence dashboard displaying rejection rates, top failure reasons, and routing recommendations per provider.
Challenges we ran into
Gemini Flash Lite is under heavy load and returns inconsistent scores under pressure. We added retry logic on the evaluator to handle this. The intelligence layer recommendations are only as good as the volume of rejection history in B2, so early pipeline runs have less signal. We also hit a git submodule issue with the frontend folder right before the deadline which cost us a few minutes.
Accomplishments that we're proud of
The rejection pipeline actually works end to end. You can watch a generation fail, see the structured failure reason written to B2, watch the retry fire with a refined prompt, and then see the intelligence tab update with the new provider stats. The /rejections/ bucket ended up being the most useful part of the whole system.
What we learned
Storing failures is as important as storing successes. The rejection data is what makes the system improve over time. B2's pricing is what makes this economically viable at scale since you would never keep every failed generation on S3. Cheap storage is not just a cost advantage, it changes what you can build.
What's next for Providence
- Add video generation via Runway once API access stabilizes
- Swap Gemini Flash Lite for a heavier model for more consistent evaluation scores
- Add a prompt refinement engine that automatically rewrites failed prompts based on the specific rejection reasons stored in B2
- Multi-tenant support so teams share a B2 bucket and the intelligence layer learns from collective pipeline history
- Export the analytics bucket as a structured dataset for fine-tuning future evaluator models
Log in or sign up for Devpost to join the conversation.