GenAI Integration Patterns & Asynchronous Workflows
In a production environment, coupling a client application directly to external Large Language Model (LLM) APIs using raw, unmediated SDK calls introduces significant architectural vulnerabilities. These vulnerabilities include API key exposure, rate limit exhaustion, lack of observability, and connection timeout errors due to non-deterministic LLM response latency.
To build resilient systems, software engineers must deploy structured Integration Patterns that decouple client clients from downstream model execution backends.
๐ 1. Core Integration Topologies
Depending on user experience targets, execution latency bounds, and backend task complexity, GenAI integrations are split into four core architectural topologies:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Client Request Ingress โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ โผ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ Synchronous โ โ Asynchronous โ โ Event-Driven โ โ Batch โ
โ API Gateway โ โ Task Queue โ โ Streaming โ โ Processing โ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโA. Synchronous API Gateway
The client triggers a blocking HTTP request to a centralized proxy gateway. The gateway applies authentication, token-bucket rate limiting, safety filters, and routes the request directly to the model. The client waits synchronously for the model to complete inference and return the payload.
- Ideal for: Fast inline evaluations, structural classification, simple data extraction, and low-latency chatbots.
- Reference Implementation: For a production-ready synchronous gateway setup, see the Production GenAI Gateway & Request Lifecycle playbook.
B. Asynchronous Task Queue (Job Worker)
The client submits an execution request to the gateway. Instead of waiting for model execution, the gateway immediately logs the job into a persistent message broker, yields a unique task_id ticket back to the client, and releases the client connection. A background worker pool fetches the task, calls the LLM, handles failures and retries, and writes the final output to a database. The client polls the status of the task_id or waits for a webhook push.
- Ideal for: Long-running multi-agent reasoning graphs, processing large files (PDF parsing/indexing), bulk report drafting, and pipelines utilizing external search retrieval.
C. Event-Driven Publish-Subscribe / Streaming
The client opens a persistent connection (such as Server-Sent Events (SSE) or WebSockets) to the gateway. As the model performs inference, individual tokens are streamed immediately back to the client. When generation is complete, the gateway publishes a transaction summary event to a message broker (e.g., Kafka or RabbitMQ) to trigger downstream microservices (e.g., vector database updates or cache invalidations).
- Ideal for: Interactive UI chat completions, real-time code editor extensions, voice assistants, and collaborative multi-user applications.
D. Batch Processing
A scheduler triggers bulk offline jobs. Large input datasets are partitioned and distributed across a cluster of workers that call LLM endpoints concurrently. The outputs are consolidated and written directly back into operational databases or search stores.
- Ideal for: Re-embedding entire databases, offline quality evaluations, weekly report compilations, and historical document parsing.
๐ 2. Topologies Comparison Matrix
Choosing the correct integration topology is a tradeoff between latency, concurrency limits, system complexity, and user experience requirements:
| Dimension | Synchronous API Gateway | Asynchronous Task Queue | Event-Driven Streaming (SSE/WS) | Batch Processing |
|---|---|---|---|---|
| Connection Lifecycle | Bounded (Strict HTTP timeout, e.g., 30s) | Unbounded (Immediate response, async processing) | Persistent (Active TCP socket session) | Offline (Triggered via cron / message event) |
| Timeout Resilience | Poor (Vulnerable to gateway timeouts on slow runs) | Excellent (Handled via Celery/worker retry limits) | Moderate (Connection drops require client reconnection) | Excellent (Retry structures manage endpoint failure) |
| System Complexity | Low (Simple client-to-server request) | High (Requires message broker, worker pool, and database state) | Medium-High (Requires socket management and session tracking) | Medium (Requires job scheduler and partitioning logic) |
| UX Impact | Wait spinner; blocks interactions | Polling progress bars or notifications | Immediate token-by-token rendering | Background updates; zero active user wait |
| Max Concurrent Scale | Bounded by gateway thread pool and connection limit | Scalable via queue throttling and worker scale | Bounded by socket file descriptor limits | Scalable via chunk-based API partitioning |
๐ 3. System Integration Architecture
The sequence diagram below maps the interaction lifecycle of an asynchronous job worker pattern. In this scenario, client connections are decoupled from model execution times, utilizing Redis as a message broker and Celery as the background task orchestrator:
๐ป 4. Production-Ready Asynchronous Task Worker
Below is a complete, self-contained implementation of an asynchronous task worker pipeline using FastAPI as the client-facing gateway API, Celery for task orchestration, and Redis serving as both the message broker and state backend.
Prerequisites
To run this application locally, ensure you have Redis running:
docker run -d -p 6379:6379 redis:alpineInstall the Python dependencies:
pip install fastapi celery redis openai pydantic uvicornComplete Codebase (app.py)
import os
import time
import logging
from typing import Optional, Dict, Any
from pydantic import BaseModel, Field
from fastapi import FastAPI, HTTPException, status
from celery import Celery
from openai import OpenAI
# 1. Initialize Logger and Configurations
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("AsyncIntegration")
REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
# 2. Celery Worker Configuration
# Broker: Enqueues tasks; Backend: Stores execution states/results
celery_app = Celery(
"tasks",
broker=REDIS_URL,
backend=REDIS_URL
)
celery_app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
timezone="UTC",
enable_utc=True,
task_track_started=True,
task_time_limit=300, # Hard timeout of 5 minutes
task_soft_time_limit=240 # Soft timeout of 4 minutes
)
# 3. Pydantic Models for Input Validation
class JobSubmission(BaseModel):
user_id: str = Field(..., description="Alphanumeric identifier for tracking costs")
prompt: str = Field(..., min_length=5, description="Input prompt for LLM processing")
model: str = Field("gpt-4o-mini", description="Target model name")
class JobStatusResponse(BaseModel):
task_id: str = Field(..., description="Unique identifier for the asynchronous task")
status: str = Field(..., description="Task state: PENDING, STARTED, SUCCESS, FAILURE")
result: Optional[str] = Field(None, description="Model response payload if successful")
error: Optional[str] = Field(None, description="Exception trace details if task failed")
duration_seconds: Optional[float] = Field(None, description="Total execution time")
# 4. Celery Task Definition
@celery_app.task(bind=True, max_retries=3)
def process_llm_job(self, user_id: str, prompt: str, model: str) -> Dict[str, Any]:
"""
Executes the LLM request inside a background worker thread.
Retries automatically on transient API failures.
"""
start_time = time.time()
logger.info(f"Starting Celery task {self.request.id} for user {user_id}")
# Initialize client inside the worker to avoid thread serialization issues
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "mock-key-if-testing"))
try:
# Standard synchronous OpenAI SDK call (executes inside worker process pool)
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "You are a production assistant executing background analysis."},
{"role": "user", "content": prompt}
],
timeout=60.0
)
duration = time.time() - start_time
logger.info(f"Task {self.request.id} completed successfully in {duration:.2f}s")
return {
"result": response.choices[0].message.content,
"duration": round(duration, 2)
}
except Exception as exc:
logger.error(f"Task {self.request.id} encountered exception: {str(exc)}")
# Retry logic for transient network or rate-limiting errors
try:
self.retry(exc=exc, countdown=2 ** self.request.retries)
except self.MaxRetriesExceededError:
logger.error(f"Task {self.request.id} exceeded maximum retries. Marking as failed.")
raise exc
# 5. FastAPI Gateway Endpoints
app = FastAPI(
title="GenAI Asynchronous Gateway",
description="Production-grade API endpoints for queuing and monitoring LLM tasks.",
version="1.0.0"
)
@app.post(
"/v1/jobs",
status_code=status.HTTP_202_ACCEPTED,
response_model=Dict[str, str],
summary="Queue a new LLM task"
)
async def submit_job(payload: JobSubmission):
"""
Submits a task to the Redis broker queue.
Immediately returns a task ID to release the HTTP connection.
"""
try:
# Trigger Celery task asynchronously using .delay()
task = process_llm_job.delay(payload.user_id, payload.prompt, payload.model)
logger.info(f"Enqueued prompt for user {payload.user_id}. Task ID: {task.id}")
return {"task_id": task.id}
except Exception as e:
logger.critical(f"Failed to submit task to broker: {str(e)}")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Message broker offline. Unable to queue task."
)
@app.get(
"/v1/jobs/{task_id}",
response_model=JobStatusResponse,
summary="Get job execution status and results"
)
async def get_job_status(task_id: str):
"""
Queries the Celery state backend to retrieve task progress.
"""
# Query Redis state database
result = celery_app.AsyncResult(task_id)
response = JobStatusResponse(
task_id=task_id,
status=result.status
)
if result.status == "SUCCESS":
task_output = result.get()
response.result = task_output.get("result")
response.duration_seconds = task_output.get("duration")
elif result.status == "FAILURE":
response.error = str(result.result)
return responseRunning the System
- Launch Celery Worker:
OPENAI_API_KEY="your-api-key" celery -A app.celery_app worker --loglevel=info - Launch FastAPI Gateway:
uvicorn app:app --host 0.0.0.0 --port 8000 - Queue a Job:
Response:
curl -X POST http://localhost:8000/v1/jobs \ -H "Content-Type: application/json" \ -d '{"user_id": "usr_94a", "prompt": "Analyze cluster logs for DB anomalies."}'{"task_id": "e89a421b-50f9-4b47-a89c-2bfa1284a7e9"} - Poll for Results:
curl http://localhost:8000/v1/jobs/e89a421b-50f9-4b47-a89c-2bfa1284a7e9
๐ณ 5. Webhook Integration Pattern (Push vs. Polling)
Relying on client-side status polling (i.e. sending regular GET /status/{task_id} queries) introduces significant API gateway load and network overhead, especially under high concurrency. In enterprise systems, developers replace polling loops with Webhook Push Notifications:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Worker Finishes Job โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Query Client Webhook URL โ
โ from Database Registries โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HTTP Client POST Request โ
โ to registered callback โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Client Server Receives โ
โ Payload & Processes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ- Webhook Registration: When submitting the job, the client includes a
webhook_urlin the request schema:{ "user_id": "usr_94a", "prompt": "Evaluate sales statistics.", "webhook_url": "https://api.client.com/webhooks/ai-callback" } - State Management: The gateway registers the task in the database and records the callback coordinates.
- Inference Execution: The worker fetches and executes the task.
- Postback Execution: Once the worker completes processing:
- It queries the state database to retrieve the
webhook_urlmapping. - It triggers an HTTP POST request delivering the execution result directly to the clientโs endpoint.
- It queries the state database to retrieve the
- Signature Verification: The postback request should contain a cryptographic signature header (e.g.
X-Webhook-Signaturecomputed using a shared HMAC secret) to allow the client server to verify that the request originated from the authentic GenAI Gateway and was not tampered with.
๐ 6. Related Sections
- Production GenAI Gateway โ Detailed architecture of request lifecycle filters, model routers, and Pydantic schema validation.
- Agentic Document Workflows (ADW) โ Background indexing, document chunking, and worker integration configurations.
- Observability & Tracing โ Logging token consumption, tracing gateway latencies, and integrating OpenTelemetry.