Progettazione di sistemi multi-modello: quando un singolo modello non è sufficiente
Scegli il pattern più semplice che funziona.
I sistemi single-model sono semplici. I sistemi multi-model sono potenti. La sfida non è scegliere i modelli, ma progettare l’architettura che li orchestra.
Un sistema multi-model non significa avere più modelli. Significa avere il modello giusto per il compito giusto al momento giusto.

Pattern architetturali
Cinque pattern coprono la maggior parte dei casi d’uso:
| Pattern | Complessità | Quando utilizzarlo | Compromesso |
|---|---|---|---|
| Single Model | Minima | Prototipazione, compiti semplici | Capacità limitata |
| Sequenziale | Bassa | Flussi di lavoro multi-step | Latenza maggiore |
| Parallelo | Media | Compiti indipendenti | Costo maggiore |
| Gerarchico | Alta | Ragionamento complesso | Orchestrazione complessa |
| Ensemble | Massima | Decisioni critiche | Costo massimo |
Scegli il più semplice che funzioni. La complessità è reale e si accumula.
Architettura sequenziale
Elabora i compiti attraverso una catena di modelli, ciascuno specializzato in un passaggio.
Pattern 1: Pipeline
Pattern Pipeline — l’output di ogni modello alimenta il successivo:
class ModelPipeline:
def __init__(self):
self.models = [
{"model": "qwen2.5-1.5b", "task": "classify"},
{"model": "qwen2.5-7b", "task": "extract"},
{"model": "qwen2.5-32b", "task": "reason"},
]
def process(self, input: str) -> str:
current = input
for model_config in self.models:
current = self.call_model(
model_config["model"],
self.create_prompt(model_config["task"], current)
)
return current
La latenza si accumula. Tre modelli in sequenza significano triplice latenza. Usa questo approccio solo quando ogni passaggio richiede effettivamente un modello diverso.
Pattern 2: Router
Pattern Router — classifica il compito, indirizza al specialista:
class ModelRouter:
def __init__(self):
self.classifier = "qwen2.5-1.5b"
self.specialists = {
"code": "qwen2.5-coder-7b",
"math": "qwen2.5-32b",
"creative": "claude-sonnet-4",
"general": "qwen2.5-7b",
}
def route(self, prompt: str) -> str:
task_type = self.classify(prompt)
model = self.specialists.get(task_type, self.specialists["general"])
return self.call_model(model, prompt)
Il classificatore è il punto debole. Se classifica male, indirizzi il compito al modello sbagliato e perdi qualità. Usa un classificatore sufficientemente valido — anche uno piccolo funziona se le categorie sono chiare.
Architettura parallela
Elabora compiti indipendenti simultaneamente.
Pattern 1: Fan-Out
Fan-out — esegui lo stesso prompt attraverso più modelli:
import asyncio
class ModelFanOut:
def __init__(self):
self.models = [
"qwen2.5-7b",
"qwen2.5-32b",
"claude-sonnet-4",
]
async def process(self, prompt: str) -> list[str]:
tasks = [self.call_model(model, prompt) for model in self.models]
return await asyncio.gather(*tasks)
Utile per il confronto, test A/B, o quando vuoi selezionare il miglior output. Costoso, ma il guadagno in qualità ne vale la pena per decisioni critiche.
Pattern 2: Voting
Voting — combina gli output attraverso il consenso:
class ModelVoting:
def __init__(self):
self.models = [
"qwen2.5-7b",
"qwen2.5-32b",
"claude-sonnet-4",
]
def vote(self, prompt: str) -> str:
responses = [self.call_model(model, prompt) for model in self.models]
from collections import Counter
votes = Counter(responses)
return votes.most_common(1)[0][0]
Il voto di maggioranza funziona per la classificazione. Per i compiti di generazione è più difficile — serve similarità semantica, non corrispondenze esatte.
Architettura gerarchica
Utilizza modelli a diversi livelli di astrazione.
Pattern 1: Planner-Executor
Planner-executor — un modello forte pianifica, modelli più piccoli eseguono:
class PlannerExecutor:
def __init__(self):
self.planner = "qwen2.5-32b"
self.executors = {
"code": "qwen2.5-coder-7b",
"search": "qwen2.5-7b",
"math": "qwen2.5-7b",
}
def process(self, task: str) -> str:
plan = self.call_model(self.planner, f"Plan: {task}")
results = []
for step in self.parse_plan(plan):
executor = self.executors.get(step["type"], "qwen2.5-7b")
result = self.call_model(executor, step["prompt"])
results.append(result)
return self.call_model(self.planner, f"Synthesize: {results}")
Il planner fa il lavoro pesante. Gli executor gestiscono compiti specifici. Questo pattern funziona bene quando il passo di pianificazione è costoso ma i passi di esecuzione sono economici.
Pattern 2: Supervisor-Worker
Supervisor-worker — un supervisore delega e revisiona:
class SupervisorWorker:
def __init__(self):
self.supervisor = "qwen2.5-32b"
self.workers = ["qwen2.5-7b", "qwen2.5-coder-7b"]
def process(self, task: str) -> str:
assignments = self.call_model(self.supervisor, f"Assign: {task}")
results = []
for assignment in self.parse_assignments(assignments):
result = self.call_model(
assignment["worker"], assignment["task"]
)
results.append(result)
return self.call_model(self.supervisor, f"Review: {results}")
Il supervisore è il collo di bottiglia. Pianifica, delega e revisiona. Assicurati che sia sufficiently veloce, altrimenti l’intero sistema rallenta.
Architettura Ensemble
Combina più modelli per decisioni critiche.
Pattern 1: Weighted Ensemble
Ensemble pesato — valuta l’output di ogni modello, scegli il più alto:
class WeightedEnsemble:
def __init__(self):
self.models = {
"qwen2.5-32b": 0.5,
"claude-sonnet-4": 0.3,
"qwen2.5-7b": 0.2,
}
def decide(self, prompt: str) -> str:
responses = {
model: self.call_model(model, prompt)
for model in self.models
}
scores = {}
for model, response in responses.items():
score = self.evaluate(response) * self.models[model]
scores[response] = scores.get(response, 0) + score
return max(scores, key=scores.get)
I pesi riflettono la tua fiducia in ciascun modello. Aggiustali in base alle prestazioni reali, non ai benchmark.
Pattern 2: Consensus Ensemble
Ensemble di consenso — richiedi accordo, escalare se non c’è:
class ConsensusEnsemble:
def __init__(self, threshold: float = 0.7):
self.threshold = threshold
self.models = [
"qwen2.5-32b",
"claude-sonnet-4",
"qwen2.5-7b",
]
def decide(self, prompt: str) -> str:
responses = [
self.call_model(model, prompt)
for model in self.models
]
from collections import Counter
votes = Counter(responses)
max_votes = max(votes.values())
if max_votes / len(self.models) >= self.threshold:
return votes.most_common(1)[0][0]
return self.call_model("qwen2.5-32b", prompt)
La soglia controlla quanto è stretto il consenso. 0.7 significa accordo di due terzi. Abbassala per decisioni più rapide, alzala per maggiore fiducia.
Quando i sistemi multi-model hanno senso
I sistemi multi-model hanno senso quando hai carichi di lavoro misti, hai bisogno di alta qualità per decisioni critiche, o stai ottimizzando per costo o latenza.
Non hanno senso quando tutti i compiti hanno complessità simile, stai prototipando, o la semplicità è più importante dell’ottimizzazione.
La regola pratica: inizia con un modello. Aggiungi altri quando incontri un vincolo reale — costo, latenza o qualità. Non architetti complessità prima di averne bisogno.
Compromessi
| Pattern | Costo | Latenza | Qualità | Complessità |
|---|---|---|---|---|
| Single Model | Minimo | Minima | Variabile | Minima |
| Sequenziale | Medio | Alta | Alta | Media |
| Parallelo | Alto | Bassa | Alta | Media |
| Gerarchico | Alto | Alta | Massima | Alta |
| Ensemble | Massimo | Media | Massima | Massima |
Ogni pattern sacrifica qualcosa. Scegli quello che si adatta ai tuoi vincoli.
Correlati
- Strategie di Routing dei Modelli — routing basato su capacità, consapevolezza del costo, consapevolezza della latenza
- Ottimizzazione dei Costi per Sistemi LLM — budgeting dei token, modelli di fallback, caching
- Guardrails LLM nella Pratica — validazione input, filtraggio output, sicurezza
- Architettura LLM — pilastro della progettazione del sistema: routing, costo, guardrails e orchestrazione