AI-skillGratis

LLM-integratie

Gebaseerd op rohitg00/awesome-claude-code-toolkit @ ebdf1d5, licentie Apache-2.0

Bijgewerkt 30 augustus 2026

Dit bestand is door ToolBrain vertaald en inhoudelijk gewijzigd op 2026-08-30, op basis van llm-integration uit rohitg00/awesome-claude-code-toolkit (Apache-2.0), zie https://github.com/rohitg00/awesome-claude-code-toolkit/blob/ebdf1d596d2cde5c5cceb32177e8d1cf4829e7d9/skills/llm-integration/SKILL.md en ../LICENSES/awesome-claude-code-toolkit-APACHE-2.0.txt.

Hoe je een taalmodel technisch aan je applicatie of workflow koppelt, bepaalt zowel de gebruikerservaring (snelheid, kosten) als de betrouwbaarheid (foutafhandeling, output-validatie). Onderstaande patronen gaan uit van de Anthropic API, maar de onderliggende principes (streaming, tool use, RAG, kostenbeheersing) gelden net zo goed voor een n8n-workflow die een LLM-node aanroept.

Basispatroon: één aanroep

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function generateResponse(
  systemPrompt: string,
  userMessage: string,
  options?: { maxTokens?: number; temperature?: number }
): Promise<string> {
  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: options?.maxTokens ?? 1024,
    temperature: options?.temperature ?? 0,
    system: systemPrompt,
    messages: [{ role: "user", content: userMessage }],
  });

  const textBlock = response.content.find(block => block.type === "text");
  return textBlock?.text ?? "";
}

Kies een lage temperatuur (of 0) voor taken die een voorspelbare, herhaalbare output vereisen — denk aan een classificatie in een n8n-flow — en een hogere waarde alleen wanneer variatie gewenst is (bijv. conceptteksten).

Streaming responses

Voor gebruikersgerichte interfaces (een chatvenster, een live-dashboard) voorkomt streaming dat de gebruiker naar een lege pagina staart terwijl het volledige antwoord wordt gegenereerd.

typescript
async function streamResponse(
  messages: Array<{ role: "user" | "assistant"; content: string }>,
  onChunk: (text: string) => void
): Promise<string> {
  const stream = client.messages.stream({
    model: "claude-sonnet-4-20250514",
    max_tokens: 4096,
    messages,
  });

  let fullText = "";

  for await (const event of stream) {
    if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
      onChunk(event.delta.text);
      fullText += event.delta.text;
    }
  }

  return fullText;
}

const response = await streamResponse(
  [{ role: "user", content: "Leg async/await in TypeScript uit" }],
  (chunk) => process.stdout.write(chunk)
);

Function calling (tool use)

Met tool use geef je het model toegang tot je eigen functies. Het model beslist zelf, op basis van de beschrijving, wanneer het een tool aanroept — de agent-lus vangt dat op, voert de tool lokaal uit en stuurt het resultaat terug.

typescript
Toon alle 48 regels
const tools: Anthropic.Tool[] = [
  {
    name: "search_database",
    description: "Doorzoek de productdatabase op naam, categorie of prijsklasse",
    input_schema: {
      type: "object" as const,
      properties: {
        query: { type: "string", description: "Zoekopdracht" },
        category: { type: "string", description: "Productcategorie-filter" },
        max_price: { type: "number", description: "Maximumprijs" },
      },
      required: ["query"],
    },
  },
];

async function agentLoop(userMessage: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage },
  ];

  while (true) {
    const response = await client.messages.create({
      model: "claude-sonnet-4-20250514",
      max_tokens: 4096,
      tools,
      messages,
    });

    if (response.stop_reason === "end_turn") {
      const text = response.content.find(b => b.type === "text");
      return text?.text ?? "";
    }

    const toolUse = response.content.find(b => b.type === "tool_use");
    if (!toolUse || toolUse.type !== "tool_use") break;

    const result = await executeToolCall(toolUse.name, toolUse.input);

    messages.push({ role: "assistant", content: response.content });
    messages.push({
      role: "user",
      content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }],
    });
  }

  return "";
}

RAG-pipeline

Retrieval-Augmented Generation haalt relevante fragmenten uit een eigen kennisbank op en geeft die als context mee aan het model, in plaats van te vertrouwen op wat het model "toevallig" weet.

typescript
Toon alle 36 regels
import { embed } from "./embeddings";

interface Chunk {
  id: string;
  text: string;
  metadata: Record<string, string>;
  embedding: number[];
}

async function retrieveAndGenerate(query: string): Promise<string> {
  const queryEmbedding = await embed(query);

  const relevantChunks = await vectorDb.search({
    vector: queryEmbedding,
    topK: 5,
    filter: { source: "documentation" },
  });

  const context = relevantChunks
    .map((chunk, i) => `[${i + 1}] ${chunk.text}`)
    .join("\n\n");

  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 2048,
    system: `Beantwoord vragen op basis van de gegeven context. Verwijs naar bronnen met [n]-notatie. Staat het antwoord niet in de context, zeg dat dan expliciet.`,
    messages: [
      {
        role: "user",
        content: `Context:\n${context}\n\nVraag: ${query}`,
      },
    ],
  });

  return response.content[0].type === "text" ? response.content[0].text : "";
}

Documenten opknippen (chunking)

typescript
function chunkDocument(
  text: string,
  options: { chunkSize: number; overlap: number }
): string[] {
  const { chunkSize, overlap } = options;
  const chunks: string[] = [];
  const sentences = text.split(/(?<=[.!?])\s+/);
  let current = "";

  for (const sentence of sentences) {
    if (current.length + sentence.length > chunkSize && current.length > 0) {
      chunks.push(current.trim());
      const words = current.split(" ");
      const overlapWords = words.slice(-Math.floor(overlap / 5));
      current = overlapWords.join(" ") + " " + sentence;
    } else {
      current += (current ? " " : "") + sentence;
    }
  }

  if (current.trim()) chunks.push(current.trim());
  return chunks;
}

Kostenbeheersing

typescript
function selectModel(task: TaskType): string {
  switch (task) {
    case "classification":
    case "extraction":
      return "claude-haiku-4-20250514";
    case "analysis":
    case "coding":
      return "claude-sonnet-4-20250514";
    case "complex-reasoning":
      return "claude-opus-4-5-20251101";
    default:
      return "claude-sonnet-4-20250514";
  }
}

Gebruik altijd het kleinste model dat de gevraagde kwaliteit haalt. Cache embeddings en antwoorden waar mogelijk, en batch aanvragen wanneer latency niet kritisch is.

Veelgemaakte fouten

  • Hele documenten meesturen terwijl alleen relevante fragmenten nodig zijn.
  • Geen retry-logica met exponentiële backoff voor API-aanroepen.
  • Tokengebruik niet bijhouden (leidt tot onverwacht hoge kosten).
  • Het duurste model gebruiken voor een simpele classificatietaak.
  • LLM-output niet valideren of saneren vóór verder gebruik in code.
  • Een RAG-pipeline bouwen zonder eerst de kwaliteit van de retrieval te evalueren.

Checklist

  • API-aanroepen zijn voorzien van retry-logica en foutafhandeling
  • Streaming wordt gebruikt voor gebruikersgerichte responses
  • Tool-schema's hebben duidelijke beschrijvingen
  • RAG-chunks hebben een passende grootte (500-1000 tokens) met overlap
  • Modelkeuze is gebaseerd op de complexiteit van de taak
  • Tokengebruik wordt bijgehouden en gemonitord voor kostenbeheersing
  • LLM-output wordt gevalideerd vóór verder gebruik
  • Embeddings worden gecachet om dubbele API-aanroepen te voorkomen

Praktijkvoorbeeld (NL)

Een Nederlandse boekhoudorganisatie voor het MKB bouwt een n8n-workflow die inkomende inkoopfacturen via e-mail classificeert en samenvat voordat een medewerker ze in het boekhoudpakket verwerkt. Voor de classificatie ("welke kostensoort, welke crediteur") gebruikt de workflow bewust het goedkoopste model, omdat het een simpele extractietaak is; alleen wanneer de factuur een afwijkend patroon vertoont (bijvoorbeeld een creditfactuur of een bedrag boven 5.000 euro) schakelt de flow over naar een zwaarder model voor een uitgebreidere analyse. De geëxtraheerde velden worden altijd tegen een schema gevalideerd voordat ze automatisch in Moneybird terechtkomen, zodat een verzonnen IBAN of een fout BTW-percentage nooit ongezien doorstroomt naar de boeking.