Zum Inhalt springen

Technik

Ein Endpunkt, kompatibel mit OpenAI und mit Ollama

Runlix bietet eine HTTP-API auf 127.0.0.1, kompatibel mit der API von OpenAI und mit der von Ollama: Chat, Vervollständigungen und native Embeddings. Editoren, Skripte und Konnektoren, die schon mit einer der beiden sprechen, zeigen darauf und funktionieren.

Aktualisiert 2026-09-18

Die Hälfte des Werts eines lokal laufenden Modells ist alles andere, was es dann benutzen kann: eine Editor-Erweiterung, ein Skript, ein Konnektor in einem anderen Programm. Alle davon sprechen eines von zwei Protokollen. Runlix ist mit beiden kompatibel, aus demselben Port, für dieselben Modelle — und ist nicht Ollama und braucht es auch nicht.

Zwei Dialekte, ein Port OpenAI und Ollama, dieselben Modelle hinter beiden — was Ihr Editor schon spricht, funktioniert.
Gezählt, nicht geschätzt Die Token-Zahlen kommen aus der Engine, und finish_reason meint, was es sagt, statt eine plausible Konstante zu sein.
Nur Loopback, kein Schlüssel An 127.0.0.1 gebunden und an nichts sonst. Soll er erreichbar sein, gehört ein Proxy davor — das ist das Stück, das weiß wie.
Fehler, die sagen, welcher Fehler 405 statt 404, wenn das Verb falsch ist, und eine Ablehnung statt einer stillen Warteschlange, wenn schon eine Generierung läuft.

Eingeschaltet wird er in den Einstellungen, mit dem Port neben dem Schalter. Voreinstellung ist 11435, einer über dem von Ollama, damit beide installiert zu haben kein Streit wird. Ist der Port belegt, springt der Schalter zurück und der Grund steht daneben, statt dass der Server einfach nicht da wäre.

Was er bereitstellt

Pfad Anmerkung
GET / Gesundheitsprüfung, beantwortet so, wie Ollama-Clients es erwarten
GET /api/tags Die Modellliste, in Ollama-Form
POST /api/show Modellkarte, die Ollama-Clients vor einem Chat abfragen
POST /api/generate Sendet standardmäßig NDJSON im Strom
POST /api/chat NDJSON; Werkzeugaufrufe in Ollamas Form
GET /v1/models OpenAI-Form
POST /v1/chat/completions Server-Sent Events; Werkzeugaufrufe in OpenAIs Form
POST /v1/embeddings Native Embeddings, OpenAI-Form — auch unter /embeddings

Die beiden Protokolle sind sich auf eine Weise uneinig, die sich nur an echten Clients zeigt, und jede Uneinigkeit unten hat eine Untersuchungssitzung gekostet.

Die Gesundheitsprüfung ist keine Zierde: Clients der Ollama-API fragen sie vor allem anderen ab, und ohne die erwartete Antwort brechen sie mit „something went wrong“ ab. /api/tags enthält ein digest, weil Ollamas eigener Kommandozeilen-Client dieses Feld auf zwölf Zeichen kürzt, ohne zu prüfen, ob es da ist, und abstürzt, wenn es fehlt. /api/show liest sowohl model als auch name, weil dieser Client beide sendet und eines leer lässt. Die Voreinstellungen fürs Streamen unterscheiden sich je Protokoll, und jedes folgt seiner eigenen Konvention: NDJSON an bei Ollama, aus bei OpenAI. Werkzeugaufrufe haben verschiedene Formen — arguments ist beim einen ein Objekt und beim anderen eine Zeichenkette, und nur eines trägt id und type.

Keine dieser Eigenheiten steht in einer Spezifikation: sie stehen im Verhalten der Clients, und der einzige Weg, sie zu finden, war, die echten laufen zu lassen und zuzusehen, woran sie scheitern.

Also: kompatibel mit der Ollama-API beim Auflisten, Anzeigen und Chatten, geprüft gegen Ollamas eigenen Client. Nicht für pull, push, create, copy, delete, ps, embed oder version — seine Modelle lädt und verwaltet Runlix selbst. Jeder OpenAI-kompatible Client, der /v1/chat/completions, /v1/models oder /v1/embeddings braucht, funktioniert.

Worauf Sie ihn wirklich richten können

Auf alles, was eines der beiden Protokolle spricht, und das ist inzwischen fast alles.

Editor-Erweiterungen, bei denen sich eine Basis-URL setzen lässt — Continue, Cline, der Assistent von Zed, der OpenAI-kompatible Modus von einem Dutzend anderer — brauchen /v1/chat/completions und /v1/models, und beide sind da. Skripte mit den offiziellen OpenAI-Bibliotheken funktionieren, indem Sie die Basis-URL auf http://127.0.0.1:11435/v1 zeigen lassen und irgendeine Zeichenkette als Schlüssel übergeben, da es keinen zu prüfen gibt. Alles, was schon mit Ollama spricht, findet die Endpunkte, die es sucht, in den Formen, die es erwartet, weil sie gegen Ollamas eigenen Kommandozeilen-Client geprüft wurden und nicht gegen die Dokumentation.

Der Port liegt absichtlich einen über dem von Ollama. Wenn Sie beide betreiben, muss keiner umziehen, und ein für das eine eingerichteter Client landet nicht stillschweigend beim anderen und antwortet mit einem Modell, das Sie nicht gewählt haben.

Was Sie bekommen, ist das Modell, das Sie genannt haben, aufgelöst unter den heruntergeladenen. Nennen Sie eines, das Sie nicht haben, sagt der Fehler, welcher Name gescheitert ist und dass er nicht unter den heruntergeladenen ist — kein allgemeines Vierhundert, das Sie Zeichenketten mit dem Auge vergleichen lässt.

Senden Sie gar keinen Namen, bekommen Sie das geladene Modell, und das ist das zuletzt benutzte und keines, das willkürlich gewählt wurde.

Was er Ihnen sagt, und wie ehrlich

Fehler, die sagen, welcher Fehler

Eine falsche Methode auf einem vorhandenen Pfad antwortet 405, nicht 404. Der Unterschied zählt, während Sie einen Client untersuchen: 404 schickt Sie einen Tippfehler in der URL suchen, 405 sagt Ihnen, die URL war richtig und das Verb nicht.

Eine zweite Generierung während einer laufenden wird abgelehnt statt eingereiht. Einreihen hieße eine unvorhersehbare Wartezeit ohne Möglichkeit zu wissen, wie lang — und für einen lokalen Server mit einem Benutzer ist eine Ablehnung, die Sie wiederholen können, nützlicher als ein Versprechen, das vier Minuten dauern könnte.

Gezählte Zahlen, nicht geschätzte

usage kommt aus der Engine: Prompt-Tokens, Antwort-Tokens, Summe. Nicht aus Zeichenzahlen abgeleitet, was die übliche Abkürzung ist und für jede Sprache um einen anderen Betrag falsch.

Auch finish_reason liest sie aus der Engine, length heißt also, dass die Antwort wirklich vom Limit abgeschnitten wurde, und ist keine Konstante, die plausibel schien.

Sampling-Parameter werden sowohl oben als auch innerhalb von options gelesen, weil Ollama-Clients sie dort ablegen, und bei jeder Anfrage aus den echten Ruhewerten der Engine gesetzt, damit nichts vom vorigen Aufrufer durchsickert.

Senden Sie einen seed, und zwei identische Anfragen geben dieselbe Antwort. Ohne variieren sie, wie ein Modell es soll — ein fehlender Startwert bleibt fehlend, statt stillschweigend null zu werden.

Schließen Sie die Verbindung auf halbem Weg, hört die Generierung auf. Ehrlich: nicht sofort — sie hört auf, wenn der Schreibpuffer voll läuft und der Socket wirft.

Streamen, in beiden Dialekten

Die beiden Protokolle streamen unterschiedlich, und jedes behält seine Konvention, weil ein für eines geschriebener Client genau diese erwartet und sonst nichts.

OpenAI-Clients bekommen Server-Sent Events: data:-Zeilen mit je einem JSON-Stück, einer gemeinsamen Kennung über alle Stücke derselben Antwort, und einem abschließenden data: [DONE]. Ollama-Clients bekommen durch Zeilenumbrüche getrennte JSON-Objekte, eines pro Zeile, das letzte mit den Zählungen. Beide kommen stückweise an, die ersten Worte erscheinen also, während der Rest noch erzeugt wird.

Eine Ausnahme, und sie ist gewollt: wenn Werkzeuge im Spiel sind, hält der Strom zurück. Ein halber JSON-Funktionsaufruf in Stücken nützt keinem Client, der je einen verarbeitet hat.

Nur localhost, und keine Authentifizierung

Der Server bindet 127.0.0.1 und sonst nichts. Das heißt: von Ihrer Maschine erreichbar und von Ihrem Netz nicht. Es gibt kein Token, kein Passwort, keinen Benutzer.

Das ist eine Entscheidung und kein Versäumnis. Authentifizierung, die zählt, ist die, die jemand pflegt, und ein Kästchen mit „Schlüssel verlangen“ auf einem Loopback-Socket liefert vor allem das Gefühl, eine zu haben. Wenn Sie das von anderswo erreichbar brauchen, setzen Sie einen Proxy davor und lassen ihn entscheiden, wer hereinkommt — er ist das Stück, das dieses Handwerk schon beherrscht.

Embeddings, aus derselben Engine

/v1/embeddings verwandelt Text in Vektoren, mit derselben Rust-Engine, die die Chat-Modelle ausführt, im selben Prozess — kein zweiter Server, kein Python. Sie liest die Encoder-Familien, die die Bibliothek mlx-embeddings für BERT und XLM-RoBERTa abdeckt, übersetzt und gegen sie geprüft auf dieselbe Weise, wie die Chat-Familien gegen mlx-lm geprüft werden.

Das Modell ist das, das Sie in den Einstellungen wählen, oder das erste heruntergeladene Embedding-Modell. Ist keines da, antwortet er mit 503, „kein Embedding-Modell geladen“ — was heißt: laden Sie eines, nicht dass der Endpunkt fehlt. Die Form der Anfrage ist die von OpenAI: eine Zeichenkette oder eine Liste davon hinein, ein Vektor je Eingabe heraus. Zu viele Texte in einer Anfrage sind ein 413, der die Grenze nennt, keine Wartezeit, die abläuft.

Was er nicht tut

Werkzeuge gibt er zurück, er führt sie nicht aus. Senden Sie tools mit, kommen die Aufrufe des Modells in der für Ihr Protokoll richtigen Form zurück; sie auszuführen ist Sache Ihres Clients. Der Chat von Runlix ist der eine Ort, an dem Runlix sie selbst ausführt.

Kennt das chat_template eines Modells keine Werkzeuge, gelingt die Anfrage trotzdem und ein Hinweis geht ins Protokoll — das OpenAI-Protokoll hat kein Feld für „ich habe das weitergereicht und niemand hat es verstanden“.

Der Parameter stop wird angenommen und ignoriert: die Engine kann Stoppfolgen, und die API reicht sie derzeit nicht weiter. Den Embedding-Endpunkt von Ollama gibt es nicht: Embeddings liegen auf den Pfaden von OpenAI. Eine Generierung zur Zeit; eine zweite gleichzeitige Anfrage wird abgelehnt, statt hinter einer Wartezeit einzureihen, deren Länge niemand kennt.

Etwas davorsetzen

Wenn Sie das wirklich von einer anderen Maschine aus erreichen wollen, ist die Form, die funktioniert, ein Reverse-Proxy auf demselben Mac, der dort lauscht, wo Sie ihn haben wollen, und an 127.0.0.1:11435 weiterleitet. Der Proxy ist der Ort für die Authentifizierung — ein Token, gegenseitiges TLS, ein Identitätsanbieter, was Ihre Lage verlangt — denn er ist ein Stück mit einem Betreuer und einem Bedrohungsmodell, und ein Kästchen in einem Einstellungsfenster ist weder das eine noch das andere.

Zwei Dinge sind dabei zu bedenken. Generierung läuft eine zur Zeit, ein geteilter Endpunkt lehnt also Anfragen ab, die während einer anderen eintreffen; sollen mehrere Menschen ihn benutzen, braucht der Proxy eine Warteschlange und eine Entscheidung darüber, wie lange jemand wartet. Und es gibt keine Abrechnung pro Aufrufer: die Nutzungszahlen in jeder Antwort gelten für diese Antwort und werden nirgends summiert.

Nachrichten werden nie flachgeklopft

Erwähnenswert, weil es der häufigste Weg ist, auf dem eine lokale API schlechtere Antworten liefert, als das Modell kann. Nachrichten werden als strukturiertes Array übergeben, und das chat_template wendet die Engine an, weil sie das Stück ist, das den Tokenizer hält.

Gemessen an einem Modell, gleicher Prompt: mit Template lautet die Antwort "Paris.". Flach in Rolle: Inhalt-Zeilen wird daraus "Paris\nassistant: …\nuser: What" — das Modell erfindet weiter Gesprächszüge. Das sieht nicht wie ein Defekt aus. Das sieht wie ein schlechteres Modell aus.