TaskMonkey Handbuch

Ablauf eines Tool-Aufrufs

Was zwischen "Modell wählt Tool" und "Ergebnis geht zurück" im Detail passiert.

Wenn das LLM entscheidet, ein Tool aufzurufen, übernimmt der ToolRunner. Er ist der Leim zwischen deklarativer Tool-Config und tatsächlichem Request.

Die Pipeline auf einen Blick

args ──▶ preprocess ──▶ API-Call ──▶ mapping ──▶ postprocess ──▶ handler ──▶ result

Jeder Schritt ist optional (außer mindestens einem von API-Call und handler). Du kannst also ein Tool bauen, das nur aus handler besteht, oder eines, das nur API-Call + Mapping ist.

Schritt für Schritt

1. Args entgegennehmen

Das Modell liefert ein JSON-Objekt, das gegen das parameters-Schema validiert wird. Fehlt ein required Feld, erhält das Modell eine Fehlermeldung zurück und versucht es erneut.

2. Placeholder auflösen

Vor dem API-Call ersetzt der Runner bestimmte Platzhalter in den Argumenten:

Platzhalter Ersetzt durch
<PDF:foo.pdf> Volltext-Inhalt der hochgeladenen Datei
<PDF_CONTENT_AVAILABLE> true/false — gibt es überhaupt PDFs im Chat?
{{user_message_count}} Anzahl Nutzer-Nachrichten in der Session

Siehe Platzhalter-Referenz.

3. preprocess

Eine optionale PHP-Funktion, die die Args umschreiben kann, bevor sie in den Request wandern. Signatur wie handler und postprocess: bekommt $args und den vollen $ctx (siehe unten).

'preprocess' => function (array $args, array $ctx): array {
    // $ctx enthält u.a.: tenant, chat_id, user_id, session, http, logger,
    // tool (Closure für Sub-Tool-Calls), api_config
    $ctx['logger']->info('preprocess läuft', ['input' => $args]);
    $args['date'] = (new \DateTime($args['date']))->format('Y-m-d');
    return $args;
},

Nützlich für Formatkonvertierungen, Default-Werte oder um zusätzliche Header aus der Session zu ziehen.

Special-Key _query: wenn preprocess ein _query Array zurückgibt, wird der Runner es als URL-Query-String an den Endpoint anhängen. Siehe api-features.md.

4. API-Call

Wenn api gesetzt ist, baut der Runner den Request aus:

  • apis.php → Basis-URL, Auth-Header, Timeouts
  • methodGET / POST / PUT / PATCH / DELETE
  • path → Pfad, Platzhalter in {curly} werden aus den Args gefüllt
  • body / query → Mapping der restlichen Args auf Payload oder Query-String

Test-Modus: Mit tm test-tool ... --dry-run überspringt der Runner alle schreibenden Requests (POST, PUT, PATCH, DELETE) und liefert einen Stub zurück. So kannst du Tools gefahrlos im Trockenlauf ausführen.

5. Mapping / Normalize

Das Response-JSON wird gegen mapping durchgeschoben:

'mapping' => [
    'id' => 'invoice_id',
    'total' => 'amounts.gross',
],

Links steht der Schlüssel, den das Modell sieht. Rechts der Pfad (dot-notation) im API-Response. Felder die im Mapping nicht auftauchen, werden weggelassen — das reduziert Token-Verbrauch und versteckt interne Felder.

6. postprocess

Nach dem Mapping läuft optional eine weitere PHP-Funktion auf dem Ergebnis.

'postprocess' => function (array $result, array $args, array $ctx): array {
    $ctx['logger']->debug('postprocess: rechne totalWithTax');
    $result['totalWithTax'] = round($result['total'] * 1.19, 2);
    return $result;
},

Unterschied zu preprocess: läuft nach dem API-Call und bekommt das Response-Objekt. Unterschied zu handler: darf keine eigenen API-Calls machen (dafür ist handler da, siehe unten).

7. handler (nur bei Handler-only oder hybrid)

Ein Handler ist die PHP-Funktion, die das Tool ausführt, wenn es keine API gibt — oder wenn du zusätzlich zur API weitere Calls (verschachtelte Tool-Aufrufe) brauchst.

Signatur: function (array $results, array $args, array $ctx): array

  • $results — Aggregat aller vorherigen Schritte (API-Response nach Mapping/postprocess, sonst leeres Array).
  • $args — die Tool-Argumente (nach preprocess).
  • $ctx — Runtime-Kontext (Array, KEIN Objekt).
'handler' => function (array $results, array $args, array $ctx): array {
    $ctx['logger']->info('handler startet', ['tool' => 'compositeExample']);

    $invoice  = $ctx['tool']('getInvoice', ['id' => $args['id']]);
    $customer = $ctx['tool']('getCustomer', ['id' => $invoice['customer_id']]);

    $ctx['logger']->success('handler fertig', ['count' => 2]);
    return ['invoice' => $invoice, 'customer' => $customer];
},

$ctx-Keys (verbindliche Referenz):

Key Typ Wofür
tenant string Tenant-Code (bloomify, evolvet, …)
chat_id string Aktueller Chat
user_id int|string Angemeldeter User (0 bei public/anon)
session \Cake\Http\Session Cake-Session
http \Cake\Http\Client Vorkonfigurierter HTTP-Client (SSL-relaxed)
logger \App\Tooling\TaskLogger Pflicht nutzen! ->info()/->debug()/->warning()/->error()/->success(). Zeilen landen in task_executions.log_output und im Terminal-Debug-Block der Kommandozentrale
log_path string Tenant-Log-Verzeichnis (falls du selbst File-Logs schreiben willst — meistens nicht nötig)
tool \Closure Sub-Tool-Call: $ctx['tool']('otherTool', ['arg' => 1]). Result geht in den turn_results-Pool. Nicht $ctx->runTool() — das ist die alte falsche Doku
api_config array Bei API-Tools: der gemergte Endpoint-Config (base_url, auth, headers, …)

$ctx ist ein Array, kein Objekt — Zugriff mit $ctx['logger'], nicht $ctx->logger.

Logger-Pflicht: Jeder Handler MUSS mindestens einmal $ctx['logger']->info() am Start und einmal am Ende (bzw. ->error() im Fail-Pfad) rufen. Ohne Logger-Aufrufe sieht der User im Terminal-Debug nur "Keine Debug-Zeilen" und muss ins Log-File wechseln — schlechte DX.

Limit: Maximal 10 verschachtelte Aufrufe pro Tool-Kette. Schützt vor Endlosschleifen.

8. Ergebnis zurück ans Modell

Das Endergebnis geht zurück in die Chat-Pipeline und wird dem LLM als tool_result serviert. Im ChatMessages-Log siehst du beides: Args, die reingingen, und das Ergebnis, das rauskam.

Logging und Debugging

Jeder Tool-Aufruf wird mit einem Eintrag in task_log_entries versehen:

  • tool_name, args, result, duration_ms
  • Bei API-Calls zusätzlich: HTTP-Status, Request-URL, Response-Bytes
  • entry_typechat oder scheduled_task — so filterst du im Log nach Quelle

Zum Anschauen: /manage/tasks → Assistant öffnen → Reiter "Ausführungen".

Häufige Fehlerbilder

Symptom Ursache
Tool wird nie aufgerufen Fehlt in der tools-Liste des Chats/Assistants
args-Validation-Error Modell hat ein Pflichtfeld vergessen — Beschreibung schärfen
401 beim API-Call OAuth abgelaufen — Verbindung unter /manage/o-auth-connections erneuern
Leeres Ergebnis obwohl API 200 liefert Mapping-Pfad passt nicht zum Response, mapping überprüfen

Weiter

Zuletzt aktualisiert: 2026-04-19