Skip to content

Repository files navigation

infranode-tool-eval

Ein deutschsprachiger Tool-Calling-Benchmark für die lm-evaluation-harness, gebaut auf den zwölf MCP-Werkzeugen der InfraNode Open-Data-API.

Warum

Deutschsprachige Benchmarks für agentische Modelle gibt es praktisch nicht. Gemessen wird hier nicht Weltwissen, sondern das, woran Agenten in der Praxis scheitern: aus zwölf Werkzeugen das richtige wählen, Parameter mit Katalogschlüsseln füllen, die man erst nachschlagen muss, einen Städtenamen mit Umlaut auf den kanonischen Slug abbilden, und weiterarbeiten, wenn eine Quelle nicht liefert.

Die Umgebung dahinter ist echt und keylos: jede Aufgabe entstammt einem tatsächlichen API-Aufruf gegen 84 deutsche Städte, kein simuliertes Tool-Schema. Weil die API keine Zugangsdaten braucht, lässt sich derselbe Aufbau auch für RL-Environments oder zur Erzeugung von SFT-Daten nutzen, ohne Secrets auf Trainingsknoten zu verteilen.

Aufbau

Datei Zweck
tools.json Die zwölf Werkzeuge als JSON-Schema, wie ein Modell sie sieht
generate.py Erzeugt tasks.jsonl aus der Live-API und verifiziert jede Gold-Antwort
tasks.jsonl Der Datensatz, aktuell 91 Aufgaben
lm_eval_task/ Task-YAML plus Prompt-Bau und Metriken
selftest.py Prüft Prompt und Bewertung, ohne lm-eval zu installieren
run_eval.py Fährt den Benchmark direkt gegen eine API, ohne lm-eval
report.py Fasst die Läufe in results/ zu einer Vergleichstabelle zusammen
measure.py Misst die Promptgröße für die Kostenschätzung

Kategorien

Kategorie Was geprüft wird Aufgaben
routing_broad Breite Frage zur Stadt führt zum Überblicks-Werkzeug, nicht zu einer geratenen Einzelabfrage 12
routing_named Wetter- und Luftfragen führen zum jeweils spezialisierten Werkzeug 12
resource_key Der richtige Katalogschlüssel im generischen Zugriff, etwa charging oder district-heating 24
slug_resolution München wird zu muenchen, Köln zu koeln, ohne erfundene Slugs 8
poi_type Der richtige POI-Typ aus der Allowlist 12
compare_multi Ein Aufruf für mehrere Städte statt einer pro Stadt 5
not_covered Die Datenart fehlt für diese Stadt. Der Aufruf bleibt trotzdem der richtige, das Modell darf nicht auf ein anderes Werkzeug ausweichen 12
two_step Die EVA-Nummer ist unbekannt, also muss zuerst die Bahnhofsliste geholt werden, nicht direkt der Abfahrtsmonitor 6

Metriken

  • call_match , Werkzeug und alle Parameter korrekt. Die Kennzahl, auf die es ankommt.
  • tool_match , nur das Werkzeug korrekt.
  • args_match , nur die Parameter korrekt.
  • parsed , es kam überhaupt ein lesbarer Werkzeugaufruf zurück.
  • call_match_<kategorie> , dieselbe Kennzahl pro Kategorie, damit eine schwache Dimension sichtbar bleibt statt im Mittelwert zu verschwinden.

Die Auswertung ist absichtlich tolerant beim Format: Code-Fences, umgebende Prosa und die Form {"name": ..., "parameters": ...} werden akzeptiert. Sonst würde der Benchmark Formatdisziplin messen statt Werkzeugwahl. Die Reihenfolge der Städte in compare ist bedeutungslos und wird normalisiert.

Nutzung

uv run selftest.py                      # Prompt und Bewertung prüfen
uv run generate.py --out tasks.jsonl    # Datensatz neu aus der Live-API bauen

lm_eval --model hf \
  --model_args pretrained=<modell> \
  --include_path lm_eval_task \
  --tasks infranode_tools_de \
  --batch_size 8

Ohne lm-eval, direkt gegen eine API

run_eval.py braucht nur die Standardbibliothek und bewertet mit denselben Funktionen wie die Task-YAML, die Zahlen sind also vergleichbar. Zwei Backends: gemini und alles, was /v1/chat/completions spricht.

GEMINI_API_KEY=... uv run run_eval.py --backend gemini --model gemini-2.5-flash

HF_TOKEN=... uv run run_eval.py --backend openai \
  --model Qwen/Qwen3-4B-Instruct-2507 \
  --base-url https://router.huggingface.co/v1

Ergebnisse landen als results/<modell>.json mit jeder einzelnen Antwort, dazu eine Tabelle auf der Konsole. Nützlich bei knappen Kontingenten:

Flag Wirkung
--rpm N Deckelt die Aufrufe pro Minute über alle Threads hinweg
--resume Übernimmt beantwortete Aufgaben aus dem vorhandenen Ergebnis und wiederholt nur die fehlgeschlagenen
--stop-after-errors N Bricht ab, wenn N Aufrufe scheitern, statt in ein leeres Tageskontingent zu laufen
--limit N Nur die ersten N Aufgaben, für einen Rauchtest

Ein unvollständiger Lauf wird als solcher ausgewiesen: complete: false im JSON und eine zweite Kennzahl, die nur die beantworteten Aufgaben wertet. Sonst sähe ein aufgebrauchtes Kontingent wie ein schlechtes Modell aus.

generate.py schreibt nur Aufgaben, deren erwartete Quelle beim Generieren tatsächlich geantwortet hat, und legt Fälle mit not_covered bewusst als eigene Kategorie ab. Zur Eval-Zeit ist kein Netzzugriff nötig.

Baseline

Baseline über 20 Modelle

Stand 27.07.2026, alle Läufe über die vollen 91 Aufgaben, ohne Fehler. Backends waren der Router von Hugging Face, die Anthropic Messages API und die OpenAI-API. Setup nennt die Abweichung von der Task-YAML: bei mehreren Modellen ließ sich das Reasoning nicht abschalten, sie hätten mit dem regulären Budget von 160 Token nur ihre eigenen Gedanken produziert und gar keine Antwort.

Modell Setup call_match tool_match args_match
deepseek-ai/DeepSeek-V3.2 Standard 92,3 98,9 92,3
Qwen/Qwen3.5-122B-A10B Standard 87,9 95,6 87,9
Qwen/Qwen3.5-35B-A3B Standard 87,9 97,8 87,9
Qwen/Qwen3.6-27B Standard 86,8 97,8 86,8
gpt-4.1-mini Standard 85,7 95,6 85,7
claude-opus-5 2048 Token 84,6 90,1 84,6
Qwen/Qwen3.6-35B-A3B Standard 84,6 97,8 84,6
claude-opus-5 Standard 82,4 89,0 82,4
gpt-4.1 Standard 78,0 82,4 78,0
claude-sonnet-5 Standard 75,8 82,4 75,8
openai/gpt-oss-20b 1024 Token 75,8 90,1 75,8
nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B Standard 73,6 75,8 73,6
gpt-5 2048 Token 71,4 72,5 71,4
Qwen/Qwen3.5-397B-A17B 8192 Token 71,4 74,7 71,4
o4-mini 2048 Token 69,2 73,6 69,2
gpt-5-mini 2048 Token 68,1 68,1 68,1
claude-haiku-4-5 Standard 67,0 83,5 67,0
gpt-5-nano 2048 Token 67,0 90,1 67,0
openai/gpt-oss-120b 1024 Token 65,9 81,3 67,0
Qwen/Qwen3-4B-Instruct-2507 Standard 63,7 94,5 63,7
Qwen/Qwen3-Next-80B-A3B-Instruct Standard 63,7 74,7 63,7

Die Aufschlüsselung nach Kategorie liefert uv run report.py. Drei Beobachtungen:

  • Der Katalogschlüssel ist die harte Kategorie. Kein Modell kommt über 75,0 in resource_key. Das Werkzeug ist meist richtig, der Ressourcenname geraten.
  • not_covered misst ein Verhalten, keine Fehlerquote. Fehlt eine Datenart für eine Stadt, treffen neun der zwanzig Modelle nie den direkten Aufruf: sie fragen stattdessen get_city_overview ab und sehen erst nach, was es überhaupt gibt. DeepSeek-V3.2 und Qwen3.5-35B-A3B rufen in allen zwölf Aufgaben direkt auf. Beides ist vertretbar, der Datensatz wertet den direkten Aufruf als Gold, weil die API darauf mit not_covered und einem Verweis auf die abgedeckten Städte antwortet. Wer das andere Verhalten bevorzugt, sollte diese Kategorie getrennt lesen.
  • Größe entscheidet nicht, Aktualität auch nicht. gpt-4.1-mini liegt vor gpt-5, und Qwen3.5-35B-A3B vor Qwen3.5-397B-A17B wie vor Nemotron-3-Ultra-550B.

Grenzen, offen benannt

  • Bewertet wird der erste Werkzeugaufruf, nicht eine ganze Kette. two_step prüft deshalb nur, dass der richtige Nachschlage-Schritt zuerst kommt. Eine mehrschrittige Variante mit echter Ausführung gegen die API ist der nächste sinnvolle Ausbau.
  • Der Datensatz ist mit 91 Aufgaben klein. Er skaliert über --max-per-category, die Grenze ist die Abdeckung der Datenarten pro Stadt.
  • Es gibt genau eine Gold-Antwort pro Frage. Bei sehr breiten Fragen wäre auch ein anderer Aufruf vertretbar, deshalb ist routing_broad bewusst auf Formulierungen beschränkt, bei denen der Überblick die klar richtige Antwort ist.

Lizenz

Code Apache 2.0. Die abgefragten Daten stehen unter den jeweiligen Quellenlizenzen, siehe sources in der API und die Attributionspflicht dort.

About

Deutschsprachiger Tool-Calling-Benchmark fuer die lm-evaluation-harness, gebaut auf den 12 MCP-Werkzeugen der InfraNode Open-Data-API

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages