n8n AI-agents
Gebaseerd op czlonkowski/n8n-skills @ 72470a0, licentie MIT
Dit bestand is door ToolBrain vertaald en inhoudelijk gewijzigd op 2026-08-30, op basis van
n8n-agents SKILL.md uit czlonkowski/n8n-skills (MIT), zie de bron_url hierboven en
../LICENSES/n8n-skills-MIT.txt.
De n8n AI Agent-node (@n8n/n8n-nodes-langchain.agent) is een meerstaps-LLM-motor met sub-nodes
voor model, memory, tools en een optionele output-parser. Dit is de diepgaande gids voor het
goed ontwerpen van agents en de bijbehorende LangChain-familie. Voor het grotere plaatje van
waar een agent past binnen een workflow: zie het item over n8n workflow-patronen (het
AI-agent-patroon). Deze gids gaat een niveau dieper: hoe bouw je een agent die daadwerkelijk goed
werkt.
Let op de nodenotatie: in workflow-JSON gebruiken de LangChain-nodes de lange vorm
(@n8n/n8n-nodes-langchain.agent, .lmChatOpenAi, .memoryBufferWindow,
.outputParserStructured, .toolWorkflow, .toolHttpRequest, .toolCode). Bij het opzoeken of
valideren van een nodetype gebruik je juist de korte vorm (nodes-langchain.agent).
Kies eerst de juiste node
De Agent-node pakken voor een taak die eigenlijk one-shot classificatie of extractie is, is de meest voorkomende overbouw. Beslis vooraf:
| Je hebt nodig… | Gebruik | Waarom |
|---|---|---|
| Tools aanroepen, over meerdere beurten redeneren, of memory | AI Agent (.agent) | De volledige lus: model + tools + memory + optionele parser. |
| One-shot tekst in → tekst uit, geen tools | Basic LLM Chain (.chainLlm) | Geen agentlus, makkelijker te debuggen. |
| Vrije tekst routeren naar één van N takken | Text Classifier (.textClassifier) | Eén node, N outputs, downstream direct aangesloten op elke tak. Niet Agent + Switch. |
| Gestructureerde velden uit vrije tekst halen | Information Extractor (.informationExtractor) | Speciaal gebouwd voor veldextractie met een schema. |
| 3-weg positief/neutraal/negatief-split | Sentiment Analysis (.sentimentAnalysis) | Ingebouwde tak-outputs. |
| Lang document samenvatten | Summarization Chain (.chainSummarization) | Map-reduce-samenvatting ingebouwd. |
| Afbeelding/audio/video genereren | De native single-call-node van de provider (OpenAI, Gemini, ElevenLabs…) | Wikkel mediageneratie NOOIT in een Agent — zie "Binaire data en de agent-grens" hieronder. |
Text Classifier in detail (het Agent+Switch-antipatroon): elke categorie heeft zowel een
naam ALS een beschrijving nodig. Het model routeert op basis van de beschrijving, niet de
naam — een categorie zonder beschrijving wordt in feite met een muntje gekozen. Zet
options.enableAutoFixing: true aan voor robuustheid bij randgevallen. Eén node, N takken, klaar.
Een Agent laten "beslissen" gevolgd door een Switch die "routeert" is twee nodes plus
prompt-overhead voor iets wat Text Classifier native doet.
Chatmodel-nodes (.lmChatOpenAi, .lmChatAnthropic, .lmChatOpenRouter, …) zijn sub-nodes —
ze draaien niet zelfstandig. Ze worden bedraad in een chain, agent, classifier of extractor via
de ai_languageModel-verbinding.
Het sub-node-patroon
De Agent heeft een hoofdinput (de prompt/het gebruikersbericht) en tot vier
sub-node-slots, elk bedraad via zijn eigen ai_*-verbindingstype:
| Slot | Verbindingstype | Verplicht? | Voorbeeldnode |
|---|---|---|---|
| model | ai_languageModel | Ja | .lmChatOpenAi, .lmChatAnthropic, .lmChatOpenRouter |
| memory | ai_memory | Optioneel | .memoryBufferWindow, .memoryPostgresChat |
| tools | ai_tool | Optioneel (maar wel het hele punt van een agent) | slackTool, .toolWorkflow, .toolHttpRequest, .toolCode |
| outputParser | ai_outputParser | Optioneel | .outputParserStructured |
Een sub-node verbindt VAN zichzelf NAAR de agent — in de workflow-JSON zit de verbinding op de
sub-node, gesleuteld op het ai_*-type. Meerdere tools verbinden allemaal naar dezelfde
ai_tool-index 0 — ze stapelen, ze splitsen niet naar aparte indices. Het eindantwoord van de
agent komt in $json.output terecht (niet .text, niet .response) — downstream-nodes
lezen {{ $json.output }}.
Twee niet-onderhandelbare regels
- Tool-namen en -beschrijvingen ZIJN onderdeel van de prompt. Het model kiest een tool
uitsluitend op basis van naam en beschrijving. Een tool genaamd
tool1met een lege beschrijving is voor het model onzichtbaar: het slaat 'm over, kiest verkeerd, of verzint parameters. Er is meestal geen foutmelding — gewoon een agent die "mijn tool niet gebruikt". Behandel dit als API-ontwerp. - Gestructureerde output moet parsen ÉN auto-fixen. Een
outputParserStructuredmetautoFix: trueen een code-vaardig fix-model is het productiepatroon. Zonder autoFix stopt één misvormde JSON-respons de hele workflow.
Sterke standaardkeuzes
- Per-tool-gebruiksinstructies horen in de toolbeschrijving, niet in de systeemprompt. Alles over hoe je deze specifieke tool aanroept hoort bij de tool zelf, zodat het meereist naar andere agents en de systeemprompt gefocust blijft.
- Sub-workflow-tools (
.toolWorkflow) voor alles wat meerdere stappen kost. Elke workflow wordt een tool met getypeerde$fromAI()-inputs, en combineert met vertakking, foutafhandeling en hergebruik. Kies dit als standaard bij twijfel. - Wikkel tools met gebruikers-zichtbare effecten in menselijke review. Verzendacties, betalingen, terugbetalingen, accountwijzigingen — hang er een goedkeuringsnode voor zodat een mens akkoord geeft voordat de tool afvuurt.
- Verhoog
maxIterations. De standaard tool-call-limiet is laag — prima voor een agent met één tool, veel te laag voor een multi-tool-agent die meerdere calls per beurt aan elkaar rijgt. Dit uit zich als "max iterations reached" of lege output. Zetoptions.maxIterationsop een realistisch plafond (15 voor een gefocuste sub-agent, 50-200 voor een brede orkestrator). - Zet de actuele datum in de systeemprompt via een dynamische datumexpressie. Een hardgecodeerde datum is meteen verouderd.
De vier toolsoorten
Kies de lichtste optie die de klus klaart:
| Toolsoort | Node | Gebruik wanneer |
|---|---|---|
| Native tool-node | slackTool, gmailTool, toolCalculator, … | De capaciteit komt overeen met precies één bestaande node + operatie. Laagste overhead. |
| Sub-workflow als tool | .toolWorkflow | Meer dan één node, herbruikbare logica, of je wilt onafhankelijk kunnen testen. De kanonieke n8n-manier — standaardkeuze bij twijfel. |
| HTTP Request Tool | .toolHttpRequest | Eén externe HTTP-API die de agent zelf moet orkestreren. Hergebruik de vooraf gedefinieerde credential van de dienst. |
| MCP Client Tool | .mcpClientTool | Een onderhouden MCP-server dekt het al, of je wilt één gepubliceerde workflow laten dienen voor meerdere agents. |
Er is ook een Custom Code Tool (.toolCode) voor pure inline berekeningen — maar zijn
runtime-contract (string in / string uit, geen $fromAI, geen $helpers) is een apart
onderwerp. Vuistregel: grijp je in de code naar $fromAI(), dan wil je eigenlijk .toolWorkflow.
$fromAI(): hoe de agent tool-parameters invult
Toolparameters die de agent zelf moet bepalen worden gewikkeld in $fromAI() — een echte
n8n-expressiehelper, gebruikt binnen de parameterexpressies van een tool-node:
={{ $fromAI('paramName', 'wat hier moet komen — wees specifiek: formaat, bereik, voorbeeld', 'string') }}- paramName — de naam die het model intern gebruikt.
- description — vertelt het model welke waarde te produceren. Dit is onderdeel van de prompt — schrijf het als JSDoc.
- type (optioneel) —
'string'(default),'number','boolean','json'. Een verkeerd type laat de aanroep falen. - defaultValue (optioneel) — gebruikt als het model het weglaat.
$fromAI() draagt alleen JSON — geen binaire data (geen base64, geen bestandsbytes). En niet
elke parameter hoeft $fromAI te zijn: geef identiteit, bevoegdheidsgrenzen en correlatie-ID's
(userId, terugbetalingsplafond, sessionId) deterministisch mee vanuit de workflowcontext,
zodat de agent ze niet fout kan invullen of zelfs maar kan zien.
Systeemprompt versus toolbeschrijving
| Hoort in de systeemprompt | Hoort in de toolbeschrijving |
|---|---|
| Persona, rol, stem | Wat déze specifieke tool doet |
| Globale output-/formaatregels ("antwoord in markdown") | Wanneer gebruik je deze tool versus andere |
| Weigerings-/veiligheidsgedrag | Wat elke parameter betekent en welke vorm die heeft |
| Universele context (huidige datum, gebruikersrol) | Tool-specifieke valkuilen (rate limits, edge cases) |
| Flow tussen tools ("na het genereren altijd tonen") | Tool-specifieke inputtransformaties |
Waarom deze splitsing: een goed beschreven tool werkt in elke agent die 'm erin zet, tooldetails "laden" alleen als het model die tool overweegt (tokenefficiëntie), en je update één toolbeschrijving in plaats van een alinea diep in een prompt van duizenden tokens.
Gestructureerde output: wanneer en hoe
Voeg een outputParserStructured-sub-node toe (bedraad via ai_outputParser) zodra downstream
strikte JSON nodig heeft, geen vrije tekst. Twee regels: gebruik schemaType: 'manual' met een
echt JSON Schema, geen voorbeeld — een voorbeeld kan verplicht-versus-optioneel, enums,
numerieke bereiken of array-beperkingen niet uitdrukken. En zet autoFix: true met een
apart, code-vaardig fix-model bedraad op de parser's eigen ai_languageModel-slot: kapotte JSON
tegen een schema herstellen is een codeertaak, een zwak fix-model produceert alleen een nieuwe
misvormde poging.
Memory: kort mentaal model
Memory is een sub-node (ai_memory). Zonder memory is elke aanroep stateloos — correct voor
one-shot-taken. Met memory houdt de agent een gesprek vast, gesleuteld op een expressie die je
aan sessionKey bindt.
memoryBufferWindow— houdt de laatste N uitwisselingen per sleutel vast, blijft bewaard over executies heen via de eigen opslag van n8n. De default voor chat. De vensterlengte staat standaard erg laag — 50 is een verstandiger startpunt. Berichten buiten het venster zijn helemaal weg.memoryPostgresChat/memoryRedisChat— alleen wanneer memory ook buiten de agent gelezen moet worden (eigen UI, analytics, ander systeem). Niet nodig om enkel een herstart te overleven — BufferWindow doet dat al.
Geef consequent een stabiele sleutel mee van trigger naar memory. Chattriggers vullen
sessionId automatisch; voor andere kanalen leid je er zelf een af (Slack thread_ts, een
webhook-gesprek-ID). Hardcodeer nooit sessionId: 'default' en zet sessionId nooit achter
$fromAI (het model verzint dan een willekeurige UUID).
Binaire data en de agent-grens
Dit is de naad waar het vaak misgaat:
- Het model KAN geüploade afbeeldingen zien (vision) via een agent-instelling die binaire afbeeldingen doorlaat.
- Tools KUNNEN GEEN binaire data ontvangen.
$fromAI()is JSON-only — geen base64, geen bytes, ook niet via non-AI-koppelingen. - De output van de agent is tekstvormig (of gestructureerde tekst met een parser). Geeft een model image/audio/video-bytes terug, dan geeft de Agent die helemaal niet door — er is downstream niets te herstellen.
Omweg: upload eerst naar opslag voordat de agent draait, geef de opslagsleutels mee in de systeemprompt, en laat tools die sleutel als stringparameter accepteren en zelf intern opnieuw ophalen. Voor eenmalige mediageneratie sla je de agent gewoon over en roep je de native single-call-node van de provider direct aan.
Menselijke review (blokkeer destructieve tools)
Als het effect van een tool menselijke goedkeuring nodig heeft (verzendacties, betalingen,
terugbetalingen, accountwijzigingen), wikkel de tool dan in een review-node die tussen de tool
en de agent op de ai_tool-verbinding zit: gewikkelde tool → review-node → Agent.
Of goedkeuring nodig is, is een product-/beleidskeuze — leg de vraag voor aan de gebruiker, adviseer op basis van de impact, en laat hen beslissen.
De kritieke regel: toon de daadwerkelijke parameters die de gewikkelde tool zal ontvangen.
Gebruik de letterlijke toolparameterwaarde in het goedkeuringsbericht, nooit een
$fromAI()-parafrase — anders keurt de mens tekst goed die het model verzon, niet de aanroep die
echt gaat plaatsvinden.
Chat-agents (Slack, Discord, Teams, Telegram)
De ene niet-onderhandelbare regel, ongeacht complexiteit: elke chat-getriggerde workflow die een antwoord post MOET het eigen gebruikers-ID van de bot uitfilteren, anders triggeren de eigen antwoorden zichzelf opnieuw in een oneindige lus die runs en tokens opsoupeert. Filter bij voorkeur op triggerniveau (een uitsluitingslijst op de trigger); filter anders in de eerste node na de trigger.
Voorbij dat filter kan een simpele bot (trigger → agent → antwoord) prima in één workflow. Splits pas op in shell + core + sub-agents zodra je laad-UX, sub-agents, hergebruik over meerdere kanalen, of robuuste foutafhandeling nodig hebt:
- Shell — trigger, anti-loop-filter, event-type-Switch, laad-/foutUX, rendert het antwoord. Geen LLM.
- Core — stateless agent, chatinput + threadId als input, memory gesleuteld op threadId, tools en sub-agents.
- Sub-agents — elk één smal domein, aangeroepen via
.toolWorkflow, stateless (volledige context zit in de chatinput).
Persistente n8n Agents
Een persistente n8n Agent is een ander artefact dan de AI Agent-node hierboven: een zelfstandig assistent-record (model, instructies, tools, skills, taken, memory, kanalen) opgeslagen en geversioneerd door n8n zelf, beheerd via de instantie-brede agentbeheertool — geen node binnen de JSON van één workflow.
| Je hebt nodig… | Gebruik |
|---|---|
Een redeneerstap binnen een workflow, bedraad met ai_*-sub-nodes | AI Agent-node (hierboven) |
| Een zelfstandige assistent met eigen levenscyclus — concept, valideren, publiceren, versies, kanalen — los van één workflow | Persistente Agent |
Vereisten: een apart toegangstoken geconfigureerd en n8n 2.34+ met de agentmodule ingeschakeld. Zonder dat token werkt niets, ook niet de alleen-lezen acties.
Bouwvolgorde: eerst het configuratieschema en de exacte muteeracties opvragen, dan de beschikbare koppelbare middelen (modellen, integraties, workflows, sub-agents, MCP-servers) opvragen, dan aanmaken, dan muteren (steeds de laatste configuratiehash meenemen), dan valideren, en pas op expliciet verzoek van de gebruiker publiceren — nooit uit eigen beweging.
Een live aanroep van de agent gebruikt echte credentials en echte tools — geen dry run. Een resultaat kan goedkeuringsverzoeken voor toolaanroepen bevatten; keur die nooit namens de gebruiker goed — leg ze voor en ga pas verder na een expliciete keuze.
Custom tools zijn een derde coderuntime — niet de Code-node (JavaScript/Python) en niet de
Custom Code Tool van de AI-agent (string in/uit, geen $fromAI()). Lees eerst het schema voordat
je er een schrijft.
Testen zonder rommel achter te laten: geef wegwerp-agents een herkenbaar testprefix in de naam en verwijder ze na afloop — een persistente Agent overleeft het gesprek dat 'm maakte, in tegenstelling tot een workflow die je gewoon inactief kunt laten staan.
RAG (retrieval augmented generation)
n8n biedt de LangChain RAG-bouwstenen (document loaders, splitters, embeddings, vector stores, retrievers). Twee vuistregels:
- Sluit goedkopere opzoekmethoden eerst uit. Exacte opzoekingen → een database- of tabelquery, geen RAG. Actualiteit → een live zoektool. Een klein/gestructureerd documentsetje → geef de agent list/fetch-tools. Grijp pas naar een vector store als er te veel documenten zijn om op te sommen en de vragen semantisch van aard zijn.
- Bedraad de vector store als een retrieval-tool (modus retrieve-as-tool,
ai_tool) zodat de agent zelf beslist wanneer opvragen relevant is en de zoekvraag zelf kan formuleren. Embed query en documenten met hetzelfde model.
Antipatronen
| Antipatroon | Wat er misgaat | Fix |
|---|---|---|
Generieke toolnamen (tool1, doStuff, runQuery) | Model kan niet zien welke tool te kiezen — slaat over of verzint parameters | Werkwoord-eerst, specifieke namen: "Zoek klantendatabase", "Genereer afbeelding met Veo" |
| Lege of ééndelige toolbeschrijvingen | Model heeft geen idee wanneer te gebruiken; slechte selectie, geen foutmelding | Schrijf een echte beschrijving: wat, wanneer, welke parameters |
| Per-tool-instructies in de systeemprompt proppen | Opgeblazen prompt, geen hergebruik, richtlijnen verstopt | Verplaats per-tool-richtlijnen naar de toolbeschrijving |
| Agent + Switch om op natuurlijke taal te routeren | Twee nodes + promptoverhead waar Text Classifier één node is | Gebruik Text Classifier — elke categorie een eigen output (naam én beschrijving) |
| Afbeelding/audio/video-generatie in een Agent wikkelen | Binaire data stroomt niet door tools of uit de agent-output | Gebruik de native single-call-node van de provider direct |
outputParserStructured zonder autoFix | Eén misvormde respons stopt de workflow | autoFix: true + een code-vaardig fix-model |
| Binaire data direct naar een tool sturen | Werkt niet — binaire data kan de toolgrens niet oversteken | Eerst opslaan, sleutels doorgeven |
Hardgecodeerde/ontbrekende/$fromAI-gestuurde sessionId | Gesprekken lopen door elkaar, of het model verzint een UUID | Geef een stabiele sleutel mee vanuit de trigger |
| Twee bijna-identieke tools | Selectie wordt onvoorspelbaar, model raakt in de war | Eén tool met interne vertakking op een parameter |
| Chatbot zonder bot-userfilter | Eigen antwoorden triggeren zichzelf opnieuw → oneindige lus | Sluit het bot-gebruikers-ID uit bij trigger of eerste node |
maxIterations op de lage default bij een multi-tool-agent | "Max iterations reached" / lege output | Verhoog options.maxIterations |
Menselijke-review-bericht gevuld via $fromAI() | Goedkeurder tekent een parafrase af, niet de echte aanroep | Gebruik de letterlijke toolparameterwaarde |
Integratie met andere onderdelen
De workflow-patronen-skill geeft het grotere plaatje van waar een agent past in een workflow. De
validatie-skill legt uit hoe je AI-connectieproblemen interpreteert (een tool bedraad op main
in plaats van ai_tool toont als losgekoppeld). De foutafhandelingsskill behandelt
onError: 'continueErrorOutput' op tool-sub-workflows en de agent-core-aanroep zelf. De
expression-syntax-skill onderbouwt {{ }}, $json.output, en $fromAI.
Checklist vóór je een agent uitrolt
Juiste node gekozen (Agent voor tools/memory/multi-turn; Text Classifier voor routering;
Information Extractor voor velden; native node voor media); model bedraad via
ai_languageModel; elke tool heeft een werkwoord-eerst-specifieke naam ÉN een echte
beschrijving; $fromAI()-beschrijvingen zijn specifiek; per-tool-richtlijnen staan in de
toolbeschrijving; huidige datum in de systeemprompt; maxIterations verhoogd voor multi-tool-
agents; memory gesleuteld op een stabiele sessiesleutel met verhoogde venstergrootte;
gestructureerde output met schemaType: 'manual' + autoFix: true + fix-model; destructieve
tools in menselijke review met letterlijke parameters; chatbots filteren het eigen bot-ID;
binaire data via vision resp. opslagsleutels; gevalideerd en geverifieerd (sub-nodes op ai_*,
niet op main).
Praktijkvoorbeeld (NL)
Een Nederlandse verzekeringstussenpersoon bouwt een AI Agent die inkomende klantvragen via
WhatsApp beantwoordt over lopende polissen. De agent krijgt drie tools: een sub-workflow-tool die
polisgegevens opzoekt op klantnummer, een HTTP Request-tool naar de schadeportal, en een
menselijke-review-tool voor het daadwerkelijk indienen van een schadeclaim — want dat laatste
mag de agent nooit zelfstandig afvuren. De sessiesleutel voor memory wordt afgeleid van het
WhatsApp-threadnummer, niet hardgecodeerd, zodat gesprekken van verschillende klanten nooit door
elkaar lopen. Bij de eerste testronde bleek maxIterations te laag ingesteld: bij een vraag die
zowel een polisopzoek als een schade-check vereiste, liep de agent tegen de limiet aan voordat
hij een antwoord kon geven, en is de waarde verhoogd naar 30.