The Limits of Monolithic AI
For the first year of building AI products, our architecture was simple: send everything to GPT-4 and call it a day. It worked - until it didn't.
The breaking point came when we needed to:
- •Process 500-page legal documents (context window limits)
- •Generate code that actually compiles (GPT-4 hallucinates syntax)
- •Analyze financial data with precision (LLMs struggle with numbers)
- •Respond in under 500ms for real-time features (GPT-4 is slow)
No single model excels at everything. The solution? Multi-model orchestration - intelligently combining specialized models to get the best of all worlds.
The Multi-Model Architecture
Our production system uses five model categories:
| Category | Models | Strengths | Typical Use |
|---|---|---|---|
| Reasoning | GPT-4, Claude 3 Opus | Complex analysis, nuance | Document analysis, synthesis |
| Speed | GPT-3.5, Claude Instant | Fast responses | Chat, simple queries |
| Code | CodeLlama, StarCoder | Syntax accuracy | Code generation, review |
| Math | Wolfram, specialized fine-tunes | Numerical precision | Calculations, data analysis |
| Embedding | text-embedding-3, Cohere | Semantic understanding | Search, classification |
Pattern 1: Intelligent Routing
The simplest multi-model pattern. Classify the request, route to the appropriate model.
The Router
We use a lightweight classifier to route requests:
interface RoutingDecision {
model: string;
confidence: number;
reasoning: string;
}
class ModelRouter {
private classifier: FastClassifier;
private modelRegistry: Map<string, ModelConfig>;
constructor() {
this.classifier = new FastClassifier({
model: 'distilbert-routing',
labels: ['reasoning', 'speed', 'code', 'math', 'embedding']
});
this.modelRegistry = new Map([
['reasoning', { model: 'gpt-4-turbo', maxTokens: 4096 }],
['speed', { model: 'gpt-3.5-turbo', maxTokens: 2048 }],
['code', { model: 'codellama-34b', maxTokens: 4096 }],
['math', { model: 'gpt-4-turbo', maxTokens: 1024, systemPrompt: mathPrompt }],
['embedding', { model: 'text-embedding-3-large' }]
]);
}
async route(request: AIRequest): Promise<RoutingDecision> {
// Fast classification (< 10ms)
const classification = await this.classifier.classify(request.text);
// Apply business rules
const adjusted = this.applyRules(request, classification);
return {
model: this.modelRegistry.get(adjusted.label)!.model,
confidence: adjusted.confidence,
reasoning: this.explainRouting(request, adjusted)
};
}
private applyRules(
request: AIRequest,
classification: Classification
): Classification {
// Rule 1: Long context always goes to reasoning models
if (request.text.length > 10000) {
return { label: 'reasoning', confidence: 0.95 };
}
// Rule 2: Code blocks suggest code task
if (request.text.includes('function') || request.text.includes('class ')) {
if (classification.label !== 'code') {
return { label: 'code', confidence: 0.85 };
}
}
// Rule 3: Numbers and calculations suggest math
const numberDensity = (request.text.match(/\d+/g) || []).length /
request.text.split(' ').length;
if (numberDensity > 0.3) {
return { label: 'math', confidence: 0.8 };
}
// Rule 4: Real-time requests need speed
if (request.priority === 'realtime' && classification.label === 'reasoning') {
return { label: 'speed', confidence: 0.9 };
}
return classification;
}
}
Routing Accuracy
After training on 50K labeled requests:
| True Category | Routing Accuracy | Common Mistakes |
|---|---|---|
| Reasoning | 94% | Misrouted to speed (4%) |
| Speed | 97% | Misrouted to reasoning (2%) |
| Code | 89% | Misrouted to reasoning (8%) |
| Math | 86% | Misrouted to speed (10%) |
Pattern 2: Sequential Pipeline
For complex tasks, chain multiple models together:
interface PipelineStage {
name: string;
model: string;
transform: (input: string, context: PipelineContext) => Promise<string>;
}
class ModelPipeline {
private stages: PipelineStage[];
constructor(stages: PipelineStage[]) {
this.stages = stages;
}
async execute(input: string): Promise<PipelineResult> {
const context: PipelineContext = {
originalInput: input,
intermediateResults: [],
metadata: {}
};
let current = input;
for (const stage of this.stages) {
const startTime = Date.now();
try {
current = await stage.transform(current, context);
context.intermediateResults.push({
stage: stage.name,
output: current,
duration: Date.now() - startTime
});
} catch (error) {
return {
success: false,
failedStage: stage.name,
error,
partialResults: context.intermediateResults
};
}
}
return {
success: true,
finalOutput: current,
stages: context.intermediateResults
};
}
}
Real Example: Document Analysis Pipeline
const documentAnalysisPipeline = new ModelPipeline([
{
name: 'extract',
model: 'gpt-3.5-turbo',
transform: async (doc) => {
// Fast extraction of key sections
return await llm.complete({
model: 'gpt-3.5-turbo',
prompt: 'Extract the key sections from this document: ' + doc,
maxTokens: 2000
});
}
},
{
name: 'analyze',
model: 'gpt-4-turbo',
transform: async (sections, context) => {
// Deep analysis with reasoning model
return await llm.complete({
model: 'gpt-4-turbo',
prompt: 'Analyze these document sections for key insights: ' + sections,
maxTokens: 4000
});
}
},
{
name: 'summarize',
model: 'claude-3-sonnet',
transform: async (analysis) => {
// Clear summary with Claude
return await llm.complete({
model: 'claude-3-sonnet',
prompt: 'Create an executive summary: ' + analysis,
maxTokens: 500
});
}
}
]);
Pipeline Performance
| Approach | Quality Score | Latency | Cost |
|---|---|---|---|
| GPT-4 only | 8.2/10 | 12.4s | $0.089 |
| Pipeline | 8.7/10 | 8.1s | $0.052 |
Pattern 3: Ensemble Voting
For high-stakes decisions, run multiple models and aggregate:
interface EnsembleConfig {
models: ModelWeight[];
aggregation: 'majority' | 'weighted' | 'unanimous';
minAgreement: number;
}
interface ModelWeight {
model: string;
weight: number;
}
class EnsembleOrchestrator {
constructor(private config: EnsembleConfig) {}
async decide(prompt: string): Promise<EnsembleResult> {
// Run all models in parallel
const responses = await Promise.all(
this.config.models.map(async (m) => ({
model: m.model,
weight: m.weight,
response: await this.callModel(m.model, prompt)
}))
);
// Extract decisions
const decisions = responses.map(r => ({
...r,
decision: this.extractDecision(r.response)
}));
// Aggregate based on strategy
const result = this.aggregate(decisions);
return {
decision: result.decision,
confidence: result.confidence,
agreement: result.agreement,
individualResponses: decisions
};
}
private aggregate(decisions: Decision[]): AggregatedResult {
if (this.config.aggregation === 'majority') {
return this.majorityVote(decisions);
}
if (this.config.aggregation === 'weighted') {
return this.weightedVote(decisions);
}
if (this.config.aggregation === 'unanimous') {
return this.unanimousVote(decisions);
}
throw new Error('Unknown aggregation: ' + this.config.aggregation);
}
private weightedVote(decisions: Decision[]): AggregatedResult {
const scores: Map<string, number> = new Map();
let totalWeight = 0;
for (const d of decisions) {
const current = scores.get(d.decision) || 0;
scores.set(d.decision, current + d.weight);
totalWeight += d.weight;
}
let maxDecision = '';
let maxScore = 0;
scores.forEach((score, decision) => {
if (score > maxScore) {
maxScore = score;
maxDecision = decision;
}
});
return {
decision: maxDecision,
confidence: maxScore / totalWeight,
agreement: decisions.filter(d => d.decision === maxDecision).length / decisions.length
};
}
}
Ensemble for Content Moderation
We use ensembles for content moderation where false positives are costly:
const moderationEnsemble = new EnsembleOrchestrator({
models: [
{ model: 'gpt-4-turbo', weight: 0.4 },
{ model: 'claude-3-opus', weight: 0.35 },
{ model: 'moderation-fine-tuned', weight: 0.25 }
],
aggregation: 'weighted',
minAgreement: 0.6
});
// Usage
const result = await moderationEnsemble.decide(
'Evaluate if this content violates our guidelines: ' + userContent
);
if (result.decision === 'violates' && result.confidence > 0.8) {
await flagContent(userContent);
} else if (result.decision === 'violates' && result.confidence > 0.5) {
await queueForHumanReview(userContent);
}
Ensemble Accuracy
| Approach | Precision | Recall | F1 Score |
|---|---|---|---|
| GPT-4 alone | 91% | 87% | 89% |
| Claude alone | 89% | 90% | 89.5% |
| Fine-tuned alone | 94% | 82% | 87.5% |
| Ensemble | 95% | 91% | 93% |
Pattern 4: Speculative Execution
Start with a fast model, verify with a slower one:
class SpeculativeExecutor {
constructor(
private fastModel: string,
private verifyModel: string,
private verifyThreshold: number
) {}
async execute(prompt: string): Promise<SpeculativeResult> {
// Start fast model
const fastStart = Date.now();
const fastResponse = await llm.complete({
model: this.fastModel,
prompt,
maxTokens: 1000
});
const fastDuration = Date.now() - fastStart;
// Quick confidence check
const needsVerification = await this.shouldVerify(prompt, fastResponse);
if (!needsVerification) {
return {
response: fastResponse,
verified: false,
model: this.fastModel,
duration: fastDuration
};
}
// Verify with slower model
const verifyStart = Date.now();
const verifyResponse = await llm.complete({
model: this.verifyModel,
prompt: this.buildVerifyPrompt(prompt, fastResponse),
maxTokens: 1500
});
const agreement = await this.checkAgreement(fastResponse, verifyResponse);
if (agreement > this.verifyThreshold) {
return {
response: fastResponse,
verified: true,
model: this.fastModel,
duration: Date.now() - fastStart
};
}
// Models disagree - use verified response
return {
response: verifyResponse,
verified: true,
model: this.verifyModel,
duration: Date.now() - fastStart,
disagreement: true
};
}
private async shouldVerify(prompt: string, response: string): Promise<boolean> {
// Verify if: high stakes, low confidence indicators, complex topic
const indicators = [
prompt.toLowerCase().includes('important'),
prompt.toLowerCase().includes('critical'),
response.includes('I think') || response.includes('probably'),
prompt.length > 2000
];
return indicators.filter(Boolean).length >= 2;
}
}
Speculative Execution Results
| Scenario | Fast-only | Verify-all | Speculative |
|---|---|---|---|
| Accuracy | 89% | 96% | 95% |
| Avg Latency | 340ms | 2100ms | 680ms |
| Cost | $0.002 | $0.018 | $0.006 |
Pattern 5: Model Fusion
Combine model outputs into something better than either alone:
interface FusionConfig {
models: string[];
fusionStrategy: 'merge' | 'select' | 'synthesize';
fusionModel: string;
}
class ModelFusion {
constructor(private config: FusionConfig) {}
async fuse(prompt: string): Promise<FusionResult> {
// Get responses from all models
const responses = await Promise.all(
this.config.models.map(model =>
llm.complete({ model, prompt, maxTokens: 2000 })
)
);
if (this.config.fusionStrategy === 'synthesize') {
// Use a model to synthesize the best response
const synthesisPrompt = this.buildSynthesisPrompt(prompt, responses);
const synthesized = await llm.complete({
model: this.config.fusionModel,
prompt: synthesisPrompt,
maxTokens: 2500
});
return {
response: synthesized,
sources: responses,
strategy: 'synthesize'
};
}
// ... other strategies
}
private buildSynthesisPrompt(prompt: string, responses: string[]): string {
return 'Original question: ' + prompt +
'\n\nResponse A: ' + responses[0] +
'\n\nResponse B: ' + responses[1] +
'\n\nSynthesize the best possible response combining insights from both.';
}
}
Fusion for Creative Tasks
Model fusion shines for creative tasks where different models have different "styles":
| Task | GPT-4 | Claude | Fused |
|---|---|---|---|
| Marketing copy | 7.8/10 | 8.2/10 | 8.9/10 |
| Technical writing | 8.5/10 | 8.1/10 | 8.8/10 |
| Story generation | 7.2/10 | 8.4/10 | 8.7/10 |
Orchestration in Production
Here's how we tie it all together:
class AIOrchestrator {
private router: ModelRouter;
private pipelines: Map<string, ModelPipeline>;
private ensembles: Map<string, EnsembleOrchestrator>;
private speculative: SpeculativeExecutor;
async process(request: AIRequest): Promise<AIResponse> {
// 1. Determine the orchestration strategy
const strategy = this.selectStrategy(request);
switch (strategy) {
case 'simple':
// Direct routing for simple requests
const routing = await this.router.route(request);
return this.directCall(routing.model, request);
case 'pipeline':
// Multi-stage pipeline for complex analysis
const pipelineId = this.selectPipeline(request);
return this.pipelines.get(pipelineId)!.execute(request.text);
case 'ensemble':
// Voting for high-stakes decisions
const ensembleId = this.selectEnsemble(request);
return this.ensembles.get(ensembleId)!.decide(request.text);
case 'speculative':
// Fast with verification for medium-stakes
return this.speculative.execute(request.text);
default:
throw new Error('Unknown strategy: ' + strategy);
}
}
private selectStrategy(request: AIRequest): OrchestrationType {
if (request.flags?.highStakes) return 'ensemble';
if (request.flags?.complex) return 'pipeline';
if (request.flags?.realtime) return 'speculative';
return 'simple';
}
}
Key Takeaways
- 1No single model wins everything - Build systems that leverage each model's strengths.
- 2Routing is the foundation - Good routing makes everything else work better.
- 3Pipelines beat monoliths - Chain specialized models for complex tasks.
- 4Ensembles for high stakes - When accuracy matters, use multiple models.
- 5Speculative execution balances speed and quality - Fast by default, verify when needed.
- 6Fusion creates new capabilities - Combined outputs often exceed individual models.
The future of AI isn't about picking the "best" model - it's about orchestrating many models to create systems more capable than any individual component.
