AI-agents zijn het onderwerp van vrijwel elk tech gesprek van de afgelopen twee jaar. Iedereen heeft een demo gezien. Veel minder mensen hebben een agent succesvol in productie gerund.
Na het bouwen van agents voor financieel beheer (IntelliWealth), recruitment (AI ATS Platform) en productieautomatisering heb ik een duidelijk beeld gekregen van wat werkt en wat niet.
Wat een agent eigenlijk is
Een AI-agent is een LLM die tools kan aanroepen en op basis van het resultaat kan beslissen wat de volgende stap is. In code ziet het er zo uit:
Gebruiker vraagt iets
→ LLM analyseert → besluit welk tool te gebruiken
→ Tool wordt uitgevoerd (API call, database query, e-mail sturen)
→ Resultaat terug naar LLM
→ LLM besluit: klaar? Of nog een stap nodig?
→ Antwoord terug naar gebruiker
Eenvoudig concept. Maar er zitten een hoop valkuilen in de details.
De stack die ik gebruik
LLM: Claude 3.5 Sonnet (primair) / GPT-4o (backup)
Orchestratie: Vercel AI SDK + LangChain voor complexe chains
Jobs: Inngest voor duurzame step functions
Database: Supabase (run history, approval requests)
Waarom Inngest voor agents?
Het grootste probleem met agents in productie is betrouwbaarheid. Een agent die 5 stappen uitvoert en na stap 3 crashed, heeft al acties ondernomen die niet teruggedraaid kunnen worden.
Inngest lost dit op met durable step functions:
export const runAgent = inngest.createFunction(
{ id: 'agent-run', retries: 3 },
{ event: 'agent/run.requested' },
async ({ event, step }) => {
// Elke step wordt apart gecached en kan hervatten na een crash
const context = await step.run('gather-context', async () => {
return await gatherUserContext(event.data.userId);
});
const plan = await step.run('create-plan', async () => {
return await llm.invoke([
new SystemMessage(AGENT_SYSTEM_PROMPT),
new HumanMessage(`Context: ${JSON.stringify(context)}\n\nTaak: ${event.data.task}`)
]);
});
// Wacht op menselijke goedkeuring als de agent iets wil sturen
if (plan.requiresApproval) {
await step.waitForEvent('approval', {
event: 'agent/approval.received',
match: 'data.runId',
timeout: '24h',
});
}
return await step.run('execute', () => executePlan(plan));
}
);Human-in-the-loop: het meest onderschatte patroon
De grootste fout die ik zag in vroege AI-projecten: agents die volledig autonoom acties uitvoeren.
E-mails versturen, orders plaatsen, data aanpassen — dit zijn acties met echte consequenties. De meeste agents zijn nog niet betrouwbaar genoeg om dit zonder menselijk toezicht te doen.
Mijn aanpak: vereis goedkeuring voor onomkeerbare acties.
type ToolCall = {
name: string;
args: Record<string, unknown>;
reversible: boolean; // Kan dit ongedaan gemaakt worden?
};
async function executeWithApproval(toolCall: ToolCall, runId: string) {
if (!toolCall.reversible) {
// Sla op in DB, stuur notificatie, wacht op goedkeuring
await db.insert(approvalRequests).values({
runId,
toolCall: JSON.stringify(toolCall),
status: 'pending',
});
await sendApprovalNotification(toolCall);
return { status: 'awaiting_approval' };
}
return await executeTool(toolCall);
}In de praktijk: de agent doet zijn analyse, stelt een actie voor, de gebruiker keurt goed of af via de dashboard, de agent voert uit. Dit geeft vertrouwen en controle.
Tool design: de agent is zo goed als zijn tools
Een agent met slechte tools zal altijd slechte resultaten leveren, ongeacht het model. Goede tools zijn:
1. Nauwkeurig beschreven
const searchContactsTool = tool(
async ({ query, limit = 10 }) => {
return await db.select().from(contacts)
.where(ilike(contacts.name, `%${query}%`))
.limit(limit);
},
{
name: 'search_contacts',
description: `
Zoek contactpersonen op naam of bedrijf.
Gebruik dit ALLEEN als je specifieke contactinformatie nodig hebt.
Geeft naam, e-mailadres en bedrijf terug.
Maximaal ${10} resultaten.
`,
schema: z.object({
query: z.string().describe('Naam of bedrijf om op te zoeken'),
limit: z.number().optional().describe('Maximum aantal resultaten (standaard 10)'),
}),
}
);2. Idempotent waar mogelijk
Als een tool twee keer wordt aangeroepen met dezelfde input, zou het hetzelfde resultaat moeten geven zonder bijeffecten.
3. Falen netjes
async function sendEmail(to: string, subject: string, body: string) {
try {
await resend.emails.send({ to, subject, html: body });
return { success: true, message: `E-mail verzonden naar ${to}` };
} catch (error) {
// Geef nuttige foutinformatie terug aan de LLM
return {
success: false,
error: `Kon e-mail niet verzenden: ${error.message}. Controleer of het e-mailadres geldig is.`
};
}
}Wat ik heb geleerd
Het model is niet het bottleneck. GPT-4o en Claude 3.5 Sonnet zijn beide goed genoeg voor de meeste taken. De bottlenecks zijn: tool design, prompt engineering en infrastructuur.
Begin klein. Start met één tool, één taak. Voeg complexiteit toe als de basis betrouwbaar werkt.
Log alles. Elke agent run, elke tool call, elke LLM response — log het naar een database. Je zult het nodig hebben voor debugging en om het gedrag van de agent te begrijpen.
Vertrouw je agent niet blind. Voeg altijd een laag human oversight toe voor acties met echte gevolgen. Dit is niet tijdelijk totdat de technologie beter is — het is een fundamenteel principe.
De toekomst van AI-agents is niet volledig autonome systemen. Het is systemen die mensen helpen sneller en beter te werken, met de juiste controles op de juiste momenten.