Construisez sur Geb. L'arabe d'abord, compatible OpenAI.
Une seule URL de base et une seule clé. Pointez votre SDK OpenAI vers Geb et obtenez des réponses solides en arabe et en plusieurs langues, le streaming, vos propres fonctions et des outils intégrés.
https://api.gebai.ca/v2Connexion requise. Les comptes gratuits ont 500 appels par mois. Aucun frais par jeton.
Changez l'URL de base et la clé. Les complétions de chat, le streaming et l'appel de fonctions fonctionnent comme prévu.
Les réponses suivent la langue de votre utilisateur, en arabe standard moderne ou en arabe égyptien à votre demande.
Clés d'idempotence, codes d'erreur clairs et une réponse à chaque appel, même quand une génération est interrompue.
1.Ce qu'est l'API, et ce qu'elle n'est pas
L'API de Geb donne à votre application le même modèle que celui qui répond dans Geb AI, par l'interface de complétions de chat que les bibliothèques clientes d'OpenAI parlent déjà. Pointez un SDK OpenAI vers https://api.gebai.ca/v2, utilisez une clé Geb, et cela fonctionne.
Geb appelle cette API API v2, et ses chemins d'URL commencent par /v2. Les clients existants qui appellent encore /v1 continuent de fonctionner : /v1/... est un alias qui atteint exactement les mêmes gestionnaires, avec le même comportement, les mêmes en-têtes, les mêmes erreurs et les mêmes quotas, et un appel ne compte qu'une fois, quel que soit le préfixe utilisé. Le nouveau code doit utiliser /v2.
Ce que vous obtenez
- Complétions de chat sur
POST /v2/chat/completions, en réponse complète ou en streaming par événements envoyés par le serveur. - Des réponses solides en arabe et en plusieurs langues. Geb répond dans la langue du dernier message de votre utilisateur, parmi 23 langues. L'arabe est une langue à part entière, pas une couche de traduction.
- Vos propres fonctions grâce au champ standard
tools(appel de fonctions). - Les outils intégrés de Geb, comme la recherche web, la météo, les générateurs de documents et de feuilles de calcul, lorsque vous les activez en les nommant.
- Sortie structurée avec
response_format(json_objectetjson_schema). - Entrée d'images dans les messages de l'utilisateur.
- Contrôle du raisonnement avec des niveaux d'effort.
- Conversations enregistrées que vous pouvez poursuivre par identifiant, séparées de toute mémoire du compte.
- Règles de réponse (
geb_acceptance) : déclarez des plages de mots, des expressions interdites et des exigences d'ancrage dans des sources, et Geb vérifie et affine la réponse avant de la renvoyer. - Aucune facturation au jeton. Votre forfait inclut un nombre d'appels par période de facturation (voir la section 10). Les jetons ne sont pas comptés.
Ce qu'elle n'est pas
- Pas un proxy OpenAI. Chaque réponse vient du modèle propre de Geb. Votre requête n'est jamais transmise au modèle d'un autre fournisseur.
- Sans mémoire. Les appels API ne lisent jamais la mémoire de votre compte Geb et n'y écrivent jamais. Un appel est construit à partir de votre seule requête (plus un fil de conversation enregistré si vous en demandez un, voir la section 5.5).
- Uniquement des complétions de chat. Il n'existe pas de points de terminaison pour les embeddings, l'audio, la génération d'images, les fichiers, le traitement par lots, l'ajustement fin (fine-tuning), Assistants ou Responses.
- Un seul choix.
n,logprobset l'ancien champfunctionssont rejetés. - Certains champs d'échantillonnage sont acceptés et ignorés.
stop,user,seed,temperature,top_p,presence_penalty,frequency_penalty,logit_bias,min_p,top_ketrepetition_penaltyne provoquent jamais d'erreur, et ils n'ont aucun effet aujourd'hui. - Non appelable depuis un navigateur sur le site d'un tiers. L'API ne répond aux requêtes préalables cross-origin que pour les sites de Geb. Appelez-la depuis votre serveur, pas depuis du JavaScript côté client. Ne mettez jamais une clé dans une page web ou une application mobile.
2.Démarrage rapide
Votre premier appel en cinq minutes. Choisissez votre langage ; le choix est mémorisé pour tous les exemples de la page.
- Obtenez une clé (section 3). Elle ressemble à
geb_live_suivi de 43 caractères. - Placez-la dans une variable d'environnement. Ne la collez jamais dans le code source.
- Faites un appel.
export GEB_API_KEY="geb_live_..." # paste your key here, in your shell only
curl https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "geb",
"messages": [
{"role": "user", "content": "Greet me in Arabic and in English, one line each."}
]
}'# pip install openai
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.gebai.ca/v2",
api_key=os.environ["GEB_API_KEY"],
)
reply = client.chat.completions.create(
model="geb",
messages=[{"role": "user", "content": "Greet me in Arabic and in English, one line each."}],
)
print(reply.choices[0].message.content)// npm install openai
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.gebai.ca/v2",
apiKey: process.env.GEB_API_KEY,
});
const reply = await client.chat.completions.create({
model: "geb",
messages: [{ role: "user", content: "Greet me in Arabic and in English, one line each." }],
});
console.log(reply.choices[0].message.content);Obtenir une clé
- Connectez-vous sur gebai.ca.
- Ouvrez Profil, puis Clés API.
- Créez une clé, copiez-la une seule fois, gardez-la sur votre serveur.
Ce que vous recevez en retour
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1790000000,
"model": "<model-id>",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
}model est l'identifiant du modèle qui a répondu, tel que le liste GET /v2/models. usage.prompt_tokens inclut les instructions de base propres à Geb, qui ouvrent chaque prompt.
Si vous avez reçu une 401, vérifiez la clé et l'en-tête Authorization (section 4). Si vous avez reçu une 429, lisez la section 10.
3.Obtenir une clé
Les clés sont créées par un compte Geb connecté, dans l'application web de Geb.
- Créez votre compte ou connectez-vous sur
https://gebai.ca. - Ouvrez Profil, puis Clés API (
https://gebai.ca/profile/api-keys). - Choisissez Créer une clé API. Donnez-lui un nom et, si vous le souhaitez, une durée de validité en jours (de 1 à 365).
- Copiez la clé immédiatement. Elle n'est affichée qu'une seule fois et ne peut pas être récupérée ensuite. Geb n'en conserve qu'un hachage.
À savoir :
- Un compte peut détenir jusqu'à 10 clés actives. Révoquez-en une pour libérer une place.
- Révoquez une clé à tout moment depuis la même page. Une clé révoquée ou expirée répond
401dès la requête suivante. - Chaque clé appartient au compte qui l'a créée. Les appels, le quota et les limites de débit sont comptés par compte, toutes clés confondues.
- Le seul périmètre de clé est
chat:write, qui couvre tous les points de terminaison de ce guide. - Tous les forfaits peuvent utiliser l'API, Gratuit compris : un compte Gratuit peut créer des clés et effectuer 500 appels par période de facturation. Le nombre d'appels inclus dans chaque forfait est indiqué à la section 10.
Gardez les clés sur votre serveur. Utilisez une clé par application ou par environnement, afin de pouvoir en révoquer une sans toucher aux autres.
4.Authentification
Envoyez la clé comme jeton porteur (bearer) à chaque requête.
Authorization: Bearer geb_live_...curl https://api.gebai.ca/v2/models -H "Authorization: Bearer $GEB_API_KEY"import os, requests
headers = {"Authorization": f"Bearer {os.environ['GEB_API_KEY']}"}
print(requests.get("https://api.gebai.ca/v2/models", headers=headers).json())const res = await fetch("https://api.gebai.ca/v2/models", {
headers: { Authorization: `Bearer ${process.env.GEB_API_KEY}` },
});
console.log(await res.json());Une requête sans clé, avec un en-tête mal formé, avec un autre schéma (par exemple Basic), ou avec une clé inconnue, révoquée ou expirée, reçoit toujours la même réponse :
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
x-request-id: 0f658b6d9742d46fec9ce4b984f99667
{"error":{"message":"A valid developer API key is required.","type":"invalid_api_key","param":null,"code":null,"request_id":"0f658b6d9742d46fec9ce4b984f99667"}}Une 403 permission_error signifie que la clé est valide mais que le compte n'a pas le droit d'utiliser l'API (par exemple, le compte est suspendu).
5.Points de terminaison
Tous les points de terminaison se trouvent sous https://api.gebai.ca, utilisent JSON et exigent la clé porteuse. Le préfixe canonique est /v2. Les mêmes chemins avec le préfixe /v1 sont un alias pour les clients existants et se comportent de façon identique. Lorsqu'une réponse ou une erreur nomme un chemin, elle nomme le préfixe que vous avez utilisé.
| Méthode et chemin | Objet |
|---|---|
GET /v2/models | Lister le modèle que vous pouvez appeler, avec ses capacités en direct. |
GET /v2/models/{id} | Récupérer ce modèle. Un identifiant inconnu donne 404. |
POST /v2/chat/completions | Créer une complétion de chat, en réponse complète ou en streaming. |
GET /v2/conversations | Lister vos conversations enregistrées. |
GET /v2/conversations/{id} | Lire une conversation enregistrée. |
DELETE /v2/conversations/{id} | Supprimer une conversation enregistrée. |
Les corps de requête peuvent être compressés en gzip ou deflate (Content-Encoding: gzip). Le corps de requête maximal est de 32 MiB après décompression.
5.1 Lister les modèles
curl https://api.gebai.ca/v2/models -H "Authorization: Bearer $GEB_API_KEY"for m in client.models.list():
print(m.id)for await (const m of client.models.list()) console.log(m.id);La réponse est une liste au format OpenAI. Chaque modèle porte des informations en direct sur ce que l'API peut faire à l'instant :
{
"object": "list",
"data": [
{
"id": "<model-id>",
"object": "model",
"created": 1755475200,
"owned_by": "geb-ai",
"context_window": 0,
"modalities": { "input": ["text", "image"], "output": ["text"] },
"capabilities": { "streaming": true, "tools": true, "vision": true, "reasoning": true },
"reasoning": {
"always_on": true,
"efforts": ["low", "medium", "high", "ultra"],
"default_effort": "<level>",
"parameters": ["reasoning", "reasoning_effort", "thinking", "enable_thinking", "..."]
},
"limits": {
"max_tokens": "answer_ceiling",
"max_tokens_default": "derived_from_context_window",
"long_non_streamed": "keepalive"
},
"geb_tools": ["web_search", "weather_forecast", "..."],
"status": { "healthy": true, "state": "<state>" }
}
]
}context_windowest le nombre de jetons que le modèle peut contenir dans une seule requête. Il vautnulllorsque Geb ne peut pas le mesurer pour le moment.capabilities.visionet"image"dansmodalities.inputsont en direct : ils ne valenttrueque si une requête avec image serait acceptée à l'instant.geb_toolsest la liste exacte des noms d'outils intégrés qu'une requête peut nommer (section 7.2).- Les valeurs ci-dessus sont données à titre d'illustration. Lisez les valeurs réelles dans votre propre appel.
Aujourd'hui, l'API sert un seul modèle Geb. Le champ model d'une complétion est accepté pour la compatibilité avec OpenAI (n'importe quelle chaîne convient) et ne sélectionne pas un modèle plutôt qu'un autre. La réponse indique l'identifiant du modèle qui a réellement répondu.
5.2 Créer une complétion de chat
POST /v2/chat/completions
| Paramètre | Type | Remarques |
|---|---|---|
model | chaîne | Facultatif. Accepté pour la compatibilité, voir ci-dessus. |
messages | tableau | Obligatoire. Rôles system, user, assistant, tool. content est une chaîne ou, pour les messages user, un tableau de parties text et image_url. |
stream | booléen | true active le streaming par événements envoyés par le serveur (section 6). |
max_tokens | entier | Plafond de la réponse visible. La réflexion est budgétée séparément (section 8). |
tools, tool_choice, parallel_tool_calls | Vos propres fonctions (section 7.1). | |
response_format | objet | {"type":"json_object"} ou {"type":"json_schema", ...}. |
stream_options | objet | {"include_usage": true} ajoute un dernier fragment d'utilisation à un flux. |
reasoning, reasoning_effort, thinking, enable_thinking | Contrôle du raisonnement (section 8). | |
geb_tools | booléen ou tableau | Outils intégrés de Geb, désactivés par défaut (section 7.2). |
geb_acceptance | objet | Règles de réponse (section 5.6). |
conversation_id | chaîne | "new" démarre une conversation enregistrée, un identifiant en poursuit une (section 5.5). |
locale | "en", "ar", "fr" | Indication facultative pour la langue de la réponse lorsque le message lui-même ne la détermine pas. |
Acceptés mais ignorés (aucune erreur, aucun effet) : stop, user, seed, temperature, top_p, presence_penalty, frequency_penalty, logit_bias, min_p, top_k, repetition_penalty.
Rejetés avec 400 : n, logprobs, functions. geb_memory: true est également rejeté (memory_unavailable), car les appels API n'ont pas de mémoire.
Les champs inconnus sont des erreurs. Une propriété que Geb ne connaît pas, par exemple max_completion_tokens ou store, renvoie 400 avec un message tel que property store should not exist. Utilisez max_tokens. Si votre bibliothèque cliente ajoute des champs d'elle-même, supprimez-les.
Le corps JSON doit être du JSON valide. Les virgules finales, les commentaires, NaN et les autres erreurs de syntaxe sont rejetés avec 400 invalid_json_body. Une clé qui apparaît deux fois dans un même objet n'est pas une erreur : comme dans tout analyseur JSON standard, la dernière valeur l'emporte. N'envoyez pas de clés en double volontairement.
Les instructions propres à Geb ouvrent chaque prompt. Geb répond en tant que Geb, et ses règles de sécurité ne peuvent pas être contournées. Votre message system vient ensuite et cadre la conversation. Ces instructions sont comptées dans usage.prompt_tokens.
Langue de la réponse. Geb répond dans la langue du dernier message de votre utilisateur et suit un changement de langue en cours de conversation. Une instruction explicite telle que « répondez en français » l'emporte. Un message qui ne décide de rien (« ok », du code) conserve la langue du dernier message clair. Le code, les commandes et les arguments d'outils restent dans leur langue naturelle. Pour l'arabe, votre message system détermine le registre : demandez « arabe standard moderne » ou « arabe égyptien ». À défaut, les livrables écrits et les traductions sont en arabe standard moderne, et la conversation est en arabe égyptien. Une demande de traduction ne renvoie que la traduction.
Réponse. Une complétion normale ressemble à la réponse du démarrage rapide. finish_reason vaut stop (terminée), length (coupée à votre max_tokens, ou écourtée, voir la section 6.4), tool_calls (votre fonction est en cours d'appel) ou content_filter. Vérifiez-le toujours avant d'analyser du JSON dans une réponse.
r = client.chat.completions.create(
model="geb",
messages=[
{"role": "system", "content": "You are a concise assistant for a travel app."},
{"role": "user", "content": "اقترح لي خطة ليوم واحد في القاهرة"},
],
max_tokens=800,
)
print(r.choices[0].message.content, r.choices[0].finish_reason, r.usage.total_tokens)const r = await client.chat.completions.create({
model: "geb",
messages: [
{ role: "system", content: "You are a concise assistant for a travel app." },
{ role: "user", content: "Plan one day in Cairo." },
],
max_tokens: 800,
});
console.log(r.choices[0].message.content, r.choices[0].finish_reason, r.usage.total_tokens);5.3 Sortie structurée
curl https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" -H "Content-Type: application/json" \
-d '{
"messages": [{"role":"user","content":"Extract: Layla, 34, lives in Giza."}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": true,
"schema": {
"type": "object",
"properties": {"name":{"type":"string"},"age":{"type":"integer"},"city":{"type":"string"}},
"required": ["name","age","city"],
"additionalProperties": false
}
}
}
}'- Les réponses
json_schemasont conformes au schéma. Envoyez"strict": truepour que Geb vérifie d'abord votre schéma : chaque objet exige alorsadditionalProperties: falseet chaque propriété doit figurer dansrequired, et un schéma qui ne peut pas satisfaire cette exigence est rejeté avec400, en nommant le chemin fautif. Sans champstrict, Geb utilise votre schéma tel quel et ne le rejette pas : un schéma qui enfreint les règles strictes n'est donc pas une erreur, mais ses réponses ne sont contraintes que dans la mesure où le schéma l'est. - Vérifiez
finish_reason. S'il vautlength, le JSON est incomplet. Un corps JSON coupé n'est jamais signalé commestop. - Un appel
response_formaten streaming est retenu jusqu'à ce que Geb ait vérifié la réponse, puis livré (section 6.3). Des signaux de présence maintiennent la connexion ouverte entre-temps. - Les outils intégrés de Geb ne s'exécutent pas lors d'un appel
response_format.
5.4 Entrée d'images
Les messages de l'utilisateur peuvent contenir des images sous forme de parties de contenu OpenAI. Les images sont envoyées au modèle de vision propre à Geb.
r = client.chat.completions.create(
model="geb",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What is in this picture?"},
{"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}},
],
}],
)const r = await client.chat.completions.create({
model: "geb",
messages: [{
role: "user",
content: [
{ type: "text", text: "What is in this picture?" },
{ type: "image_url", image_url: { url: "https://example.com/cat.jpg" } },
],
}],
});- Sources : URL
https://(récupérées par Geb) ou URIdata:image/{png,jpeg,webp,gif};base64,.... Les URLhttp://et les autres schémas sont rejetés. - Formats : PNG, JPEG, WebP, GIF (première image). Les octets doivent correspondre au type déclaré.
- Limites : au plus 16 images par requête, 20 MB par image, 64 MB de données d'image décodées par requête. Envoyez les grandes images sous forme d'URL : le corps entier de la requête est plafonné à 32 MiB.
- Les URL qui pointent vers des adresses privées ou internes sont refusées (
image_url_forbidden). - Les images d'une requête sont présentées avec le dernier message de l'utilisateur, dans l'ordre. Un message contenant uniquement des images est valide. Les parties autres que
textetimage_url(audio, fichiers) sont rejetées. - Si le modèle de vision est indisponible à ce moment-là, vous recevez
503 vision_unavailable, jamais une supposition à partir du texte seul.GET /v2/modelsindiquecapabilities.vision: falsedans le même état.
5.5 Conversations
Vous pouvez envoyer tout l'historique à chaque requête, comme avec n'importe quelle API de chat. Geb peut aussi enregistrer le fil pour vous.
- Envoyez
"conversation_id": "new". L'identifiant revient dans la réponse (conversation_id) et dans le dernier fragment du flux. - Envoyez cet identifiant lors des appels suivants pour poursuivre la conversation.
- Si votre requête contient un message
assistantoutool, vous avez envoyé l'historique : vos messages sont utilisés exactement tels quels. Si elle ne contient que des messagessystemetuser, Geb ajoute le fil enregistré au prompt, en commençant par les tours les plus récents, autant que la fenêtre de contexte le permet. Le nouveau tour est enregistré dans les deux cas. - Un fil enregistré n'est pas la mémoire du compte. Le chat ne la lit jamais, et aucun autre compte ne peut la lire.
| Route | Résultat |
|---|---|
GET /v2/conversations?limit=50&after=<cursor> | {"object":"list","data":[{"id","object":"conversation","created_at","last_message_at","title"}],"has_more","next_cursor"}, de la plus récente à la plus ancienne. |
GET /v2/conversations/{id}?limit=100&after=<cursor> | La conversation avec messages: [{"role","content","created_at"}] du plus ancien au plus récent, plus message_count, has_more et next_cursor. |
DELETE /v2/conversations/{id} | {"id","object":"conversation.deleted","deleted":true}. |
Un identifiant qui n'est pas le vôtre répond 404. Ces trois routes comptent comme des appels API dans votre quota, comme une complétion ; un appel qui échoue n'en consomme pas.
curl https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" -H "Content-Type: application/json" \
-d '{"conversation_id":"new","messages":[{"role":"user","content":"Remember: my shop sells dates."}]}'5.6 Règles de réponse (`geb_acceptance`)
Pour un travail où la réponse doit satisfaire une liste de contrôle, déclarez les règles. Geb rédige, vérifie, affine ce qui échoue, et renvoie la meilleure version avec un rapport. Les vérifications de règles ne sont pas facturées : l'appel reste un seul appel.
{
"messages": [{"role": "user", "content": "Write a 300 word product summary from the sources below. ..."}],
"geb_acceptance": {
"version": 1,
"rules": [
{"type": "word_range", "path": "", "min": 250, "max": 350},
{"type": "forbidden_phrases", "paths": ["/**"], "phrases": ["world-class", "cutting-edge"]},
{"type": "claims_grounded", "paths": ["/**"]}
],
"on_fail": "return_best_with_report"
}
}- Types de règles :
word_range,forbidden_phrases,no_repeated_paragraphs,cited_ids_named,min_named_sources,claims_grounded,no_copy_from,phrases_require_source. Unpathest un pointeur JSON (JSON Pointer) dans la réponse ; pour une réponse en texte brut, utilisez""ou/**. - La réponse porte
x_geb_acceptance: {"passed": true, "rounds": 1}, oupassed: falseavec une listefailures. on_fail: "error_422"transforme un échec final en422 geb_acceptance_failed.- Un contrat invalide donne
400 invalid_geb_acceptance, oùparamnomme la règle etrule_indexsa position. - Un flux avec
geb_acceptanceest retenu jusqu'à la fin des vérifications (section 6.3).
5.7 En-têtes présents dans chaque réponse
| En-tête | Signification |
|---|---|
x-request-id | L'identifiant de cet appel, présent dans chaque réponse, réussie ou non. Il est égal à error.request_id en cas d'erreur. Citez-le au support. |
x-geb-api-version | 2 avec le préfixe /v2 comme avec le préfixe /v1. |
X-RateLimit-Limit | La limite par minute, 120 pour tous les forfaits (section 10.2). |
X-Quota-Limit-Month, X-Quota-Remaining-Month, X-Quota-Reset | Votre position dans le quota mensuel d'appels (section 10.1). |
x-geb-reasoning-effort, x-geb-reasoning-adjusted | Le niveau de raisonnement utilisé (section 8.4). |
6.Streaming
Définissez "stream": true. La réponse est de type text/event-stream : des événements envoyés par le serveur, chacun étant une ligne data: <json> suivie d'une ligne vide.
curl -N https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" -H "Content-Type: application/json" \
-d '{"stream":true,"stream_options":{"include_usage":true},
"messages":[{"role":"user","content":"Count to five in Arabic."}]}'stream = client.chat.completions.create(
model="geb",
messages=[{"role": "user", "content": "Count to five in Arabic."}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.choices and chunk.choices[0].finish_reason:
print("\nfinish_reason:", chunk.choices[0].finish_reason)const stream = await client.chat.completions.create({
model: "geb",
messages: [{ role: "user", content: "Count to five in Arabic." }],
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) {
const choice = chunk.choices[0];
if (choice?.delta?.content) process.stdout.write(choice.delta.content);
if (choice?.finish_reason) console.log("\nfinish_reason:", choice.finish_reason);
}6.1 Ordre des trames
- Une ligne de commentaire de remplissage au début (les commentaires commencent par
:), qui vide les tampons des proxys. Ignorez-la. - Un premier
chat.completion.chunkdont ledeltaporterole: "assistant". - Des fragments de contenu, chacun avec du texte dans
delta.content. - Un dernier fragment avec
delta: {}et unfinish_reason. Il porte aussiconversation_idlorsque vous utilisez des conversations. - Si vous définissez
stream_options.include_usage: un fragment de plus avec"choices": []et un objetusage. data: [DONE].
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"<model-id>","choices":[{"index":0,"delta":{"role":"assistant","content":"One"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"<model-id>","choices":[{"index":0,"delta":{"content":", two"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"<model-id>","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"<model-id>","choices":[],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
data: [DONE]Les fragments dont le tableau choices est vide sont des métadonnées (utilisation, et extensions de Geb telles que x_geb_acceptance et x_geb_partial). Un analyseur strict doit les ignorer lors de la lecture du texte. Geb ne diffuse jamais en streaming le texte de son raisonnement.
6.2 Signaux de maintien de connexion
Pendant que Geb réfléchit ou vérifie une réponse, la connexion reste ouverte et Geb écrit une ligne de commentaire, : heartbeat, environ toutes les 15 secondes. Les commentaires sont ignorés par tous les analyseurs SSE. Si vous écrivez votre propre analyseur, ignorez toute ligne qui commence par :.
Les en-têtes de réponse d'un flux incluent x-request-id, vos en-têtes X-Quota-* (section 10) et les en-têtes de raisonnement (section 8). Si vous fermez la connexion, Geb cesse de générer.
6.3 Flux retenus
Avec response_format ou geb_acceptance, Geb vérifie la réponse avant de l'envoyer. Le flux s'ouvre avec le commentaire : geb is checking the reply before sending it, puis seulement des lignes : heartbeat, puis le contenu vérifié. Si vous avez besoin des jetons au fur et à mesure de leur génération, n'envoyez aucun de ces deux champs.
6.4 Comment un flux se termine
Un flux se termine toujours par data: [DONE], de l'une des trois manières suivantes.
- Complet. Un dernier fragment avec
finish_reasonvalantstop(outool_calls), puis[DONE]. - Réponse partielle. Si la génération se bloque ou échoue après l'envoi d'une partie du texte, Geb termine le flux avec ce qu'il a envoyé : un fragment de métadonnées
{"choices":[],"x_geb_partial":{"reason":"stalled","code":"geb_dispatch_stalled","complete":false,"chars":123}}, puis un dernier fragment avecfinish_reason: "length", puis[DONE]. Traitez-le comme une réponse incomplète.reasonvautstalled,edge_deadline,deadlineouupstream_error. Les réponses partielles ne sont proposées que pour les appels en texte brut ; un appel avectoolsouresponse_formatreçoit à la place la trame d'erreur ci-dessous. - Erreur dans le flux. Si rien d'exploitable n'a été produit, une trame
data: {"error": {...}}suivie dedata: [DONE]. Le statut HTTP était déjà200, l'erreur ne peut donc pas être un statut HTTP. Les SDK officiels d'OpenAI lèvent une erreur lorsqu'ils voient cette trame.
Un flux qui se ferme sans [DONE] signifie que la connexion a été perdue. Traitez-le comme un échec et faites une nouvelle tentative (utilisez une Idempotency-Key, section 9.3).
Les problèmes que Geb peut détecter avant de commencer, comme une mauvaise clé, une erreur de validation ou un quota épuisé, reviennent sous la forme de véritables statuts d'erreur HTTP, et non de 200.
6.5 Analyser un flux sans SDK
import json, os, requests
r = requests.post(
"https://api.gebai.ca/v2/chat/completions",
headers={"Authorization": f"Bearer {os.environ['GEB_API_KEY']}"},
json={"stream": True, "messages": [{"role": "user", "content": "Hello"}]},
stream=True,
)
r.raise_for_status()
done = False
for raw in r.iter_lines(decode_unicode=True):
if not raw or raw.startswith(":"):
continue # blank line or keepalive comment
payload = raw[len("data:"):].strip()
if payload == "[DONE]":
done = True
break
frame = json.loads(payload)
if "error" in frame:
raise RuntimeError(frame["error"])
for choice in frame.get("choices", []):
print(choice["delta"].get("content") or "", end="")
if not done:
raise RuntimeError("stream ended without [DONE]; retry")const res = await fetch("https://api.gebai.ca/v2/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GEB_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ stream: true, messages: [{ role: "user", content: "Hello" }] }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const decoder = new TextDecoder();
let buffer = "";
let done = false;
for await (const part of res.body) {
buffer += decoder.decode(part, { stream: true });
let i;
while ((i = buffer.indexOf("\n")) >= 0) {
const line = buffer.slice(0, i).trim();
buffer = buffer.slice(i + 1);
if (!line || line.startsWith(":")) continue;
const payload = line.slice(5).trim();
if (payload === "[DONE]") { done = true; continue; }
const frame = JSON.parse(payload);
if (frame.error) throw new Error(JSON.stringify(frame.error));
for (const c of frame.choices ?? []) process.stdout.write(c.delta?.content ?? "");
}
}
if (!done) throw new Error("stream ended without [DONE]; retry");6.6 Longs appels sans streaming
Un appel sans streaming qui s'exécute encore après environ 70 secondes reçoit ses en-têtes 200 plus tôt, suivis d'un seul caractère espace environ toutes les 25 secondes jusqu'à l'arrivée du corps JSON. Un espace en tête est du JSON valide : JSON.parse, fetch().json(), Python requests et les SDK OpenAI lisent donc le corps sans modification. La réponse porte x-geb-keepalive: on lorsque cela s'est produit.
Si un appel échoue après l'envoi de ces en-têtes, le statut reste 200 et le corps est l'objet d'erreur ; lorsque x-geb-keepalive: on est présent, vérifiez donc la présence d'une clé error au niveau supérieur. Envoyez l'en-tête de requête x-geb-keepalive: off pour refuser ce mécanisme et obtenir à la place une 504 geb_edge_deadline au bout d'environ 95 secondes.
Pour les générations longues, utilisez le streaming. Une réponse en streaming n'a pas de limite de durée totale tant qu'elle progresse.
7.Appels d'outils et appel de fonctions
Il existe deux types d'outils différents. Ils sont indépendants et peuvent être envoyés ensemble.
tools (vos fonctions) | geb_tools (outils intégrés de Geb) | |
|---|---|---|
| À qui appartiennent les fonctions | À vous | À Geb : recherche web, météo, documents, code |
| Qui les exécute | Vous | Geb |
| Effets de bord | Aucun du côté de Geb | Réels (par exemple un fichier généré) |
| Par défaut | Utilisées lorsque vous les envoyez | Désactivés |
Si vous envoyez les deux, le modèle voit vos fonctions, les appels qu'il renvoie sont à exécuter par vous, et l'exécuteur d'outils propre à Geb ne s'exécute pas pour ce tour.
7.1 Vos fonctions (`tools`)
Vous décrivez vos fonctions, Geb décide quand en appeler une, et vous l'exécutez puis renvoyez le résultat. C'est le protocole standard d'OpenAI : les SDK fonctionnent donc sans modification.
curl https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" -H "Content-Type: application/json" \
-d '{
"parallel_tool_calls": false,
"messages": [{"role":"user","content":"What is the weather in Alexandria?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'Le modèle répond avec finish_reason: "tool_calls" et message.content valant null :
{
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "chatcmpl-tool-9d22d13b24ef97ed",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\": \"Alexandria\"}" }
}]
},
"finish_reason": "tool_calls"
}]
}Exécutez la fonction, puis envoyez une deuxième requête contenant votre historique, le tour de l'assistant exactement tel que reçu, et un message tool par appel :
import json
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "What is the weather in Alexandria?"}]
first = client.chat.completions.create(
model="geb", messages=messages, tools=tools, parallel_tool_calls=False
)
call = first.choices[0].message.tool_calls[0]
result = {"city": "Alexandria", "temp_c": 27, "sky": "clear"} # your code runs here
messages.append(first.choices[0].message)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"name": call.function.name,
"content": json.dumps(result),
})
final = client.chat.completions.create(model="geb", messages=messages, tools=tools)
print(final.choices[0].message.content)const tools = [{
type: "function",
function: {
name: "get_weather",
description: "Current weather for a city.",
parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
},
}];
const messages = [{ role: "user", content: "What is the weather in Alexandria?" }];
const first = await client.chat.completions.create({
model: "geb", messages, tools, parallel_tool_calls: false,
});
const call = first.choices[0].message.tool_calls[0];
const result = { city: "Alexandria", temp_c: 27, sky: "clear" }; // your code runs here
messages.push(first.choices[0].message);
messages.push({
role: "tool",
tool_call_id: call.id,
name: call.function.name,
content: JSON.stringify(result),
});
const final = await client.chat.completions.create({ model: "geb", messages, tools });
console.log(final.choices[0].message.content);Règles :
tools[].typedoit valoir"function".function.namecorrespond à^[a-zA-Z0-9_-]{1,64}$et doit être unique.function.parametersest un objet JSON Schema. Au plus 128 outils, et au plus 128 KiB sérialisés.function.argumentsest une chaîne encodée en JSON, dans les deux sens. Dans un messagetool,contentdoit être une chaîne : sérialisez vous-même votre résultat.- Répondez à chaque appel. Si un tour de l'assistant a émis trois appels, envoyez trois messages
toolavant la complétion suivante. Sinon, vous obtenez400 unanswered_tool_callqui nomme les identifiants manquants. C'est délibéré : sans réponse, un modèle peut inventer les résultats manquants. tool_choice: omis ou"auto"laisse le modèle décider,"none"interdit les appels,"required"exige au moins un appel, et{"type":"function","function":{"name":"..."}}n'autorise que cette fonction. Avec"required"ou une fonction nommée, si le modèle répond en prose au lieu d'appeler, Geb le relance une fois avant de répondre. Cela coûte un passage de modèle supplémentaire en latence, et non un second appel décompté de votre quota. Si le modèle répond encore en prose, vous recevez cette prose avecfinish_reason: "stop": vérifiez doncfinish_reasonavant de liretool_calls. Avec un choix nommé, Geb filtre aussi la réponse sur cette fonction. Si vous avez besoin de la garantie la plus forte, utilisez"required"avec un catalogue d'une seule fonction. Sur un flux avec"required"ou une fonction nommée, le texte est retenu jusqu'à la fin du tour puis envoyé, de sorte qu'une réponse en prose arrive d'un seul bloc à la fin.- Envoyez
"parallel_tool_calls": falsesauf si vous voulez plusieurs appels par tour. Vous obtenez ainsi exactement un appel propre par tour. - Geb supprime les appels en double identiques octet pour octet et écarte un dernier appel coupé par
length. Les arguments sont sinon transmis tels quels, y compris des arguments qui ne sont pas du JSON valide : validez-les donc avant de les utiliser. - Le streaming fonctionne. Les appels d'outils arrivent sous forme de trames standard
delta.tool_calls(une trame d'ouverture avecidetname, puis des fragments d'arguments, accumulés parindex). Ils arrivent ensemble à la fin du tour, sans être entremêlés à la génération. Le résultat accumulé est identique à celui d'un flux OpenAI. - Un aller-retour d'outil représente deux requêtes, et chacune compte comme un appel.
7.2 Outils intégrés de Geb (`geb_tools`)
Par défaut, un appel API est un simple appel au modèle : une génération, aucun outil, aucun effet de bord. Activez les outils avec geb_tools.
| Valeur | Comportement |
|---|---|
absent, false, [] | Aucun outil Geb. C'est le comportement par défaut. |
true | Tous les outils Geb disponibles à l'instant. Le modèle décide quand les utiliser. |
["web_search", "weather_forecast"] | Seulement les outils nommés. Le modèle voit exactement ceux-là, et l'appel à tout autre est refusé. |
curl https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" -H "Content-Type: application/json" \
-d '{
"messages": [{"role":"user","content":"What did the ECB decide at its last meeting?"}],
"geb_tools": ["web_search"]
}'r = client.chat.completions.create(
model="geb",
messages=[{"role": "user", "content": "Weather in Cairo this weekend?"}],
extra_body={"geb_tools": ["weather_forecast"]}, # the SDK merges extra_body into the request body
)const r = await client.chat.completions.create({
model: "geb",
messages: [{ role: "user", content: "Weather in Cairo this weekend?" }],
geb_tools: ["weather_forecast"], // TypeScript: add // @ts-expect-error, the SDK types do not know this field
});Les noms publics des outils (la liste en direct pour votre appel se trouve dans geb_tools sur GET /v2/models) :
| Nom | Ce qu'il fait |
|---|---|
web_search | Recherche web : titres, liens et extraits. |
web_grounded_search | Recherche web qui lit aussi les premières pages, pour des réponses avec citations. |
url_fetch | Récupère et lit une URL. |
polinex_news | Actualités récentes du flux Polinex pour les pays qu'il couvre. |
weather_forecast | Conditions actuelles et prévisions pour un lieu. |
currency_convert | Conversion de devises en direct. |
wikipedia_summary | Résumé d'un article Wikipedia. |
places_search | Lieux et commerces près d'un emplacement. |
geocode | De l'adresse aux coordonnées, et inversement. |
directions | Un itinéraire entre des lieux. |
distance_matrix | Distance et durée de trajet entre plusieurs points. |
static_map | Une image de carte pour un lieu. |
code_execute | Exécute du Python dans un bac à sable isolé. Les fichiers qu'il écrit sont renvoyés sous forme de liens. |
generate_text_file | Produit un fichier texte (md, csv, json, html et autres) et renvoie un lien de téléchargement. |
build_document | Construit un document Word (.docx) et renvoie un lien de téléchargement. |
build_workbook | Construit un classeur Excel (.xlsx) et renvoie un lien de téléchargement. |
build_slides | Construit une présentation PowerPoint (.pptx) et renvoie un lien de téléchargement. |
- Un nom que Geb ne reconnaît pas, ou qui est désactivé, renvoie
400 invalid_geb_toolsavec la liste des noms valides danserror.valid_geb_tools. Au plus 32 noms. - Avec une liste, la restriction est appliquée deux fois : le modèle ne voit que les outils nommés, et tout appel à un autre outil n'est pas exécuté (le modèle reçoit une erreur d'outil et répond sans lui).
- Les trois outils
build_*sont séparés volontairement. Nommerbuild_documentn'autorise qu'un fichier Word. - La construction de documents part de votre propre texte d'instruction, jamais de mots désignant un format de fichier dans un contenu que vous avez collé. Si la seule mention de « xlsx » ou de « slides » se trouve dans un bloc de code ou dans un long texte collé, Geb répond en texte.
- Les outils peuvent prendre plus de temps qu'une complétion simple. Les fichiers produits par les outils sont stockés sous votre compte et renvoyés sous forme de liens à durée limitée.
- La mémoire de votre compte, l'historique des chats, les connecteurs (Google, Microsoft, Dropbox, etc.) et les autres outils du compte ne sont pas disponibles via l'API.
8.Raisonnement
Geb réfléchit toujours avant de répondre. Vous choisissez l'intensité. Geb ne renvoie pas le texte de son raisonnement ; vous recevez la réponse, et les jetons de réflexion sont inclus dans usage.completion_tokens.
8.1 Niveaux
| Niveau | Signification |
|---|---|
low | Réflexion la plus courte. |
medium | Le niveau par défaut pour les questions plus difficiles. |
high | Réflexion longue. Correspond au réglage maximal du modèle. |
ultra | Le réglage maximal du modèle. |
GET /v2/models liste les niveaux (reasoning.efforts) et la valeur par défaut du serveur (reasoning.default_effort).
8.2 Ce que signifie « raisonnement désactivé »
La réflexion n'est jamais désactivée. Chaque variante « off » ("off", "none", false, "disabled", 0) laisse la réflexion active avec un budget de réflexion de zéro jeton : le modèle clôt aussitôt son raisonnement et commence la réponse. Vous obtenez une réponse normale, sans étape de raisonnement. Utilisez-le pour les requêtes simples et sensibles à la latence. Pour les problèmes difficiles, préférez medium ou high.
8.3 Comment le demander
Utilisez la forme que votre client envoie déjà. Aucune n'est jamais rejetée.
| Paramètre | Exemples |
|---|---|
reasoning | "high", "off", "on", {"effort": "medium"} |
reasoning_effort | "low", "medium", "high", "ultra", "minimal", "none", "xhigh" |
thinking | {"type": "enabled", "budget_tokens": 3000}, {"type": "disabled"}, true |
enable_thinking | true, false |
chat_template_kwargs | {"enable_thinking": false}, {"reasoning_effort": "high"} |
extra_body | {"reasoning_effort": "medium"}, pour les clients qui envoient extra_body tel quel |
Correspondances : minimal devient low. xhigh, max et maximum deviennent ultra. Une valeur budget_tokens correspond au niveau le plus proche (jusqu'à 1024, low ; jusqu'à 2048, medium ; jusqu'à 4096, high ; au-delà, ultra). Un niveau inconnu (par exemple "turbo") utilise la valeur par défaut du serveur.
Si vous envoyez plusieurs formes qui se contredisent, la première de cette liste qui nomme un niveau l'emporte : reasoning, reasoning_effort, thinking, enable_thinking, chat_template_kwargs, extra_body. Une forme qui ne fait qu'activer la réflexion ne remplace jamais un niveau nommé ailleurs.
curl https://api.gebai.ca/v2/chat/completions \
-H "Authorization: Bearer $GEB_API_KEY" -H "Content-Type: application/json" \
-d '{"reasoning_effort":"high","messages":[{"role":"user","content":"Plan a 3-city sales route to minimise travel."}]}' -ir = client.chat.completions.create(
model="geb",
messages=[{"role": "user", "content": "Plan a 3-city sales route to minimise travel."}],
reasoning_effort="high",
)
# "ultra" is not in the SDK's typed list, send it through extra_body:
r = client.chat.completions.create(
model="geb",
messages=[{"role": "user", "content": "Prove that the square root of 2 is irrational."}],
extra_body={"reasoning": "ultra"},
)const r = await client.chat.completions.create({
model: "geb",
messages: [{ role: "user", content: "Plan a 3-city sales route to minimise travel." }],
reasoning_effort: "high",
});8.4 Ce qui revient
| En-tête de réponse | Valeur |
|---|---|
x-geb-reasoning-effort | Le niveau utilisé, ou default si vous n'en avez envoyé aucun. |
x-geb-reasoning-adjusted | Présent lorsqu'une valeur a été convertie ou ignorée, par exemple enable_thinking=false->budget 0 (thinking stays on; think block closed). |
8.5 max_tokens et réflexion
max_tokens est le plafond de la réponse visible. La réflexion est budgétée séparément, en plus de celui-ci : un petit max_tokens n'affame donc pas le raisonnement, et Geb ne multiplie jamais votre nombre. Si la réponse atteint le plafond, elle est coupée à cet endroit et finish_reason vaut length. Lorsque vous ne définissez pas max_tokens, le budget de la réponse est déduit de la fenêtre de contexte. usage.completion_tokens inclut les jetons de réflexion.
9.Erreurs et nouvelles tentatives
9.1 L'enveloppe
Chaque réponse d'erreur d'une route /v2 (et de son alias /v1) est du JSON, dans un seul format :
{
"error": {
"message": "A human-readable explanation.",
"type": "invalid_request_error",
"param": "messages",
"code": "invalid_json_body",
"request_id": "0f658b6d9742d46fec9ce4b984f99667"
}
}typeest la classe de l'erreur. Branchez votre logique surtypeetcode, jamais sur la formulation demessage.paramnomme le champ fautif lorsqu'un champ est en cause, sinonnull.codeest un motif plus précis, lisible par une machine, ounull.request_idest toujours présent et est aussi envoyé dans l'en-tête de réponsex-request-id. Chaque réponse portex-request-id, y compris les réponses réussies. Citez-le lorsque vous contactez le support. Envoyez votre proprex-request-idsi vous le souhaitez ; Geb peut tout de même renvoyer son propre identifiant de trace.- Certaines erreurs ajoutent
retry_after_msau niveau supérieur, à côté deerror, etRetry-Aftercomme en-tête (en secondes). - Les échecs survenant après le début d'un flux arrivent dans le flux (section 6.4). Les échecs survenant après le début d'un long appel sans streaming arrivent sous forme de corps
200avec une cléerror(section 6.6). - Une requête vers un chemin qui n'existe pas renvoie une 404 simple
{"message", "error", "statusCode"}, et non cette enveloppe. Si vous la voyez, vérifiez l'URL.
9.2 Catalogue
| HTTP | type | code | Signification et action à mener |
|---|---|---|---|
| 400 | invalid_request_error | null | Échec de validation : propriété inconnue, mauvais type, messages vide, champ non pris en charge (n, logprobs, functions), message trop volumineux. Corrigez la requête. |
| 400 | invalid_request_error | invalid_json_body | Le corps n'est pas du JSON strict. |
| 400 | invalid_request_error | invalid_content_encoding | Le corps n'est pas du gzip ou du deflate valide pour son Content-Encoding. |
| 400 | invalid_request_error | invalid_geb_tools | geb_tools incorrect. error.valid_geb_tools liste les noms acceptés. |
| 400 | invalid_request_error | invalid_geb_acceptance | geb_acceptance incorrect. param et rule_index localisent la règle. |
| 400 | invalid_request_error | memory_unavailable, invalid_geb_memory | geb_memory: true, ou une valeur non booléenne. Les appels API n'ont pas de mémoire. |
| 400 | invalid_request_error | unsupported_tool_choice_mode, unknown_tool, duplicate_tool_name, too_many_tools, tools_too_large, invalid_type, invalid_value, missing_required_parameter | Un problème de tools ou de tool_choice. param indique le chemin, par exemple tools[2].function.name. |
| 400 | invalid_request_error | unknown_tool_call_id, unanswered_tool_call | Un message tool ne correspond à aucun appel antérieur, ou un appel n'a pas de résultat. |
| 400 | invalid_request_error | invalid_content, unsupported_content_part, invalid_image_url, unsupported_image_type, invalid_image, too_many_images, image_too_large, images_too_large, image_url_forbidden, image_fetch_failed, image_fetch_timeout | Un problème d'image ou de partie de contenu (section 5.4). |
| 401 | invalid_api_key | null | Clé absente, mal formée, inconnue, révoquée ou expirée. Ne réessayez pas. |
| 403 | permission_error | null | La clé est valide mais le compte n'a pas le droit d'utiliser l'API. Ne réessayez pas. |
| 404 | not_found_error | null | Identifiant de modèle inconnu lors d'une récupération, ou conversation qui n'est pas la vôtre. |
| 409 | idempotency_key_reuse, idempotent_request_in_progress | le même nom que type | Même Idempotency-Key avec un corps différent, ou le premier appel est encore en cours (section 9.3). |
| 413 | invalid_request_error | request_too_large | Le corps décompressé dépasse la limite de 32 MiB. |
| 415 | invalid_request_error | unsupported_content_encoding | Content-Encoding autre que gzip ou deflate. |
| 422 | invalid_request_error | geb_acceptance_failed | on_fail: "error_422" et la réponse ne respecte toujours pas vos règles. Non comptée dans votre quota. |
| 429 | rate_limit_exceeded | null | Limite de rafale par minute. Attendez Retry-After secondes. |
| 429 | rate_limit_error | geb_concurrency_limit | Trop de requêtes simultanées pour votre compte. Attendez Retry-After, ou la fin de l'une d'elles. |
| 429 | quota_exceeded | null | Quota mensuel d'appels épuisé. La seule solution est d'attendre la réinitialisation, ou de changer de forfait. |
| 502 | server_error | empty_answer | Le modèle a terminé sans réponse. Réessayez immédiatement : la même requête réussit généralement. retry_after_ms vaut 0. |
| 503 | server_busy | null ou maintenance | Geb est occupé ou brièvement indisponible. Attendez Retry-After (environ 5 secondes lorsqu'il est occupé, jusqu'à 120 en cas de maintenance). |
| 503 | server_error | vision_unavailable | Le modèle de vision est indisponible pour le moment. Réessayez plus tard ou envoyez sans images. |
| 503 | server_error | degenerate_output | La réponse a été coupée pour des raisons de qualité et aucune nouvelle tentative propre n'était possible. Réessayez. |
| 503 | server_error | geb_instance_shutdown | L'instance du serveur redémarre. Réessayez immédiatement. |
| 504 | server_error | geb_edge_deadline, geb_dispatch_stalled, geb_dispatch_deadline | La requête a pris trop de temps ou n'a pas progressé. Réessayez avec "stream": true. |
| 5xx | server_error | null | Tout le reste. Le message est volontairement générique. Réessayez avec un délai croissant et citez request_id si le problème persiste. |
Sur un flux, les mêmes erreurs arrivent sous forme de trame d'erreur dans le flux, plus geb_incomplete_stream (la génération s'est arrêtée avant d'être terminée ; réessayez).
9.3 Conseils pour les nouvelles tentatives
- Ne réessayez jamais
400,401,403,404,409 idempotency_key_reuse,413,415,422. La requête doit changer. - Réessayez après l'attente pour
429 rate_limit_exceededetgeb_concurrency_limit(utilisezRetry-After), et pour503 server_busy. - N'insistez pas sur
429 quota_exceeded. Lisezreset_atdans le corps ouX-Quota-Reset, et arrêtez-vous jusque-là. - Réessayez immédiatement, une ou deux fois pour
502 empty_answeret503 geb_instance_shutdown. - Passez au streaming, puis réessayez pour
504. - Ajoutez un délai exponentiel avec variation aléatoire (jitter) pour les autres
5xx, avec un plafond de quelques tentatives. - Envoyez toujours une
Idempotency-Keysur les appels que vous pourriez réessayer. C'est une chaîne quelconque d'au plus 255 caractères, unique pour chaque requête logique. Le premier appel s'exécute normalement. Une répétition avec la même clé et le même corps renvoie la réponse enregistrée (Idempotency-Replayed: true), sans nouvelle génération et sans consommer de quota, pendant 24 heures. La réponse rejouée a le même contenu, les mêmes champs et les mêmes valeurs que la première réponse ; l'ordre des clés à l'intérieur de l'objet JSON peut différer : comparez donc le JSON analysé, pas les octets. La même clé avec un corps différent donne409 idempotency_key_reuse. Une répétition pendant que le premier appel est encore en cours donne409 idempotent_request_in_progress: attendez, ne bouclez pas. Un flux qui a échoué en cours de route n'est pas enregistré, donc une nouvelle tentative s'exécute à nouveau. L'en-têteIdempotency-Statusindiquestored,replayedoudisabled. - Réglez généreusement le délai d'expiration de votre client, ou utilisez le streaming. Les SDK OpenAI envoient leur délai d'expiration dans un en-tête, et Geb renvoie la meilleure réponse partielle (
finish_reason: "length",x_geb_partial) plutôt que rien lorsque votre propre délai approche.
Chaque appel se termine par l'une de trois issues : la réponse, une réponse partielle marquée finish_reason: "length" avec x_geb_partial, ou une erreur structurée. Une réponse partielle sans streaming ressemble à ceci :
{
"choices": [{"index": 0, "message": {"role": "assistant", "content": "..."}, "finish_reason": "length"}],
"x_geb_partial": {"reason": "stalled", "code": "geb_dispatch_stalled", "complete": false, "chars": 812}
}9.4 Une fonction d'aide pour les nouvelles tentatives
import os, random, time, uuid, requests
URL = "https://api.gebai.ca/v2/chat/completions"
HEADERS = {"Authorization": f"Bearer {os.environ['GEB_API_KEY']}"}
def complete(body, attempts=5):
key = str(uuid.uuid4()) # same key on every attempt
for n in range(attempts):
r = requests.post(URL, json=body, headers={**HEADERS, "Idempotency-Key": key}, timeout=620)
if r.ok:
data = r.json()
if "error" in data: # error after a keepalive commit
raise RuntimeError(data["error"])
return data
err = r.json().get("error", {})
code, kind = err.get("code"), err.get("type")
if r.status_code in (400, 401, 403, 404, 413, 415, 422) or kind == "quota_exceeded":
raise RuntimeError(f"{r.status_code} {kind}/{code}: {err.get('message')} ({err.get('request_id')})")
if code in ("empty_answer", "geb_instance_shutdown"):
wait = 0
elif r.status_code == 504:
raise RuntimeError("timed out: retry with stream=True")
else:
wait = float(r.headers.get("Retry-After", 2 ** n)) + random.random()
time.sleep(wait)
raise RuntimeError("gave up after retries")import { randomUUID } from "node:crypto";
export async function complete(body, attempts = 5) {
const key = randomUUID(); // same key on every attempt
for (let n = 0; n < attempts; n++) {
const res = await fetch("https://api.gebai.ca/v2/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GEB_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(body),
});
const data = await res.json();
if (res.ok && !data.error) return data;
const err = data.error ?? {};
if ([400, 401, 403, 404, 413, 415, 422].includes(res.status) || err.type === "quota_exceeded")
throw new Error(`${res.status} ${err.type}/${err.code}: ${err.message} (${err.request_id})`);
if (res.status === 504) throw new Error("timed out: retry with stream: true");
const wait = ["empty_answer", "geb_instance_shutdown"].includes(err.code)
? 0
: (Number(res.headers.get("Retry-After")) || 2 ** n) * 1000 + Math.random() * 1000;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error("gave up after retries");
}10.Limites et quotas
10.1 Quota mensuel d'appels
Tous les forfaits incluent l'API, Gratuit compris, avec un nombre d'appels API par période de facturation. Les jetons ne sont pas comptés et il n'y a aucune facturation au jeton.
| Forfait | Appels par période |
|---|---|
| Gratuit | 500 |
| Basic | 1 500 |
| Pro | 10 000 |
| Master | 100 000 |
| Developer | Illimité |
- La période est la période de facturation de votre abonnement, ou le mois civil (UTC) si vous n'avez pas d'abonnement.
- Seuls les appels réussis comptent. Un appel qui échoue ne consomme pas de quota. Une réponse rejouée à partir d'une
Idempotency-Keyne consomme pas de quota. Les routes de conversation comptent comme des appels. Un aller-retour d'outil compte pour deux appels. - Au-delà du quota, l'API répond
429 quota_exceededavec les détails :
{
"error": {
"type": "quota_exceeded",
"message": "Monthly API request quota reached: your Free plan includes 500 calls per billing period and 500 have been used. The quota resets at 2026-11-01T00:00:00.000Z.",
"tier": "free",
"limit": 500,
"used": 500,
"remaining": 0,
"reset_at": "2026-11-01T00:00:00.000Z",
"request_id": "..."
}
}Chaque réponse réussie indique votre position dans des en-têtes : X-Quota-Limit-Month, X-Quota-Remaining-Month et X-Quota-Reset. Une 429 de quota les porte aussi, avec Retry-After défini sur le nombre de secondes jusqu'à la réinitialisation.
curl -s -D - -o /dev/null https://api.gebai.ca/v2/models -H "Authorization: Bearer $GEB_API_KEY" | grep -i "x-quota\|x-ratelimit"r = requests.post(URL, json=body, headers=HEADERS)
print(r.headers.get("X-Quota-Remaining-Month"), "calls left until", r.headers.get("X-Quota-Reset"))const res = await fetch(url, { method: "POST", headers, body });
console.log(res.headers.get("X-Quota-Remaining-Month"), "calls left until", res.headers.get("X-Quota-Reset"));10.2 Limites de débit
| Limite | Valeur |
|---|---|
| Requêtes par minute (rafale), par compte | 120 pour tous les forfaits : Gratuit, Basic, Pro, Master et Developer. Comptées sur l'ensemble des clés d'un compte. |
| Requêtes simultanées, par compte | Une part équitable de la capacité disponible à ce moment-là. Au-delà : 429 geb_concurrency_limit. |
L'utilisation de l'API est mesurée par le nombre mensuel d'appels (section 10.1). La limite par minute n'existe que pour arrêter les rafales et les abus, elle est donc la même pour tous les forfaits. Elle est envoyée dans X-RateLimit-Limit. Une 429 de rafale porte Retry-After. Étalez les rafales, ou utilisez moins de requêtes en streaming, plus longues.
10.3 Limites des requêtes
| Limite | Valeur |
|---|---|
| Corps de requête | 32 MiB après décompression. gzip et deflate acceptés. |
| Messages et taille des messages | Bornés par la fenêtre de contexte du modèle (context_window sur GET /v2/models). Un dépassement donne une 400 qui nomme la borne. |
| Images | 16 par requête, 20 MB chacune, 64 MB décodés au total. |
Vos outils (tools) | 128 fonctions, 128 KiB sérialisés. |
geb_tools | 32 noms. |
Idempotency-Key | 255 caractères, conservée 24 heures. |
| Clés actives par compte | 10. |
| Conversations enregistrées | Aucune limite, conservées jusqu'à ce que vous les supprimiez. |
10.4 Durée
Le streaming n'a pas de limite de durée totale tant que le modèle progresse. Un appel sans streaming est maintenu en vie au-delà du délai d'expiration en périphérie (section 6.6). Un appel qui ne progresse pas pendant trop longtemps se termine par une réponse partielle ou une 504 plutôt que de rester bloqué.
11.Compatibilité avec les SDK OpenAI
Les SDK Python et JavaScript d'OpenAI fonctionnent avec deux modifications : l'URL de base et la clé.
from openai import OpenAI
client = OpenAI(base_url="https://api.gebai.ca/v2", api_key="geb_live_...")import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.gebai.ca/v2", apiKey: "geb_live_..." });# Anything that reads the standard environment variables also works:
export OPENAI_BASE_URL="https://api.gebai.ca/v2"
export OPENAI_API_KEY="geb_live_..."Ce qui fonctionne de la même façon : chat.completions.create (en réponse complète et en streaming), models.list et models.retrieve, l'appel de fonctions, response_format, stream_options.include_usage, les parties de contenu image, et les erreurs au format OpenAI avec Retry-After.
Différences à prévoir :
- Uniquement des complétions de chat. Les appels aux embeddings, à l'audio, aux images, aux fichiers, aux traitements par lots ou à l'API Responses n'existent pas.
- Les champs inconnus renvoient 400. Ne passez que les champs listés à la section 5.2. En particulier, utilisez
max_tokenset nonmax_completion_tokens, et n'envoyez passtorenimetadata. - Les champs propres à Geb (
geb_tools,geb_acceptance,conversation_id,locale) passent parextra_bodyen Python (le SDK le fusionne dans le corps de la requête). En JavaScript, passez-les comme propriétés supplémentaires. - Les champs d'échantillonnage sont ignorés.
temperature,top_p,stop,seedet les pénalités sont acceptés et n'ont aucun effet. - Un seul modèle.
modelne sélectionne rien. Utilisezclient.models.list()pour voir ce qui est servi. - Niveaux de raisonnement.
reasoning_effortacceptelow,medium,high, et aussiultra(envoyez-le viaextra_bodysi la version de votre SDK type le champ). - Utilisation côté serveur. N'appelez pas l'API depuis un navigateur sur votre propre domaine.
- Champs de réponse supplémentaires. Les réponses peuvent porter des champs propres à Geb tels que
conversation_idetx_geb_*. Ils sont additifs. - Nouvelles tentatives. Les SDK réessaient d'eux-mêmes sur
429et5xx. Ajoutez uneIdempotency-Keyviaextra_headerspour qu'un appel réessayé ne soit ni exécuté ni compté deux fois :
client.chat.completions.create(
model="geb",
messages=[{"role": "user", "content": "Hello"}],
extra_headers={"Idempotency-Key": "order-7421-reply-1"},
)await client.chat.completions.create(
{ model: "geb", messages: [{ role: "user", content: "Hello" }] },
{ headers: { "Idempotency-Key": "order-7421-reply-1" } },
);12.Traitement des données
- Aucune mémoire du compte. Les appels API ne lisent jamais la mémoire de votre compte Geb et n'y écrivent jamais. Cela vaut pour chaque clé et chaque forfait.
- Les conversations enregistrées n'existent que si vous utilisez
conversation_id. Elles sont séparées de la mémoire, lisibles uniquement par votre compte, et supprimées lorsque vous supprimez la conversation. - Journaux de l'API. Chaque appel est enregistré pour l'exploitation, le support et la prévention des abus : le contenu de la requête et de la réponse, le modèle, les paramètres, le nombre de jetons, la latence, le statut, votre agent utilisateur, l'identifiant de la clé et le pays de l'appelant. Les identifiants et secrets présents dans le contenu (clés, jetons, mots de passe, URL signées) sont masqués avant stockage. Les images en ligne sont stockées sous forme de taille et d'empreinte uniquement. Une copie de chaque appel est aussi visible par vous, et supprimable par vous, dans vos propres données de l'application Geb. Le journal d'exploitation est conservé pendant toute la vie du compte et supprimé lorsque le compte est supprimé. Les journaux de l'API ne sont jamais utilisés comme mémoire.
- Les fichiers d'outils et les fichiers générés sont stockés sous votre compte et renvoyés sous forme de liens à durée limitée.
N'envoyez pas de données que vous n'avez pas le droit d'envoyer à un service tiers. Si vous avez besoin d'un accord de traitement des données, contactez Geb avant le lancement.
13.Version et assistance
Geb API v2. Servie sur https://api.gebai.ca/v2, avec /v1 conservé comme alias pour les clients existants. Révision 1.1 du guide, 28 octobre 2026.
Ce guide décrit le comportement de l'API en production à cette date. Les champs marqués comme ignorés pourront être pris en compte plus tard ; de nouveaux champs facultatifs peuvent apparaître dans les réponses. Traitez les champs de réponse inconnus comme additifs.
Lorsque vous avez besoin d'aide, indiquez le request_id du corps de l'erreur (ou l'en-tête x-request-id), l'heure de l'appel et la forme de la requête sans la clé. Contactez Geb via https://gebai.ca/contact.