---
title: "Agent-Plugin"
description: "Ein ReAct-stiliges Werkzeugnutzungs-Agent. Erweitern Sie das Werkzeug, um ihm jede PHP-Fähigkeit zu geben, die Sie schreiben können."
---

Das Agent-Plugin ermöglicht es Ihnen, einer LLM **die Fähigkeit zu geben** — SQL-Abfragen auszuführen, HTTP-Endpunkte anzusprechen, Dateien zu lesen oder alles andere zu tun, was Sie als PHP-Methode ausdrücken können. Es führt eine ReAct-artige Schleife durch: Denken → Werkzeug-Aufruf → Beobachten → Wiederholen → Antworten.

## Fünf Zeilen, ein Agent
```php
use Nibiru\Module\Ai\Ai;
use Nibiru\Module\Ai\Plugin\Tools\PdoQuery;

$ai = new Ai();
echo $ai->agent()
        ->withTools([new PdoQuery()])
        ->run('How many active users do we have?');
// → "We have 1,247 active users." (after the agent ran SELECT count(*)…)
```
## Wie es funktioniert
```
user task
    ↓
LLM gets system prompt with tool definitions
    ↓
LLM emits ```tool {"tool":"pdo_query","args":{"sql":"SELECT…"}}```
    ↓
Agent runs the tool, captures result
    ↓
LLM gets observation, decides: more tools or final answer?
    ↓
"FINAL: 1,247 active users."
```
Das Protokoll verwendet einen **gepufferten JSON-Sentinel** — `\`\`\`tool {...}\`\`\`` — den jeder Modell erzeugen kann. Es ist keine native Tool-Aufruf-API erforderlich, daher funktioniert es auf jedem Ollama-Modell out of the box. (Modelle, die eine native Tool-Aufruf-API unterstützen, können über einen Subclass eingesetzt werden, der `parseToolCall()` überschreibt.)

## Eingebaute Tools

Nibiru bietet drei Produkte an:

| Werkzeug | Was es macht |
|---|---|
| `Tools\PdoQuery` | Einzelner schreibgeschützter `SELECT`-Befehl gegen die App-Datenbank. Blockiert INSERT/UPDATE/DELETE/DROP/TRUNCATE/ALTER. Gibt bis zu 50 Zeilen als JSON zurück. |
| `Tools\HttpGet` | Holt eine HTTP/HTTPS-URL mit optionalen Headern. Gibt den Body zurück, abgeschnitten auf 8 KB. |
| `Tools\FileRead` | Liest eine Projektdatei über einen relativen Pfad. Blockiert die `..`-Reise. Gibt bis zu 8 KB zurück. |
```php
use Nibiru\Module\Ai\Plugin\Tools;

$agent = $ai->agent()->withTools([
    new Tools\PdoQuery(),
    new Tools\HttpGet(),
    new Tools\FileRead(),
]);

// Multi-step task
echo $agent->run(
    'Read application/controller/loginController.php and tell me '
  . 'whether it implements rate limiting.'
);
```
Der Agent wird `file_read` mit dem Pfad aufrufen, die Quelle beobachten und basierend auf dem tatsächlichen Gesehenen antworten – nicht auf das, was es sich vorstellt.

## Ein benutzerdefiniertes Tool schreiben

Erweitern Sie `Tool`:
```php
namespace App\AiTools;

use Nibiru\Module\Ai\Plugin\Tool;

class StripeRefund extends Tool
{
    public function name(): string { return 'stripe_refund'; }

    public function description(): string {
        return 'Issue a Stripe refund for a charge ID.';
    }

    public function schema(): array {
        return [
            'charge_id' => [
                'type' => 'string',
                'description' => 'A Stripe charge ID, e.g. ch_3K…',
                'required' => true,
            ],
            'amount_cents' => [
                'type' => 'integer',
                'description' => 'Amount to refund in cents. Omit for full refund.',
                'required' => false,
            ],
        ];
    }

    public function execute(array $args): mixed {
        $stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET_KEY'));
        $refund = $stripe->refunds->create(array_filter([
            'charge' => $args['charge_id'],
            'amount' => $args['amount_cents'] ?? null,
        ]));
        return json_encode([
            'refund_id' => $refund->id,
            'status'    => $refund->status,
            'amount'    => $refund->amount,
        ]);
    }
}
```
Dann stecken Sie es ein:
```php
$ai->agent()
   ->withTools([new \App\AiTools\StripeRefund(), new Tools\PdoQuery()])
   ->run('Refund order #4421 — they were charged twice.');
```
Der Agent wird `pdo_query` verwenden, um die Gebühr zu finden, und dann `stripe_refund` mit dieser Gebührs-ID aufrufen.

## Den Trace betrachten
```php
$agent = $ai->agent()->withTools([new Tools\PdoQuery()]);
$answer = $agent->run('How many products in the gold-plating category?');

foreach ($agent->trace() as $step) {
    echo "Step {$step['step']}: action={$step['action']}\n  obs={$step['observation']}\n";
}
```
Nützlich für das Debuggen, die Nachverfolgung von Abläufen oder zum Erstellen einer "Zeige dein Arbeitsvorgehen"-Benutzeroberfläche.

## Sicherheit

- **`PdoQuery` blocks writes.** Wenn Sie Schreibzugriff benötigen, erstellen Sie eine subklasse mit erhöhten Rechten und einer Überwachungsnachverfolgung. Heben Sie die SELECT-nur-Einschränkung im eingebauten Tool nicht auf.
- **`HttpGet` allows any URL by default.** Sperren Sie es über eine Zulassungsliste in `[AI] http_allowed_hosts[]` (geplant) oder erstellen Sie eine `RestrictedHttpGet`-Subklasse, die URLs filtert.
- **`FileRead` blocks `..`.** Es ist auf den Anwendungsstamm beschränkt.
- **Max iterations.** `agent.max_iterations = 6` in der INI verhindert unkontrollierte Schleifen. Erhöhen Sie vorsichtig.
- **Tool timeout.** `agent.tool_timeout = 30` (Sekunden). Ein Tool, das hängt, wird die Anfrage nicht ewig blockieren.

## Häufige Fallen

- **Vergessen Sie `withTools()`.** Ohne Werkzeuge ist der Agent nur ein regulärer `Chat`.
- **Lassen Sie dem Agenten Geheimnisse sehen.** Legen Sie niemals API-Schlüssel, rohe Passwörter oder PII in die Antwort eines Tools — das Modell erhält den vollständigen String.
- **Lange Werkzeugausgaben.** Jede Beobachtung wird an die Konversation angehängt. Ein Tool, das 50 KB ausgibt, erschöpft schnell den Kontext. Die eingebaute Tools haben eine Obergrenze von 8 KB; tun Sie das gleiche in Ihren benutzerdefinierten Tools.
- **Kein Werkzeugaufruf in der Antwort = endgültige Antwort.** Wenn das Modell eine endgültige Antwort produziert, die *aussehen* wie einen Werkzeugaufruf, aber nicht validiert wird, behandelt der Agent es als endgültig. Seien Sie explizit im Prompt: "Geben Sie einen Werkzeugaufruf ODER eine endgültige Antwort aus, niemals beides."
