# AI-agents evalueren en testen [[TOC]] ## Waarom agent-evaluatie anders is Een gewone LLM-aanroep evalueer je op de output: klopt het antwoord? Een agent heeft een pad, namelijk de reeks stappen die het nam om tot een antwoord te komen. Een agent kan het juiste antwoord bereiken via het verkeerde pad (inefficient, duur) of het verkeerde antwoord via het juiste pad (logisch maar fout startpunt). Je evalueert daarom beide. Platforms die alleen de eindoutput scoren, missen het grootste deel van de faalmomenten die middenin de uitvoering ontstaan: een verkeerde tool-keuze, een mislukte retry of een planner die de taak verkeerd opknipt. Evalueer dus elke stap in het traject apart. :::info title="Drie evaluatieniveaus" 1. **Traject**: waren de stappen logisch en efficient? 2. **Tool-gebruik**: werden de juiste tools op het juiste moment aangeroepen? 3. **Eindresultaat**: lost het de gebruikersvraag op? ::: ## Testset opbouwen Begin met het verzamelen van representatieve taken. Voor betrouwbare geaggregeerde metrics is een richtgetal van enkele honderden cases gangbaar, maar begin klein met een handvol scherpe gevallen en breid uit. ```typescript interface AgentTestCase { id: string; user_input: string; expected_tools: string[]; expected_outcome_description: string; ground_truth?: string; difficulty: "easy" | "medium" | "hard"; } const testCases: AgentTestCase[] = [ { id: "TC001", user_input: "Zoek het meest recente rapport over Q3 verkopen en maak een samenvatting", expected_tools: ["search_drive", "read_file"], expected_outcome_description: "Samenvatting van het Q3-verkooprapport met kernpunten", difficulty: "medium", }, { id: "TC002", user_input: "Stuur een e-mail naar jan@bedrijf.nl met de agenda van aanstaande maandag", expected_tools: ["list_upcoming_events", "send_email"], expected_outcome_description: "Bevestiging dat e-mail is verstuurd met agenda-inhoud", difficulty: "medium", }, ]; ``` :::tip title="Begin met je echte foutgevallen" De waardevolste testcases komen uit productie. Verzamel de gesprekken waar je agent eerder de mist in ging en zet ze om in testcases. Zo bouw je een testset die jouw echte faalmodi dekt in plaats van bedachte scenario's. ::: ## Tool-gebruik evalueren ```typescript interface AgentRun { test_case_id: string; tool_calls: { name: string; input: Record; output: string }[]; final_response: string; total_tokens: number; duration_ms: number; } function evaluateToolUsage(run: AgentRun, testCase: AgentTestCase): ToolEvalResult { const usedTools = new Set(run.tool_calls.map(tc => tc.name)); const expectedTools = new Set(testCase.expected_tools); const precision = [...usedTools].filter(t => expectedTools.has(t)).length / usedTools.size; const recall = [...expectedTools].filter(t => usedTools.has(t)).length / expectedTools.size; const f1 = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0; return { precision, recall, f1, extra_tools: [...usedTools].filter(t => !expectedTools.has(t)), missing_tools: [...expectedTools].filter(t => !usedTools.has(t)), }; } ``` Precision laat zien hoeveel van de aangeroepen tools daadwerkelijk verwacht waren, recall hoeveel van de verwachte tools ook echt zijn aangeroepen. De F1-score combineert beide tot een enkel getal dat je makkelijk over runs heen kunt volgen. ## LLM-as-judge voor eindresultaat ```typescript async function judgeOutcome( userInput: string, expectedDescription: string, actualResponse: string ): Promise<{ score: number; reasoning: string }> { const response = await anthropic.messages.create({ model: "claude-opus-4-8", max_tokens: 500, messages: [{ role: "user", content: `Beoordeel de kwaliteit van dit agent-antwoord. Gebruikersvraag: ${userInput} Verwacht resultaat: ${expectedDescription} Feitelijk antwoord: ${actualResponse} Geef een score van 1-5: 1 = Volledig onjuist of irrelevant 2 = Gedeeltelijk relevant maar mist kern 3 = Correct maar onvolledig 4 = Correct en compleet 5 = Uitstekend, overtreft verwachting Antwoord als JSON: {"score": <1-5>, "reasoning": ""}`, }], }); return JSON.parse(extractText(response)); } ``` :::warn title="Absolute scores driften" Een LLM-as-judge is sterk in relatieve vergelijkingen (is A beter dan B?), maar absolute scores op een schaal van 1 tot 5 zijn minder stabiel en kunnen tussen runs verschuiven. Gebruik waar mogelijk een referentieantwoord en overweeg paarsgewijze vergelijking in plaats van losse cijfers wanneer je twee versies tegen elkaar afzet. ::: ## Efficiency-metrics ```typescript function calculateEfficiency(run: AgentRun): EfficiencyMetrics { return { tool_calls_count: run.tool_calls.length, unique_tools_used: new Set(run.tool_calls.map(tc => tc.name)).size, duplicate_calls: run.tool_calls.length - new Set( run.tool_calls.map(tc => `${tc.name}:${JSON.stringify(tc.input)}`) ).size, total_tokens: run.total_tokens, duration_ms: run.duration_ms, tokens_per_step: run.total_tokens / run.tool_calls.length, }; } ``` Let vooral op `duplicate_calls`: identieke tool-aanroepen wijzen op een agent die in een lus zit of zijn eigen werk niet onthoudt. Dat is duur en traag, en vaak een teken dat je system prompt of tool-output beter moet. ## Testpipeline automatiseren ```typescript async function runEvaluationSuite( testCases: AgentTestCase[], agent: AgentFunction ): Promise { const results = await Promise.all( testCases.map(async (tc) => { const run = await agent(tc.user_input); const toolEval = evaluateToolUsage(run, tc); const outcomeScore = await judgeOutcome( tc.user_input, tc.expected_outcome_description, run.final_response ); const efficiency = calculateEfficiency(run); return { test_case: tc, run, tool_eval: toolEval, outcome_score: outcomeScore, efficiency }; }) ); return { total_cases: testCases.length, avg_outcome_score: average(results.map(r => r.outcome_score.score)), avg_tool_f1: average(results.map(r => r.tool_eval.f1)), avg_duplicate_calls: average(results.map(r => r.efficiency.duplicate_calls)), results, }; } ``` ## Regressietests Voeg bij elke agent-update een regressietest toe, zodat een verbetering op het ene scenario geen onverwachte achteruitgang op een ander veroorzaakt: ```typescript describe("Agent regression tests", () => { it("lost een e-mail-plus-agenda-taak op", async () => { const result = await agent("Stuur de agenda van morgen naar mijn team"); expect(result.tool_calls.map(tc => tc.name)).toContain("list_upcoming_events"); expect(result.tool_calls.map(tc => tc.name)).toContain("send_email"); expect(result.final_response).toMatch(/verstuurd|verzonden/i); }); }); ``` :::howto title="Zo zet je evaluatie in je CI/CD-pipeline" 1. Bewaar je testset (`AgentTestCase[]`) als versiebeheerde fixture in de repo. 2. Draai `runEvaluationSuite` als aparte job bij elke pull request die de agent raakt. 3. Stel drempelwaarden in, bijvoorbeeld `avg_tool_f1 >= 0.8` en `avg_outcome_score >= 3.5`. 4. Laat de pipeline falen wanneer een metric onder de drempel zakt, en log het volledige rapport als artefact. 5. Vergelijk per release de scores met de vorige run om sluipende regressie zichtbaar te maken. ::: :::faq ### Hoe vaak moet ik evaluatie draaien? Bij elke wijziging aan de system prompt, tool-definities of modelversie. Automatiseer het in je CI/CD en stel een drempelwaarde in, bijvoorbeeld dat de gemiddelde score niet meer dan 10 procent mag dalen. ### Is LLM-as-judge betrouwbaar? Redelijk betrouwbaar voor relatieve vergelijkingen, dus de vraag of A beter is dan B. Voor absolute scores is het minder stabiel. Kalibreer door menselijke beoordelingen te vergelijken met de scores van de judge op een subset, en gebruik waar mogelijk een referentieantwoord. ### Hoe evalueer ik agents met side-effects? Gebruik een sandbox-omgeving met een aparte database, een mock-e-mailserver of een geisoleerde Drive-map. Evalueer nooit in productie, want je agent kan echte e-mails versturen of bestanden wijzigen. ### Wat doe ik met lage scores? Analyseer de mislukte testcases en bepaal waar het misgaat: tool-selectie, parameterkwaliteit of het eindresultaat. Elk vraagt een andere fix, namelijk betere tool-beschrijvingen, strakkere schema's of een herziene system prompt. ### Welk model gebruik ik als judge? Gebruik een capabel model dat losstaat van het model dat je agent aandrijft, zodat de judge niet zijn eigen output beoordeelt. Een sterk redeneermodel zoals de actuele Opus-generatie werkt goed; controleer in de modeloverzichten welke versie op het moment van bouwen de aanbevolen keuze is. ### Hoeveel testcases heb ik nodig? Begin met enkele scherpe gevallen om je pipeline werkend te krijgen, en breid uit richting enkele honderden voordat je geaggregeerde metrics echt vertrouwt. Met te weinig cases zegt een gemiddelde score weinig. :::