Je agent werkt vandaag. Maar werkt hij nog na je volgende promptaanpassing, modelwissel of nieuwe tool? Wie dat op gevoel beoordeelt, ziet regressies pas als gebruikers klagen. Een evaluatie-harness lost dat op: een script dat je agent langs vaste testcases draait en een overzicht print van wat slaagt en wat faalt. Dit stuk volgt de opzet van een tutorial van Darsh Shah op freeCodeCamp (17 juli 2026): lokaal, met LangChain, Ollama en Qwen 3.5, dus zonder API-kosten (Bron: freeCodeCamp).
Update 2 november 2026 De codevoorbeelden zijn gelijkgetrokken met de bron en de huidige LangChain-documentatie. Het model is nu
qwen3.5:4b(wasqwen3), de judge geeft een simpel YES of NO in plaats van JSON en de testcases hebben een eigen rubric voor de judge.
Stap 1: zet de lokale basis klaar
Installeer Ollama en haal het model binnen. Maak daarna een virtuele Python-omgeving en installeer LangChain met de Ollama-koppeling. De tutorial vraagt langchain>=1.0.0; de huidige versie is 1.4.7 (Bron: PyPI).
ollama pull qwen3.5:4b
python3 -m venv venv
source venv/bin/activate
pip install langchain langchain-core langchain-ollama
Het 4B-model is zo’n 3,3 tot 4 GB groot en ondersteunt tool-aanroepen (Bron: Ollama). Heb je weinig werkgeheugen, dan kan qwen3.5:0.8b, maar dan worden de oordelen van de judge rommeliger. Nieuw met Ollama? Lees eerst wat Ollama is en hoe lokale modellen werken.
Dan de agent zelf. Twee kleine tools en create_agent uit LangChain 1.x (Bron: LangChain):
from datetime import datetime
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
@tool
def current_time() -> str:
"""Return the current date and time."""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def word_count(text: str) -> int:
"""Count the number of words in a text."""
return len(text.split())
model = ChatOllama(model="qwen3.5:4b", temperature=0)
agent = create_agent(
model=model,
tools=[current_time, word_count],
system_prompt="You are a helpful assistant with access to tools.",
)
Gevorderden De harness behandelt de agent als black box. Hij stopt er een vraag in en kijkt naar wat eruit komt: het laatste antwoord en de tools die onderweg zijn aangeroepen. Je hoeft je agent dus niet aan te passen om hem testbaar te maken. Wat een agent precies is, lees je in AI-agents: wat zijn ze.
Stap 2: leg testcases vast als data
De kern van de harness is een lijst testcases, los van je code. Per case leg je vast wat je vraagt, welk woord in het antwoord moet staan, welke tool de agent hoort te gebruiken (of None als hij juist géén tool mag gebruiken) en een rubric voor de judge:
TEST_CASES = [
{
"input": "How many words are in the sentence 'AI changes everything'?",
"expected_keyword": "3",
"expected_tool": "word_count",
"judge_rubric": "The answer says the sentence has 3 words.",
},
{
"input": "What is the capital of France? Answer without using any tools.",
"expected_keyword": "Paris",
"expected_tool": None,
"judge_rubric": "The answer names Paris and adds no invented facts.",
},
]
Begin klein: vijf tot tien cases die je belangrijkste gedrag afdekken, met minstens één vraag waarbij de agent geen tool hoort te gebruiken. Juist die case viel in de bron bij de eerste run om, omdat het model de instructie negeerde (Bron: freeCodeCamp).
Stap 3: combineer harde checks met een judge
Per testcase draait de harness eerst de agent en verzamelt het antwoord en de gebruikte tools. Daarna volgen twee soorten controle. De harde checks zijn goedkoop en onbetwistbaar: staat het verplichte woord in het antwoord en klopt de toolkeuze? De judge, een tweede instantie van hetzelfde model met een eigen prompt, beoordeelt of het antwoord aan de rubric voldoet.
judge = ChatOllama(model="qwen3.5:4b", temperature=0)
def run_agent(case):
result = agent.invoke({"messages": [{"role": "user", "content": case["input"]}]})
answer = result["messages"][-1].content
tools_used = [
call["name"]
for msg in result["messages"]
for call in (getattr(msg, "tool_calls", None) or [])
]
return answer, tools_used
def check_keyword(case, answer):
return case["expected_keyword"].lower() in answer.lower()
def check_tool(case, tools_used):
if case["expected_tool"] is None:
return tools_used == []
return case["expected_tool"] in tools_used
def llm_judge(case, answer):
prompt = (
f"User input: {case['input']}\n"
f"Agent answer: {answer}\n"
f"Rubric: {case['judge_rubric']}\n"
"Does the answer meet the rubric? Reply with just YES or NO."
)
reply = judge.invoke(prompt).content
return reply.strip().upper().startswith("YES")
Een kale YES of NO is voor een klein model makkelijker te volgen dan een JSON-structuur. Je script hoeft er dan ook niets ingewikkelds mee te doen. Dat is ook de keuze in de bron.
Gevorderden Judge-modellen hebben eigen voorkeuren, zoals een zwak voor lange antwoorden. Stel daarom per criterium een aparte ja-of-nee-vraag in plaats van één totaaloordeel. Houd de temperatuur op 0, zodat herhaalde runs zo veel mogelijk hetzelfde oordeel geven. Een klein model als de 4B-variant kan zich vergissen, dus lees een paar oordelen per run zelf na. Meer over dit vakgebied vind je bij evals en harness engineering.
Stap 4: draai de harness bij elke wijziging
Laat het script per case het antwoord, de tools en PASS of FAIL met de reden printen. Aan het eind volgt een totaal zoals 3/4 passed:
def run_evals():
passed = 0
for case in TEST_CASES:
answer, tools_used = run_agent(case)
reasons = []
if not check_keyword(case, answer):
reasons.append("keyword missing")
if not check_tool(case, tools_used):
reasons.append(f"wrong tools: {tools_used}")
if not llm_judge(case, answer):
reasons.append("judge said NO")
print(f"[Answer] {answer}\n[Tools] {tools_used}")
print("[PASS]" if not reasons else f"[FAIL] {', '.join(reasons)}")
passed += not reasons
print(f"{passed}/{len(TEST_CASES)} passed")
run_evals()
Draai dit na elke promptwijziging, modelwissel of nieuwe tool. In de bron haalde de eerste run 3 van de 4 cases: de agent gebruikte een tool terwijl dat niet mocht. Na een strengere systeemprompt slaagden alle vier. Precies dat soort stille regressie wil je in seconden zien, voordat een gebruiker hem vindt.
Meer over waar agents nu staan en waar ze vastlopen, lees je in het dossier AI-agents.
