Kapitel 18/Premium Rohtext.md aktualisiert

This commit is contained in:
2025-10-22 10:37:59 +00:00
parent 50ce8b2643
commit 975652f28d

View File

@@ -336,27 +336,585 @@ sodass Elyra keine weiteren Nachrichten beantwortet, bis erneut ein gültiger Tr
👉 **Screenshot geeignet:** WhatsApp-Chat mit Elyra, die freundlich auf eine Zuschauerfrage reagiert und danach automatisch inaktiv wird.
### Technische Grundlage
#### Node 1 Webhook (Eingang: WhatsApp-Nachricht)
Die Logik besteht aus einer kleinen n8n-Kette mit klar definierten Knoten:
**Zweck:** Empfängt eingehende WhatsApp-Nachrichten, die über WAHA an diesen Workflow weitergeleitet werden.
| Node | Aufgabe |
|------|----------|
| **Webhook (Trigger)** | empfängt eingehende WhatsApp-Nachrichten über WAHA |
| **IF (Bedingung)** | prüft, ob die Nachricht „Ich brauche deine Hilfe, Elyra“ enthält |
| **KI-Node (OpenAI / Ollama)** | verarbeitet den Text und erstellt eine Antwort |
| **HTTP Request → WAHA** | sendet die Antwort an den Chat |
| **Wait / Timeout-Node** | beendet nach 10 Minuten Inaktivität |
| **IF („Danke, Elyra“) → Stop** | erkennt das manuelle Opt-Out und stoppt sofort |
**Node-Typ:** Webhook
**Name:** Elyra Nachrichteneingang
Elyra interagiert damit ausschließlich auf Anfrage,
arbeitet ressourcenschonend und wahrt alle datenschutzrelevanten Grenzen.
**Parameter:**
- **HTTP Method:** `POST`
- **Path:** `/elyra/inbound`
- **Response Mode:** `On Received`
- **Response Code:** `200`
- **Response Data:** `No Response Body`
> [!IMPORTANT]
> Elyra darf **niemals eigenständig** Nachrichten senden oder neue Chats starten.
> Jede Kommunikation muss eindeutig vom Nutzer ausgehen.
> Nur so bleibt die Nutzung im Einklang mit den WhatsApp-AGB.
> [!NOTE]
> Die Webhook-URL wird nach dem Speichern automatisch angezeigt, z.B.:
> `https://n8n.deinedomain.tld/webhook/elyra/inbound`
>
> Diese Adresse muss im WAHA-Backend als Ziel für eingehende Nachrichten konfiguriert werden
> üblicherweise per Weiterleitung oder Trigger-Konfiguration in deinem WhatsApp-Bot-Modul.
Mit dieser Implementierung erhält dein UCC eine intelligente, aber kontrollierte Interaktionsebene,
die sinnvoll zwischen Automatisierung und Nutzerhoheit vermittelt.
👉 **Screenshot geeignet:** Webhook-Node mit sichtbarem Pfad `/webhook/elyra/inbound`
#### Node 2 Function (chatId extrahieren + vorbereiten)
**Zweck:** Extrahiert die `chatId` aus der eingehenden Nachricht und speichert sie zur späteren Wiederverwendung.
Außerdem werden alle empfangenen Daten in einheitlicher Struktur für den weiteren Verlauf vorbereitet.
**Node-Typ:** Function
**Name:** Elyra Nachricht vorbereiten
```js
const body = items[0].json || {};
const chatId = body.chatId || 'unbekannt';
const text = (body.text || '').trim().toLowerCase();
const timestamp = body.timestamp || new Date().toISOString();
// Global für spätere Prüfung speichern
$input.item.json.chatId = chatId;
$input.item.json.text = text;
$input.item.json.timestamp = timestamp;
return [$input.item];
```
> [!TIP]
> WAHA überträgt bei eingehenden Nachrichten u.a. `chatId`, `text` und `timestamp`.
> Diese Felder werden hier bewusst standardisiert, damit nachfolgende Nodes keine Sonderfälle behandeln müssen.
👉 **Screenshot geeignet:** Function-Node mit geöffnetem Code, markierte Zeile `const chatId = ...`
#### Node 3 IF (Trigger: Elyra deaktivieren?)
**Zweck:** Erkennt, ob der Nutzer Elyra gezielt deaktivieren will z.B. durch die Nachricht „Danke, Elyra“.
**Node-Typ:** IF
**Name:** Elyra Deaktivierung erkennen
**Bedingung:**
- **Mode:** `All`
- **Type:** `String`
- **Value 1:** `{{$json["text"]}}`
- **Operation:** `Contains`
- **Value 2:** `danke, elyra`
> [!TIP]
> Du kannst weitere Formulierungen ergänzen z.B. `tschüss elyra`, `ich bin fertig`, etc.
> Aktuell genügt ein einzelner präziser Begriff zur Deaktivierung.
👉 **Screenshot geeignet:** IF-Node mit Bedingung `text contains "danke, elyra"`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 4 *Elyra Session deaktivieren*
- **FALSE →** weiter mit Node 5 *Elyra Aktivierung erkennen*
#### Node 4 Set (Elyra-Session deaktivieren)
**Zweck:** Setzt den Aktivierungsstatus der Elyra-Session für diesen Chat auf „inaktiv“.
**Node-Typ:** Set
**Name:** Elyra Session deaktivieren
**Operation:**
- **Add Field:** `elyra_active` → `false`
> [!NOTE]
> Die Variable `elyra_active` wird später mit der `chatId` kombiniert,
> um eine **sitzungsspezifische Statusvariable** zu erzeugen, z.B.:
> `elyra_active_491751234567@c.us`
👉 **Screenshot geeignet:** Set-Node mit Feld `elyra_active = false`
#### Node 5 IF (Trigger: Elyra aktivieren?)
**Zweck:** Erkennt, ob Elyra durch den Nutzer aktiviert werden soll z.B. mit „Ich brauche deine Hilfe, Elyra“.
**Node-Typ:** IF
**Name:** Elyra Aktivierung erkennen
**Bedingung:**
- **Mode:** `All`
- **Type:** `String`
- **Value 1:** `{{$json["text"]}}`
- **Operation:** `Contains`
- **Value 2:** `ich brauche deine hilfe, elyra`
> [!TIP]
> Achte auf die Kleinschreibung, wenn du den Text vorher mit `.toLowerCase()` vereinheitlichst (siehe Node 2).
> Weitere Aktivierungsphrasen kannst du über zusätzliche OR-Verzweigungen ergänzen.
👉 **Screenshot geeignet:** IF-Node mit Bedingung `text contains "ich brauche deine hilfe, elyra"`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 6 *Sessionzähler prüfen*
- **FALSE →** weiter mit Node 9 *Elyra Aktivstatus prüfen*
#### Node 6 Function (Sessionzähler prüfen)
**Zweck:** Zählt die aktuell aktiven Elyra-Sessions anhand der globalen Variablen
und entscheidet, ob eine weitere Session zulässig ist (Maximalgrenze: 10).
**Node-Typ:** Function
**Name:** Elyra Sessionzähler prüfen
```js
// Aktuelle Liste aller aktiven Sessions abrufen
const allKeys = Object.keys($global);
const activeSessions = allKeys.filter(k => k.startsWith('elyra_active_') && $global.get(k) === true);
// Ergebnis als Flag setzen
item.sessionCount = activeSessions.length;
item.sessionAllowed = activeSessions.length < 10;
return [item];
```
> [!NOTE]
> Jede aktive Elyra-Session wird im weiteren Verlauf als globale Variable gespeichert, z.B.:
> `elyra_active_491751234567@c.us = true`
>
> Diese Function zählt exakt, wie viele davon derzeit auf `true` stehen.
👉 **Screenshot geeignet:** Function-Node mit markierter Zeile `item.sessionAllowed = ...`
**Anschlusslogik:**
- **→** weiter mit Node 7 *Aktivierung zulässig?* (IF)
#### Node 7 IF (Aktivierung zulässig?)
**Zweck:** Prüft, ob die Elyra-Aktivierung für diesen Nutzer erlaubt ist (d.h. weniger als 10 aktive Sessions).
**Node-Typ:** IF
**Name:** Elyra Aktivierung zulässig?
**Bedingung:**
- **Mode:** `All`
- **Type:** `Boolean`
- **Value 1:** `{{$json["sessionAllowed"]}}`
- **Operation:** `Is true`
👉 **Screenshot geeignet:** IF-Node mit Bedingung `sessionAllowed is true`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 8 *Elyra Session aktivieren*
- **FALSE →** weiter mit Node 9 *Elyra Aktivstatus prüfen*
#### Node 8 Set (Elyra Session aktivieren)
**Zweck:** Aktiviert Elyra für diesen Nutzer, indem eine globale Sitzungsvariable gesetzt wird.
**Node-Typ:** Set
**Name:** Elyra Session aktivieren
**Operation:**
- **Add Field:**
- `elyra_active` → `true`
> [!NOTE]
> Die eigentliche Speicherung erfolgt erst im nächsten Schritt durch Kombination mit der `chatId`.
> Dieses Feld dient als Zwischenschritt für die globale Speicherung mit Namen wie:
> `elyra_active_491751234567@c.us`
👉 **Screenshot geeignet:** Set-Node mit Feld `elyra_active = true`
**Anschlusslogik:**
- **→** weiter mit Node 9 *Elyra Aktivstatus prüfen*
#### Node 9 IF (Elyra ist aktiv?)
**Zweck:** Prüft, ob für diesen Chat eine Elyra-Session aktiv ist.
**Node-Typ:** IF
**Name:** Elyra Aktivstatus prüfen
**Bedingung:**
- **Mode:** `All`
- **Type:** `Expression`
- **Value 1:**
```js
{{
const key = 'elyra_active_' + $json["chatId"];
return $global.get(key) === true;
}}
```
- **Operation:** `Is true`
> [!TIP]
> Diese Prüfung stellt sicher, dass Elyra nur antwortet, wenn eine Sitzung explizit aktiv ist.
> Ohne vorherige Aktivierung wird der Nutzer ignoriert oder optional weitergeleitet.
👉 **Screenshot geeignet:** IF-Node mit geöffneter Expression und `return $global.get(...) === true`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 10 *Thema klassifizieren*
- **FALSE →** weiter mit Node 16 *Antwort: Elyra ist nicht aktiv*
#### Node 10 HTTP Request (Themenklassifizierung über Ollama)
**Zweck:** Übergibt die empfangene WhatsApp-Nachricht an das lokale Mistral-Modell in Ollama,
um eine präzise Themenklassifizierung zu erhalten z.B. `streamzeit`, `eventinfo`, `zeiten`, `adminanfrage`, `chatbefehl` oder `nicht zulässig`.
**Node-Typ:** HTTP Request
**Name:** Elyra Thema klassifizieren (Ollama)
**Parameter:**
- **HTTP Method:** `POST`
- **URL:** `http://ollama.deinedomain.tld:11434/api/chat`
- **Headers:**
- `Content-Type`: `application/json`
- **Body Content Type:** `JSON`
- **Body Parameters:**
```json
{
"model": "mistral",
"messages": [
{
"role": "system",
"content": "Du bist ein Klassifizierungsagent für WhatsApp-Nachrichten. Ordne die folgende Nachricht exakt einem der folgenden Themen zu:\n\n- streamzeit\n- eventinfo\n- zeiten\n- adminanfrage\n- chatbefehl\n\nWenn keine eindeutige Zuordnung möglich ist oder der Inhalt nicht erlaubt ist, antworte exakt mit: nicht zulässig."
},
{
"role": "user",
"content": "={{$json[\"text\"]}}"
}
],
"stream": false
}
```
> [!NOTE]
> Die Antwort muss **exakt** einem der oben genannten Schlüsselwörter entsprechen.
> Dadurch kannst du im nächsten Schritt zuverlässig prüfen, ob Elyra antworten darf auch bei hoher Auslastung.
👉 **Screenshot geeignet:** HTTP-Request-Node mit geöffnetem Body, markierter Promptabschnitt `"Ordne die folgende Nachricht ..."`
**Anschlusslogik:**
- **→** weiter mit Node 11 *Zugriffsprüfung: Thema + Sessionauslastung*
#### Node 11 Function (Zugriffsprüfung: Thema + Sessionauslastung)
**Zweck:** Entscheidet, ob die Anfrage trotz möglicher Überlast bearbeitet werden darf.
Bevorzugte Themen wie `streamzeit`, `eventinfo` und `zeiten` dürfen auch bei Volllast durch
alles andere wird bei Erreichen der Kapazitätsgrenze blockiert.
**Node-Typ:** Function
**Name:** Elyra Zugriff prüfen
```js
const thema = $json["message"]?.content?.toLowerCase() || "nicht zulässig";
const chatId = $json["chatId"];
// Speichern für spätere Entscheidung
item.thema = thema;
item.chatId = chatId;
// Bevorzugte Themen
const priorisiert = ["streamzeit", "eventinfo", "zeiten"];
// Aktive Sessions zählen
const allKeys = Object.keys($global);
const aktive = allKeys.filter(k => k.startsWith('elyra_active_') && $global.get(k) === true);
const sessionCount = aktive.length;
// Entscheidung
item.sessionCount = sessionCount;
item.zugriffErlaubt = sessionCount < 10 || priorisiert.includes(thema);
item.zugriffVerweigert = !item.zugriffErlaubt || thema === "nicht zulässig";
return [item];
```
> [!TIP]
> Durch diese Logik reagiert Elyra auch bei starker Auslastung noch auf einfache Standardfragen.
> Nur komplexe oder nicht erlaubte Inhalte werden in dem Fall geblockt oder verzögert.
👉 **Screenshot geeignet:** Function-Node mit sichtbarer Logik `zugriffErlaubt = sessionCount < 10 || `
**Anschlusslogik:**
- **TRUE →** weiter mit Node 12 *Antwort generieren (Ollama)*
- **FALSE →** weiter mit Node 16 *Antwort: Kapazitätsgrenze oder Thema abgelehnt*
#### Node 12 Set (Kontext für Themenverzweigung vorbereiten)
**Zweck:** Bereitet die vom Klassifizierungsmodell gelieferte Themenkategorie und alle relevanten Kontextdaten für die nachfolgende Verzweigung vor.
Dieser Node bündelt alle Variablen, die für die spätere Entscheidung im Switch-Node benötigt werden.
**Node-Typ:** Set
**Name:** Elyra Kontext vorbereiten
**Felder hinzufügen:**
- `thema` → `={{$json["message"]["content"]}}`
- `chatId` → `={{$json["chatId"]}}`
- `text` → `={{$json["text"]}}`
- `timestamp` → `={{$json["timestamp"]}}`
> [!NOTE]
> Die Variable `thema` stammt direkt aus der Klassifizierungsantwort von Mistral (Node 10).
> Diese wird im nächsten Node verwendet, um über einen `Switch`-Node gezielt auf das Thema zu reagieren.
👉 **Screenshot geeignet:** Set-Node mit Feldern `thema`, `chatId`, `text`, `timestamp`
**Anschlusslogik:**
- **→** weiter mit Node 13 *Switch: Thema auswählen*
#### Node 13 Switch (Thema auswählen und Pfad verzweigen)
**Zweck:** Leitet die Verarbeitung abhängig von der klassifizierten Themenkategorie an den passenden Ablauf weiter z.B. Datenabruf, Rechteprüfung oder feste Antwort.
**Node-Typ:** Switch
**Name:** Elyra Thema verzweigen
**Bedingungstyp:** `String`
**Vergleichswert:** `={{$json["thema"]}}`
**Cases:**
- **streamzeit** → weiter mit Node 14 *Twitch-Kalender abrufen*
- **eventinfo** → weiter mit Node 15 *Eventtext setzen*
- **zeiten** → weiter mit Node 16 *Sendezeit abrufen*
- **chatbefehl** → weiter mit Node 17 *Standardantwort setzen*
- **adminanfrage** → weiter mit Node 18 *Adminrechte prüfen*
- **nicht zulässig** → weiter mit Node 19 *Antwort: Thema nicht erlaubt*
> [!TIP]
> Der Switch kann später beliebig erweitert werden, z.B. für `netdatainfo`, `feedback`, etc.
> Die Themenbezeichnungen müssen exakt zu denen aus dem Klassifizierungs-Prompt (Node 10) passen.
👉 **Screenshot geeignet:** Switch-Node mit geöffneten CaseBedingungen `streamzeit`, `adminanfrage`, etc.
**Anschlusslogik:**
- Je nach Thema → zu Node 1419 (jeweils themenspezifisch)
#### Node 14 Execute Subworkflow (Twitch-Kalender abfragen)
**Zweck:** Führt einen ausgelagerten Workflow aus, der den nächsten geplanten Streamtermin über die Twitch-API abruft
und die Antwort in strukturierter Form (z.B. als `streaminfo`) zurückliefert.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Nächsten Stream abrufen
**Parameter:**
- **Workflow:** `elyra_next_stream` *(wird als JSON-Download bereitgestellt)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `timestamp`: `={{$json["timestamp"]}}`
> [!NOTE]
> Dieser SubWorkflow wird nicht im Tutorial dokumentiert, sondern separat als JSON-Datei bereitgestellt.
> Er ruft die Twitch-API auf, extrahiert den nächsten geplanten Stream
> und speichert die Information im Feld `streaminfo` für die spätere Antwortgenerierung.
👉 **Screenshot geeignet:** Execute Sub-workflow Node mit gewähltem Workflow `elyra_next_stream` und aktiver Option `Wait for Completion`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 15 Execute Subworkflow (Event-Info abrufen)
**Zweck:** Führt einen externen Workflow aus, der die Beschreibung des nächsten geplanten Events liefert
z.B. aus einer Kalenderquelle, Textdatei oder Datenbank.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Eventinfo abrufen
**Parameter:**
- **Workflow:** `elyra_eventinfo` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `timestamp`: `={{$json["timestamp"]}}`
> [!NOTE]
> Auch dieser Workflow wird **nicht im Tutorial beschrieben**, sondern als JSON-Datei zum Download bereitgestellt.
> Er liefert z.B. das Feld `eventinfo` zurück, das später in den Antwort-Prompt eingebaut werden kann.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow-Namen `elyra_eventinfo` und gesetzten Parametern
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 16 Execute Subworkflow (Sendezeit abrufen)
**Zweck:** Führt einen ausgelagerten Workflow aus, der die aktuelle oder nächste Sendezeit ermittelt
z.B. über eine ICS-Datei, einen festgelegten Wochenplan oder eine API-basierte Quelle.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Sendezeit abrufen
**Parameter:**
- **Workflow:** `elyra_zeiten` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `timestamp`: `={{$json["timestamp"]}}`
> [!NOTE]
> Der Workflow `elyra_zeiten` wird separat als JSON bereitgestellt
> und extrahiert z.B. aus einem Kalender die geplante Startzeit des Streams.
> Die Ausgabe erfolgt idealerweise als String im Feld `sendezeit`.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow `elyra_zeiten` und gesetzten Übergabeparametern
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 17 Execute Subworkflow (Chatbefehle aus StreamElements abrufen)
**Zweck:** Führt einen ausgelagerten Workflow aus, der die öffentlich zugängliche StreamElementsCommandsSeite abruft,
alle verfügbaren Chatbefehle extrahiert und als JSON-String bereitstellt.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Chatbefehle laden
**Parameter:**
- **Workflow:** `elyra_chatcommands` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
> [!NOTE]
> Dieser SubWorkflow ruft z.B. `https://streamelements.com/bratonien_tv/commands` auf,
> analysiert das HTML und extrahiert die Befehle samt Beschreibung als JSONObjekt.
> Das Ergebnis wird später an Ollama übergeben, damit Elyra kontextbezogen passende Befehle vorschlagen kann.
>
> Auch andere Anbieter wie **Nightbot**, **Moobot** oder **Botisimo** stellen vergleichbare Listen zur Verfügung
> im Rahmen dieses Tutorials verwenden wir jedoch ausschließlich **StreamElements** als zentrale Bot-Instanz.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow-Namen `elyra_chatcommands`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 18 Execute Subworkflow (Adminrechte prüfen)
**Zweck:** Führt einen externen Workflow aus, der prüft, ob der aktuelle Absender (`chatId`) zu den autorisierten Admins gehört.
Nur wenn das zutrifft, wird die Anfrage weiterverarbeitet sonst erhält der Nutzer eine entsprechende Ablehnung.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Adminrechte prüfen
**Parameter:**
- **Workflow:** `elyra_admincheck` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
> [!NOTE]
> Der Sub-Workflow `elyra_admincheck` vergleicht die `chatId` mit einer internen Liste autorisierter Nutzer
> (z.B. als JSON-Datei, Umgebungsvariable oder Datenbankeintrag).
> Ist der Nutzer nicht autorisiert, wird das Feld `admin_erlaubt` auf `false` gesetzt und die Anfrage verworfen.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow `elyra_admincheck` und Variable `chatId`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 19 Set (Ablehnungsprompt für nicht erlaubtes Thema)
**Zweck:** Bereitet den Prompt für eine freundlich-bestimmte Ablehnungsantwort durch Ollama vor.
Dies erfolgt, wenn das Thema vom Klassifizierungsmodell als `nicht zulässig` eingestuft wurde.
**Node-Typ:** Set
**Name:** Elyra Thema abgelehnt (Prompt vorbereiten)
**Felder hinzufügen:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `ablehnungskontext`:
```text
Die folgende Anfrage konnte keinem zugelassenen Thema zugeordnet werden.
Formuliere eine kurze, aber klare Antwort, die dem Nutzer freundlich mitteilt,
dass diese Art von Anfrage aktuell nicht unterstützt wird.
Die Antwort soll sachlich bleiben, aber höflich und professionell wirken.
```
👉 **Screenshot geeignet:** Set-Node mit sichtbarem Feld `ablehnungskontext`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 20 HTTP Request (Antwort generieren Ollama)
**Zweck:** Sendet die Nutzeranfrage zusammen mit dem relevanten Kontext (z.B. Chatbefehle, Eventinfos, Ablehnungshinweis)
an das lokale Sprachmodell **Mistral** über den Ollama-Endpunkt und erhält eine formulierte Antwort zurück.
**Node-Typ:** HTTP Request
**Name:** Elyra Antwort generieren (Ollama)
**Parameter:**
- **Method:** `POST`
- **URL:**
```
http://ollama.<deine-domain>.tld/api/generate
```
- **Headers:**
- `Content-Type`: `application/json`
- **Body → JSON:**
```json
{
"model": "mistral",
"stream": false,
"prompt": "{{$json['ablehnungskontext'] || ''}}{{$json['kontext'] || ''}}\n\nNachricht:\n{{$json['text']}}"
}
```
> [!NOTE]
> Der Prompt setzt sich aus verschiedenen Quellen zusammen je nach Pfad:
> bei `chatbefehl`: Liste aller Chatkommandos (`kontext`)
> bei `eventinfo` oder `sendezeit`: Zusatzinfos aus Sub-Workflows
> bei `nicht erlaubt`: Ablehnungshinweis (`ablehnungskontext`)
>
> Der finale Prompt enthält immer den Originaltext des Nutzers.
> [!TIP]
> Du kannst über die `prompt:`-Zusammensetzung bei Bedarf eigene Regeln einbauen,
> z.B. Priorisierungen, stilistische Hinweise oder Sprachwechsel.
👉 **Screenshot geeignet:** HTTP-Request-Node mit Body-Feld `prompt`
**Anschlusslogik:**
- **→** weiter mit Node 21 *Antwort an WAHA senden*
#### Node 21 HTTP Request (Antwort über WAHA senden)
**Zweck:** Sendet die von Ollama generierte oder anderweitig erzeugte Antwort als WhatsApp-Nachricht über die WAHAAPI an den Nutzer.
**Node-Typ:** HTTP Request
**Name:** Elyra Antwort senden (WAHA)
**Parameter:**
- **Method:** `POST`
- **URL:**
```
https://waha.<deine-domain>.tld/api/sendText
```
- **Headers:**
- `Content-Type`: `application/json`
- *(optional)* `Authorization`: `Bearer <DEIN_WAHA_API_TOKEN>`
- **Body → JSON:**
```json
{
"chatId": "={{$json[\"chatId\"]}}",
"text": "={{$json[\"antwort\"] || $json[\"text\"]}}"
}
```
> [!NOTE]
> Das Feld `antwort` enthält entweder die direkte Antwort aus einem vorherigen SetNode
> oder die von **Ollama** generierte Antwort aus dem HTTP-RequestNode davor.
> Falls `antwort` leer ist, wird ersatzweise `text` genutzt z.B. für statische Fallbacks.
👉 **Screenshot geeignet:** HTTP Request mit gesetztem Body `chatId` + `text`
**Anschlusslogik:**
-**Ende des Workflows**