0Pricing
AI Prompt Engineering · Lektion

Batch-Verarbeitung und asynchrone Ausführung

OpenAI Batch API, asynchrones Python und nebenläufige Prompt-Ausführung.

Batch-Verarbeitung und asynchrone Ausführung ist eine kostenlose AI Prompt Engineering-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Prompt Engineering-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Prompt Engineering-Kurs umfasst insgesamt 4 Lektionen.

Warum Batch-Verarbeitung und Async?

Tausende LLM-Anfragen nacheinander zu verarbeiten, ist langsam und teuer. Batch-Verarbeitung gruppiert Anfragen und reduziert die Kosten um 50 %. Asynchrone Ausführung parallelisiert Anfragen, um den Durchsatz innerhalb der Rate-Limits zu maximieren. Zusammen reduzieren beide Ansätze sowohl die Kosten als auch die Gesamtlaufzeit erheblich.

OpenAI Batch API: 50 % Kostenersparnis

Die OpenAI Batch API verarbeitet Anfragen asynchron im Hintergrund (bis zu 24 Stunden) zum halben Preis der normalen API. Sie eignet sich ideal für Evaluierungsläufe, die Verarbeitung von Datensätzen und nicht echtzeitkritische Workloads.

import openai
import json

client = openai.OpenAI(api_key='YOUR_API_KEY')

# Step 1: Create batch input file (JSONL format)
batch_requests = [
    {
        'custom_id': f'request-{i}',
        'method': 'POST',
        'url': '/v1/chat/completions',
        'body': {
            'model': 'gpt-4o-mini',
            'messages': [
                {'role': 'user', 'content': f'Summarize this document: {doc}'}
            ],
            'max_tokens': 200
        }
    }
    for i, doc in enumerate(['Doc A text...', 'Doc B text...', 'Doc C text...'])
]

# Write to JSONL file
with open('batch_input.jsonl', 'w') as f:
    for req in batch_requests:
        f.write(json.dumps(req) + '\n')

# Step 2: Upload the file
batch_file = client.files.create(
    file=open('batch_input.jsonl', 'rb'),
    purpose='batch'
)
print(f'Batch file uploaded: {batch_file.id}')

Batch-Auftrag übermitteln und abfragen

Laden Sie nach dem Hochladen der Eingabedatei den Batch-Auftrag an und fragen Sie seinen Status bis zum Abschluss regelmäßig ab. Die Batch API verarbeitet Anfragen innerhalb von 24 Stunden (kleine Batches sind normalerweise deutlich schneller).

import time

# Step 3: Create batch job
batch = client.batches.create(
    input_file_id=batch_file.id,
    endpoint='/v1/chat/completions',
    completion_window='24h'
)
print(f'Batch created: {batch.id} | Status: {batch.status}')

# Step 4: Poll for completion
def wait_for_batch(batch_id, poll_interval=30, timeout=3600):
    start = time.time()
    while time.time() - start < timeout:
        batch = client.batches.retrieve(batch_id)
        print(f'Status: {batch.status} | '
              f'Completed: {batch.request_counts.completed}/ '
              f'{batch.request_counts.total}')
        if batch.status == 'completed':
            return batch
        if batch.status in ('failed', 'expired', 'cancelling', 'cancelled'):
            raise RuntimeError(f'Batch {batch_id} ended with status: {batch.status}')
        time.sleep(poll_interval)
    raise TimeoutError('Batch polling timed out')

# batch = wait_for_batch(batch.id)

Batch-Ergebnisse abrufen

Sobald der Batch abgeschlossen ist, laden Sie die Ausgabedatei herunter und wandeln die JSONL-Ergebnisse wieder in ein nutzbares Format um.

def retrieve_batch_results(batch):
    if not batch.output_file_id:
        raise ValueError('No output file — batch may have failed')

    # Download output file
    content = client.files.content(batch.output_file_id).text

    # Parse JSONL: one result per line
    results = {}
    for line in content.strip().split('\n'):
        if not line:
            continue
        result = json.loads(line)
        custom_id = result['custom_id']
        if result.get('error'):
            results[custom_id] = {'error': result['error']}
        else:
            response_body = result['response']['body']
            text = response_body['choices'][0]['message']['content']
            results[custom_id] = {'text': text}

    # Report error rate
    errors = sum(1 for r in results.values() if 'error' in r)
    print(f'Retrieved {len(results)} results, {errors} errors')
    return results

# results = retrieve_batch_results(batch)
# for req_id, result in results.items():
#     print(req_id, result.get('text', result.get('error', ''))[:50])

Asynchrones Python mit asyncio.gather()

Für Parallelität in Echtzeit (außerhalb von Batches) führt asyncio von Python mit asyncio.gather() mehrere API-Aufrufe gleichzeitig aus und wartet, bis alle abgeschlossen sind. Dadurch wird die Gesamtlaufzeit bei Workloads mit mehreren Anfragen erheblich verkürzt.

import asyncio
import openai

async_client = openai.AsyncOpenAI(api_key='YOUR_API_KEY')

async def async_completion(messages, model='gpt-4o-mini', max_tokens=200):
    response = await async_client.chat.completions.create(
        model=model,
        messages=messages,
        max_tokens=max_tokens
    )
    return response.choices[0].message.content

async def process_parallel(prompts):
    tasks = [
        async_completion([{'role': 'user', 'content': p}])
        for p in prompts
    ]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return results

# Usage
async def main():
    prompts = ['Explain photosynthesis.', 'Explain gravity.', 'Explain evolution.']
    results = await process_parallel(prompts)
    for prompt, result in zip(prompts, results):
        if isinstance(result, Exception):
            print(f'ERROR: {result}')
        else:
            print(f'{prompt[:30]}... -> {result[:60]}...')

# asyncio.run(main())
print('asyncio.gather: all 3 requests fire simultaneously')

Rate-Limit-bewusste parallele Aufrufe

Zu viele parallele Anfragen führen zu Rate-Limit-Fehlern. Ein Semaphor begrenzt die Parallelität, damit die Rate-Limits eingehalten und gleichzeitig möglichst hohe Durchsätze erzielt werden.

import asyncio

# Rate limits (example for gpt-4o-mini):
# RPM (requests per minute): 500
# TPM (tokens per minute): 200,000

MAX_CONCURRENT = 20  # stay well below rate limit

async def process_with_rate_limit(prompts, max_concurrent=MAX_CONCURRENT):
    semaphore = asyncio.Semaphore(max_concurrent)
    results = [None] * len(prompts)

    async def bounded_completion(i, prompt):
        async with semaphore:
            try:
                result = await async_completion(
                    [{'role': 'user', 'content': prompt}]
                )
                results[i] = result
            except openai.RateLimitError as e:
                print(f'Rate limited on prompt {i}: {e}')
                await asyncio.sleep(60)  # back off and retry
                result = await async_completion(
                    [{'role': 'user', 'content': prompt}]
                )
                results[i] = result

    await asyncio.gather(*[
        bounded_completion(i, p) for i, p in enumerate(prompts)
    ])
    return results

print('Semaphore limits to', MAX_CONCURRENT, 'concurrent requests')

Anthropic Batch API

Anthropic bietet ebenfalls eine Message Batches API mit einer ähnlichen Kostenstruktur wie die Batch API von OpenAI an. Batches werden asynchron verarbeitet, und ihre Ergebnisse werden abgefragt oder gestreamt.

import anthropic

client = anthropic.Anthropic(api_key='YOUR_API_KEY')

# Create a batch of messages
batch = client.messages.batches.create(
    requests=[
        {
            'custom_id': f'doc-{i}',
            'params': {
                'model': 'claude-haiku-4-5',
                'max_tokens': 200,
                'messages': [{
                    'role': 'user',
                    'content': f'Classify the sentiment of: {text}'
                }]
            }
        }
        for i, text in enumerate([
            'Amazing product, exceeded expectations!',
            'Terrible quality, broke after one use.',
            'It works as described.'
        ])
    ]
)
print(f'Batch created: {batch.id} | Status: {batch.processing_status}')

# Poll for completion
# while (batch := client.messages.batches.retrieve(batch.id)).processing_status != 'ended':
#     time.sleep(30)

# Retrieve results
# for result in client.messages.batches.results(batch.id):
#     print(result.custom_id, result.result.message.content[0].text[:50])

Durchsatzoptimierung: Batch-Strategien

Maximieren Sie den Durchsatz, indem Sie abhängig von Ihren Latenzanforderungen und den Eigenschaften Ihres Workloads die passende Batch-Strategie auswählen.

throughput_strategies = {
    'API Batch (OpenAI/Anthropic)': {
        'cost': '50% of normal price',
        'latency': 'Minutes to hours (background processing)',
        'best_for': 'Offline workloads: eval runs, dataset labeling, report generation',
        'max_batch_size': '50,000 requests per batch'
    },
    'asyncio.gather()': {
        'cost': 'Normal price',
        'latency': 'Same as slowest individual request',
        'best_for': 'Real-time parallel enrichment, multi-step pipelines',
        'max_concurrent': '10-50 depending on rate limits'
    },
    'Streaming + Concurrent': {
        'cost': 'Normal price',
        'latency': 'First token arrives faster, total similar',
        'best_for': 'User-facing applications needing perceived speed',
        'pattern': 'asyncio with stream=True per request'
    },
    'Worker Queue (Celery, RQ)': {
        'cost': 'Normal price',
        'latency': 'Variable (depends on queue depth)',
        'best_for': 'High-volume production with auto-scaling workers',
        'backends': 'Redis, RabbitMQ'
    }
}

for strategy, details in throughput_strategies.items():
    print(f'{strategy}: {details["best_for"][:60]}')

Fehlerbehandlung und Wiederholungslogik für Batch/Async

Parallele und Batch-Workloads benötigen eine robuste Fehlerbehandlung. Fehler einzelner Anfragen dürfen nicht den gesamten Batch zum Absturz bringen — protokollieren Sie sie, wiederholen Sie die Anfragen mit Backoff und melden Sie aggregierte Erfolgsquoten.

import asyncio
import random

async def resilient_completion(prompt, max_retries=3, base_delay=1.0):
    for attempt in range(max_retries):
        try:
            return await async_completion(
                [{'role': 'user', 'content': prompt}]
            )
        except openai.RateLimitError:
            wait = base_delay * (2 ** attempt) + random.uniform(0, 1)
            print(f'Rate limited. Waiting {wait:.1f}s (attempt {attempt+1})')
            await asyncio.sleep(wait)
        except openai.APITimeoutError:
            print(f'Timeout on attempt {attempt+1}')
            await asyncio.sleep(base_delay)
        except openai.APIError as e:
            if e.status_code >= 500:
                await asyncio.sleep(base_delay * (attempt + 1))
            else:
                raise  # Don't retry 4xx errors
    raise RuntimeError(f'Failed after {max_retries} attempts')

async def batch_with_error_reporting(prompts):
    tasks = [resilient_completion(p) for p in prompts]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    successes = sum(1 for r in results if not isinstance(r, Exception))
    print(f'Batch complete: {successes}/{len(prompts)} succeeded')
    return results

Große Eingaben für die Batch-Verarbeitung aufteilen

Dokumente, die größer als das Kontextfenster des Modells sind, müssen vor der Batch-Verarbeitung in Abschnitte aufgeteilt werden. Jeder Abschnitt wird zu einer separaten Batch-Anfrage; die Ergebnisse werden später zusammengeführt oder zusammengefasst.

def chunk_document(text, max_tokens=3000, overlap_tokens=200):
    '''
    Split a long document into overlapping chunks for batch processing.
    Approximate: 1 token ~ 4 characters
    '''
    max_chars = max_tokens * 4
    overlap_chars = overlap_tokens * 4
    chunks = []
    start = 0
    while start < len(text):
        end = min(start + max_chars, len(text))
        # Try to break at a sentence boundary
        if end < len(text):
            last_period = text.rfind('.', start, end)
            if last_period > start + max_chars // 2:
                end = last_period + 1
        chunks.append({'text': text[start:end], 'start': start, 'end': end})
        start = end - overlap_chars  # overlap for context continuity
    return chunks

def batch_summarize_long_document(document_text, summary_prompt):
    chunks = chunk_document(document_text)
    print(f'Document split into {len(chunks)} chunks')
    # Create one batch request per chunk
    batch_inputs = [
        {'custom_id': f'chunk-{i}',
         'content': summary_prompt + '\n\n' + chunk['text']}
        for i, chunk in enumerate(chunks)
    ]
    # Submit all chunks as one batch job
    return batch_inputs

long_doc = 'Lorem ipsum ' * 5000  # ~20K character document
chunks = chunk_document(long_doc)
print(f'Chunks: {len(chunks)}, first chunk length: {len(chunks[0]["text"])} chars')

Fortschrittsverfolgung für große Batches

Zeigen Sie bei großen Batch-Aufträgen (mit Tausenden von Anfragen) den Fortschritt in Echtzeit an, damit Betreiber den Durchsatz überwachen und die Abschlusszeit einschätzen können.

import asyncio
import time

async def batch_with_progress(prompts, max_concurrent=20):
    semaphore = asyncio.Semaphore(max_concurrent)
    completed = 0
    total = len(prompts)
    start_time = time.time()
    results = [None] * total

    async def process_one(i, prompt):
        nonlocal completed
        async with semaphore:
            results[i] = await resilient_completion(prompt)
            completed += 1

        elapsed = time.time() - start_time
        rate = completed / elapsed if elapsed > 0 else 0
        eta = (total - completed) / rate if rate > 0 else float('inf')

        if completed % 10 == 0 or completed == total:
            print(f'Progress: {completed}/{total} '
                  f'({completed/total:.0%}) | '
                  f'{rate:.1f} req/s | '
                  f'ETA: {eta:.0f}s')

    await asyncio.gather(*[
        process_one(i, p) for i, p in enumerate(prompts)
    ])
    return results

print('Progress tracking: reports every 10 completions with ETA.')

Kurzer Check

Sie müssen über Nacht 10.000 Dokumente für eine Sentimentanalyse klassifizieren. Sie möchten die Kosten minimieren und benötigen keine Ergebnisse in Echtzeit. Welcher Ansatz ist am besten geeignet?

Zusammenfassung von Batch und Async

Batch-Verarbeitung und asynchrone Ausführung sind für Prompt Engineering im großen Maßstab unverzichtbar:

  • OpenAI/Anthropic Batch API: 50 % Kostenersparnis, Verarbeitung im Hintergrund, bis zu 50.000 Anfragen pro Batch
  • asyncio.gather(): parallele Echtzeitanfragen, die alle gleichzeitig gestartet werden und auf sämtliche Ergebnisse warten
  • Semaphor: Rate-Limit-bewusste Steuerung der Parallelität (typischerweise 10–50 parallele Anfragen)
  • Exponential Backoff: Wiederholung mit sich verdoppelnder Verzögerung bei Rate-Limit- oder Timeout-Fehlern
  • Resilient Gather: return_exceptions=True verhindert, dass ein einzelner Fehler den gesamten Batch zum Absturz bringt
  • Fortschrittsverfolgung: Bei großen Aufträgen abgeschlossene Vorgänge zusammen mit Rate und voraussichtlicher Restzeit melden

Häufig gestellte Fragen

Ist die Lektion „Batch-Verarbeitung und asynchrone Ausführung“ kostenlos?

Ja — der vollständige Text von „Batch-Verarbeitung und asynchrone Ausführung“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Prompt Engineering-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Prompt Engineering-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Batch-Verarbeitung und asynchrone Ausführung“?

OpenAI Batch API, asynchrones Python und nebenläufige Prompt-Ausführung. Du übst AI Prompt Engineering mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um AI Prompt Engineering zu starten?

Keine Vorkenntnisse erforderlich. AI Prompt Engineering auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.

Wie lange dauert die Lektion „Batch-Verarbeitung und asynchrone Ausführung“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser AI Prompt Engineering-Lektion Code schreiben und ausführen?

Ja. Jede AI Prompt Engineering-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Caching-Strategien für Prompts
  2. Batch-Verarbeitung und asynchrone Ausführung
  3. Lastverteilung über Modelle hinweg
  4. Überwachung und Alerting für Prompt-Pipelines
← Zurück zu AI Prompt Engineering