Progettazione di sistemi multi-modello: quando un singolo modello non è sufficiente

Scegli il pattern più semplice che funziona.

Indice

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 di progettazione per sistemi LLM multi-model

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

Iscriviti

Ricevi nuovi articoli su sistemi, infrastruttura e ingegneria AI.