Documentation de l'API MacLustr
Deux grands modèles de langage servis localement sur le cluster MacLustr via une API compatible OpenAI, avec streaming token-par-token.
Llama 3.3 70B · 4-bit Qwen2.5 72B · 4-bit MLX · Apple Silicon SSE streaming
Choisis le modèle : les exemples de code ci-dessous s'adaptent automatiquement.
Base URLs
| Modèle | Base URL | Nom du modèle (champ model) |
|---|---|---|
| Llama 3.3 70B | https://llama.maclustr.io | Llama-3.3-70B-Instruct-4bit |
| Qwen2.5 72B | https://qwen.maclustr.io | Qwen2.5-72B-Instruct-4bit |
Les deux serveurs exposent exactement la même surface d'API. Le champ model dans le
corps de requête est optionnel (chaque base URL ne sert qu'un seul modèle).
Authentification
Aucune clé requise pour l'instant — les endpoints sont publics derrière ngrok (TLS terminé par ngrok).
N'envoie pas de données sensibles. Un en-tête Authorization est accepté mais ignoré, ce qui
permet d'utiliser les SDK OpenAI sans modification.
GET /health
Vérifie que le serveur et le modèle sont chargés.
curl __BASE__/health
Réponse
{ "status": "ok", "model": "__MODELNAME__" }GET /v1/models
Liste le(s) modèle(s) disponible(s), au format OpenAI.
{
"object": "list",
"data": [{ "id": "__MODELNAME__", "object": "model", "owned_by": "maclustr" }]
}POST /v1/chat/completions
Génération conversationnelle. Le modèle applique son chat template aux messages
(rôles system, user, assistant).
Requête (streaming)
curl -N __BASE__/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"messages": [
{"role": "system", "content": "Tu es un assistant utile."},
{"role": "user", "content": "Explique la mémoire unifiée en 2 phrases."}
],
"max_tokens": 512,
"temperature": 0.7,
"top_p": 1.0,
"stream": true
}'Réponse en streaming
Suite d'événements text/event-stream, chacun préfixé par data: ,
terminée par data: [DONE].
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"__MODELNAME__",
"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"__MODELNAME__",
"choices":[{"index":0,"delta":{"content":"La"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]Réponse non-streaming ("stream": false)
{
"id": "chatcmpl-…",
"object": "chat.completion",
"model": "__MODELNAME__",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "La mémoire unifiée…" },
"finish_reason": "stop"
}]
}POST /v1/completions
Complétion brute à partir d'un prompt texte (sans chat template).
curl -N __BASE__/v1/completions \
-H 'Content-Type: application/json' \
-d '{ "prompt": "La capitale de la France est", "max_tokens": 16, "stream": true }'Chaque chunk contient choices[0].text (au lieu de delta.content) ;
objet "text_completion".
Paramètres
| Champ | Type | Défaut | Description |
|---|---|---|---|
messages | array | — | Chat uniquement. Liste {role, content}. |
prompt | string | — | Complétion uniquement. Texte d'entrée. |
max_tokens | int | 1024 | Nombre max de tokens générés. |
temperature | float | 0.7 | 0 = déterministe, >1 = plus créatif. |
top_p | float | 1.0 | Nucleus sampling. |
top_k | int | 0 | Garde les k tokens les plus probables (0 = désactivé). |
min_p | float | 0.0 | Seuil de probabilité minimale relative. |
repetition_penalty | float | 1.0 | >1 pénalise la répétition. |
presence_penalty | float | — | Pénalise les tokens déjà apparus. |
frequency_penalty | float | — | Pénalise selon la fréquence. |
seed | int | — | Génération reproductible. |
stop | string / array | — | Séquence(s) d'arrêt — coupe la génération. |
stream | bool | false | Si true, renvoie un flux SSE. |
model | string | auto | Optionnel — ignoré (un modèle par base URL). |
Streaming (SSE)
Format text/event-stream. Parser : découper sur \n\n, retirer le préfixe
data: , ignorer [DONE], parser le JSON, lire
choices[0].delta.content (chat) ou choices[0].text (complétion).
Erreurs
| Code | Signification |
|---|---|
200 | OK (y compris flux SSE). |
422 | Corps JSON invalide / champ manquant. |
502/504 (ngrok) | Le serveur du modèle est hors ligne ou redémarre. |
__BASE__/health avant de débugger un client.Exemple — SDK OpenAI (Python)
L'API étant compatible OpenAI, il suffit de pointer base_url vers la base URL du modèle.
from openai import OpenAI
client = OpenAI(base_url="__BASE__/v1", api_key="not-needed")
stream = client.chat.completions.create(
model="__MODELID__",
messages=[{"role": "user", "content": "Salut !"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)Exemple — Python (requests, streaming)
import json, requests
r = requests.post(
"__BASE__/v1/chat/completions",
json={"messages": [{"role": "user", "content": "Salut !"}], "stream": True},
stream=True,
)
for line in r.iter_lines():
if not line or not line.startswith(b"data: "):
continue
data = line[6:]
if data == b"[DONE]":
break
delta = json.loads(data)["choices"][0]["delta"]
print(delta.get("content", ""), end="", flush=True)Exemple — JavaScript (fetch, navigateur/Node)
const res = await fetch("__BASE__/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [{ role: "user", content: "Salut !" }],
stream: true,
}),
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let i;
while ((i = buf.indexOf("\n\n")) >= 0) {
const line = buf.slice(0, i).trim(); buf = buf.slice(i + 2);
if (!line.startsWith("data:")) continue;
const d = line.slice(5).trim();
if (d === "[DONE]") continue;
const j = JSON.parse(d);
process.stdout.write(j.choices[0].delta.content || "");
}
}Passerelle web (chat / playground)
L'interface unifiée chat.maclustr.io / playground.maclustr.io proxifie ces deux API sur une même origine. Endpoints internes :
| Endpoint | Rôle |
|---|---|
POST /api/chat | Proxy chat — body {model:"llama"|"qwen", messages, system?, max_tokens, temperature, top_p}, toujours en streaming. |
POST /api/complete | Proxy complétion brute — body {model, prompt, …}. |
GET /api/models | Liste des modèles de la passerelle. |
GET /api/health | État agrégé des deux modèles. |