Rezept: Daten-Browser per Chat
Mitarbeiter fragen Datenbank-Auswertungen in natürlicher Sprache an.
„Zeig mir alle Kunden mit mehr als 5 Bestellungen letzten Monat" — solche Anfragen sind klassisch BI-Tool-Material, oder Tickets ans Tech-Team. Mit TaskMonkey kannst du es deinem Team direkt verfügbar machen, sicher und ohne SQL-Kenntnisse.
Ziel
- Mitarbeitende fragen in natürlicher Sprache nach Daten
- Plattform übersetzt in SQL gegen Supabase
- Ergebnis kommt als Tabelle in den Chat
- Read-only — keine Schreib-Operationen, kein Risiko
Voraussetzungen
- Supabase-Projekt mit den geschäftlichen Daten
- Service-Key mit nur SELECT-Rechten (oder RLS-Setup)
1. API in apis.php
<?php
return [
'apis' => [
'supabase_readonly' => [
'base_url' => 'https://xxx.supabase.co/rest/v1/',
'headers' => [
'apikey' => 'eyJhbGciOi…', // read-only Key
'Authorization' => 'Bearer eyJhbGciOi…',
'Accept' => 'application/json',
],
],
],
];
Wichtig: Verwende einen Key, der nur SELECT kann. Entweder über RLS-Policies oder ein dediziertes DB-Rolle. Sonst kann das Modell theoretisch DELETE/UPDATE schicken.
2. Generisches Such-Tool
tools/data/queryTable.php:
<?php
return [
'tools' => [
'queryTable' => [
'description' => 'Liest Daten aus einer der bekannten Tabellen mit PostgREST-Syntax. Tabellen: orders, customers, products, line_items.',
'parameters' => [
'type' => 'object',
'properties' => [
'table' => [
'type' => 'string',
'enum' => ['orders', 'customers', 'products', 'line_items'],
],
'select' => [
'type' => 'string',
'description' => 'Spalten, kommasepariert. Z.B. "id,total,customer_id"',
],
'filter' => [
'type' => 'object',
'description' => 'PostgREST-Filter, z.B. {"status": "eq.pending", "total": "gt.100"}',
],
'order' => [
'type' => 'string',
'description' => 'z.B. "created_at.desc"',
],
'limit' => [
'type' => 'integer',
'description' => 'Max 100',
],
],
'required' => ['table'],
],
'handler' => function (array $results, array $args, array $ctx): array {
$params = [];
if (!empty($args['select'])) $params['select'] = $args['select'];
foreach (($args['filter'] ?? []) as $col => $val) {
$params[$col] = $val;
}
if (!empty($args['order'])) $params['order'] = $args['order'];
$params['limit'] = min((int)($args['limit'] ?? 25), 100);
$http = new \Cake\Http\Client();
$url = "https://xxx.supabase.co/rest/v1/{$args['table']}?" . http_build_query($params);
$apiKey = 'eyJhbGciOi…';
$res = $http->get($url, [], [
'headers' => [
'apikey' => $apiKey,
'Authorization' => 'Bearer ' . $apiKey,
],
]);
return [
'rows' => $res->getJson() ?? [],
'count' => count($res->getJson() ?? []),
];
},
],
],
];
3. Schema-Hilfs-Tool (optional)
Damit das Modell weiß, welche Spalten welche Tabelle hat:
tools/data/getSchema.php:
<?php
return [
'tools' => [
'getSchema' => [
'description' => 'Liefert das Schema (Spalten und Typen) der bekannten Tabellen.',
'parameters' => ['type' => 'object', 'properties' => []],
'handler' => function (array $results, array $args, array $ctx) {
return [
'orders' => [
'id', 'created_at', 'customer_id', 'total', 'status',
'shipping_method', 'payment_method',
],
'customers' => [
'id', 'created_at', 'email', 'name', 'company', 'country',
],
'products' => [
'id', 'sku', 'name', 'price', 'category', 'stock',
],
'line_items' => [
'id', 'order_id', 'product_id', 'quantity', 'line_total',
],
];
},
],
],
];
4. Assistant
assistants/daten.php:
<?php
return [
'assistants.daten' => [
'name' => 'Daten-Browser',
'description' => 'Datenbank-Auswertungen in natürlicher Sprache.',
'icon' => '🔎',
'tools' => [
'getSchema',
'queryTable',
'setSuggestions',
],
'greeting' => 'Welche Daten interessieren dich?',
'suggestions' => [
'Bestellungen heute',
'Top-Kunden letzten Monat',
'Lagerbestand kritisch',
],
],
'assistants.daten.prompt' => <<<PROMPT
Du bist der Daten-Browser. Mitarbeitende fragen nach
geschäftlichen Daten in natürlicher Sprache, du wandelst sie in
queryTable-Aufrufe.
Vorgehensweise:
1. Wenn unklar welche Tabelle: getSchema einmal aufrufen, um die
Struktur zu kennen
2. queryTable mit passenden Filtern bauen
3. Ergebnis als Markdown-Tabelle zurückgeben — maximal 25 Zeilen
in der Anzeige; bei mehr: "weitere X Zeilen vorhanden, soll ich
filtern?"
4. Bei Kennzahl-Fragen ("wie viele …"): nur die Zahl + Kontext
Wichtig:
- Niemals nach UPDATE/INSERT/DELETE fragen — du hast nur Lesezugriff
- Bei Filter-Beispielen aus der natürlichen Sprache:
"letzte Woche" = created_at >= heute - 7 Tage
"über 100€" = total > 100
"offen/pending" = status = 'pending'
- PostgREST-Operatoren: eq, neq, gt, gte, lt, lte, like, ilike, in
- Komplexere Auswertungen (Joins, Aggregate): erkläre was theoretisch
möglich wäre, biete einen Workaround an
Sprich kurz und sachlich. Keine Floskeln.
PROMPT,
];
5. Testen
tm chat --task daten
You: zeig mir alle offenen Bestellungen über 200€
⚡ getSchema()
⚡ queryTable(table=orders, filter={status: eq.pending, total: gt.200}, ...)
Bot:
| ID | Kunde | Betrag | Status |
| --- | ----------- | ------- | ------- |
| 1042 | Müller GmbH | 287,90 € | pending |
| 1067 | Schmidt KG | 425,00 € | pending |
...
Sicherheitshinweise
- Keine sensiblen Spalten im
getSchemalisten, die das Modell nicht ausliefern soll (z. B.internal_notes,password_hash) - RLS in Supabase aktivieren und den Read-Key entsprechend einschränken
- Audit:
tm monitorzeigt alle Queries — regelmäßig durchschauen - Keine personenbezogenen Daten an LLM-Provider geben, wenn nicht durch Auftragsverarbeitung gedeckt
Erweiterungen
- Mehr Tabellen freigeben schrittweise, je nach Bedarf
- Vorgefertigte Reports als zusätzliche Tools (z. B.
topCustomersByRevenue) — wenn dieselbe Anfrage immer wieder kommt - Export: Tool, das das Ergebnis als CSV in Dropbox legt
- Visualisierung: bei aggregierten Daten ein Chart-Tool aufrufen, das eine QuickChart-URL zurückgibt