API opérationnelle · ai.bakeli.tech

L'IA pour toute
l'infrastructure
Bakeli

Un seul endpoint pour tous vos projets. Modèles LLM locaux, zéro coût d'API, données sur vos serveurs.

Premier appel API
import requests resp = requests.post( "https://ai.bakeli.tech/v1/chat", headers={"Authorization": "Bearer <API_KEY>"}, json={ "model": "openai/gpt-oss-20b", # Groq par défaut "messages": [{"role": "user", "content": "Bonjour !"}] } ) print(resp.json()["message"]["content"])
Tout ce dont vos apps ont besoin

Une API simple, puissante, et compatible avec vos stacks existants.

💬
Chat conversationnel
Historique multi-tours, contexte système, réponses structurées.
📄
Analyse de fichiers
PDF, Excel, Word, CSV, images — extraction automatique et raisonnement. Aucune config nécessaire.
Streaming temps réel
Réponses en streaming ndjson pour une UX fluide.
🤖
Multi-modèles
Llama 3, Mistral, Gemma, LLaVA-Phi3 (Ollama) + Groq cloud + Gemini (Google). Changez par requête.
🔐
Auth par clé API
Header Bearer standard. Une clé par application.
☁️
Fallback intelligent
Cascade automatique : Groq → Gemini → Ollama. Zéro downtime.
4 endpoints, tout compris

Interface REST simple, même pattern que Gemini et OpenAI.

POST /v1/chat Chat texte avec historique et streaming
POST /v1/chat/file Analyse de fichiers — PDF, images, texte
POST /v1/chat/url Chat avec fichiers via URL publique
POST /v1/extract Extraction structurée (JSON) depuis fichiers
GET /v1/models Liste des modèles disponibles
GET /v1/health Healthcheck sans authentification
Guides par framework

Exemples complets et modules réutilisables pour chaque stack.

🐍
Django / Python
Guide complet
Module client réutilisable, views, historique.
🔴
Laravel / PHP
Guide complet
Service injectable, facade, upload de fichiers.
⚛️
React / Next.js
Guide complet
API Route sécurisée, hook React, streaming.
/ Documentation v1.0
Opérationnel
Bakeli SI · AI RedTeam

Vue d'ensemble

API IA centralisée pour toute l'infrastructure Bakeli SI. Un seul endpoint pour tous les projets — Django, Laravel, React, mobile.

Architecture

Flux
Tes apps → https://ai.bakeli.tech → NPM → ai-RedTeam:8000 ┬→ Groq API (défaut chat)
                                                             ├→ Gemini API (défaut fichiers + fallback)
                                                             └→ Ollama (fallback final)
💡Le routage est automatique : openai/gpt-oss-20b (Groq) par défaut pour /v1/chat. Gemini (gemini-3-flash-preview) est le backend par défaut pour /v1/chat/file (meilleur pour vision + documents) et sert de fallback secondaire si Groq est en rate limit. Ollama est le fallback final.

Endpoints

MéthodeEndpointDescriptionAuth
POST/v1/chatChat texte + streaming (Groq → Gemini → Ollama)
POST/v1/chat/fileChat + fichier joint (Gemini par défaut)
POST/v1/chat/urlChat + fichier via URL publique (Gemini par défaut)
POST/v1/extractExtraction structurée (JSON) depuis fichiers
GET/v1/modelsListe des modèles (Ollama + Groq)
GET/v1/healthHealthcheck
Démarrage

Démarrage rapide

Ton premier appel en moins de 2 minutes.

1
Obtenir la clé API
Demande la API_KEY à l'équipe infra.
2
Premier appel avec cURL
bash
curl -X POST https://ai.bakeli.tech/v1/chat \
  -H "Authorization: Bearer <ta-cle-api>" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"Bonjour !"}]}'

Réponse attendue

json — 200 OK
{ "model": "openai/gpt-oss-20b", "message": { "role": "assistant", "content": "Bonjour !" }, "done": true }
💡Envoie tout l'historique dans messages pour maintenir le contexte entre les tours.
Sécurité

Authentification

Tous les endpoints sauf /v1/health requièrent une clé API.

Header requis

HTTP
Authorization: Bearer <API_KEY>
json — 401
{ "error": "Unauthorized" }
⚠️Ne jamais exposer la clé API côté browser. Appels uniquement depuis le backend.

Stocker la clé

.env
BAKELI_AI_URL=https://ai.bakeli.tech
BAKELI_AI_KEY=ta-cle-api-secrete
API Reference

POST /v1/chat

Chat texte avec historique. Supporte le streaming ndjson.

POST/v1/chat
ChampTypeDéfautDescription
modelstringopenai/gpt-oss-20bModèle à utiliser (Groq par défaut, fallback Ollama)
messagesarrayrequisTableau {role, content}
streambooleanfalseActive le streaming
Routage automatique : les modèles avec préfixe openai/, llama-, mixtral-, gemma-, qwen-, deepseek- sont envoyés à Groq. Si Groq est indisponible, le système bascule automatiquement sur llama3.2 via Ollama. Les autres modèles vont directement vers Ollama.

Exemples

bash
curl -X POST https://ai.bakeli.tech/v1/chat \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [
      {"role": "system", "content": "Tu es un assistant Bakeli SI."},
      {"role": "user",   "content": "Explique Docker en 3 lignes."}
    ]
  }'
python
import requests

resp = requests.post(
    "https://ai.bakeli.tech/v1/chat",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
        "model": "openai/gpt-oss-20b",
        "messages": [
            {"role": "system", "content": "Tu es un assistant Bakeli SI."},
            {"role": "user",   "content": "Explique Docker en 3 lignes."},
        ],
    },
    timeout=120,
)
print(resp.json()["message"]["content"])
php
$ch = curl_init('https://ai.bakeli.tech/v1/chat');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer <API_KEY>',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'model'    => 'openai/gpt-oss-20b',
        'messages' => [
            ['role' => 'system', 'content' => 'Tu es un assistant Bakeli SI.'],
            ['role' => 'user',   'content' => 'Explique Docker en 3 lignes.'],
        ],
    ]),
    CURLOPT_TIMEOUT => 120,
]);
$r = json_decode(curl_exec($ch), true);
echo $r['message']['content'];
javascript
const res = await fetch("https://ai.bakeli.tech/v1/chat", {
  method: "POST",
  headers: {
    "Content-Type":  "application/json",
    "Authorization": "Bearer <API_KEY>",
  },
  body: JSON.stringify({
    model: "openai/gpt-oss-20b",
    messages: [
      { role: "system", content: "Tu es un assistant Bakeli SI." },
      { role: "user",   content: "Explique Docker en 3 lignes." },
    ],
  }),
});
const data = await res.json();
console.log(data.message.content);
python — via Groq (routage automatique)
# Il suffit de choisir un modèle Groq — le routage est automatique
import requests

resp = requests.post(
    "https://ai.bakeli.tech/v1/chat",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
        "model": "llama-3.3-70b-versatile",  # ← préfixe "llama-" → Groq
        "messages": [
            {"role": "user", "content": "Explique Docker en 3 lignes."},
        ],
    },
    timeout=60,
)
print(resp.json()["message"]["content"])
Modèles Groq disponibles : llama-3.3-70b-versatile, llama-3.1-8b-instant, mixtral-8x7b-32768, gemma-7b-it, deepseek-r1-distill-llama-70b. Voir la page Groq pour la liste complète.

Réponse — cascade de fallback

Le gateway utilise une cascade de fallback automatique : Groq → Gemini-3 → Gemini-2.5 → Ollama. Quand un backend est indisponible, la réponse inclut deux champs supplémentaires :

json — fallback Gemini-3 (si Groq rate limit)
{
  "model":           "gemini-3-flash-preview",
  "message":         { "role": "assistant", "content": "Docker est..." },
  "done":            true,
  "fallback":        true,
  "fallback_reason": "Groq rate limit"
}
json — fallback Gemini-2.5 (si Gemini-3 épuisé)
{
  "model":           "gemini-2.5-flash",
  "message":         { "role": "assistant", "content": "Docker est..." },
  "done":            true,
  "fallback":        true,
  "fallback_reason": "Groq rate limit → gemini-3-flash-preview quota épuisé"
}
json — fallback Ollama (si tous les quotas épuisés)
{
  "model":           "llama3.2",
  "message":         { "role": "assistant", "content": "Docker est..." },
  "done":            true,
  "fallback":        true,
  "fallback_reason": "Groq rate limit → tous modèles Gemini épuisés"
}
🔄Le champ "fallback": true te permet de détecter côté client quel backend a répondu. La cascade complète est : Groq (défaut) → Gemini-3Gemini-2.5Ollama (llama3.2).
API Reference

POST /v1/chat/file

Envoie un fichier avec un prompt. Le gateway utilise Gemini par défaut (meilleur pour vision + documents), détecte automatiquement le type et extrait le contenu.

POST/v1/chat/file

Content-Type : multipart/form-data

ChampTypeDescription
promptstringQuestion sur le fichier
modelstringModèle (défaut : gemini-3-flash-preview)
filefileFichier à analyser

Logique de traitement automatique

Type de fichierBackend par défautCe que fait le gateway
image/* (jpg, png, webp…) Gemini-3 Vision multimodale native via SDK Google. Si échec → llava-phi3. Cascade : gemini-3 → gemini-2.5 → llava-phi3
application/pdfGemini-3Lecture PDF multimodale native via SDK Google. Si échec (ex: PDF corrompu) → extraction PyMuPDF. Cascade : gemini-3 → gemini-2.5 → extraction + llama3.2
.xlsx / .xlsGemini-3Lecture Excel multimodale native via SDK Google. Si échec → extraction openpyxl/xlrd. Cascade : gemini-3 → gemini-2.5 → extraction + llama3.2
.docxGemini-3Lecture Word multimodale native via SDK Google. Si échec → extraction python-docx. Cascade : gemini-3 → gemini-2.5 → extraction + llama3.2
.csv / .tsvGemini-3Lecture CSV multimodale native via SDK Google. Si échec → extraction csv. Cascade : gemini-3 → gemini-2.5 → extraction + llama3.2
text/*, .json, .sql, code…Gemini-3Lecture texte multimodale native via SDK Google. Si échec → injection texte brut. Cascade : gemini-3 → gemini-2.5 → llama3.2
Gemini-3 (gemini-3-flash-preview) est le modèle par défaut pour /v1/chat/file — il supporte le multimodal natif via le SDK Google : images, PDF, Excel, Word, CSV sont envoyés directement. En cas d'échec (fichier corrompu, format non supporté), le gateway bascule automatiquement sur l'extraction de texte.
🔄Double fallback : 1) Multimodal natif → 2) Extraction de texte → 3) Ollama. Si gemini-3-flash-preview est épuisé, bascule sur gemini-2.5-flash. Si les deux sont épuisés ou si le fichier ne peut pas être lu, bascule sur Ollama avec extraction (llava-phi3 pour images, llama3.2 pour documents).

Exemples

bash — PDF, Excel, Word, CSV (Gemini par défaut)
curl -X POST https://ai.bakeli.tech/v1/chat/file \
  -H "Authorization: Bearer <API_KEY>" \
  -F "prompt=Résume en 5 points clés" \
  -F "file=@/chemin/document.pdf"
bash — Image (Gemini vision, par défaut)
curl -X POST https://ai.bakeli.tech/v1/chat/file \
  -H "Authorization: Bearer <API_KEY>" \
  -F "prompt=Décris cette image en détail" \
  -F "file=@/chemin/image.jpg"
bash — Image via Ollama (vision locale)
curl -X POST https://ai.bakeli.tech/v1/chat/file \
  -H "Authorization: Bearer <API_KEY>" \
  -F "prompt=Décris cette image" \
  -F "model=llava-phi3" \
  -F "file=@/chemin/image.jpg"
python — Document (PDF, Excel, Word, CSV…)
import requests

with open("rapport.pdf", "rb") as f:
    resp = requests.post(
        "https://ai.bakeli.tech/v1/chat/file",
        headers={"Authorization": "Bearer <API_KEY>"},
        data={"prompt": "Résume en 5 points"},  # Gemini par défaut
        files={"file": f},
        timeout=180,
    )
print(resp.json()["message"]["content"])
python — Image (Gemini vision, par défaut)
with open("image.jpg", "rb") as f:
    resp = requests.post(
        "https://ai.bakeli.tech/v1/chat/file",
        headers={"Authorization": "Bearer <API_KEY>"},
        data={"prompt": "Décris cette image en détail"},  # Gemini par défaut
        files={"file": ("image.jpg", f, "image/jpeg")},
        timeout=180,
    )
python — Image (vision locale via llava-phi3)
with open("image.jpg", "rb") as f:
    resp = requests.post(
        "https://ai.bakeli.tech/v1/chat/file",
        headers={"Authorization": "Bearer <API_KEY>"},
        data={"prompt": "Décris cette image", "model": "llava-phi3"},
        files={"file": ("image.jpg", f, "image/jpeg")},
        timeout=180,
    )
php — Document
$ch = curl_init('https://ai.bakeli.tech/v1/chat/file');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer <API_KEY>'],
    CURLOPT_POSTFIELDS     => [
        'prompt' => 'Résume en 5 points',
        // pas de 'model' → gemini-3-flash-preview par défaut
        'file'   => new CURLFile('/chemin/document.pdf'),
    ],
    CURLOPT_TIMEOUT => 180,
]);
$r = json_decode(curl_exec($ch), true);
API Reference

POST /v1/chat/url

Chat avec un fichier ou une image accessible via URL publique. Le gateway télécharge le fichier et le traite comme /v1/chat/file. Gemini par défaut avec fallback automatique vers Ollama.

POST/v1/chat/url

Content-Type : application/json

ChampTypeDescription
urlstringURL publique du fichier (requis, doit commencer par http:// ou https://)
promptstringQuestion sur le fichier (optionnel, défaut : "Analyse ce fichier et résume son contenu.")
modelstringModèle (optionnel, défaut : gemini-3-flash-preview)
Gemini-3 (gemini-3-flash-preview) est le modèle par défaut — il supporte le multimodal natif via le SDK Google. Le fichier est téléchargé depuis l'URL, sauvegardé temporairement, puis traité comme /v1/chat/file.

Cascade de fallback automatique

Le système essaie 2 modèles Gemini (3 tentatives chacun) avant de basculer vers Ollama :

Cascade complète (7 essais)
Tentative 1-3 :
  gemini-3-flash-preview → gemini-2.5-flash
  ↓ (si tous échouent, attente 120s entre tentatives)

Fallback final :
  Ollama (llama3.2-vision pour images, llama3.2 pour autres fichiers)

Taux de succès : ~99.9% (7 essais au total)
🔄Fallback automatique : Si tous les modèles Gemini échouent (quotas épuisés, clés invalides, erreurs réseau), le système bascule automatiquement vers Ollama. La réponse contiendra "fallback": true et "fallback_reason".

Types de fichiers supportés

TypeExtensionsBackend
Imagesjpg, png, gif, webpGemini-3 → llama3.2-vision
Documentspdf, docx, docGemini-3 → llama3.2
Tableursxlsx, xls, csvGemini-3 → llama3.2
Textetxt, json, sql, codeGemini-3 → llama3.2

Exemples

bash — Image via URL
curl -X POST https://ai.bakeli.tech/v1/chat/url \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/image.jpg",
    "prompt": "Décris cette image en détail"
  }'
bash — PDF via URL
curl -X POST https://ai.bakeli.tech/v1/chat/url \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/document.pdf",
    "prompt": "Résume ce document en 5 points"
  }'
bash — Avec modèle spécifique
curl -X POST https://ai.bakeli.tech/v1/chat/url \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/image.jpg",
    "prompt": "Décris cette image",
    "model": "gemini-2.5-flash"
  }'
python — Image via URL
import requests

resp = requests.post(
    "https://ai.bakeli.tech/v1/chat/url",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
        "url": "https://example.com/image.jpg",
        "prompt": "Décris cette image en détail"
    },
    timeout=180,
)
print(resp.json()["message"]["content"])
python — PDF via URL
import requests

resp = requests.post(
    "https://ai.bakeli.tech/v1/chat/url",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
        "url": "https://example.com/document.pdf",
        "prompt": "Résume ce document en 5 points"
    },
    timeout=180,
)
print(resp.json()["message"]["content"])
php — Image via URL
$ch = curl_init('https://ai.bakeli.tech/v1/chat/url');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer <API_KEY>',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'url'    => 'https://example.com/image.jpg',
        'prompt' => 'Décris cette image',
    ]),
    CURLOPT_TIMEOUT => 180,
]);
$r = json_decode(curl_exec($ch), true);
echo $r['message']['content'];
javascript — Image via URL
const res = await fetch("https://ai.bakeli.tech/v1/chat/url", {
  method: "POST",
  headers: {
    "Content-Type":  "application/json",
    "Authorization": "Bearer <API_KEY>",
  },
  body: JSON.stringify({
    url: "https://example.com/image.jpg",
    prompt: "Décris cette image",
  }),
});
const data = await res.json();
console.log(data.message.content);

Réponse — succès avec Gemini

json — 200
{
  "message": {
    "role": "assistant",
    "content": "Cette image montre..."
  },
  "done": true
}

Réponse — fallback Ollama

Si tous les modèles Gemini échouent, le système bascule automatiquement vers Ollama :

json — 200 (fallback activé)
{
  "model": "llama3.2-vision",
  "message": {
    "role": "assistant",
    "content": "Cette image montre..."
  },
  "done": true,
  "fallback": true,
  "fallback_reason": "Tous les modèles Gemini indisponibles après 3 tentatives"
}
🔄Le champ "fallback": true indique que le fallback a été utilisé. Le champ "fallback_reason" explique pourquoi (quotas épuisés, clés invalides, erreurs réseau, etc.).

Gestion d'erreurs

CodeCauseSolution
400URL manquante ou invalideVérifier que l'URL commence par http:// ou https://
400Fichier vide (0 bytes)Vérifier que l'URL pointe vers un fichier valide
400Erreur de téléchargement (404, timeout)Vérifier que l'URL est accessible publiquement
408Timeout (30s)Le fichier est trop volumineux ou le serveur est lent

Cas d'usage

  • Analyser une image depuis un lien public : Partage d'image sur Slack, Discord, etc.
  • Résumer un PDF hébergé en ligne : Documentation, rapports, articles
  • Extraire des données d'un fichier Excel partagé : Google Sheets, Dropbox, etc.
  • Discuter avec un document sans l'uploader : Fichiers volumineux, liens temporaires
Avantage : Pas besoin d'uploader le fichier depuis le client. Le gateway télécharge le fichier côté serveur, ce qui est plus rapide et plus sécurisé.

Limitations

  • URL publique uniquement : Le fichier doit être accessible sans authentification
  • Timeout de 30 secondes : Pour les fichiers volumineux, préférer /v1/chat/file
  • Taille maximale : Dépend de la configuration du serveur (généralement 50 MB)
API Reference

POST /v1/extract

Extraction de données structurées (JSON) à partir de fichiers. Idéal pour OCR de factures, extraction de tableaux, parsing de documents structurés.

POST/v1/extract

Content-Type : multipart/form-data

ChampTypeDéfautDescription
filefilerequisFichier à analyser (image, PDF, Excel, etc.)
user_promptstringrequisInstructions d'extraction (ex: "Extraire les données de facture en JSON")
system_promptstringInstructions système optionnelles (ex: "Tu es un expert en OCR")
modelstringgemini-3-flash-previewModèle à utiliser
temperaturefloat0.2Créativité (0.0-1.0, plus bas = plus déterministe)
formatstringjsonFormat de sortie : json ou text
Gemini-3 est le modèle par défaut pour l'extraction structurée — il excelle dans la compréhension de documents complexes et la génération de JSON structuré. La température basse (0.2) garantit des résultats cohérents et reproductibles.

Cas d'usage

Cas d'usageExemple de promptType de fichier
OCR de factures"Extraire les données de facture en JSON avec numéro, date, montant, articles"Image, PDF
Extraction de tableaux"Convertir ce tableau en JSON avec colonnes nom, prénom, email"Excel, PDF, Image
Parsing de CV"Extraire nom, compétences, expériences, formation en JSON"PDF, Word, Image
Analyse de reçus"Extraire montant total, date, commerçant, articles en JSON"Image, PDF
Extraction de formulaires"Extraire tous les champs du formulaire en JSON"Image, PDF

Exemples

bash — Extraction de facture (JSON)
curl -X POST https://ai.bakeli.tech/v1/extract \
  -H "Authorization: Bearer <API_KEY>" \
  -F "file=@facture.jpg" \
  -F "user_prompt=Extraire les données de facture en JSON avec les champs: numero_facture, date, montant_total, articles (nom, quantite, prix_unitaire)" \
  -F "system_prompt=Tu es un expert en OCR de factures" \
  -F "temperature=0.2"
bash — Extraction de tableau Excel
curl -X POST https://ai.bakeli.tech/v1/extract \
  -H "Authorization: Bearer <API_KEY>" \
  -F "file=@donnees.xlsx" \
  -F "user_prompt=Convertir ce tableau en JSON avec colonnes: nom, prenom, email, telephone"
python — Extraction de facture
import requests
import json

with open("facture.jpg", "rb") as f:
    resp = requests.post(
        "https://ai.bakeli.tech/v1/extract",
        headers={"Authorization": "Bearer <API_KEY>"},
        data={
            "user_prompt": "Extraire les données de facture en JSON avec: numero_facture, date, montant_total, articles",
            "system_prompt": "Tu es un expert en OCR de factures",
            "temperature": "0.2",
            "format": "json",
        },
        files={"file": f},
        timeout=180,
    )

result = resp.json()
if result.get("data"):
    # JSON parsé automatiquement
    facture = result["data"]
    print(f"Facture n°{facture['numero_facture']}")
    print(f"Montant: {facture['montant_total']}")
else:
    # Fallback sur raw_text si parsing JSON échoue
    print(result["raw_text"])
python — Fonction réutilisable
def extract_structured_data(
    filepath: str,
    user_prompt: str,
    system_prompt: str = "",
    temperature: float = 0.2,
) -> dict:
    """Extrait des données structurées depuis un fichier."""
    with open(filepath, "rb") as f:
        resp = requests.post(
            "https://ai.bakeli.tech/v1/extract",
            headers={"Authorization": f"Bearer {settings.BAKELI_AI_KEY}"},
            data={
                "user_prompt": user_prompt,
                "system_prompt": system_prompt,
                "temperature": str(temperature),
                "format": "json",
            },
            files={"file": f},
            timeout=180,
        )
    resp.raise_for_status()
    result = resp.json()
    return result.get("data") or result.get("raw_text")

# Utilisation
facture = extract_structured_data(
    "facture.jpg",
    "Extraire numero_facture, date, montant_total, articles en JSON",
    "Tu es un expert en OCR de factures",
)
php — Extraction de facture
$ch = curl_init('https://ai.bakeli.tech/v1/extract');

$file = new CURLFile('facture.jpg', 'image/jpeg', 'facture.jpg');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer <API_KEY>'],
    CURLOPT_POSTFIELDS     => [
        'file'          => $file,
        'user_prompt'   => 'Extraire numero_facture, date, montant_total, articles en JSON',
        'system_prompt' => 'Tu es un expert en OCR de factures',
        'temperature'   => '0.2',
        'format'        => 'json',
    ],
    CURLOPT_TIMEOUT => 180,
]);

$response = json_decode(curl_exec($ch), true);

if ($response['data']) {
    // JSON parsé automatiquement
    $facture = $response['data'];
    echo "Facture n°" . $facture['numero_facture'];
} else {
    // Fallback sur raw_text
    echo $response['raw_text'];
}

Réponse — Format JSON

Quand format=json (défaut), le gateway tente de parser le JSON automatiquement :

json — 200 Succès (JSON parsé)
{
  "model": "gemini-3-flash-preview",
  "data": {
    "numero_facture": "FAC-2024-001",
    "date": "2024-03-15",
    "montant_total": 1250.00,
    "articles": [
      { "nom": "Ordinateur portable", "quantite": 1, "prix_unitaire": 1000.00 },
      { "nom": "Souris sans fil", "quantite": 2, "prix_unitaire": 125.00 }
    ]
  },
  "raw_text": "```json\n{...}\n```",
  "done": true
}
json — 200 Parsing JSON échoué (fallback raw_text)
{
  "model": "gemini-3-flash-preview",
  "data": null,
  "raw_text": "Voici les données extraites: ...",
  "done": true,
  "warning": "Réponse non-JSON, voir raw_text"
}
💡Le champ data contient le JSON parsé si possible, sinon null. Le champ raw_text contient toujours la réponse brute du modèle (utile pour debug).

Réponse — Format texte

Quand format=text, la réponse est retournée telle quelle :

json — 200 Format texte
{
  "model": "gemini-3-flash-preview",
  "text": "Le document contient les informations suivantes:\n- Numéro: FAC-2024-001\n- Date: 15/03/2024\n...",
  "done": true
}

Cascade de fallback

Comme pour /v1/chat/file, l'extraction utilise une cascade automatique : Gemini-3 → Gemini-2.5 → Ollama.

json — Fallback Gemini-2.5 (quota épuisé)
{
  "model": "gemini-2.5-flash",
  "data": { /* ... */ },
  "done": true,
  "fallback": true,
  "fallback_reason": "gemini-3-flash-preview quota épuisé"
}
json — Fallback Ollama (tous quotas épuisés)
{
  "model": "llama3.2",
  "data": { /* ... */ },
  "done": true,
  "fallback": true,
  "fallback_reason": "Gemini indisponible"
}

Bonnes pratiques

PratiqueDescription
Prompt précisSpécifie exactement les champs JSON attendus : "Extraire en JSON avec champs: nom, prenom, email"
System promptUtilise system_prompt pour définir le rôle : "Tu es un expert en OCR de factures"
Température basseGarde temperature=0.2 (ou moins) pour des résultats reproductibles
Validation JSONVérifie toujours data != null avant d'utiliser le JSON parsé
Fallback raw_textSi data est null, parse manuellement raw_text
Timeout généreuxUtilise timeout=180 (3 min) pour les gros fichiers
🎯Astuce : Pour des résultats optimaux, fournis un exemple de structure JSON dans ton prompt : "Extraire en JSON format: {\"nom\": \"...\", \"prenom\": \"...\", \"email\": \"...\"}"
API Reference

GET /v1/models

Liste les modèles disponibles — Ollama (local) et Groq (cloud) si la clé est configurée.

bash
curl https://ai.bakeli.tech/v1/models -H "Authorization: Bearer <API_KEY>"

Réponse

json — 200
{
  "models": [
    { "name": "llama3.2:latest", "size": 2019383552 },
    { "name": "mistral:latest",  "size": 4109854720 },
    { "name": "llama-3.3-70b-versatile", "provider": "groq" },
    { "name": "mixtral-8x7b-32768",      "provider": "groq" }
  ]
}
💡Les modèles Groq apparaissent uniquement si GROQ_API_KEY est définie. Ils ont un champ "provider": "groq".
API Reference

GET /v1/health

Healthcheck sans authentification. Utilisé par NPM et les outils de monitoring.

bash
curl https://ai.bakeli.tech/v1/health
json — OK
{ "RedTeam": "ok", "ollama": "ok" }
json — Ollama arrêté
{ "RedTeam": "ok", "ollama": "unreachable" }
Intégration

Django / Python

Module client réutilisable à copier dans chaque app Django de l'infrastructure Bakeli.

1 — Variables d'environnement

.env
BAKELI_AI_URL=https://ai.bakeli.tech
BAKELI_AI_KEY=ta-cle-api
settings.py
BAKELI_AI_URL = env("BAKELI_AI_URL", default="https://ai.bakeli.tech")
BAKELI_AI_KEY = env("BAKELI_AI_KEY")

2 — Module client

python — utils/ai_client.py
import requests
from django.conf import settings

def ask(messages: list, model: str = "openai/gpt-oss-20b") -> str:
    """Envoie une conversation, retourne le texte de réponse."""
    resp = requests.post(
        f"{settings.BAKELI_AI_URL}/v1/chat",
        headers={"Authorization": f"Bearer {settings.BAKELI_AI_KEY}"},
        json={"model": model, "messages": messages},
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json()["message"]["content"]

def ask_with_file(prompt: str, filepath: str, model: str = "openai/gpt-oss-20b") -> str:
    """Envoie un fichier avec un prompt."""
    with open(filepath, "rb") as f:
        resp = requests.post(
            f"{settings.BAKELI_AI_URL}/v1/chat/file",
            headers={"Authorization": f"Bearer {settings.BAKELI_AI_KEY}"},
            data={"prompt": prompt, "model": model},
            files={"file": f},
            timeout=180,
        )
    return resp.json()["message"]["content"]

3 — Utilisation dans une view

python — views.py
from utils.ai_client import ask

def assistant(request):
    historique = request.session.get("chat_history", [])
    historique.append({"role": "user", "content": request.POST.get("message")})
    reponse = ask(historique)
    historique.append({"role": "assistant", "content": reponse})
    request.session["chat_history"] = historique
    return JsonResponse({"reponse": reponse})
Intégration

Laravel / PHP

Service injectable pour une intégration élégante dans l'architecture Laravel.

1 — Config

.env
BAKELI_AI_URL=https://ai.bakeli.tech
BAKELI_AI_KEY=ta-cle-api
config/ai.php
<?php
return [
    'url'     => env('BAKELI_AI_URL', 'https://ai.bakeli.tech'),
    'key'     => env('BAKELI_AI_KEY'),
    'model'   => env('BAKELI_AI_MODEL', 'openai/gpt-oss-20b'),
    'timeout' => 120,
];

2 — AiService

php — app/Services/AiService.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;

class AiService
{
    private string $url, $key, $model;

    public function __construct() {
        $this->url   = config('ai.url');
        $this->key   = config('ai.key');
        $this->model = config('ai.model');
    }

    public function ask(array $messages, string $model = null): string
    {
        return Http::withToken($this->key)
            ->timeout(config('ai.timeout'))
            ->post($this->url . '/v1/chat', [
                'model'    => $model ?? $this->model,
                'messages' => $messages,
            ])->throw()->json('message.content');
    }

    public function askWithFile(string $prompt, string $path, string $model = null): string
    {
        return Http::withToken($this->key)
            ->timeout(180)
            ->attach('file', file_get_contents($path), basename($path))
            ->post($this->url . '/v1/chat/file', [
                'prompt' => $prompt,
                'model'  => $model ?? $this->model,
            ])->throw()->json('message.content');
    }
}

3 — Controller

php — AssistantController.php
class AssistantController extends Controller
{
    public function __construct(private AiService $ai) {}

    public function chat(Request $request)
    {
        $history   = session('chat_history', []);
        $history[] = ['role' => 'user', 'content' => $request->input('message')];
        $reply     = $this->ai->ask($history);
        $history[] = ['role' => 'assistant', 'content' => $reply];
        session(['chat_history' => $history]);
        return response()->json(['reply' => $reply]);
    }
}
Intégration

React / Next.js

API Route sécurisée côté serveur + hook React réutilisable côté client.

⚠️Ne jamais appeler ai.bakeli.tech directement depuis le browser. Utilise une API Route Next.js comme proxy.

1 — Variables d'environnement

.env.local
# Côté serveur uniquement (sans NEXT_PUBLIC_)
BAKELI_AI_URL=https://ai.bakeli.tech
BAKELI_AI_KEY=ta-cle-api

2 — API Route (proxy sécurisé)

typescript — app/api/ai/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
  const body = await req.json();
  const resp = await fetch(`${process.env.BAKELI_AI_URL}/v1/chat`, {
    method: 'POST',
    headers: {
      'Content-Type':  'application/json',
      'Authorization': `Bearer ${process.env.BAKELI_AI_KEY}`,
    },
    body: JSON.stringify(body),
  });
  return NextResponse.json(await resp.json());
}

3 — Hook React

typescript — hooks/useAI.ts
import { useState, useCallback } from 'react';

type Message = { role: 'user' | 'assistant'; content: string };

export function useAI(model = 'openai/gpt-oss-20b') {
  const [loading, setLoading]   = useState(false);
  const [messages, setMessages] = useState<Message[]>([]);

  const ask = useCallback(async (userMessage: string) => {
    const updated = [...messages, { role: 'user' as const, content: userMessage }];
    setMessages(updated);
    setLoading(true);
    try {
      const res  = await fetch('/api/ai', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ model, messages: updated }),
      });
      const data  = await res.json();
      const reply = data.message.content;
      setMessages(prev => [...prev, { role: 'assistant', content: reply }]);
      return reply;
    } finally { setLoading(false); }
  }, [messages, model]);

  return { ask, loading, messages, reset: () => setMessages([]) };
}

4 — Composant Chat

tsx — ChatWidget.tsx
import { useState } from 'react';
import { useAI } from '@/hooks/useAI';

export default function ChatWidget() {
  const { ask, loading, messages } = useAI();
  const [input, setInput] = useState('');

  return (
    <div>
      {messages.map((m, i) => <div key={i}>{m.content}</div>)}
      <input value={input} onChange={e => setInput(e.target.value)} />
      <button onClick={() => { ask(input); setInput(''); }} disabled={loading}>
        {loading ? '...' : 'Envoyer'}
      </button>
    </div>
  );
}
Intégration

Migration depuis Gemini

Remplace l'API Gemini native par le gateway Bakeli AI. Le format messages est identique, et Gemini est maintenant intégré comme backend du gateway.

Bonne nouvelle : Gemini est maintenant intégré au gateway ! Tu peux continuer à utiliser Gemini via ai.bakeli.tech, avec en bonus le fallback automatique vers Ollama si Gemini est down.

Avant → Après

python — AVANT (Gemini natif)
import google.generativeai as genai
genai.configure(api_key="AIza...")
model = genai.GenerativeModel("gemini-pro")
text = model.generate_content("Explique Docker").text
python — APRÈS (Gateway Bakeli AI avec Gemini)
from utils.ai_client import ask

# Option 1 : Utiliser Groq par défaut (+ fallback Gemini automatique)
text = ask([{"role": "user", "content": "Explique Docker"}])

# Option 2 : Forcer Gemini directement
text = ask([{"role": "user", "content": "Explique Docker"}], model="gemini-3-flash-preview")

Avantages du gateway vs Gemini natif

FonctionnalitéGemini natifGateway Bakeli AI
Accès à Gemini✅ Direct✅ Via gateway (même API)
Fallback automatique❌ Aucun✅ Gemini → Ollama
Multi-backends❌ Gemini uniquement✅ Groq + Gemini + Ollama
Clé API unique❌ Clé Gemini✅ Une clé pour tout
Hébergement local❌ Cloud Google✅ Ollama en fallback
CoûtQuota gratuit limitéGratuit (Ollama) + quota Gemini

Checklist de migration

#Action
1Ajouter BAKELI_AI_URL et BAKELI_AI_KEY dans le .env
2Ajouter les variables dans settings.py / config/ai.php
3Copier le module client dans le projet
4Remplacer les appels genai.* par le module client
5(Optionnel) Garder GEMINI_API_KEY dans le .env du serveur pour activer Gemini côté gateway
6Supprimer google-generativeai de requirements.txt
💡Si tu veux continuer à utiliser Gemini comme backend principal, passe model="gemini-3-flash-preview" dans tes appels. Le gateway routera vers Gemini avec cascade automatique (gemini-3 → gemini-2.5 → Ollama).
Référence

Modèles disponibles

Modèles LLM installés sur le serveur Ollama. GET /v1/models pour la liste en temps réel.

Modèles Ollama (local)

IdentifiantTailleRAM minUsage
llama3.22.0 GB4 GBUsage général, rapide ⚡
mistral4.1 GB8 GBRaisonnement, texte long
gemma3:4b3.3 GB6 GBMultilingue, proche Gemini
llava-phi32.9 GB6 GBVision — analyse d'images 🖼️
phi32.3 GB4 GBCode, logique
💡llama3.2 est le modèle de fallback automatique quand Groq est indisponible. Le modèle principal par défaut est openai/gpt-oss-20b (Groq). Pour les modèles Groq cloud, voir la page Groq.
Référence

Groq — Modèles cloud

Modèles hébergés sur Groq Cloud, accessibles via le même endpoint /v1/chat. Le routage est automatique selon le préfixe du modèle.

⚠️Nécessite que GROQ_API_KEY soit définie dans les variables d'environnement du serveur.

Configuration

.env
GROQ_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxx
GROQ_BASE_URL=https://api.groq.com/openai/v1  # optionnel

Préfixes routés vers Groq

PréfixeExemple
openai/openai/gpt-oss-20b ⭐ défaut
llama-llama-3.3-70b-versatile
mixtral-mixtral-8x7b-32768
gemma-gemma-7b-it
qwen-qwen-qwq-32b
deepseek-deepseek-r1-distill-llama-70b
whisper-whisper-large-v3

Modèles Groq recommandés

IdentifiantContexteUsage
openai/gpt-oss-20b⭐ Modèle par défaut du gateway
llama-3.3-70b-versatile128kUsage général, haute qualité
llama-3.1-8b-instant128kRapide, faible latence ⚡
mixtral-8x7b-3276832kRaisonnement, texte long
gemma-7b-it8kLéger, multilingue
deepseek-r1-distill-llama-70b128kRaisonnement avancé 🧠
qwen-qwq-32b128kRaisonnement, code

Exemple d'appel

bash
curl -X POST https://ai.bakeli.tech/v1/chat \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [{"role": "user", "content": "Explique les LLM en 3 lignes."}]
  }'
La réponse a le même format que pour Ollama : {"model": "...", "message": {"role": "assistant", "content": "..."}, "done": true}
🔄En cas d'indisponibilité de Groq, le gateway bascule automatiquement sur llama3.2 via Ollama. La réponse contiendra "fallback": true.

Erreurs spécifiques Groq

CodeCauseSolution
503GROQ_API_KEY non configurée → fallback OllamaDéfinir la variable d'environnement
401Clé Groq invalide ou expirée → fallback OllamaVérifier la clé sur console.groq.com
429Rate limit Groq dépassé → fallback OllamaRéduire la fréquence ou changer de modèle
Référence

Gemini — Google AI

Modèles Gemini de Google, accessibles via une API OpenAI-compatible. Gemini est le backend par défaut pour /v1/chat/file (meilleur pour vision + documents) et sert de fallback secondaire après Groq pour /v1/chat.

Gemini excelle sur la vision (images), les documents longs (PDF, Excel, Word) et le raisonnement multimodal. Il est automatiquement utilisé pour tous les fichiers.

Configuration

.env
GEMINI_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai/
GEMINI_MODEL=gemini-3-flash-preview  # chat classique
GEMINI_FILE_MODEL=gemini-3-flash-preview  # défaut pour /chat/file
GEMINI_FALLBACK_MODEL=gemini-2.5-flash  # fallback si quota épuisé
🔑Obtenir une clé API gratuite sur Google AI Studio. Le quota gratuit est généreux : 1500 requêtes/jour pour Gemini Flash.

API OpenAI-compatible

Le gateway utilise deux APIs de Gemini selon le contexte :

  • Chat texte (/v1/chat) : API OpenAI-compatible (v1beta/openai/) pour la compatibilité avec le client OpenAI Python/JS
  • Fichiers (/v1/chat/file) : SDK natif Google (google-genai) pour le multimodal natif (images, PDF, Excel, Word, CSV)
python — Chat texte (API OpenAI-compatible)
from openai import OpenAI

client = OpenAI(
    api_key="AIzaSy...",
    base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
)
response = client.chat.completions.create(
    model="gemini-3-flash-preview",
    messages=[{"role": "user", "content": "Bonjour !"}]
)
print(response.choices[0].message.content)
python — Fichiers (SDK natif Google)
from google import genai

client = genai.Client(api_key="AIzaSy...")
with open("document.pdf", "rb") as f:
    file_part = genai.types.Part.from_bytes(
        data=f.read(),
        mime_type="application/pdf"
    )
    response = client.models.generate_content(
        model="gemini-3-flash-preview",
        contents=["Résume ce document", file_part]
    )
print(response.text)

Modèles Gemini disponibles

IdentifiantContexteCapacitésUsage
gemini-3-flash-preview1M tokensTexte, Vision, Audio⭐ Défaut — rapide, multimodal, dernière génération
gemini-2.5-flash1M tokensTexte, Vision, Audio🔄 Fallback — si quota gemini-3 épuisé
💡Les deux modèles supportent nativement le multimodal via le SDK Google : images, PDF, Excel, Word, CSV sont envoyés directement sans extraction de texte. Gemini lit les fichiers nativement. La cascade automatique : gemini-3-flash-previewgemini-2.5-flashOllama (avec extraction pour llava-phi3 images, llama3.2 texte).

Quand Gemini est utilisé

EndpointConditionComportement
/v1/chat/file Toujours (par défaut) Gemini traite tous les fichiers en multimodal natif via le SDK Google (pas d'extraction). Cascade : gemini-3-flash-previewgemini-2.5-flashOllama (avec extraction : llava-phi3 pour images, llama3.2 pour documents)
/v1/chat Si Groq rate limit (429) Cascade : Groq → gemini-3-flash-previewgemini-2.5-flashOllama (llama3.2). La réponse contient "fallback": true
/v1/chat Si Groq down Cascade : Groq → gemini-3-flash-previewgemini-2.5-flashOllama
/v1/chat Si model=gemini-* Appel direct vers Gemini avec cascade interne si quota épuisé

Exemples d'utilisation

Chat classique (fallback automatique)

bash — Groq rate limit → Gemini prend le relais
curl -X POST https://ai.bakeli.tech/v1/chat \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [{"role": "user", "content": "Explique Docker"}]
  }'

# Si Groq est en rate limit, la réponse sera :
{
  "model": "gemini-3-flash-preview",
  "message": { "role": "assistant", "content": "Docker est..." },
  "done": true,
  "fallback": true,
  "fallback_reason": "Groq rate limit"
}

# Si gemini-3-flash-preview est aussi épuisé :
{
  "model": "gemini-2.5-flash",
  "message": { "role": "assistant", "content": "Docker est..." },
  "done": true,
  "fallback": true,
  "fallback_reason": "Groq rate limit → gemini-3-flash-preview quota épuisé"
}

Forcer Gemini directement

python — Appel direct vers Gemini
import requests

resp = requests.post(
    "https://ai.bakeli.tech/v1/chat",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
        "model": "gemini-3-flash-preview",  # ← force Gemini
        "messages": [
            {"role": "user", "content": "Explique les LLM en 3 lignes"}
        ]
    },
    timeout=60
)
print(resp.json()["message"]["content"])

# Si gemini-3-flash-preview est épuisé, bascule automatiquement sur gemini-2.5-flash

Fichiers (Gemini par défaut)

bash — Image (Gemini vision)
curl -X POST https://ai.bakeli.tech/v1/chat/file \
  -H "Authorization: Bearer <API_KEY>" \
  -F "prompt=Décris cette image en détail" \
  -F "file=@image.jpg"
# Pas besoin de spécifier model= → Gemini par défaut
python — PDF (Gemini + extraction texte)
import requests

with open("rapport.pdf", "rb") as f:
    resp = requests.post(
        "https://ai.bakeli.tech/v1/chat/file",
        headers={"Authorization": "Bearer <API_KEY>"},
        data={"prompt": "Résume ce document en 5 points"},
        files={"file": f},
        timeout=180
    )
print(resp.json()["message"]["content"])

Avantages de Gemini

CapacitéDescription
🖼️ Vision nativeAnalyse d'images en multimodal natif via SDK Google
📄 Multimodal natifLit PDF, Excel, Word, CSV directement sans extraction de texte
📊 Documents longsContexte jusqu'à 1M tokens
🌍 MultilingueExcellent sur le français, l'arabe, le wolof
RapideGemini 3 Flash : latence ultra-faible
💰 Quota gratuit1500 requêtes/jour gratuites
🔄 Fallback intelligentCascade interne : gemini-3 → gemini-2.5 → Ollama

Erreurs spécifiques Gemini

CodeCauseSolution
503GEMINI_API_KEY non configurée → fallback OllamaDéfinir la variable d'environnement
401Clé Gemini invalide → fallback OllamaVérifier la clé sur AI Studio
429Quota Gemini dépassé → fallback OllamaAttendre le reset quotidien ou upgrader vers API payante
400Fichier trop volumineux (>20 MB)Compresser l'image ou découper le PDF
🔄En cas d'erreur Gemini ou quota épuisé, le gateway bascule automatiquement : gemini-3-flash-previewgemini-2.5-flashOllama (llava-phi3 pour images, llama3.2 pour texte). La réponse contiendra "fallback": true.

Cascade complète de fallback

Flux
# /v1/chat (texte)
Groq (openai/gpt-oss-20b) → Gemini-3 → Gemini-2.5 → Ollama (llama3.2)

# /v1/chat/file (fichiers)
Gemini-3 (gemini-3-flash-preview) → Gemini-2.5 (gemini-2.5-flash) → Ollama
  ├─ Images : llava-phi3
  └─ Documents : llama3.2
Référence

Codes d'erreur

Erreurs possibles, leurs causes et solutions.

CodeCauseSolution
400JSON mal forméVérifier le body
401Clé API invalideVérifier Authorization: Bearer ...
503Tous les backends down (Groq + Gemini + Ollama)Vérifier la config et docker start ollama
502ai-RedTeam arrêtédocker-compose up -d ai-RedTeam
504TimeoutUtiliser llama-3.1-8b-instant (Groq) ou llama3.2 (Ollama)
429Rate limit Groq → fallback Gemini automatiqueNormal, Gemini prend le relais

Cascade de fallback

Le gateway utilise une cascade intelligente pour garantir la disponibilité :

Flux de fallback
# Chat texte (/v1/chat)
1. Groq (openai/gpt-oss-20b) — défaut
2. Gemini-3 (gemini-3-flash-preview) — si Groq 429 ou down
3. Gemini-2.5 (gemini-2.5-flash) — si Gemini-3 429 ou down
4. Ollama (llama3.2) — si tous les quotas épuisés

# Fichiers (/v1/chat/file)
1. Gemini-3 (gemini-3-flash-preview) — défaut
2. Gemini-2.5 (gemini-2.5-flash) — si Gemini-3 429 ou down
3. Ollama — si tous les quotas épuisés
   ├─ llava-phi3 (images)
   └─ llama3.2 (documents)
Quand un fallback est utilisé, la réponse contient "fallback": true et "fallback_reason": "..." pour tracer le backend utilisé.

Diagnostic

bash
curl https://ai.bakeli.tech/v1/health
docker logs -f ai-RedTeam
docker ps | grep -E "ollama|ai-RedTeam"