jbergner 9b3227348d
All checks were successful
release-tag / release-image (push) Successful in 1m33s
init
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00
2026-07-27 17:32:35 +02:00

GLPI AI Agent (Go + Ollama)

Produktionsorientierter, bewusst policy-gesteuerter Ticket-Agent für GLPI 11. Er liest neue/geänderte Tickets über die GLPI High-Level API, schlägt eine Kategorie vor, sucht freigegebene Wissenseinträge und kann nach mehreren harten Sicherheitsprüfungen eine erste Antwort schreiben.

Sicherheitsmodell

  • DRY_RUN=true ist der Default.
  • AUTO_REPLY=false ist der Default.
  • Das LLM erhält keine GLPI-Tools und kann keine Schreiboperation direkt auslösen.
  • Kategorie-IDs werden gegen die aus GLPI geladene Kategorie-Liste validiert.
  • Automatische Antworten stammen nicht aus freiem LLM-Text, sondern aus einem freigegebenen Knowledge-Dokument (auto_reply=true).
  • Die Knowledge-Suche ist fail-closed: Nur Quellen aus KNOWLEDGE_ALLOWED_SOURCES werden überhaupt geladen/indexiert.
  • Auto-Replies benötigen zusätzlich eine Quelle aus KNOWLEDGE_AUTO_REPLY_SOURCES sowie passende Sprach-/Stil-Metadaten.
  • Endnutzer-Antworten werden zentral mit konfigurierter Anrede, Grußformel und Signatur gerahmt.
  • Vor einer Antwort werden Followups zweimal geprüft: vor der KI-Analyse und unmittelbar vor dem Schreiben.
  • Sobald irgendein Followup existiert, antwortet der Agent nicht.
  • Pro Ticket wird innerhalb eines Prozesses seriell gearbeitet; Polling/Webhook-Ereignisse werden dedupliziert.
  • GLPI-Schreibfehler werden nicht automatisch wiederholt, um Doppelwrites zu vermeiden.
  • Das Dashboard ist read-only und standardmäßig mit HTTP Basic Auth geschützt.
  • Audit-Trail: data/runs.jsonl.

Wichtige Grenze: Die zweite Followup-Prüfung minimiert Race Conditions, kann ohne einen atomaren Conditional-Write auf GLPI-Seite aber kein mathematisch vollständig atomisches "check-and-write" garantieren. Für einen einzelnen Agent-Prozess ist zusätzlich ein Ticket-Lock aktiv.

Voraussetzungen

  • GLPI 11.0.6+ empfohlen (API v2.3).
  • High-Level API in GLPI aktiviert.
  • OAuth Client in Setup > OAuth Clients mit Password Grant und api Scope.
  • Dedizierter GLPI-Servicebenutzer mit minimal nötigen Rechten: Tickets lesen, Kategorien lesen/ändern (falls genutzt), Followups lesen/hinzufügen (falls Auto-Reply genutzt).
  • Ollama mit Chat- und Embedding-Modell.

Beim Start lädt der Agent /api.php/doc.json und prüft, ob die erwarteten Kernrouten vorhanden sind. Dadurch schlägt ein API-Mismatch früh und sichtbar fehl. Die mitgelieferten Tests laufen gegen HTTP-Mocks; eine echte GLPI-Instanz konnte in dieser Build-Umgebung nicht angebunden werden, daher ist der Shadow-Mode auf deiner Installation vor Live-Schreibzugriff zwingend.

Start mit Docker Compose

cp .env.example .env
$EDITOR .env

docker compose up -d ollama
docker compose exec ollama ollama pull qwen3:8b
docker compose exec ollama ollama pull embeddinggemma

docker compose up -d --build agent

Dashboard: http://127.0.0.1:7080/

Vor dem ersten Live-Betrieb unbedingt mehrere Tage/Wochen im Shadow Mode lassen:

DRY_RUN=true
AUTO_CATEGORY=true
AUTO_REPLY=false

Danach zunächst nur Kategorieänderungen:

DRY_RUN=false
AUTO_CATEGORY=true
AUTO_REPLY=false

Erst nach Auswertung der Audit-Daten einzelne KB-Einträge mit auto_reply=true freigeben und anschließend:

AUTO_REPLY=true
GLPI_AGENT_USER_ID=123

GLPI-Endpunkte

Default ist GLPI_API_VERSION=v2.3. Der Client verwendet:

  • OAuth: POST /api.php/token
  • Tickets: /api.php/v2.3/Assistance/Ticket
  • Followups: /api.php/v2.3/Assistance/Ticket/{id}/Timeline/Followup
  • Kategorien: /api.php/v2.3/Dropdowns/ITILCategory
  • OpenAPI-Prüfung: /api.php/doc.json

Die OpenAPI-Dokumentation deiner Installation ist die maßgebliche Quelle, weil GLPI die API-Dokumentation dynamisch aus Core und aktivierten Plugins erzeugt.

Wissensbasis, Quellen und Kommunikationspolicy

Jede Datei in knowledge/ ist JSON und trägt eine explizite Herkunft sowie Kommunikations-Metadaten:

{
  "id": "KB-128",
  "title": "GlobalProtect Gateway nicht erreichbar",
  "text": "Beschreibung, Fehlermeldungen, Voraussetzungen ...",
  "answer": "Bitte trennen Sie die bestehende VPN-Verbindung vollständig und starten Sie den VPN-Client anschließend neu.",
  "auto_reply": true,
  "min_score": 0.92,
  "categories": [22],
  "keywords": ["GlobalProtect", "Gateway not responding"],
  "source": "internal-kb",
  "source_uri": "kb://network/vpn/128",
  "language": "de-DE",
  "communication_style": "formal"
}

Die aktive Source-Policy wird über die Umgebung festgelegt:

# Nur diese Quellen werden geladen, eingebettet und durchsucht.
KNOWLEDGE_ALLOWED_SOURCES=internal-kb,glpi-kb,vendor-docs

# Nur diese Teilmenge darf eine automatische Antwort auslösen.
KNOWLEDGE_AUTO_REPLY_SOURCES=internal-kb,glpi-kb

KNOWLEDGE_AUTO_REPLY_SOURCES muss eine Teilmenge von KNOWLEDGE_ALLOWED_SOURCES sein. Mit KNOWLEDGE_AUTO_REPLY_SOURCES=none kann die Quellenfreigabe für Auto-Replies vollständig deaktiviert werden. Dokumente aus nicht erlaubten Quellen werden nicht in die Suchmenge aufgenommen und damit auch nicht an Ollama übergeben. Ein Knowledge-Dokument ohne source führt absichtlich zu einem Startfehler, damit die Herkunft nicht implizit geraten wird.

In dieser Version ist knowledge/ weiterhin der einzige physische Knowledge-Connector. source ist ein verbindliches Herkunfts-/Vertrauenslabel für importierte Dokumente. Ein späterer GLPI-KB-, Wiki- oder Hersteller-Connector kann dieselbe Policy unverändert verwenden.

Für die Kommunikation gelten zentrale Vorgaben:

COMMUNICATION_LANGUAGE=de-DE
COMMUNICATION_STYLE=formal
COMMUNICATION_SALUTATION=Guten Tag,
COMMUNICATION_CLOSING=Mit freundlichen Grüßen
COMMUNICATION_SIGNATURE=IT-Service

Ein Auto-Reply ist nur erlaubt, wenn language und communication_style des freigegebenen Knowledge-Dokuments exakt zur aktiven Policy passen. Das Feld answer enthält nur den fachlich freigegebenen Nachrichtentext; Anrede, Grußformel und Signatur werden von der Go-Policy zentral ergänzt. Damit kann das Modell diese Kommunikationsvorgaben nicht überschreiben.

categories begrenzt Auto-Reply auf die angegebenen Zielkategorien. Eine leere Liste bedeutet keine zusätzliche Kategorie-Einschränkung. min_score kann die globale Schwelle je Artikel verschärfen.

Bei aktiviertem RAG erzeugt Ollama Embeddings über /api/embed; der Cache landet in data/embeddings.json. Für Ticket und Knowledge wird dasselbe Embedding-Modell verwendet.

Operativer Kontext: Changes, Major Incidents, Uptime Kuma und Geräte

Der Agent kann vor der LLM-Entscheidung zusätzliche read-only Betriebsdaten einsammeln. Diese Daten werden normalisiert und als Fakten in den Prompt aufgenommen; das Modell erhält keine direkten Zugangsdaten und keine zusätzlichen Schreibwerkzeuge.

Change Calendar

CHANGE_CALENDAR_ENABLED=true
GLPI_CHANGE_PATH=/Assistance/Change
GLPI_CHANGE_FILTER=
GLPI_CHANGE_LIMIT=100
CHANGE_LOOKBACK=48h
CHANGE_LOOKAHEAD=24h

Der Agent lädt Changes im konfigurierten Zeitfenster, berechnet eine deterministische Relevanz zum Ticket (u. a. Tickettext, verknüpfte Geräte/Standorte) und übergibt höchstens die relevantesten Einträge an Ollama. GLPI_CHANGE_PATH wird beim Start gegen /api.php/doc.json geprüft. Bei einer Installation mit abweichender Route oder Filter-Syntax muss die Konfiguration an das OpenAPI-Schema der eigenen Instanz angepasst werden.

Aktive Major Incidents

MAJOR_INCIDENTS_ENABLED=false
GLPI_MAJOR_INCIDENT_FILTER=
GLPI_MAJOR_INCIDENT_LIMIT=20

Major Incidents werden bewusst nicht automatisch aus beliebigen Tickets erraten. Sie sind eine explizit vom Betreiber definierte Teilmenge der GLPI-Tickets. Erst wenn GLPI_MAJOR_INCIDENT_FILTER die in deiner Umgebung gültige Filterdefinition enthält, sollte MAJOR_INCIDENTS_ENABLED=true gesetzt werden. Ein zum aktuellen Ticket relevanter Major Incident blockiert standardmäßig einen normalen Auto-Reply; die Kategorieanalyse darf weiterlaufen.

Beispiel-Idee (die konkrete Syntax muss zu deinem GLPI-OpenAPI-Schema passen): ein Filter auf eine dedizierte Kategorie, Priorität/Impact oder ein eigenes Kennzeichen für Major Incidents.

Aktuelle Störungen mit Uptime Kuma

Für interne Uptime-Kuma-Instanzen ist der authentifizierte Prometheus-Endpunkt der empfohlene Modus:

UPTIME_KUMA_ENABLED=true
UPTIME_KUMA_URL=https://uptime.example.org
UPTIME_KUMA_MODE=metrics
UPTIME_KUMA_API_KEY=CHANGE_ME
UPTIME_KUMA_TIMEOUT=10s
UPTIME_KUMA_MAX_ISSUES=20

Der Client liest ausschließlich /metrics, verwendet den Uptime-Kuma-API-Key als HTTP-Basic-Auth-Passwort und gibt nur Monitore weiter, die nicht UP sind. Der API-Key wird nicht an Ollama übergeben.

Alternativ können bereits veröffentlichte Statusseiten gelesen werden:

UPTIME_KUMA_MODE=status_page
UPTIME_KUMA_STATUS_PAGES=it-services,network
UPTIME_KUMA_INCLUDE_MAINTENANCE=true

In diesem Modus liest der Agent /api/status-page/<slug> und /api/status-page/heartbeat/<slug> und berücksichtigt gepinnte Incidents, DOWN/PENDING-Monitore und optional Wartungen. Dieser Modus eignet sich nur für Informationen, die auf der betreffenden Statusseite ohnehin veröffentlicht werden dürfen.

Beziehungen zwischen Benutzer und Gerät

USER_DEVICE_CONTEXT_ENABLED=true
GLPI_USER_DEVICE_PATHS=/Assets/Computer
GLPI_USER_DEVICE_FILTER_TEMPLATE=user.id=={{user_id}}
GLPI_USER_DEVICE_LIMIT=20

Der Agent nutzt zunächst direkt am Ticket verknüpfte GLPI-Items. Zusätzlich werden soweit der Ticket-Response Requester-IDs enthält über die konfigurierten Asset-Routen dem Benutzer zugeordnete Geräte gelesen. Die Pfade werden beim Start gegen die OpenAPI-Dokumentation geprüft; die Filter-Syntax ist installationsabhängig und sollte im Shadow Mode verifiziert werden.

Die normalisierten Gerätedaten dienen u. a. dazu, Changes und Störungen besser zum Ticket zuzuordnen. Es werden keine Assets geändert.

Fail-closed Verhalten

CONTEXT_ENABLED=true
CONTEXT_TIMEOUT=12s
CONTEXT_RELEVANCE_MIN_SCORE=0.20
CONTEXT_BLOCK_AUTO_REPLY_ON_ERRORS=true
CONTEXT_BLOCK_AUTO_REPLY_ON_INCIDENT=true

Mit den sicheren Defaults gilt:

  • Fällt eine aktivierte Kontextquelle aus, wird der Lauf als unvollständig markiert und kein Auto-Reply gesendet.
  • Ein relevanter Major Incident oder eine relevante Uptime-Kuma-Störung blockiert einen normalen Standard-Auto-Reply.
  • Kategorieanalyse und Auditierung können trotzdem stattfinden.
  • Kontextquellen haben ausschließlich Leserechte.
  • Im Dashboard/Audit erscheinen pro Lauf die Anzahl der gefundenen Changes, Incidents, Uptime-Issues und Geräte sowie Warnungen bei unvollständigem Kontext.

Web- und Monitoring-Endpunkte

  • / Dashboard (Basic Auth)
  • /api/status Status JSON inklusive aktiver Sprache/Stil- und Quellenpolicy (Basic Auth)
  • /api/runs?limit=50 letzte Audit-Läufe (Basic Auth)
  • /healthz Prozess lebt
  • /readyz GLPI und Ollama erreichbar
  • /metrics Prometheus Textformat
  • POST /webhook/glpi optionaler Webhook-Eingang, geschützt durch X-Webhook-Secret

Das Polling bleibt immer aktiv und dient als Fallback. Der Webhook-Parser akzeptiert übliche Ticket-ID-Felder sowie Ticket-URLs; prüfe die konkrete Payload deiner GLPI-Webhook-Konfiguration im Shadow Mode.

Keine Doppelantworten

Der Schreibpfad ist bewusst streng:

  1. Ticket laden.
  2. Followups laden. Existiert eines: Stop.
  3. Knowledge sowie read-only Betriebskontext (Changes, Major Incidents, Uptime Kuma, Benutzer/Geräte) laden.
  4. KI klassifizieren lassen; Kontextdaten sind nur Fakten, keine ausführbaren Anweisungen.
  5. Policy Engine validiert IDs, Confidence, RAG-Score, Quellenfreigabe, Sprache/Stil und die Kontext-Gates.
  6. Optional Kategorie ändern.
  7. Direkt vor Auto-Reply Ticket und Followups erneut laden. Existiert jetzt ein Followup oder hat sich die Entscheidungsgrundlage geändert: Stop.
  8. Freigegebenen KB-Antworttext als Followup schreiben.

GLPI_AGENT_USER_ID wird bei AUTO_REPLY=true absichtlich verlangt, damit die Betreiberkonfiguration eindeutig einem dedizierten GLPI-Konto zugeordnet ist. Der aktuelle Code blockiert bei jedem vorhandenen Followup einschließlich eines früheren Agent-Followups und ist damit konservativer als nur "fremde" Antworten zu prüfen.

Produktionshinweise

  • Dashboard hinter Reverse Proxy mit TLS betreiben; Compose bindet Port 8080 absichtlich nur an 127.0.0.1.
  • GLPI über HTTPS anbinden. Plain HTTP wird standardmäßig abgelehnt (GLPI_ALLOW_INSECURE_HTTP=false).
  • .env niemals committen; besser Docker/Kubernetes Secrets oder systemd EnvironmentFile mit restriktiven Dateirechten verwenden.
  • Servicekonto nach Least-Privilege-Prinzip konfigurieren.
  • Für mehrere parallele Agent-Replikate muss die lokale Queue/State-Sperre durch einen verteilten Store/Lock (z. B. PostgreSQL/Redis) ersetzt werden. Die mitgelieferte Version ist für eine aktive Agent-Instanz ausgelegt.
  • Vor Live-Auto-Reply Tests mit echten anonymisierten Ticketmustern durchführen.
  • Knowledge-Antworten fachlich freigeben und versionieren.

Build & Tests

Das Projekt verwendet nur die Go-Standardbibliothek; damit gibt es keine Laufzeit-Abhängigkeiten im Agent-Binary.

make fmt
make test
make vet
make build
Description
No description provided
Readme MIT 72 MiB
Languages
Go 83.5%
HTML 16%
PowerShell 0.3%
Makefile 0.1%