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.
finish_reason meint, was es
sagt, statt eine plausible Konstante zu sein.
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.