Übersicht
Ein Plugin ist ein Go-Wert, derpluginapi.Plugin sowie ein oder mehrere optionale
Hook-Interfaces implementiert. Jede Guardrail ist eine Instanz eines
Plugins, ebenso wie eine benutzerdefinierte Routing-Strategie für ein
virtuelles Modell. Es gibt einen einzigen Vertrag im Paket
github.com/enterpilot/gomodel/pluginapi und drei Wege, ein Plugin auszuliefern:
Unabhängig vom Weg erscheint ein Plugin-Typ unter seinem Manifest-Namen auf der Seite Guardrails
im Dashboard, in
GET /admin/plugins und als type in guardrails.rules.
pluginapi hängt ausschließlich von der Standardbibliothek ab. Ein Plugin importiert dieses Paket
und sonst nichts aus GoModel.
Phasen
Ein Plugin deklariert inManifest.Kinds, welche Hooks es implementiert. GoModel führt die
Inhaltsphasen aus dem passenden Workflow aus:
Welche Instanz in welcher Phase und in welcher Reihenfolge läuft, ist eine Entscheidung des
Workflows (
steps[].phase und steps[].step). Dieselbe Instanz darf einmal pro Phase vorkommen.
Zwei weitere Hook-Arten existieren im Vertrag, werden in diesem Release aber nicht ausgeführt:
request (vor der Modellauflösung) und complete (nachdem die Client-Response geschrieben wurde).
Ein Manifest darf sie deklarieren; GoModel prüft, ob die Interfaces implementiert sind, und
ignoriert sie zur Laufzeit. Die Art route wird von virtuellen Modellen verwendet, siehe
Routing-Strategie-Plugins.
Die Phasen gelten für /v1/chat/completions, /v1/responses und /v1/messages.
Anthropic-Requests werden vor jedem Hook in die Chat-Form übersetzt, sodass ein Plugin nur eine
Struktur sieht; Meta.Dialect meldet anthropic_messages, wenn das relevant ist. Inline-Items von
/v1/batches durchlaufen die Phase prompt, wenn guardrails.enable_for_batch_processing
aktiviert ist.
Ausführung der Kette
Innerhalb einer Phase werden Instanzen nachstep gruppiert:
- Steps laufen in aufsteigender Reihenfolge. Ein späterer Step sieht die Änderungen eines früheren.
- Innerhalb eines Steps laufen Instanzen, deren Manifest
Mutates: falseangibt, parallel auf einer flachen Kopie des Exchange (ihreValuesund Response-Header werden zurückgeführt). Der Step darf höchstens eine Instanz mitMutates: trueenthalten, die nach den lesenden Instanzen läuft. Zwei mutierende Instanzen im selben Step sind ein Konfigurationsfehler. - Die Entscheidungen eines Steps werden nach Schweregrad zusammengeführt:
block>respond>warn>allow. Die erste blockierende Entscheidung beendet die Kette nach ihrem Step.
Entscheidungen
Jeder Hook gibt einepluginapi.Decision zurück:
respond ist das „Blockieren mit einer sicheren Nachricht“, das die meisten Guardrail-Produkte
anbieten: Agent-Schleifen laufen weiter, statt einen 4xx-Fehler auszulösen. Verwenden Sie block,
wenn der Aufrufer einen Fehler sehen soll.
Detail wird im Audit-Eintrag gespeichert und darf keine Secrets enthalten.
NoStore bei einer beliebigen Entscheidung hält die Response aus dem
Response-Cache heraus, sowohl exakt als auch semantisch, sodass der nächste
Request mit gleichem oder ähnlichem Body die Plugins erneut durchläuft, statt eine gespeicherte
Antwort wiederzugeben. Setzen Sie es, wenn die Antwort Request-spezifische Daten enthält, die ein
Plugin auf dem Rückweg wiederherstellt, zum Beispiel ein De-Anonymisierer, der die
personenbezogenen Daten des Aufrufers wieder in den Assistant-Text einsetzt: Eine gecachte Kopie
würde diesen Text an jeden weitergeben, der als Nächstes einen passenden Prompt sendet. Es wird aus
jeder Phase und jeder Instanz des Requests berücksichtigt, unabhängig von der zusammengeführten
Entscheidung.
Audit-Trail
Jede ausgeführte Instanz hinterlässt ein Ergebnis in der Listedata.guardrails des
Audit-Eintrags, in Ausführungsreihenfolge über die Phasen prompt, response und stream
hinweg, stille allow-Entscheidungen eingeschlossen:
Ein konfigurierter Step ohne Ergebnis ist nicht gelaufen: Ein früherer Block oder eine frühere
Antwort hat den Request beendet, oder ein Cache-Treffer hat den Modellaufruf übersprungen. Das
Dashboard färbt das Workflow-Diagramm eines Audit-Eintrags anhand dieser Liste ein, und sein Tab
„Guardrails“ listet die Ergebnisse auf. Die Liste ist die Spur der Entscheidungen; die
nachfolgend beschriebenen Request-Revisionen sind die Spur der Bodys.
Entscheidungen der Phase
prompt werden außerdem an die Revisionskette des Requests angehängt
(dieselbe Stelle, an der Request-Umschreibungen festgehalten werden), ein Eintrag pro Instanz in
Step-Reihenfolge. Eine Instanz, die den Prompt bearbeitet hat, ist eine geänderte Revision, die den
Request so enthält, wie er direkt nach diesem Step aussah, einschließlich der Größen. So lässt sich
eine Kette von Änderungen Step für Step nachvollziehen, und die letzte ist das, was weitergeleitet
wurde. Die Request-Bodys werden unter denselben Schaltern LOGGING_LOG_BODIES und
LOGGING_LOG_REVISION_BODIES gespeichert wie Request-Umschreibungen. Jeder bearbeitende Step
hinterlässt auf dem Request-Pfad nur eine Kopie des Prompts; das Anwenden dieser Kopien auf den
Request und das Kodieren der Bodys erfolgt danach, parallel zum Provider-Aufruf.
LOGGING_LOG_GUARDRAIL_STEPS=false überspringt sie und hält die Änderungen einer Kette als eine
einzige Revision fest, die den Request in der weitergeleiteten Form enthält. Eine Warnung, ein
Block, eine Antwort oder ein Fehler ohne Änderung ist ein Eintrag ohne Änderung, der die
Entscheidung enthält; ein stilles allow hinterlässt keine Revision. Entscheidungen der Phasen
response und stream werden nur in data.guardrails festgehalten, und die Beanstandungen
darunter werden mit der Request-ID geloggt.
Fehlermodi und Timeouts
Jede Instanz hat einenfail_mode und einen timeout_ms:
Der Instanzname eines Fehlers im Modus
closed landet in den Logs und im Audit-Eintrag, aber nie
beim Client. Panics innerhalb eines Plugins werden abgefangen und als Fehler behandelt.
Timeouts werden zum Fristende durchgesetzt. Ein Hook erhält einen Context, der bei
timeout_ms endet (und wenn der Client die Verbindung trennt); GoModel hört in diesem Moment auf
zu warten, unabhängig davon, ob der Hook zurückgekehrt ist. Ein Hook, der seinen Context
ignoriert, läuft im Hintergrund weiter, ohne Auswirkung auf den Request – berücksichtigen Sie daher
ctx.Done() in allem, was blockiert. Ein aufgegebener Hook zählt als Fehler. Er schlägt nur dann
im Modus open fehl, wenn er nichts von dem berühren konnte, womit der Request fortfährt: Ein
nicht mutierender Hook läuft auf seiner eigenen Kopie des Exchange, die verworfen wird. Ein
mutierender Hook oder ein Stream-Hook im laufenden Stream, der seine Frist überschreitet, lässt den
Request immer fehlschlagen, selbst mit fail_mode: open, weil seinen Änderungen nicht mehr
vertraut werden kann. Init hat eine feste Frist von 10 s, die genauso behandelt wird: Ein Init,
das bis dahin nicht zurückgekehrt ist, lässt die Instanz fehlschlagen.
Behalten Sie den Standardwert bei. Stellen Sie eine Instanz nur dann auf open um, wenn sie ein
Beobachter ist, dessen Ausfall akzeptabel ist (ein Header-Tagger, ein Klassifikator, der nur
warnt).
Lebenszyklus von Instanzen
GoModel erzeugt pro Guardrail-Definition einen Plugin-Wert und ruftInit einmal auf.
Definitionen werden regelmäßig aus dem Speicher neu geladen (workflows.refresh_interval,
Standardwert 1 Minute) sowie bei jeder Admin-Änderung; eine Instanz, deren Typ, Konfiguration,
user_path, fail_mode und timeout_ms unverändert sind, übersteht das Neuladen, sodass Zustand,
den ein Plugin ansammelt (Zähler, Caches, Verbindungen), erhalten bleibt. Eine ersetzte oder
gelöschte Instanz wird stillgelegt und später geschlossen, frühestens zwei Refresh-Intervalle nach
der Stilllegung und erst dann, wenn nichts sie mehr hält: Kompilierte Workflows halten ihre
Instanzen, bis der Workflow-Snapshot sie verwirft, und Requests halten ihre Instanzen für die Dauer
einer Phase (einschließlich eines langen Streams). Ein Hook wird nach Close nie aufgerufen; ein
Aufruf, der eine geschlossene Instanz erreicht, schlägt stattdessen gemäß dem fail_mode der
Instanz fehl. Beim Herunterfahren wird jede Instanz geschlossen.
Streaming
Bereits an den Client gesendete Bytes lassen sich nicht zurückholen, daher wählt ein Stream-Plugin inStreamHook.StreamPolicy() einen von drei Modi:
Lookbehind (nur
transform): GoModel hält pro Fenster die letzten LookbehindChars Zeichen
zurück und präsentiert jedes Delta zusammen mit diesem Rest, sodass ein Muster, das sich über zwei
Chunks erstreckt (eine Telefonnummer, ein API-Key), in einem Event sichtbar ist, bevor ein Teil
davon den Client erreicht. Der Text einer Choice ist ein Fenster, die Argumente jedes ihrer Tool
Calls (StreamEvent.Call) jeweils ein weiteres, sodass ein Platzhalter, der auf zwei
Argument-Deltas aufgeteilt ist, auf dieselbe Weise umgeschrieben wird. Ein Delta einer anderen Art
für dieselbe Choice leert zuerst die Fenster der anderen Arten dieser Choice, sodass Text und Tool
Calls ihre Reihenfolge behalten, während parallele Tool Calls unabhängig voneinander streamen. Ein
Muster von bis zu LookbehindChars + 1 Zeichen wird immer erkannt. 0 deaktiviert die Funktion.
Der Rest kommt bereits mit den früheren Änderungen des Plugins zurück, und StreamEvent.Overlap
gibt an, wie viele führende Zeichen er umfasst: Ein Plugin darf nur Treffer bearbeiten, die über
Overlap hinausreichen, da ein Treffer innerhalb davon bereits beim vorherigen Mal behandelt
wurde (das eingebaute string_replace macht das so, sodass eine Regel wie a => aa einmal
angewendet wird und nicht auf ihre eigene Ausgabe). Ein Treffer, der das Ende des Fensters
berührt, kann mit dem nächsten Delta noch wachsen, daher darf ein Plugin ihn unbearbeitet lassen:
Der Rest zeigt ihn beim nächsten Mal erneut. StreamEvent.Final markiert das letzte Event eines
Fensters (der Stream endete oder ein Delta einer anderen Art hat es geleert), nach dem nichts mehr
zurückgehalten wird; eine so aufgeschobene Änderung ist dann fällig. string_replace macht das bei
Treffern, die in seinen Lookbehind passen, sodass ein offenes Muster den gesamten Key abdeckt. Ein
Chat-Chunk, der Text zusammen mit finish_reason oder usage enthält, wird ebenfalls neu
segmentiert; diese Felder werden einmal gesendet, mit dem letzten Text des Chunks oder in einem
Chunk mit leerem Text, wenn das Plugin diesen Text entfernt hat.
Zusammenfassen (nur transform): Ein Hook mit hohen Kosten pro Aufruf, etwa ein Klassifikator
oder ein Named-Entity-Detektor, der einen Sidecar aufruft, kann sich keinen Aufruf pro Token
leisten. Mit MinChunkChars sammelt GoModel die Text-Deltas einer Choice, bis mindestens so viele
neue Zeichen ausstehen, und präsentiert sie als ein Text-Event; ein Nicht-Text-Event und das Ende
des Streams leeren einen kürzeren Lauf. Das lässt sich mit Lookbehind kombinieren: Der
zurückgehaltene Rest steht am Anfang des Fensters und wird weiterhin von Overlap gezählt, sodass
derselbe Bearbeitungsvertrag gilt. Der Client wartet jeweils auf bis zu MinChunkChars Zeichen,
und der größte Wert unter den transform-Instanzen eines Streams gilt für alle, begrenzt auf
16384 Zeichen. 0, der Standardwert, präsentiert Deltas so, wie sie eintreffen.
Signiertes Reasoning: Auf /v1/messages erreicht Anthropic-Thinking den Client so, wie der
Provider es signiert hat, da der Client es mit der Signatur zurücksendet. Seine Reasoning-Events
tragen StreamEvent.Signed; GoModel ignoriert replace und drop bei ihnen und macht Änderungen
an diesem Reasoning rückgängig, die an der zusammengesetzten Response vorgenommen wurden
(buffer-Plugins und die Phase response).
Chat-Chunks mit mehreren Choices werden in einen Chunk pro Choice aufgeteilt, bevor Plugins sie
sehen, sodass jede Choice geprüft wird; die usage des Providers bleibt am letzten davon.
Wiederholter Text (Responses API): Nachdem ein Delta ersetzt oder verworfen wurde, werden die
Events, die den vollständigen Text eines Teils wiederholen (response.output_text.done,
response.content_part.done, response.output_item.done und das abschließende
response.completed), auf den tatsächlich ausgegebenen Text umgeschrieben, sodass Clients, die
statt der Deltas die Completion lesen, dasselbe Ergebnis sehen.
Event-Größe (transform und observe): Ein einzelnes SSE-Event, das größer als 4 MiB ist,
kann im laufenden Stream nicht geprüft werden. Der Stream endet daher im Modus closed mit dem
Code event_too_large, statt das Event an den Plugins vorbei weiterzuleiten. Kommentare und
Keep-Alive-Zeilen sind nie betroffen.
Puffern: Der Upstream wird in einen begrenzten Puffer gelesen (MaxBufferBytes, Standardwert
4 MiB; bei Überschreitung schlägt der Request im Modus closed mit dem Code response_too_large
fehl). Der Puffer wird von allen Plugins, die den Stream puffern, und von der Response-Kette
gemeinsam genutzt, daher gilt die größte angeforderte Obergrenze – und der Standardwert, sobald die
Response-Kette läuft oder ein Plugin keine Obergrenze setzt. Während des Lesens sendet GoModel alle
15 s den SSE-Kommentar : gomodel-buffering, damit Proxys und Clients nicht in einen Timeout
laufen; SDKs ignorieren Kommentarzeilen. Nachdem die Response-Kette gelaufen ist, werden die
ursprünglichen Bytes unverändert wiedergegeben (allow, warn), aus der bearbeiteten Completion
wird ein Stream synthetisiert (allow mit Änderungen, respond) oder es wird ein einzelner
Fehler-Chunk gesendet (block).
Gemischte Ketten: Wenn eine Stream-Instanz buffer anfordert oder der Workflow einen
response-Step enthält, wird der gesamte Stream gepuffert, und transform-Instanzen laufen über
die Wiedergabe.
Warnungen bei Streams: Wenn Response- oder Stream-Plugins laufen, sendet GoModel die
HTTP-Header erst mit den ersten Bytes des Bodys statt vorab. Ein warn, das über einer gepufferten
Response entschieden wird, erreicht den Client daher als Header X-GoModel-Guardrail, sofern das
Puffern vor dem ersten Keep-Alive-Kommentar (15 s) abgeschlossen war. Ein später entschiedenes
warn (in OnStreamEnd eines transform-Streams oder nachdem ein Keep-Alive gesendet wurde) kann
bereits gesendete Header nicht mehr ändern; es wird trotzdem im Audit-Trail und in den Logs
festgehalten.
Abbrechen eines Streams: Eine terminate-Entscheidung im Modus transform oder ein
block/respond aus OnStreamEnd beendet den Stream mit finish_reason: "content_filter"
(stop_reason: "end_turn" bei Anthropic), gefolgt von [DONE]. Der Client behält, was er bereits
empfangen hat. Wenn vor der Entscheidung nichts nach außen gelangen darf, muss das Plugin den Modus
buffer verwenden; die eingebauten string_replace und llm_judge tun genau das für block und
respond.
Der Exchange
Jeder Hook erhält einen*pluginapi.Exchange, in jeder Phase eines Requests dasselbe Objekt:
Änderungen erfolgen über Methoden, nicht über Feldzuweisungen, sodass GoModel nur das neu kodiert,
was sich geändert hat, und unberührte Nachrichten jedes Provider-spezifische Feld behalten
(
cache_control, mehrteiliger Inhalt, zusätzliche Felder). Das Entfernen einer Nachricht, die
einen Tool Call enthält, dessen Ergebnis noch vorhanden ist (oder umgekehrt), gibt einen
DanglingToolError zurück, der die Partnernachricht nennt; der Request wird abgelehnt, wenn das
Paar getrennt bleibt.
Ein Plugin, das Text durchsucht oder umschreibt, muss die Teile nicht selbst durchlaufen:
Prompt.TextTargets(roles...) gibt ein TextTarget pro Textteil zurück (reiner Text sowie Text in
Tool-Ergebnissen) mit Nachrichten-ID, Rolle, Teil-Indizes und aktuellem Text, und
Prompt.SetTargetText(target, text) schreibt die Änderung zurück. Completion.TextTargets() und
Completion.SetTargetText tun dasselbe für die Choices einer Response. Die eingebauten
string_replace und llm_based_altering sind auf diese Weise geschrieben.
Auch Medien lassen sich bearbeiten. Part.DecodeMedia() gibt die Inline-Bytes und den Medientyp
eines als Data-URI gesendeten Bilds oder von Chat-Input-Audio zurück, und
Prompt.SetMedia(msgID, partIdx, mediaType, data) ersetzt den Payload eines Bild- oder
Audio-Teils: Der Teil wird wieder als Data-URI (Bilder) bzw. als base64-Input-Audio ausgegeben,
und die übrigen Felder des Wire-Teils, etwa detail eines Bilds, bleiben erhalten. Ein per URL
referenziertes Bild hat keine Inline-Daten zum Dekodieren; ein Plugin, das es schwärzen möchte,
ruft es selbst ab. Medien innerhalb eines Tool-Ergebnisses werden mit SetToolResult ersetzt.
Meta.Cache.PlannedPrefixMessages teilt einem Prompt-Plugin mit, wie viele führende Nachrichten
der Cache-Planer des Providers als gecachtes Präfix markieren wird. Anhängen erhält den Cache;
Bearbeitungen innerhalb des Präfixes machen ihn für die Session ungültig.
Konfiguration
Das Plugin-System aktivieren
.so-Dateien,
keine Guardrails und keine Routing-Strategie-Plugins (ein virtuelles Modell mit
strategy: plugin wird abgelehnt). GET /admin/plugins und die Endpunkte unter
/admin/guardrails antworten mit 503 und dem Code feature_unavailable, und das Dashboard
blendet die Seite Plugins & Guardrails aus. Guardrails sind Plugin-Instanzen, daher schaltet
GUARDRAILS_ENABLED=true das Plugin-System implizit ein; setzen Sie PLUGINS_ENABLED=true allein,
um Routing-Strategie-Plugins zu verwenden oder Guardrail-Definitionen zu verwalten, ohne sie
auszuführen.
Instanzen
Instanzen werden inguardrails.rules deklariert oder im Dashboard erstellt; beide landen im
selben Speicher. Die Einstellungen der einzelnen Typen finden Sie unter
Guardrails.
config.yaml werden in den verwalteten Standard-Workflow an der Position order in
phase übernommen. type akzeptiert ein optionales Präfix plugin:.
.so-Dateien laden
file-Wert muss innerhalb eines der search_paths aufgelöst werden (symbolischen
Links wird gefolgt, und sie werden geprüft). Eine Datei, die nicht aufgelöst, verifiziert oder
geöffnet werden kann, führt zu einem Startfehler, der die Datei nennt.
Installationen ohne config.yaml können dieselben Einträge über die Umgebung angeben:
PLUGINS_LOAD ist eine kommagetrennte Liste von file-Werten, jeweils optional als file=sha256,
um den Digest zu fixieren. Nur das letzte =, gefolgt von genau 64 Hexadezimalzeichen, leitet eine
Digest-Fixierung ein; jeder andere Eintrag ist ein Dateiname. Ist die Variable nicht leer, ersetzt
sie die Liste plugins.load aus config.yaml (Umgebung > Konfiguration, wie bei jedem anderen
über die Umgebung konfigurierbaren Wert).
GUARDRAILS_ENABLED bleibt der Schalter, der entscheidet, ob konfigurierte Instanzen auf den
Traffic angewendet werden.
Dashboard
- Seite Plugins & Guardrails: Create Guardrail listet jedes Plugin auf, das einen Hook für
prompt,responseoderstreamimplementiert. Das Formular wird aus demConfigSchemades Plugins erzeugt:text,textarea,number,select,checkboxes,bool(ein Schalter),list(Freitext-Einträge, einer pro Zeile, gespeichert als String-Array),secret(maskiert;GETgibt********zurück, und das Zurücksenden genau dieses Literals behält den gespeicherten Wert bei) undmodel(dieselbe durchsuchbare Modellauswahl wie im Playground und in den Editoren für virtuelle Modelle, die die Modelle des Gateways auflistet; ein Alias oder ein virtuelles Modell wird eingetippt). Der Abschnitt Advanced enthält Fail mode (Default, Closed, Open) und Timeout (ms); diese gelten für die Instanz in jedem Workflow, der sie referenziert. - Plugin-Liste: Unten auf der Seite Plugins & Guardrails steht jeder geladene Plugin-Typ mit
Titel und Namen (
Header Edit, dannheader_edit– der Name ist das, worauf sich die Konfiguration bezieht), Version, Hooks, Quelle (eingebaut, registriert oder der Pfad der.so) und Zustand. Guardrail-Plugins (deren Instanzen eine Richtlinie auf Prompts, Responses oder Streams anwenden) tragen ein Schild-Symbol und werden zuerst aufgeführt; Routing-Strategien sind ebenfalls Plugins, aber keine Guardrails. Ein Plugin, das nicht geladen werden konnte, wird mit seinem Fehler aufgeführt. Die Seite wird angezeigt, sobald das Plugin-System aktiviert ist. - Seite Workflows: Jeder Step hat eine Auswahl Phase; das Dropdown für Instanzen bietet nur Instanzen an, deren Plugin diese Phase implementiert.
Shared-Object-Plugins
Das Go-Paketplugin gibt die Regeln vor, und sie sind streng:
- Plattform: Linux, macOS und FreeBSD, und nur in einem Binary, das mit
CGO_ENABLED=1gebaut wurde. Das Standard-Binary und das Standard-Image von GoModel sind statisch und lehnen.so-Dateien mit einer entsprechenden Fehlermeldung ab. Verwenden Siemake build-plugins(bin/gomodel-plugins) oder das Imagegomodel:<version>-plugins(make image-plugins, gebaut ausDockerfile.plugins, glibc-Laufzeit). - Exakte Toolchain: Das Plugin muss mit derselben Go-Version, denselben Build-Flags
(
-trimpath,-race,-tags) und identischen Quellen jedes gemeinsam genutzten Pakets gebaut werden. Nur die Standardbibliothek undpluginapiwerden gemeinsam genutzt, daher wirken sich interne Änderungen an GoModel nie auf ein Plugin aus, aber jedes GoModel-Release und jedes Update der Go-Toolchain (einschließlich Patch-Releases) erfordert einen Neubau. Machen Sie daraus einen CI-Schritt. - Kein Entladen: Eine
.sobleibt bis zum Neustart geladen. Änderungen an einer geladenen Datei werden beim Neustart wirksam; das Bearbeiten der Konfiguration einer Instanz führtInitohne Neustart erneut auf einem frischen Wert aus. - Vertrauenswürdiger Code: Das Laden einer
.soist gleichbedeutend mit einer Änderung des Binarys. Lassen Siesearch_pathsim Besitz von root und fixieren Sie im Produktivbetriebsha256.
Bauen und prüfen
plugin build führt go build -buildmode=plugin mit den Flags aus, die im ausführenden
gomodel-Binary hinterlegt sind, erzwingt CGO_ENABLED=1, fixiert GOTOOLCHAIN auf die Go-Version
des Hosts, prägt eine Variable GoModelBuildInfo in das Plugin ein und lehnt eine Ausgabe ab,
deren Go-Version von der des Hosts abweicht. Bauen Sie immer mit dem Binary, das das Plugin laden
wird. plugin inspect öffnet die Datei und gibt ihr Manifest, ihr Konfigurationsschema und ihre
Build-Informationen aus. Ein abgelehnter Ladevorgang nennt beide Seiten:
plugin-builder von Dockerfile.plugins und führen Sie es gegen
Ihr Plugin-Verzeichnis aus:
gomodel --version gibt die Go- und pluginapi-Versionen des Binarys aus.
docs/example_plugins/README.md beschreibt, wie Sie ein Plugin in einem eigenen Go-Modul bauen.
Ein Plugin schreiben
Einpackage main, das func GoModelPlugin() pluginapi.Plugin exportiert, ist ein
Shared-Object-Plugin; derselbe Typ, registriert mit ext.RegisterPlugin, ist ein einkompiliertes
Plugin. Dieses Beispiel blockiert einen Request, wenn die letzte Benutzernachricht ein
konfiguriertes Wort enthält:
-
Initerhält die Konfiguration nach der Validierung gegenConfigSchema: Standardwerte angewendet, Zahlen, Booleans und Listen konvertiert, unbekannte Keys abgelehnt, erforderliche Keys geprüft. Zeilenorientiertetextarea-Felder akzeptieren sowohl eine YAML-Liste von Strings als auch einen Block-Skalar;list-Felder akzeptieren eine Liste oder eine kommagetrennte Zeile und kommen als JSON-Array an;bool-Felder akzeptierentrue/falseals Boolean oder Text und kommen als JSON-Boolean an.Initwird einmal pro konfigurierter Instanz aufgerufen und erneut, wenn die Instanz bearbeitet wird. -
Lesen Sie die Konfiguration mit
pluginapi.ParseConfigund dessen typisierten Lesemethoden (String,Choice,Bool,Int,Float,OptionalFloat,List,Lines,Roles,BlockStatus) statt mit einem Struct: Jede Lesemethode wendet dieselben Konvertierungen an, die das Dashboard und config.yaml benötigen, gibt bei einem Problem ihren Standardwert zurück, undErr()meldet das erste Problem mit dem betroffenen Key. Ein Decoder ist so eine einfache Liste von Zuweisungen plus eine Prüfung.RoleOptions()ist die Checkbox-Liste, dieRolesversteht. -
Geben Sie Befunde über
pluginapi.Enforcementaus: Lesen Sieaction,message,block_statusund optionalrespond_textdarin ein; anschließend liefertEnforce(code, detail)den Block-Fehler, die Respond-Completion oder die Warnung, undReject(code, detail)liefert Block oder Respond, selbst wenn die Aktionwarnist (für einen Befund, der nicht durchgelassen werden darf).BlockStatusField()ist das übliche Feldblock_status. Die eingebauten Guardrails nutzen dies gemeinsam, sodass sich ihr Block, Respond und Warn gleich verhalten. -
Exportieren Sie einen Konstruktor (
func GoModelPlugin() pluginapi.Plugin), damit eine Datei mehrere Instanzen bedienen kann. Einvar GoModelPlugin pluginapi.Pluginfunktioniert ebenfalls, beschränkt die Datei aber auf eine einzige Instanz. -
Setzen Sie
Mutates: true, wenn das PluginPrompt,Responseoder den Stream bearbeitet. Nicht mutierende Plugins dürfen sich einen Step teilen und laufen parallel. -
Setzen Sie
Guardrail: true, wenn die Instanzen des Plugins Guardrails sind: Richtlinien, die auf Prompts, Responses oder Streams angewendet werden, unabhängig davon, ob sie blockieren, antworten, warnen oder umschreiben. Jedes eingebaute Traffic-Plugin setzt es (llm_judge,string_replace,tag_replace,header_edit,system_prompt,llm_based_altering); eine Routing-Strategie wiecheapest_healthyberührt nie den Traffic und lässt es auf false. Die Laufzeit ignoriert das Flag; das Dashboard markiert Guardrail-Plugins und ihre Instanzen mit einem Schild-Symbol und führt sie zuerst auf. -
Der an
InitübergebeneHostbietetLogger()(vorab mit Plugin und Instanz getaggt),Inference().Complete(...)(eine Chat Completion über das Gateway mit Ursprungplugin, für Budgets und Audit auf<user path>/guardrails/<instance>begrenzt),Metrics()(Namen mit dem Präfixplugin_<name>_) undHTTPClient()für Aufrufe externer Dienste wie eines Klassifikators oder eines Detektors für personenbezogene Daten. Der Client wird von allen Instanzen gemeinsam genutzt, verwendet die Proxy-Umgebung und den Verbindungspool des Gateways und hat als Absicherung einen Timeout von 60 s; erstellen Sie Requests mithttp.NewRequestWithContext, damittimeout_msder Instanz jeden Aufruf begrenzt.History()gibt in diesem Release einen Fehler zurück. -
Der Host implementiert außerdem das optionale
pluginapi.DecisionsHost. Prüfen Sie es per Type Assertion auf demHostund rufen Sie dannDecisions().Decide(ctx, req)auf, um einem Decision-Modell typisierte Fragen zu einem Input zu stellen, so wie esPOST /v1/decisionstut. Eine Frage ist ein Prädikat (Wahrscheinlichkeit für Ja), eine Auswahl oder eine Bewertung. Der Aufruf wird wieInference().Completeeingegrenzt und erfasst.Decisions().DecisionModel(model)meldet, ob ein Modell ausschließlich Decisions beantwortet, etwa jedesjev-Modell.llm_judgenutzt beides, um anhand einer Wahrscheinlichkeit zu blockieren, statt ein Chat-Urteil zu parsen: -
Fügen Sie
Summarize(config json.RawMessage) stringhinzu, um in der Guardrail-Liste eine einzeilige Zusammenfassung darzustellen, undNormalize(config) (json.RawMessage, error), um eine Konfiguration vor dem Speichern zu kanonisieren. Beides ist optional. -
Implementieren Sie
pluginapi.HealthChecker(Health(ctx) error), wenn eine Instanz von etwas außerhalb des Prozesses abhängt, etwa einem Klassifikator-Sidecar. GoModel prüft dies außerhalb des Request-Pfads, sobald die Instanz erstellt ist, und bei jeder Guardrail-Aktualisierung (workflows.refresh_interval, standardmäßig eine Minute), mit einer Frist von 5 s. Eine fehlschlagende Prüfung markiert die Instanz inGET /admin/guardrailsund im Dashboard alsdegraded, mit dem Fehler als Begründung, und wird einmal pro Zustandswechsel geloggt; der Fehlertext darf keine Secrets enthalten und wird auf 256 Zeichen gekürzt. Der Zustand dient nur der Information: Der Traffic läuft weiterhin gemäß ihremfail_modedurch die Instanz. -
Implementieren Sie
pluginapi.ContentEditor(EditsContent() bool), wenn ein Plugin, dasMutatesdeklariert, so konfiguriert werden kann, dass es nicht bearbeitet, etwa ein Detektor, der markiert statt umzuschreiben. GoModel fragt eine konfigurierte Instanz vor Arbeiten, die es nur für bearbeitende Plugins ausführt – dem Wiedergeben des gespeicherten Verlaufs eines verketteten Responses-Requests, statt den Providerprevious_response_idselbst auflösen zu lassen. Es ändert nie, wie ein Hook läuft, und bei einem mutierenden Plugin ohne diese Methode wird davon ausgegangen, dass es bearbeitet. -
Die Feld-Inputs
secretundmodelgibt es für API-Keys externer Klassifikatoren und für Modellauswahlen; einsecret-Wert wird in jeder Admin-Response maskiert. Verwenden Sieboolfür einen Schalter undlistfür eine offene Liste von Strings (Entitätstypen, eine Allowlist), statt diese in einemselectoder einemtextareazu kodieren. -
Fügen Sie Feldern, die zu einem virtuellen Modell statt zur Instanz gehören,
Scope: pluginapi.ScopeRoutehinzu (nur bei Routing-Strategien).
docs/example_plugins/keywordblock/main.go ist ein ausführlicheres, kommentiertes Beispiel mit
Hooks für prompt und response, und die eingebauten Plugins unter internal/plugins/builtin/
sind Referenzimplementierungen jeder Hook-Art.
Ein Plugin testen
pluginapi/plugintest testet ein Plugin ohne GoModel:
plugintest.NewHost(replies...)ist ein Fake-Host: vorgegebene Inferenz-Antworten (oder eineReply-Funktion oder einErr), Decisions, die von einerAnswer-Funktion beantwortet werden (ohne sie wird jede Frage abgelehnt),DecisionModelsfürDecisionModel, aufgezeichneteRequests()undDecisionRequests(), aufgezeichneteRecorded()-Metriken und ein einstellbarer HTTP-Client für einen Fake-Sidecar, der mithttptestgestartet wird.plugintest.Init,Text,Prompt,CompletionundExchangeerstellen die Fixtures, die ein Hook-Aufruf benötigt, mit sauberer Änderungsverfolgung und einemValues-Speicher.plugintest.RunStream(ctx, hook, x, events)steuert einenStreamHookso, wie es der Host unter dessenStreamPolicytut: Zusammenfassen von Chunks, der zurückgehaltene Lookbehind-Rest wird mitOverlaperneut gezeigt,pass,replace,dropundterminatewerden auf das gesamte Fenster angewendet, und im gepufferten Modus wird die Completion fürOnResponsezusammengesetzt. Das Ergebnis enthält den Text, den ein Client pro Choice erhalten hätte, die zugestellten Events, dieterminate-Entscheidung und die End-Entscheidung.
string_replace, wobei New der Konstruktor dieses Plugins ist:
x.Response selbst, bevor Sie RunStream aufrufen, wenn der Hook Tool Calls, Nutzung oder
einen Finish-Reason sehen muss.
Routing-Strategie-Plugins
Ein Plugin, daspluginapi.RouteStrategy implementiert, ist eine Load-Balancing-Strategie für
virtuelle Modelle:
RouteRequest enthält die Source des virtuellen Modells, die Candidates in konfigurierter
Reihenfolge (Provider, Modell, provider/model, Gewichtung und Preise pro Million Tokens, sofern
bekannt), die Session-ID und das Sticky-SessionTarget, Meta sowie die strategy_config des
virtuellen Modells als JSON (Prompt ist in diesem Release nil). Select gibt das gewählte Ziel
Qualified und einen optionalen Reason zurück (nur für Debug-Logs); OnAttemptEnd meldet
Erfolg, Status, Latenz und Timeouts, damit sich die Strategie anpassen kann. Failover-Ketten,
Kapazitätsprüfungen und das Festlegen von Sessions bleiben Aufgabe von GoModel.
Wählen Sie die Strategie am virtuellen Modell aus:
strategy_configwird gegen die routenbezogenen Felder des Plugins validiert (route_fieldsinGET /admin/plugins).strategy: pluginohnestrategy_pluginist ein Validierungsfehler, undPUT /admin/virtual-modelslehnt einen Namen ab, der kein geladenes Plugin mitroute-Hook ist.- Die
weightder Ziele wird bei der Plugin-Strategie ignoriert. - Instanzbezogene Felder eines Route-Plugins (ein Endpunkt, ein API-Key) stammen aus einer
Guardrail-Definition, deren Name und Typ beide dem Plugin-Namen entsprechen; ohne eine solche
Definition wird das Plugin mit
{}initialisiert. Instanzbezogenesecret-Werte erreichen ein Route-Plugin derzeit geschwärzt. Das Speichern dieser Definition baut die Instanz neu auf; die vorherige wird geschlossen, sobald ihre laufendenSelect-Aufrufe zurückgekehrt sind. - GoModel greift auf gewichtetes Round Robin zurück und loggt eine Warnung pro virtuellem Modell,
wenn das Plugin fehlt, kein Route-Plugin ist, nicht initialisiert werden konnte, die
strategy_configungültig ist oderSelecteinen Fehler liefert, einen Panic auslöst, länger als 250 ms braucht oder ein Ziel außerhalb des verfügbaren Pools zurückgibt.
plugin:<name> pro geladenem Route-Plugin auf (VIRTUAL_MODEL_STRATEGIES in der
Laufzeitkonfiguration ist round_robin,cost,failover[,adaptive],plugin:<name>,...); wird er
ausgewählt, erscheinen die Route-Felder des Plugins unter der Zieltabelle.
cheapest_healthy ist die eingebaute Referenzstrategie (internal/plugins/builtin/routeexample).
Sie speichert die letzten 50 Ergebnisse pro Ziel und wählt das günstigste (prefer: cheapest, der
Standardwert) oder das Ziel mit der niedrigsten Median-Latenz (prefer: fastest), dessen
Fehlerrate höchstens max_error_rate beträgt (Standardwert 0.2). Ein gesundes SessionTarget
behält sie bei, und wenn kein Ziel gesund ist, berücksichtigt sie alle Kandidaten.
Admin-Endpunkte
Alle Endpunkte erfordern dieselben Admin-Zugangsdaten wie der Rest von/admin.
GET /admin/plugins
Jeder dem Gateway bekannte Plugin-Typ, einschließlich solcher, die nicht geladen werden konnten:source ist builtin, registered (über ext einkompiliert) oder der absolute Pfad der .so.
fields sind die instanzbezogenen Schemafelder, route_fields die routenbezogenen. Eine mit
gomodel plugin build gebaute .so meldet zusätzlich built_with (Go- und pluginapi-Versionen).
GET /admin/guardrails/types
Ein Eintrag pro Plugin, das einen Hook fürprompt, response oder stream implementiert, in der
Form, die der Guardrail-Editor darstellt:
phases, source, mutates und guardrail sind neu (auch die Instanzansichten von
GET /admin/guardrails enthalten guardrail); fields[].input kann jetzt secret, model,
bool oder list sein, und fields[].scope ist "" (Instanz) oder "route".
GET, PUT, DELETE /admin/guardrails
Definitionen erhaltenfail_mode (closed | open, leer für den Standardwert der Phase) und
timeout_ms (0 für keinen); Ansichten enthalten zusätzlich phases (aus dem Plugin-Manifest),
summary und health (ok | degraded, mit health_error und health_checked_at bei Plugins,
die eine Zustandsprüfung implementieren). PUT akzeptiert beide neuen Felder. Ein gespeicherter
secret-Wert wird als ******** zurückgegeben; senden Sie ihn unverändert zurück, um ihn zu
behalten, oder "", um ihn zu löschen.
GET /admin/workflows/guardrails
Die für Workflow-Steps verfügbaren Instanzen mit den jeweils unterstützten Phasen:Workflow-Payload Version 2
POST /admin/workflows akzeptiert schema_version: 2 mit steps[], die jeweils eine phase
enthalten; Payloads der Version 1 (guardrails: [{ref, step}]) bleiben gültig und werden
unverändert zurückgegeben. Workflow-Ansichten stellen die Ketten-Hashes pro Phase als
chain_hashes bereit. Siehe Workflows.