Introduction
In the modern enterprise, Large Language Models (LLMs) are no longer just experimental toys; they are critical infrastructure. However, moving from a single-user prototype to a production-grade API serving thousands of concurrent requests presents significant architectural challenges. A scalable LLM system must handle high throughput, manage expensive compute resources efficiently, maintain stateful conversations, and ensure low latency all while integrating complex data sources. Traditional monolithic APIs fail under these conditions due to blocking I/O, lack of intelligent caching, and rigid processing pipelines. To achieve true scalability, we must adopt an asynchronous, event-driven architecture powered by multi-agent orchestration. By decoupling the user interface from the heavy lifting of reasoning and retrieval, we can build systems that scale horizontally and provide consistent performance.
This article details the design and implementation of a scalable Enterprise Knowledge Assistant. We will use FastAPI for high-performance asynchronous handling, LangGraph for stateful multi-agent workflows, Graph RAG for efficient data retrieval, and Redis for distributed memory and caching. The result is a robust Proof of Concept (POC) that demonstrates how to build an LLM API ready for enterprise load.
Real-Time Use Case: Global Supply Chain Intelligence Platform
Consider a multinational logistics company managing thousands of shipments daily. Operations managers need real-time insights into shipment delays, weather impacts, and inventory levels. They ask complex questions like: "What is the status of Shipment #402? If it's delayed by weather, find alternative routes from our Graph database and estimate the new arrival time."
A scalable system must:
Handle Concurrency: Process hundreds of such queries simultaneously without blocking.
Optimize Retrieval: Use Graph RAG to quickly traverse complex supply chain relationships instead of scanning raw text.
Maintain State: Remember previous context if the manager asks follow-up questions.
Cache Responses: Serve identical or similar queries instantly from a cache to reduce LLM costs and latency.
Architecture Overview
FastAPI Backend: Provides non-blocking I/O for handling thousands of concurrent HTTP requests.
LangGraph Orchestrator: Manages the multi-agent workflow (Router, Retriever, Synthesizer) with persistent state.
Graph RAG (Neo4j): Stores supply chain topology (Ports, Ships, Routes) for fast, relationship-aware retrieval.
Redis Layer: Handles distributed session memory (for conversation history) and semantic caching (for repeated queries).
React Frontend: A responsive dashboard for operations managers to interact with the system.
Step-by-Step Implementation
Step 1: Environment Setup
# requirements.txt
fastapi==0.109.0
uvicorn==0.27.0
langgraph==0.2.0
langchain-community==0.3.0
neo4j==5.14.0
redis==5.0.0
pydantic==2.6.0
openai==1.12.0
Step 2: Defining the Scalable State Model
We use Pydantic for validation and TypedDict for LangGraph state.
from typing import TypedDict, List, Optional
from pydantic import BaseModel, Field
class QueryRequest(BaseModel):
query: str = Field(..., description="User question")
session_id: str = Field(..., description="Unique user session for memory")
user_id: str = Field(..., description="Authenticated user ID")
class AgentState(TypedDict):
query: str
session_id: str
retrieved_context: List[dict]
final_answer: str
cache_hit: bool
steps_taken: List[str]
Step 3: Redis Integration for Caching and Memory
Redis is crucial for scalability. It prevents redundant LLM calls and stores conversation history across distributed API instances.
import redis
import json
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def check_cache(query: str) -> Optional[str]:
"""Check if a similar query was recently answered"""
cached = redis_client.get(f"cache:{hash(query)}")
return cached.decode('utf-8') if cached else None
def save_to_cache(query: str, answer: str):
"""Store result with a TTL of 1 hour"""
redis_client.setex(f"cache:{hash(query)}", 3600, answer)
def get_session_memory(session_id: str) -> List[dict]:
"""Retrieve conversation history"""
history = redis_client.get(f"session:{session_id}")
return json.loads(history) if history else []
def update_session_memory(session_id: str, message: dict):
"""Append new message to history"""
key = f"session:{session_id}"
current = get_session_memory(session_id)
current.append(message)
redis_client.setex(key, 86400, json.dumps(current)) # 24h TTL
Step 4: Graph RAG Retrieval Node
Instead of vector similarity alone, we use Neo4j to traverse supply chain relationships.
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
def retrieve_supply_chain_context(query: str) -> List[dict]:
"""Extract entities and query graph for relationships"""
# Simplified: In production, use an LLM to extract 'shipment_id' from query
shipment_id = "SHIP_402"
with driver.session() as session:
result = session.run("""
MATCH (s:Shipment {id: $sid})-[:ON_ROUTE]->(r:Route)-[:CONNECTS]->(p:Port)
RETURN r.status, p.name, r.estimated_delay
""", sid=shipment_id)
return [record.data() for record in result]
Step 5: LangGraph Multi-Agent Workflow
The workflow orchestrates the logic. Note the use of checkpointer for persistence.
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.redis import RedisSaver
llm = ChatOpenAI(model="gpt-4-turbo")
def router_node(state: AgentState) -> AgentState:
"""Check cache first for scalability"""
cached = check_cache(state['query'])
if cached:
state['final_answer'] = cached
state['cache_hit'] = True
state['steps_taken'].append("Cache Hit")
else:
state['cache_hit'] = False
state['steps_taken'].append("Cache Miss - Proceeding to Retrieval")
return state
def retriever_node(state: AgentState) -> AgentState:
if state['cache_hit']:
return state
context = retrieve_supply_chain_context(state['query'])
state['retrieved_context'] = context
state['steps_taken'].append("Graph RAG Retrieval Complete")
return state
def synthesizer_node(state: AgentState) -> AgentState:
if state['cache_hit']:
return state
history = get_session_memory(state['session_id'])
prompt = f"History: {history}\nContext: {state['retrieved_context']}\nQuery: {state['query']}"
response = llm.invoke(prompt).content
state['final_answer'] = response
# Save to cache and memory
save_to_cache(state['query'], response)
update_session_memory(state['session_id'], {"role": "assistant", "content": response})
state['steps_taken'].append("LLM Synthesis Complete")
return state
# Build Graph with Redis Checkpointer for distributed state
checkpointer = RedisSaver()
workflow = StateGraph(AgentState)
workflow.add_node("router", router_node)
workflow.add_node("retriever", retriever_node)
workflow.add_node("synthesizer", synthesizer_node)
workflow.set_entry_point("router")
workflow.add_conditional_edges("router",
lambda s: END if s['cache_hit'] else "retriever")
workflow.add_edge("retriever", "synthesizer")
workflow.add_edge("synthesizer", END)
app = workflow.compile(checkpointer=checkpointer)
Step 6: High-Performance FastAPI Backend
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
api_app = FastAPI(title="Scalable LLM API")
api_app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"])
@api_app.post("/ask-supply-chain")
async def ask_agent(request: QueryRequest):
config = {"configurable": {"thread_id": request.session_id}}
initial_state = AgentState(
query=request.query,
session_id=request.session_id,
retrieved_context=[],
final_answer="",
cache_hit=False,
steps_taken=[]
)
result = await app.ainvoke(initial_state, config=config)
return {
"answer": result['final_answer'],
"metadata": {
"cache_hit": result['cache_hit'],
"steps": result['steps_taken']
}
}
Step 7: React Frontend Dashboard
import React, { useState } from 'react';
import axios from 'axios';
const SupplyChainDashboard = () => {
const [query, setQuery] = useState('');
const [response, setResponse] = useState(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
try {
const res = await axios.post('http://localhost:8000/ask-supply-chain', {
query,
session_id: 'USER_SESSION_001',
user_id: 'OPS_MGR_1'
});
setResponse(res.data);
} catch (error) {
console.error(error);
}
setLoading(false);
};
return (
<div className="p-6 max-w-2xl mx-auto">
<h1 className="text-2xl font-bold mb-4">Supply Chain Intelligence</h1>
<input
className="w-full border p-2 rounded mb-2"
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Ask about shipment status..."
/>
<button onClick={handleSubmit} disabled={loading} className="bg-blue-600 text-white px-4 py-2 rounded">
{loading ? 'Processing...' : 'Get Insights'}
</button>
{response && (
<div className="mt-4 p-4 bg-gray-50 rounded border">
<p className="font-semibold">Answer:</p>
<p>{response.answer}</p>
<div className="mt-2 text-xs text-gray-500">
<p>Cache Hit: {response.metadata.cache_hit ? 'Yes' : 'No'}</p>
<p>Steps: {response.metadata.steps.join(' -> ')}</p>
</div>
</div>
)}
</div>
);
};
export default SupplyChainDashboard;
Conclusion
Designing a scalable LLM API requires moving beyond simple request-response patterns. By leveraging FastAPI for asynchronous concurrency, Redis for distributed caching and memory, and LangGraph for stateful orchestration, we create a system that is both resilient and efficient. The integration of Graph RAG ensures that data retrieval remains fast even as the knowledge base grows. This architecture not only reduces latency and costs through intelligent caching but also provides the structural integrity needed for enterprise-grade AI applications. As demand grows, this system can scale horizontally by adding more API instances and Redis nodes, ensuring consistent performance for global users.
Join the conversation! Your thoughts help the community grow.