25 min

Der Ausgangszustand

Bevor irgendetwas verbessert wird, muss jede Runde nachlesbar sein. Rohausgabe, gelesener Aufruf, Ergebnis, finish_reason.

Meilenstein Elf Werkzeuge aus den Sim-Welten stehen als Aufrufliste da, und ein Lauf des großen Modells liegt vollständig protokolliert vor.

Bevor du irgendetwas verbesserst, musst du sehen können, was passiert. Das ist Stufe eins aus dem Kapitel Zusammenspiel, und sie kostet einen Tag. Wer sie überspringt, misst später gegen ein Gefühl.

Woher die Werkzeuge kommen

Warum nicht einfach ein paar Werkzeuge ausdenken? Weil du hinterher nicht sagen kannst, ob ein Aufruf richtig war. Also nimmt der Kurs die, die es schon gibt. Im mcp-Container dieses Repos laufen zwei Sim-Welten, die für die Parcours-Aufgaben gebaut wurden:

DateiWeltWerkzeuge
mcp/tools/sim_calendar.tsDer Kalender von Simhavenlist_calendars, get_events, create_event, invite, book_room
mcp/tools/sim_crm.tsDas CRM derselben Stadtlist_contacts, get_contact, create_contact, update_contact, delete_contact, get_leads_csv

Elf Werkzeuge. Für das Modell sind sie eine Liste von Schemata, also holt man sie einmal in genau der Form heraus, in der eine Chat-Schnittstelle sie erwartet. Ein Auszug aus werkzeuge.json:

{
  "type": "function",
  "function": {
    "name": "update_contact",
    "description": "Change fields of one contact. Only the fields you pass are touched - everything else stays as it is. `notes` REPLACES the note; if you want to keep what is there, read it first and pass the combined text. `stand` is set to now.",
    "parameters": {
      "type": "object",
      "properties": {
        "contact_id": { "type": "string", "description": "Id of the contact, a uuid." },
        "status": { "type": "string", "description": "One of: neu, kontaktiert, kunde, verloren." }
      },
      "required": ["contact_id"]
    }
  }
}

Die Beschreibungen sind wörtlich die aus dem Server. Sie zu glätten wäre verlockend und falsch. Der Spezialist soll lernen, was im Betrieb wirklich in seinem Prompt steht, und im Betrieb steht dort ein Satz über ein Feld namens notes, das den bestehenden Eintrag ersetzt statt ihn zu ergänzen - mit der Warnung dahinter, dass du erst lesen musst, wenn du behalten willst.

Der Auftakt, den alle drei Stände teilen

Ein großes Modell mit Werkzeugen ist ein Planer. Auf „zeig mir die Termine im Kalender brandt“ ruft es gern erst list_calendars auf, um den Schlüssel nachzusehen - vernünftiges Agentenverhalten, und für diese Prüfbank trotzdem der falsche Zug, weil gemessen werden soll, wie eine Absicht zum Aufruf wird, und nicht die Planung darum herum.

Also bekommen alle drei Stände denselben Auftakt, wörtlich:

AUFTAKT = (
    "You are the tool layer of Simhaven. The calendar keys are brandt, keller, "
    "ruben, raum and organisator; contact ids appear literally in the request. "
    "Answer a request with exactly the tool calls it asks for, no exploratory "
    "lookups. If no tool fits, or a required argument is missing, answer in "
    "words instead."
)

Drei Sätze, und der letzte ist der wichtigste. Er erlaubt, nichts zu tun. Ohne ihn wäre jede Absage ein Regelverstoß, und die Prüfstufen S4 und S5 würden etwas messen, worum niemand gebeten hat.

Was protokolliert wird

Was gehört in so ein Protokoll? Vier Dinge je Runde, und keines darfst du weglassen:

  • Die Rohausgabe, unverändert. Nicht die geparste Fassung, nicht die bereinigte. Scheitert der Parser, siehst du nur daran, warum.
  • Der gelesene Aufruf, also Name und Argumente nach dem Parsen.
  • Das Ergebnis in der Form, in der das Modell es zu sehen bekäme.
  • finish_reason und Dauer. Ein Aufruf, den die Tokengrenze abgeschnitten hat, sieht im Protokoll aus wie ein Formatfehler. finish_reason ist der einzige Unterschied.

Im Messskript des Kurses sieht das so aus:

protokoll.append({
    "tag": tag, "fall": fall["id"], "stufe": fall["stufe"], "runde": k,
    "roh_inhalt": (msg.get("content") or "")[:400],
    "ist": ist, "soll": soll, "form_ok": form_ok,
    "treffer": treffer, "schritte_soll": len(soll),
    "schritte_ist": len(ist), "gelungen": gelungen,
    "finish_reason": finish, "dauer_s": round(dauer, 3), "fehler": fehler,
})

Eine Zeile je Anfrage, als JSON auf die Platte. Das reicht. Du brauchst dafür keine Benchmark-Infrastruktur, und wer eine baut, bevor die erste Zahl steht, hat sich verlaufen.

Was das Protokoll sofort verrät

Der erste Lauf des großen Modells gegen die elf Werkzeuge braucht im Median 6,42 Sekunden je Anfrage, im besten Fall 2,28 und im schlechtesten 25,42. Die Spanne ist der eigentliche Befund dieses Kapitels. Ein Modell, das denkt, bevor es aufruft, ist bei einer einfachen Übersetzung nicht viermal langsamer als ein Spezialist - im schlechtesten Fall ist es hundertmal langsamer.

Ob dich das stört, hängt davon ab, wo der Aufruf sitzt. In einem Stapellauf über Nacht fällt es nicht auf. Vor einem wartenden Menschen schon, und erfahrungsgemäß merkt man es dort auch als Erstes.

Damit steht der Ausgangszustand. Was fehlt, ist die Zahl, gegen die man ihn hält.