Files
567-labs--instructor/docs/blog/posts/learn-async.md
T
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

265 lines
8.1 KiB
Markdown

---
authors:
- jxnl
categories:
- LLM Techniques
comments: true
date: 2023-11-13
description: "Master Python asyncio.gather and asyncio.as_completed for efficient concurrent LLM processing with Instructor. Learn async programming patterns, rate limiting, and performance optimization for AI applications."
draft: false
slug: learn-async
tags:
- asyncio
- asyncio.gather
- asyncio.as_completed
- OpenAI
- Python
- data processing
- async programming
- concurrent processing
- LLM optimization
---
# Mastering Python asyncio.gather and asyncio.as_completed for LLM Processing
Learn how to use Python's `asyncio.gather` and `asyncio.as_completed` for efficient concurrent processing of Large Language Models (LLMs) with Instructor. This comprehensive guide covers async programming patterns, rate limiting strategies, and performance optimization techniques.
<!-- more -->
!!! notes "Complete Example Code"
You can find the complete working example on [GitHub](https://github.com/jxnl/instructor/blob/main/examples/learn-async/run.py)
## Understanding asyncio.gather vs asyncio.as_completed
Python's `asyncio` library provides two powerful methods for concurrent execution:
- **`asyncio.gather`**: Executes all tasks concurrently and returns results in the same order as input
- **`asyncio.as_completed`**: Returns results as they complete, regardless of input order
Both methods significantly outperform sequential processing, but they serve different use cases.
## Complete Setup: Async LLM Processing
Here's a complete, self-contained example showing how to set up async processing with Instructor:
```python
import instructor
from pydantic import BaseModel
# Set up the async client with Instructor
client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
class Person(BaseModel):
name: str
age: int
occupation: str
async def extract_person(text: str) -> Person:
"""Extract person information from text using LLM."""
return await client.create(
model="gpt-4o-mini",
response_model=Person,
messages=[{"role": "user", "content": f"Extract person info: {text}"}],
)
# Sample dataset
dataset = [
"John Smith is a 30-year-old software engineer",
"Sarah Johnson is a 25-year-old data scientist",
"Mike Davis is a 35-year-old product manager",
"Lisa Wilson is a 28-year-old UX designer",
"Tom Brown is a 32-year-old DevOps engineer",
"Emma Garcia is a 27-year-old frontend developer",
"David Lee is a 33-year-old backend developer",
]
```
## Method 1: Sequential Processing (Baseline)
```python
async def sequential_processing() -> List[Person]:
"""Process items one by one - slowest method."""
start_time = time.time()
persons = []
for text in dataset:
person = await extract_person(text)
persons.append(person)
print(f"Processed: {person.name}")
end_time = time.time()
print(f"Sequential processing took: {end_time - start_time:.2f} seconds")
return persons
# Run sequential processing
# persons = await sequential_processing()
```
## Method 2: asyncio.gather - Concurrent Processing
```python
async def gather_processing() -> List[Person]:
"""Process all items concurrently and return in order."""
start_time = time.time()
# Create tasks for all items
tasks = [extract_person(text) for text in dataset]
# Execute all tasks concurrently
persons = await asyncio.gather(*tasks)
end_time = time.time()
print(f"asyncio.gather took: {end_time - start_time:.2f} seconds")
# Results maintain original order
for person in persons:
print(f"Processed: {person.name}")
return persons
# Run gather processing
# persons = await gather_processing()
```
## Method 3: asyncio.as_completed - Streaming Results
```python
async def as_completed_processing() -> List[Person]:
"""Process items concurrently and handle results as they complete."""
start_time = time.time()
persons = []
# Create tasks for all items
tasks = [extract_person(text) for text in dataset]
# Process results as they complete
for task in asyncio.as_completed(tasks):
person = await task
persons.append(person)
print(f"Completed: {person.name}")
end_time = time.time()
print(f"asyncio.as_completed took: {end_time - start_time:.2f} seconds")
return persons
# Run as_completed processing
# persons = await as_completed_processing()
```
## Method 4: Rate-Limited Processing with Semaphores
```python
async def rate_limited_extract_person(
text: str, semaphore: asyncio.Semaphore
) -> Person:
"""Extract person info with rate limiting."""
async with semaphore:
return await extract_person(text)
async def rate_limited_gather(concurrency_limit: int = 3) -> List[Person]:
"""Process items with controlled concurrency using asyncio.gather."""
start_time = time.time()
# Create semaphore to limit concurrent requests
semaphore = asyncio.Semaphore(concurrency_limit)
# Create rate-limited tasks
tasks = [rate_limited_extract_person(text, semaphore) for text in dataset]
# Execute with rate limiting
persons = await asyncio.gather(*tasks)
end_time = time.time()
print(
f"Rate-limited gather (limit={concurrency_limit}) took: {end_time - start_time:.2f} seconds"
)
return persons
async def rate_limited_as_completed(concurrency_limit: int = 3) -> List[Person]:
"""Process items with controlled concurrency using asyncio.as_completed."""
start_time = time.time()
persons = []
# Create semaphore to limit concurrent requests
semaphore = asyncio.Semaphore(concurrency_limit)
# Create rate-limited tasks
tasks = [rate_limited_extract_person(text, semaphore) for text in dataset]
# Process results as they complete
for task in asyncio.as_completed(tasks):
person = await task
persons.append(person)
print(f"Rate-limited completed: {person.name}")
end_time = time.time()
print(
f"Rate-limited as_completed (limit={concurrency_limit}) took: {end_time - start_time:.2f} seconds"
)
return persons
# Run rate-limited processing
# persons = await rate_limited_gather(concurrency_limit=2)
# persons = await rate_limited_as_completed(concurrency_limit=2)
```
## Performance Comparison
Here are typical performance results when processing 7 items:
| Method | Execution Time | Concurrency | Use Case |
|--------|---------------|-------------|----------|
| Sequential | 6.17 seconds | 1 | Baseline |
| asyncio.gather | 0.85 seconds | 7 | Fast processing, ordered results |
| asyncio.as_completed | 0.95 seconds | 7 | Streaming results |
| Rate-limited gather | 3.04 seconds | 2 | API-friendly |
| Rate-limited as_completed | 3.26 seconds | 2 | Streaming + rate limiting |
## When to Use Each Method
### Use asyncio.gather when:
- You need results in the same order as input
- All tasks must complete successfully
- You want the fastest possible execution
- Memory usage isn't a concern
### Use asyncio.as_completed when:
- You want to process results as they arrive
- Order doesn't matter
- You're streaming data to clients
- You want to handle large datasets efficiently
### Use rate limiting when:
- Working with API rate limits
- Being respectful to external services
- Managing resource consumption
- Building production applications
## Key Takeaways
1. **asyncio.gather** is fastest for ordered results
2. **asyncio.as_completed** is best for streaming and large datasets
3. **Rate limiting** is essential for production applications
4. **Error handling** should be implemented for robustness
5. **Monitoring** helps optimize performance
## Related Resources
- [Python asyncio Documentation](https://docs.python.org/3/library/asyncio.html)
- [Real Python Async IO Tutorial](https://realpython.com/async-io-python/)
- [Instructor Documentation](https://python.useinstructor.com)
- [OpenAI Async API Guide](https://platform.openai.com/docs/guides/async)
---
**Next Steps**: Learn about [error handling patterns](../../concepts/error_handling.md) or explore [rate limiting with tenacity](../../concepts/retrying.md) for production applications.