Open WebUI erweitern: Tools, Functions und Pipelines selbst bauen und einbinden
Open WebUI mit eigenen Python-Tools und Filter-Functions erweitern: GLPI-Ticket-Tool, SearXNG-Suche, PII-Filter, Pipelines als Legacy einordnen und sicher betreiben.
Geprüft am 30.09.2026
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Open WebUI ist weit mehr als ein Chat-Frontend für Ollama: Mit eigenen Python-Erweiterungen legt es Helpdesk-Tickets an oder sucht im lokalen SearXNG, ohne dass Anfragen das eigene Netz verlassen. Sie brauchen Python-Grundkenntnisse (Klassen, async/await, Type Hints).
Voraussetzungen
- Aktuelle Open-WebUI-Version als Docker-Container (Function Calling im Modus „Native“, Standard)
- Zugang zum Admin-Panel (Administratorrolle)
- Ein Tool-Use-fähiges LLM lokal via Ollama (empfohlen: llama3.1:8b, qwen2.5:14b, mistral-nemo) oder remote per API
- API-Zugangsdaten für einzubindende Dienste (z. B. GLPI App-Token + User-Token, SearXNG-URL)
- Docker-Umgebung (für Pipelines zusätzlich: freier Port 9099)
Schritt 1: Die drei Erweiterungsebenen verstehen
Open WebUI unterscheidet drei Erweiterungstypen nach Ausführungsort, Berechtigung und Anwendungsfall:
| Merkmal | Tools | Functions (Pipe/Filter/Action) | Pipelines (Legacy) |
|---|---|---|---|
| Ausführungsort | Open-WebUI-Prozess | Open-WebUI-Prozess | Separater Container (Port 9099) |
| Wer ruft auf | LLM (Function Calling) | Automatisch / Event-basiert | Als Modell-Endpunkt |
| pip-Pakete | Ja (via requirements-Header) | Ja (via requirements-Header) | Ja (volle Kontrolle) |
| Klassenname (Pflicht) | Tools | Pipe / Filter / Action | Pipeline |
| Zugriff | Workspace (Nutzer) | Nur Admin-Panel | Admin-Panel (URL-Verbindung) |
| Empfohlen | Ja | Ja | Nein (Legacy) |
| Typischer Usecase | Kalender, Tickets, APIs, Suche | PII-Filter, Custom-Backend, Chat-Buttons | RAG, Multi-Modell-Orchestrierung (alt) |
Der entscheidende Unterschied zwischen Tool und Filter: Ein Tool ruft das LLM selbst auf; es entscheidet anhand Ihrer Docstrings, ob es die Methode braucht. Ein Filter läuft bei jeder Nachricht, unsichtbar für das Modell. Tools eignen sich für optionale Aktionen, Filter für verpflichtende Transformationen.
Schritt 2: GLPI-Ticket-Tool schreiben
Tools sind Python-Dateien mit einer Klasse namens Tools; daran erkennt Open WebUI den Typ. Jede Methode, die das LLM aufrufen soll, braucht vollständige Type Hints und einen aussagekräftigen Docstring, denn daraus erzeugt Open WebUI das JSON-Schema für das Modell.
"""
title: GLPI Ticket Creator
author: Marcel Schoenfelder
description: Erstellt GLPI-Tickets aus dem Chat heraus
requirements: requests>=2.31.0
version: 1.0.0
license: MIT
"""
import requests
from pydantic import BaseModel, Field
from typing import Callable, Any
class Tools:
class Valves(BaseModel):
GLPI_URL: str = Field(
default="https://glpi.example.com",
description="GLPI-Server-URL"
)
GLPI_API_TOKEN: str = Field(
default="",
description="GLPI App-Token",
json_schema_extra={"input": {"type": "password"}}
)
GLPI_USER_TOKEN: str = Field(
default="",
description="GLPI User-Token",
json_schema_extra={"input": {"type": "password"}}
)
def __init__(self):
self.valves = self.Valves()
async def create_ticket(
self,
title: str,
description: str,
priority: int = 3,
__event_emitter__: Callable[[dict], Any] = None,
__user__: dict = {}
) -> str:
"""
Erstellt ein neues Ticket im GLPI-Helpdesk-System.
:param title: Kurztitel des Tickets (Pflicht).
:param description: Ausfuehrliche Fehlerbeschreibung (Pflicht).
:param priority: Prioritaet 1=sehr hoch bis 6=sehr niedrig, Standard 3=mittel.
:return: Ticket-ID und Link als Bestaetigung.
"""
if __event_emitter__:
await __event_emitter__({
"type": "status",
"data": {"description": "Erstelle GLPI-Ticket...", "done": False}
})
headers = {
"Content-Type": "application/json",
"App-Token": self.valves.GLPI_API_TOKEN,
"Authorization": f"user_token {self.valves.GLPI_USER_TOKEN}"
}
payload = {
"input": {
"name": title,
"content": description,
"priority": priority
}
}
try:
resp = requests.post(
f"{self.valves.GLPI_URL}/apirest.php/Ticket",
json=payload, headers=headers, timeout=10
)
resp.raise_for_status()
ticket_id = resp.json().get("id", "unbekannt")
if __event_emitter__:
await __event_emitter__({
"type": "status",
"data": {"description": f"Ticket #{ticket_id} erstellt!", "done": True}
})
return (
f"Ticket erstellt: ID #{ticket_id} | "
f"URL: {self.valves.GLPI_URL}/front/ticket.form.php?id={ticket_id}"
)
except requests.RequestException as e:
return f"Fehler beim Erstellen des Tickets: {str(e)}"Der Parameter __event_emitter__ liefert Statusmeldungen während der Ausführung. Die Valves-Klasse speichert Konfigurationswerte (API-Keys, URLs) in der Open-WebUI-Datenbank statt im Skript; json_schema_extra={"input": {"type": "password"}} maskiert Tokens in der Oberfläche. Ältere Anleitungen setzen self.citation = True; laut Dokumentation hat das keine Wirkung mehr.
Verifizieren: Nach dem Einspielen in Schritt 4 meldet der Editor beim Speichern keinen Fehler, und das Tool erscheint in der Liste unter Workspace → Tools.
Schritt 3: SearXNG-Such-Tool einbinden
Mit einer lokalen SearXNG-Instanz erhält das LLM eine Websuche ohne externe API-Schlüssel:
"""
title: SearXNG Web Search
author: s-edv.com
description: Sucht im lokalen SearXNG-Suchserver
requirements: requests>=2.31.0
version: 1.0.0
"""
import requests
from pydantic import BaseModel, Field
from typing import Callable, Any
class Tools:
class Valves(BaseModel):
SEARXNG_URL: str = Field(
default="http://localhost:8080",
description="URL der lokalen SearXNG-Instanz"
)
MAX_RESULTS: int = Field(
default=5,
description="Maximale Anzahl Suchergebnisse"
)
def __init__(self):
self.valves = self.Valves()
async def search_web(
self,
query: str,
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Sucht im lokalen SearXNG nach aktuellen Informationen.
:param query: Suchanfrage als Stichwort oder Frage.
:return: Liste der Suchergebnisse mit Titel, URL und Snippet.
"""
if __event_emitter__:
await __event_emitter__({"type": "status",
"data": {"description": f"Suche nach: {query}", "done": False}})
try:
resp = requests.get(
f"{self.valves.SEARXNG_URL}/search",
params={"q": query, "format": "json"},
timeout=10
)
resp.raise_for_status()
results = resp.json().get("results", [])[:self.valves.MAX_RESULTS]
if not results:
return "Keine Ergebnisse gefunden."
output = []
for i, r in enumerate(results, 1):
output.append(
f"{i}. {r.get('title','')}\n"
f" {r.get('url','')}\n"
f" {r.get('content','')[:200]}"
)
if __event_emitter__:
await __event_emitter__({"type": "status",
"data": {"description": f"{len(results)} Ergebnisse gefunden", "done": True}})
return "\n\n".join(output)
except Exception as e:
return f"Suchfehler: {str(e)}"SearXNG liefert JSON nur, wenn json in der settings.yml unter search.formats freigeschaltet ist; sonst antwortet es mit 403. Die Suchmaschinen wählt SearXNG nach seiner eigenen Konfiguration.
Verifizieren: curl "http://SEARXNG-IP:8080/search?q=test&format=json" liefert vom Open-WebUI-Host aus JSON mit dem Schlüssel results. Im Container gilt localhost als der Container selbst; tragen Sie in den Valves die erreichbare Adresse ein.
Schritt 4: Tool im Admin-Panel registrieren und zuweisen
Tools spielen Sie im Browser über den Code-Editor ein:
- In Open WebUI: Workspace → Tools → „+“
- Python-Code einfügen, Titel und Beschreibung vergeben, speichern
- Beim Speichern installiert Open WebUI die Pakete aus dem
requirements-Header per pip - Tool dem Modell zuweisen: Workspace → Models → Modell bearbeiten → Tools-Tab → Tool aktivieren
- Alternativ pro Chat: im Eingabefeld über das Plus- bzw. Tools-Symbol aktivieren
- Valves (API-Keys, URLs) konfigurieren: Workspace → Tools → Zahnrad-Icon neben dem Tool
Verifizieren: Schreiben Sie im Chat mit dem Tool-fähigen Modell z. B. „Erstelle ein GLPI-Ticket: Drucker in Büro 3 druckt nicht.“ Nach der Statusmeldung „Erstelle GLPI-Ticket...“ antwortet das Modell mit Ticket-ID und Link. Erscheint keine Statusmeldung und das Modell antwortet nur mit Text, ist entweder das Tool nicht zugewiesen oder das Modell unterstützt kein Function Calling.
Schritt 5: Eine Filter-Function für PII-Redaktion schreiben
Functions verwalten Administratoren im Admin-Panel; sie greifen automatisch in den Nachrichtenstrom ein. Den Typ bestimmt der Klassenname, hier Filter. Auch Functions können Pakete über den requirements-Header nachladen.
"""
title: PII Redaction Filter
author: s-edv.com
description: Entfernt E-Mail-Adressen aus Nachrichten vor der KI-Verarbeitung
version: 1.0.0
"""
import re
from pydantic import BaseModel
class Filter:
class Valves(BaseModel):
priority: int = 10
def __init__(self):
self.valves = self.Valves()
async def inlet(self, body: dict, __user__: dict = {}) -> dict:
"""Wird vor der Modell-Anfrage ausgefuehrt – bereinigt Nutzereingabe."""
EMAIL_RE = re.compile(
r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
)
messages = body.get("messages", [])
for msg in messages:
if msg.get("role") == "user" and isinstance(msg.get("content"), str):
msg["content"] = EMAIL_RE.sub("[REDACTED_EMAIL]", msg["content"])
return body
async def outlet(self, body: dict, __user__: dict = {}) -> dict:
"""Wird nach der Modell-Antwort ausgefuehrt – unveraendert durchlassen."""
return bodyFilter haben drei Hook-Methoden: inlet() greift vor dem Modell ein, outlet() nach der Antwort, und stream() während des Streamings. Functions legen Sie unter Admin Panel → Functions → „+“ an und aktivieren sie global oder pro Modell.
Verifizieren: Schreiben Sie „Wiederhole wörtlich: max.mustermann@example.com“. Das Modell gibt [REDACTED_EMAIL] statt der Adresse zurück, weil es die Adresse nie erhalten hat.
Schritt 6: Pipelines (Legacy) weiterbetreiben
Pipelines bezeichnet das Projekt als Legacy und empfiehlt sie nicht mehr für neue Installationen. Für den Betrieb oder die Migration eines bestehenden Setups:
docker run -d \
-p 9099:9099 \
--add-host=host.docker.internal:host-gateway \
-v pipelines:/app/pipelines \
--name pipelines \
--restart always \
ghcr.io/open-webui/pipelines:mainVerbindung in Open WebUI herstellen: Admin Panel → Settings → Connections → OpenAI-API hinzufügen
- API URL:
http://host.docker.internal:9099 - API Key:
0p3n-w3bu!
0p3n-w3bu! ist der öffentlich bekannte Standardschlüssel; setzen Sie über -e PIPELINES_API_KEY=... einen eigenen und veröffentlichen Sie Port 9099 nicht ins Internet. Eine bestehende Pipeline lässt sich meist als Pipe-Function migrieren: Klasse von Pipeline in Pipe umbenennen und Methoden an die Function-Schnittstelle anpassen; der separate Container entfällt.
Verifizieren: Unter Admin Panel → Settings → Pipelines erscheint die Verbindung, und die Pipeline-Modelle stehen in der Modellauswahl.
Schritt 7: Multi-Worker-Deployments absichern
Betreiben Sie Open WebUI mit mehreren Workern oder Replikas, deaktivieren Sie die pip-Installation zur Laufzeit; gleichzeitige Installationen können den Dienst stören:
ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS=FalseInstallieren Sie die Pakete stattdessen im eigenen Image vor:
# Eigenes Dockerfile (FROM ghcr.io/open-webui/open-webui:main)
RUN pip install "requests>=2.31.0" "caldav>=1.3.0"Verifizieren: docker exec open-webui env | grep ENABLE_PIP zeigt False, und docker exec open-webui pip show caldav gibt die Paketversion aus.
Vergleich: Einbindungsschritte auf einen Blick
| Schritt | Tool | Filter-Function | Pipeline (Legacy) |
|---|---|---|---|
| Wo anlegen | Workspace → Tools → Neu | Admin → Functions → Neu | Eigener Container deployen |
| Code einfügen | Browser-Editor | Browser-Editor | Datei im /pipelines-Ordner |
| Aktivieren | Modell zuweisen oder im Chat | Global oder pro Modell/Chat | OpenAI-Endpunkt in Connections |
| Konfigurieren | Valves im Workspace | Valves im Admin-Panel | Valves im Pipeline-Container |
Troubleshooting / Typische Fehler
Tool wird nie aufgerufen
Ursache 1: Die Klasse heißt nicht exakt Tools. Ursache 2: Fehlende Type Hints; ohne sie entsteht kein gültiges JSON-Schema. Ursache 3: Das Modell unterstützt kein Function Calling. Prüfen Sie mit ollama show <modell>, ob unter „Capabilities“ tools steht.
Modell ruft Tool unzuverlässig auf
Häufigster Grund sind unklare oder fehlende :param:/:return:-Beschreibungen. Auch kleine Modelle (7B und darunter) rufen Tools oft unzuverlässig auf; verwenden Sie ein größeres Modell mit tools-Capability.
Function oder Tool kann ein Paket nicht importieren
Das Paket fehlt im requirements-Header, oder ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS=False verhindert die Installation. Ergänzen Sie den Header und speichern Sie erneut, oder installieren Sie das Paket im eigenen Image vor (Schritt 7).
Race-Condition bei pip-Install (Multi-Worker)
Siehe Schritt 7: ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS=False setzen und Pakete im Image vorinstallieren.
Status-Events flackern oder verschwinden
Die Event-Typen message und chat:message:delta funktionieren nur im nicht mehr unterstützten Legacy-Modus; im Standardmodus „Native“ werden sie überschrieben. Verwenden Sie status-Events und geben Sie das Ergebnis per return zurück.
API-Keys sind in der Oberfläche lesbar
Deklarieren Sie Valve-Felder mit Secrets als Field(default="", json_schema_extra={"input": {"type": "password"}}); das maskiert den Wert in der Oberfläche. Gespeichert wird er weiterhin in der Datenbank. Secrets gehören nie in den Quellcode.
Häufige Fragen
Brauche ich ein bestimmtes Ollama-Modell, damit Tools funktionieren?
Ja, das Modell muss Function Calling unterstützen; ollama show <modell> muss unter „Capabilities“ tools zeigen. Hinweise zur Auswahl gibt die Anleitung Ollama-Modelle richtig auswählen.
Was ist der Unterschied zwischen Tool und Filter?
Ein Tool ruft das LLM bei Bedarf auf, ein Filter läuft bei jeder Nachricht automatisch, siehe Schritt 1.
Kann ich externe Python-Bibliotheken in Functions nutzen?
Ja, in Tools und Functions über den requirements-Header; Open WebUI installiert die Pakete beim Speichern. In Produktivumgebungen mit mehreren Workern installieren Sie sie im Image vor.
Muss ich Open WebUI neu starten, wenn ich ein Tool hinzufüge?
Nein, Tools und Functions werden beim Speichern geladen. Die Installation von Paketen kann dabei einige Sekunden dauern.
Kann ein Tool auch in die Datenbank schreiben?
Ja, ein Tool führt beliebigen Python-Code mit den Rechten des Open-WebUI-Prozesses aus, also auch Schreibzugriffe auf Datenbanken, Dateien und APIs. Prüfen Sie den Code daher vorab und vergeben Sie API-Tokens mit minimalen Rechten.
Wie sichere ich API-Keys in Tools und Functions ab?
Über Valves mit Passwortfeld (siehe Troubleshooting). Für nutzerspezifische Keys gibt es UserValves, die jeder Nutzer selbst setzt.
Fazit
Tools und Filter-Functions erweitern Open WebUI ohne zusätzliche Container. Beginnen Sie mit einem einfachen Tool wie der SearXNG-Suche, um das Zusammenspiel von LLM, Docstrings und Valves kennenzulernen; Filter-Functions steuern danach die gesamte Nachrichtenverarbeitung. Prüfen Sie jeden Code vor der Installation, denn er läuft mit den Rechten des Servers.
Für den nächsten Schritt lohnt sich ein Blick auf die Absicherung der Plattform selbst: Open WebUI absichern: Benutzergruppen, RBAC und Modell-Zugriffssteuerung zeigt, wie Sie den Zugriff auf Tools und Modelle steuern. Für komplexe Workflows bietet die n8n-Anleitung einen Einstieg in die KI-Automatisierung.
Weiterführende Anleitungen und Quellen
- Ollama und Open WebUI mit Docker: eigenes lokales KI-Sprachmodell ohne Cloud betreiben
- Open WebUI absichern: Benutzergruppen, RBAC und Modell-Zugriffssteuerung
- Ollama-Modelle 2026 richtig auswählen: VRAM, Quantisierung und Modellvergleich
- n8n mit Docker: KI-Workflows und Automatisierung self-hosted
- SearXNG auf dem Synology NAS: private Metasuchmaschine ohne Tracking
- Open WebUI Docs: Tools & Functions Overview
- Open WebUI Docs: Tool Development Guide
- Open WebUI Docs: Events / EventEmitter Reference


