Build an AI Content Factory · 25 min · Python · asyncio
Wire the Orchestrator
Six independent API calls run one after another take 6x as long as they need to for no reason -- they don't depend on each other.
Hiring signal: Knowing when tasks are actually independent (and can run in parallel) versus when they have real dependencies is a fundamental systems-design skill that applies far beyond content pipelines.
What you will learn
- Run 6 independent async API calls concurrently with asyncio.gather
- Handle partial failure -- one generator failing shouldn't lose the other 5
- Log runtime and per-generator status for the full pipeline run
Introduction
Run all 6 generators from Lesson 3 back-to-back and time it — probably 60-90 seconds, since each one is a separate network round trip to Claude waiting on the previous one to finish. None of these 6 calls depend on each other's output. Today you fix that.
asyncio.to_thread: concurrency without rewriting 6 functions as async
import asyncio
import time
async def run_all_generators(source_text: str, voice_profile: str) -> dict:
generators = {
"blog": generate_blog_post,
"twitter": generate_twitter_thread,
"linkedin": generate_linkedin_post,
"newsletter": generate_newsletter_section,
"youtube": generate_youtube_script_outline,
"email": generate_email,
}
start = time.time()
tasks = {
name: asyncio.to_thread(fn, source_text, voice_profile)
for name, fn in generators.items()
}
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
print(f"All 6 generators completed in {time.time() - start:.1f}s")
return dict(zip(tasks.keys(), results))
asyncio.to_thread runs Lesson 3's ordinary synchronous functions in a background thread, which is enough to get real concurrency for I/O-bound work like waiting on an API response -- you don't need to rewrite all 6 generators using async def and await just to parallelize them. This is a genuinely useful pattern anytime you have existing synchronous functions you want to run concurrently without a full async rewrite.
return_exceptions=True is the difference between "one fails" and "all fail"
Without it, asyncio.gather raises the first exception it sees and cancels everything else in flight -- one rate-limited generator call would silently discard 5 successful results you already paid for. With return_exceptions=True, a failed generator's slot in the results just contains the exception object instead of a string, and the other 5 complete normally. Lesson 5's output step needs to handle both cases in the same dict.
Unlock the full lesson
You've read the first 2 sections. The rest of this lesson covers Aggregating results, including the failures, What You're Building — plus a hands-on lab, quiz, and project artifact.
Create a free account to unlock Phase 0 and Phase 1 of every course — no credit card.
Browse all courses · View pricing · DeVenture Academy