AI Engineering๐Ÿ”„ Integration Patterns
๐Ÿ›ก๏ธ
Running AI agents in production? Harness governs spend, access, and audit trailsโ€”so your team maintains control while agents safely handle production workflows. Visit โ†’

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:

DimensionSynchronous API GatewayAsynchronous Task QueueEvent-Driven Streaming (SSE/WS)Batch Processing
Connection LifecycleBounded (Strict HTTP timeout, e.g., 30s)Unbounded (Immediate response, async processing)Persistent (Active TCP socket session)Offline (Triggered via cron / message event)
Timeout ResiliencePoor (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 ComplexityLow (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 ImpactWait spinner; blocks interactionsPolling progress bars or notificationsImmediate token-by-token renderingBackground updates; zero active user wait
Max Concurrent ScaleBounded by gateway thread pool and connection limitScalable via queue throttling and worker scaleBounded by socket file descriptor limitsScalable 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:alpine

Install the Python dependencies:

pip install fastapi celery redis openai pydantic uvicorn

Complete 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 response

Running the System

  1. Launch Celery Worker:
    OPENAI_API_KEY="your-api-key" celery -A app.celery_app worker --loglevel=info
  2. Launch FastAPI Gateway:
    uvicorn app:app --host 0.0.0.0 --port 8000
  3. Queue a Job:
    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."}'
    Response: {"task_id": "e89a421b-50f9-4b47-a89c-2bfa1284a7e9"}
  4. 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    โ”‚
                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  1. Webhook Registration: When submitting the job, the client includes a webhook_url in the request schema:
    {
      "user_id": "usr_94a",
      "prompt": "Evaluate sales statistics.",
      "webhook_url": "https://api.client.com/webhooks/ai-callback"
    }
  2. State Management: The gateway registers the task in the database and records the callback coordinates.
  3. Inference Execution: The worker fetches and executes the task.
  4. Postback Execution: Once the worker completes processing:
    • It queries the state database to retrieve the webhook_url mapping.
    • It triggers an HTTP POST request delivering the execution result directly to the clientโ€™s endpoint.
  5. Signature Verification: The postback request should contain a cryptographic signature header (e.g. X-Webhook-Signature computed 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.


๐Ÿš€ 10K+ page views in last 7 days
Developer Handbook 2026 ยฉ Exemplar.