Blog / Ollama · AI Agents · Node.js

Meerdere Ollama modellen parallel aansturen vanuit één lokale agent

P
Pim vd Molen

Soms werk ik aan projecten waarbij ik een lokale AI agent nodig heb voor meerdere taken tegelijk: code reviewen, documentatie samenvatten én gebruikersvragen beantwoorden. Het probleem is dat één model zelden overal even goed in is. Een model dat uitblinkt in codeanalyse presteert matig bij het verwerken van vrije tekst, en andersom. Wat ik daarom doe is meerdere Ollama modellen parallel aansturen vanuit één centrale agent, zodat elk verzoek terechtkomt bij het model dat er het meeste van maakt.

De architectuur achter een multi-model agent

Ollama draait op http://localhost:11434 en stelt elk geladen model beschikbaar via dezelfde REST API. Dat maakt het verrassend eenvoudig om meerdere modellen naast elkaar te draaien, zolang je machine genoeg geheugen heeft. Op mijn lokale machine draai ik doorgaans codellama:13b voor codetaken en mistral:7b voor algemene tekstvragen. Door ze allebei te laden in Ollama hoef ik niet te switchen: ik stuur gewoon naar een ander model door in mijn aanroep de model parameter te wijzigen.

De centrale agent bestaat uit een Node.js script dat binnenkomende verzoeken analyseert en op basis van het type taak bepaalt welk model de aanroep afhandelt. Dat klinkt ingewikkelder dan het is. De router is eigenlijk weinig meer dan een eenvoudige classificatiestap, waarna het juiste model de eigenlijke verwerking doet. Dat classificeren kan zelf ook door een lichtgewicht model worden gedaan, zodat je geen harde if-else logica hoeft te schrijven die je later toch moet onderhouden.

const MODELS = {
  code: "codellama:13b",
  text: "mistral:7b",
  classify: "phi3:mini"
};

async function callOllama(model, prompt) {
  const response = await fetch("http://localhost:11434/api/generate", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ model, prompt, stream: false })
  });
  const data = await response.json();
  return data.response;
}

Taakclassificatie als eerste stap

Voordat de agent een verzoek doorstuurt, moet hij weten wat voor soort verzoek het is. Daarvoor roep ik phi3:mini aan als classificatiemodel. Het is klein, snel en voldoende nauwkeurig voor een binaire of ternaire classificatie. Het voordeel van een apart classificatiemodel is dat je de routing logica niet hardcoded in je codebase zit: het model beslist op basis van de inhoud, niet op basis van sleutelwoorden die je zelf hebt bedacht.

async function classifyTask(input) {
  const prompt = `
Je krijgt een gebruikersvraag. Antwoord met precies één woord: "code" als de vraag over programmeren gaat, of "text" als het een algemene vraag is.

Vraag: "${input}"
Antwoord:
  `.trim();

  const result = await callOllama(MODELS.classify, prompt);
  const type = result.trim().toLowerCase();
  return MODELS[type] ?? MODELS.text;
}

Dit patroon werkt goed in de praktijk. Zeker als je verzoeken binnenkrijgt via een API die je zelf aanbiedt, wil je de routing flexibel houden. Een collega die via Postman een vraag instuurt over een SQL-query krijgt dan codellama te zien, terwijl een vraag over projectplanning via mistral loopt. Dat hoeft de collega zelf niet te weten.

Parallelle verwerking van meerdere taken

Interessanter wordt het als je niet één verzoek per keer verwerkt, maar meerdere tegelijk. Stel dat ik een batchverwerking bouw waarbij voor elk ingestuurd stuk code zowel een review als een samenvatting in gewone taal moet worden gegenereerd. Dan wil ik die twee aanroepen parallel doen in plaats van sequentieel, want anders wacht alles op elkaar.

async function processCodeInput(userCode) {
  const [review, explanation] = await Promise.all([
    callOllama(MODELS.code, `Geef een korte code review van het volgende:\n\n${userCode}`),
    callOllama(MODELS.text, `Leg in gewone taal uit wat de volgende code doet:\n\n${userCode}`)
  ]);

  return { review, explanation };
}

Door Promise.all te combineren met twee verschillende modellen, verloopt de batchverwerking merkbaar sneller. Op mijn machine scheelt dat bij een gemiddeld verzoek al snel tien tot vijftien seconden per item, afhankelijk van de modelgrootte en invoer. Dat telt op als je honderd fragmenten verwerkt.

Houd er wel rekening mee dat Ollama de modellen in sequence op de GPU laadt als je geen GPU hebt met genoeg VRAM voor meerdere tegelijkertijd. Bij CPU-gebaseerd draaien is parallellisme nog steeds nuttig door de threading van Node.js, maar de winst is minder uitgesproken dan bij een machine met een krachtige GPU. Op mijn MacBook Pro met Apple Silicon werken twee gelijktijdige aanroepen naar verschillende modellen verrassend soepel via het unified memory-systeem.

Een minimale HTTP-wrapper als agent entry point

Om de agent bruikbaar te maken vanuit andere tools, zet ik er een kleine HTTP server omheen. Dat maakt het mogelijk om vanuit Laravel, een CLI tool of zelfs een eenvoudige webpagina aanroepen te doen naar mijn lokale agent zonder rechtstreeks afhankelijk te zijn van de Ollama API zelf.

import http from "http";

const server = http.createServer(async (req, res) => {
  if (req.method !== "POST") {
    res.writeHead(405);
    res.end("Method not allowed");
    return;
  }

  let body = "";
  req.on("data", chunk => (body += chunk));
  req.on("end", async () => {
    try {
      const { input, mode } = JSON.parse(body);
      let result;

      if (mode === "batch") {
        result = await processCodeInput(input);
      } else {
        const model = await classifyTask(input);
        result = { response: await callOllama(model, input) };
      }

      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(result));
    } catch (err) {
      res.writeHead(500);
      res.end(JSON.stringify({ error: err.message }));
    }
  });
});

server.listen(3100, () => console.log("Agent draait op http://localhost:3100"));

Vanuit een Laravel applicatie stuur ik dan een simpele POST naar http://localhost:3100 met de input en de gewenste mode. Dat scheelt enorm in configuratie: de Laravel kant hoeft niets te weten over Ollama, modelversies of classificatielogica. Die verantwoordelijkheid ligt bij de agent.

$response = Http::post('http://localhost:3100', [
    'input' => $userInput,
    'mode'  => 'batch',
]);

$data = $response->json();

Dit patroon gebruik ik ook als ik tijdelijk een AI-feature wil uitproberen in een bestaand project, zonder de productiecodebase te vervuilen. De agent draait lokaal, de integratie is wegwerpbaar, en als ik een ander model wil testen, verander ik de constante bovenin het script en herstart ik de server.

Wat ik zelf nog verder wil verkennen is het opslaan van modelkeuzes per gebruiker of per projectcontext in een kleine SQLite database, zodat de agent na verloop van tijd leert welk model voor welk type vraag het beste scoorde. Dat vereist wat feedbacklogica, maar de basis staat er al. Voor nu werkt de statische classificatie via een klein taalmodel goed genoeg voor dagelijks gebruik, en het voelt een stuk beter dan altijd dezelfde generalist inzetten voor taken die vragen om specialisme.

// VEELGESTELDE VRAGEN