tacman / ai-batch-bundle
Async batch AI processing for Symfony. Implements OpenAI Batch API and Anthropic Message Batches with Symfony Scheduler polling. Proposed for symfony/ai.
Package info
github.com/tacman/ai-batch-bundle
Type:symfony-bundle
pkg:composer/tacman/ai-batch-bundle
Requires
- php: ^8.4
- symfony/framework-bundle: ^7.4|^8.0
- symfony/http-client: ^7.4|^8.0
- symfony/scheduler: ^7.4|^8.0
Requires (Dev)
- phpunit/phpunit: ^11.0|^12.0|^13.0
- roave/security-advisories: dev-latest
- symfony/ai-agent: *
Suggests
- doctrine/orm: For persisting AiBatch entities
- symfony/ai-agent: For building batch requests from Agent/MessageBag patterns
- symfony/messenger: For async result processing
README
Async batch AI processing for Symfony. Implements the OpenAI Batch API and Anthropic Message Batches API with a Symfony Scheduler poller.
Proposed for inclusion in symfony/ai as
BatchCapablePlatformInterface — a 5-method extension of PlatformInterface.
Why batch?
| Synchronous | Batch API | |
|---|---|---|
| Cost | $0.0008 / image (gpt-4o-mini) | $0.0004 / image (50% off) |
| Rate limits | Standard pool | Separate, much higher pool |
| Timeout risk | Yes (large sets) | None — 24h window |
| Results | Immediate | ~10 min (up to 24h) |
| Best for | Interactive, ≤100 items | Enrichment pipelines, ≥1000 items |
Quick demo
The demo/ directory shows the full pattern with a fun example:
generate programmer-targeted advertising copy for products from dummyjson.com,
using product images (vision) + descriptions.
cd demo composer install # Add your OPENAI_API_KEY to .env.local # Synchronous — 2 products, results immediately bin/console app:ads --limit=2 # Batch — all 194 products, 50% cheaper bin/console app:ads --batch # ✅ 194 products submitted to OpenAI Batch API (50% cost discount applies!) # ┌──────────────────┬────────────────────────────┐ # │ Local batch ID │ 1 │ # │ Provider batch │ batch_6789abc... │ # │ Status │ submitted │ # │ Requests │ 194 │ # │ Est. cost │ $0.0776 │ # │ vs sync cost │ $0.1552 (you save $0.0776) │ # └──────────────────┴────────────────────────────┘ # # Results will be ready in ~10 minutes. # bin/console app:fetch-batch 1 # Check status and display results bin/console app:fetch-batch 1 # > ⏳ Still processing (47 / 194) bin/console app:fetch-batch 1 # > ✅ completed # # Product #1 — Essence Mascara Lash Princess ($9.99) # Like git blame for your lashes — it shows exactly who's responsible # for those dramatic, volumizing commits. Cruelty-free, just like your # code reviews should be. # # Product #2 — Fingertip Skateboard ($29.99) # Finally, something you can debug with your fingers. Ships in 3-5 days, # which is faster than your CI pipeline. # Watch mode — polls every 30s until done bin/console app:fetch-batch 1 --watch
Installation
composer require tacman/ai-batch-bundle
Add to config/bundles.php:
Tacman\AiBatch\TacmanAiBatchBundle::class => ['all' => true],
Add to .env:
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-... # optional
Run the schema update:
bin/console doctrine:schema:update --force
Usage
Build and submit a batch
use Tacman\AiBatch\Model\BatchRequest; use Tacman\AiBatch\Service\AiBatchBuilder; $batch = $batchBuilder->build( datasetKey: 'my-collection', task: 'image_enrichment', records: $normalizedRecords, // iterable requestFactory: fn(array $row) => new BatchRequest( customId: $row['id'], systemPrompt: 'You are a museum cataloguer...', userPrompt: 'Describe this image and extract keywords.', model: 'gpt-4o-mini', imageUrl: $row['thumbnail_url'], // image_url, not base64 ), ); $batch = $batchBuilder->submit($batch); $entityManager->persist($batch); $entityManager->flush(); echo "Batch #{$batch->id} submitted: {$batch->providerBatchId}";
Check and apply results
$job = $batchClient->checkBatch($batch->providerBatchId); if ($job->isComplete()) { foreach ($batchClient->fetchResults($job) as $result) { // $result->customId maps back to your record id // $result->content is the parsed JSON response $enrichment = MediaEnrichment::fromNormalized($records[$result->customId]); $enrichment->applyAiEnrichment($result->content); // push to zm, update DB, etc. } }
Automatic polling with Symfony Scheduler
The bundle registers a PollBatchesTask that fires every 2 minutes.
It dispatches PollBatchesMessage which your handler processes:
// In your app — implement a handler that calls checkBatch() on all // AiBatch entities with status='processing'
Proposed symfony/ai interface
This bundle implements BatchCapablePlatformInterface — proposed for
symfony/ai as a 5-method extension of PlatformInterface:
interface BatchCapablePlatformInterface { public function supportsBatch(): bool; public function submitBatch(array $requests, array $options = []): BatchJob; public function checkBatch(string $batchId): BatchJob; public function fetchResults(BatchJob $job): iterable; // yields BatchResult public function cancelBatch(string $batchId): BatchJob; }
Implementations:
- ✅
OpenAiBatchClient— OpenAI/v1/batches - ✅
AnthropicBatchClient— Anthropic/v1/messages/batches - ❌ Mistral — no batch API yet
- 📋 Google Vertex AI batch prediction — planned
License
MIT — contributions welcome.