Progettazione di Sistemi Multi-Modello: Quando un Solo Modello Non Basta
Scegli il pattern più semplice che funzioni.
I sistemi single-model sono semplici. I sistemi multi-model sono potenti. La sfida non consiste nel scegliere i modelli, ma nel 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 utilizzare | Compromesso |
|---|---|---|---|
| Single Model | Minima | Prototipazione, compiti semplici | Capacità limitata |
| Sequential | Bassa | Flussi di lavoro multi-step | Latenza maggiore |
| Parallel | Media | Compiti indipendenti | Costo maggiore |
| Hierarchical | 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 ciascun modello alimenta il successivo:
class ModelPipeline:
def __init__(self):
self.models = [
{"model": "qwen3-1.7b", "task": "classify"},
{"model": "qwen3-8b", "task": "extract"},
{"model": "qwen3-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 tre volte la latenza. Utilizza questo approccio solo se ogni passaggio richiede effettivamente un modello diverso.
Pattern 2: Router
Pattern Router — classifica il compito, indirizzalo allo specialista:
class ModelRouter:
def __init__(self):
self.classifier = "qwen3-1.7b"
self.specialists = {
"code": "qwen2.5-coder-7b",
"math": "qwen3-32b",
"creative": "claude-sonnet-4",
"general": "qwen3-8b",
}
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à. Utilizza un classificatore sufficientemente buono: 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 = [
"qwen3-8b",
"qwen3-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, per test A/B o quando si desidera selezionare il miglior output. È costoso, ma il guadagno in qualità ne vale la pena per le decisioni critiche.
Pattern 2: Voting
Voting — combina gli output attraverso il consenso:
class ModelVoting:
def __init__(self):
self.models = [
"qwen3-8b",
"qwen3-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 potente pianifica, modelli più piccoli eseguono:
class PlannerExecutor:
def __init__(self):
self.planner = "qwen3-32b"
self.executors = {
"code": "qwen2.5-coder-7b",
"search": "qwen3-8b",
"math": "qwen3-8b",
}
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"], "qwen3-8b")
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 = "qwen3-32b"
self.workers = ["qwen3-8b", "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 abbastanza veloce, altrimenti l’intero sistema rallenta.
Architettura Ensemble
Combina più modelli per decisioni critiche.
Pattern 1: Weighted Ensemble
Ensemble pesato — valuta l’output di ciascun modello, scegli il punteggio più alto:
class WeightedEnsemble:
def __init__(self):
self.models = {
"qwen3-32b": 0.5,
"claude-sonnet-4": 0.3,
"qwen3-8b": 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. Regolali in base alle prestazioni effettive, non ai benchmark.
Pattern 2: Consensus Ensemble
Ensemble di consenso — richiedi accordo, escali se non c’è:
class ConsensusEnsemble:
def __init__(self, threshold: float = 0.7):
self.threshold = threshold
self.models = [
"qwen3-32b",
"claude-sonnet-4",
"qwen3-8b",
]
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("qwen3-32b", prompt)
La soglia controlla quanto è rigoroso il consenso. 0.7 significa accordo di due terzi. Abbassala per decisioni più rapide, alzala per maggiore sicurezza.
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 una complessità simile, stai prototipando o la semplicità è più importante dell’ottimizzazione.
La regola empirica: inizia con un modello. Aggiungine altri quando incontri un vincolo reale — costo, latenza o qualità. Non architetturi complessità prima di averne bisogno.
Compromessi
| Pattern | Costo | Latenza | Qualità | Complessità |
|---|---|---|---|---|
| Single Model | Minimo | Minima | Variabile | Minima |
| Sequential | Medio | Alta | Alta | Media |
| Parallel | Alto | Bassa | Alta | Media |
| Hierarchical | 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à, consapevole del costo e della latenza
- Ottimizzazione dei Costi per Sistemi LLM — budgeting dei token, modelli di fallback, caching
- Guardrails per LLM in Pratica — convalida degli input, filtraggio degli output, sicurezza
- Architettura LLM — pilastro della progettazione del sistema: routing, costi, guardrails e orchestrazione