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
Tes apps → https://ai.bakeli.tech → NPM → ai-RedTeam:8000 → Ollama → LLM
Endpoints
| Méthode | Endpoint | Description | Auth |
|---|---|---|---|
POST | /v1/chat | Chat texte + streaming | ✅ |
POST | /v1/chat/file | Chat + fichier joint | ✅ |
GET | /v1/models | Liste des modèles | ✅ |
GET | /v1/health | Healthcheck | — |
Démarrage rapide
Ton premier appel en moins de 2 minutes.
API_KEY à l'équipe infra.curl -X POST https://ai.bakeli.tech/v1/chat \ -H "Authorization: Bearer <ta-cle-api>" \ -H "Content-Type: application/json" \ -d '{"model":"llama3.2","messages":[{"role":"user","content":"Bonjour !"}]}'
Réponse attendue
{ "model": "llama3.2", "message": { "role": "assistant", "content": "Bonjour !" }, "done": true }messages pour maintenir le contexte entre les tours.Authentification
Tous les endpoints sauf /v1/health requièrent une clé API.
Header requis
Authorization: Bearer <API_KEY>{ "error": "Unauthorized" }Stocker la clé
BAKELI_AI_URL=https://ai.bakeli.tech BAKELI_AI_KEY=ta-cle-api-secrete
POST /v1/chat
Chat texte avec historique. Supporte le streaming ndjson.
| Champ | Type | Défaut | Description |
|---|---|---|---|
model | string | llama3.2 | Modèle à utiliser |
messages | array | requis | Tableau {role, content} |
stream | boolean | false | Active le streaming |
Exemples
curl -X POST https://ai.bakeli.tech/v1/chat \ -H "Authorization: Bearer <API_KEY>" \ -H "Content-Type: application/json" \ -d '{ "model": "llama3.2", "messages": [ {"role": "system", "content": "Tu es un assistant Bakeli SI."}, {"role": "user", "content": "Explique Docker en 3 lignes."} ] }'
import requests resp = requests.post( "https://ai.bakeli.tech/v1/chat", headers={"Authorization": "Bearer <API_KEY>"}, json={ "model": "llama3.2", "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"])
$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' => 'llama3.2', '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'];
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: "llama3.2", 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);
Réponse
{
"model": "llama3.2",
"message": { "role": "assistant", "content": "Docker est..." },
"done": true
}POST /v1/chat/file
Envoie un fichier (PDF, image, texte) avec un prompt. Le modèle analyse le contenu.
Content-Type : multipart/form-data
| Champ | Type | Description |
|---|---|---|
prompt | string | Question sur le fichier |
model | string | Modèle (défaut: llama3.2) |
file | file | Fichier à analyser |
| Type MIME | Traitement | Modèle recommandé |
|---|---|---|
image/* | Base64 → champ images | llava, gemma3:4b |
application/pdf | Texte extrait → contexte (8k) | llama3.2, mistral |
text/* | Injection directe | Tous |
Exemples
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 "model=llama3.2" \ -F "file=@/chemin/document.pdf"
with open("document.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", "model": "llama3.2"}, files={"file": f}, timeout=180, )
$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', 'model' => 'llama3.2', 'file' => new CURLFile('/chemin/document.pdf'), ], CURLOPT_TIMEOUT => 180, ]); $r = json_decode(curl_exec($ch), true);
GET /v1/models
Liste les modèles installés sur l'instance Ollama.
curl https://ai.bakeli.tech/v1/models -H "Authorization: Bearer <API_KEY>"
Réponse
{ "models": [{ "name": "llama3.2:latest", "size": 2019383552 }, ...] }GET /v1/health
Healthcheck sans authentification. Utilisé par NPM et les outils de monitoring.
curl https://ai.bakeli.tech/v1/health{ "RedTeam": "ok", "ollama": "ok" }{ "RedTeam": "ok", "ollama": "unreachable" }Django / Python
Module client réutilisable à copier dans chaque app Django de l'infrastructure Bakeli.
1 — Variables d'environnement
BAKELI_AI_URL=https://ai.bakeli.tech BAKELI_AI_KEY=ta-cle-api
BAKELI_AI_URL = env("BAKELI_AI_URL", default="https://ai.bakeli.tech") BAKELI_AI_KEY = env("BAKELI_AI_KEY")
2 — Module client
import requests from django.conf import settings def ask(messages: list, model: str = "llama3.2") -> 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 = "llama3.2") -> 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
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})
Laravel / PHP
Service injectable pour une intégration élégante dans l'architecture Laravel.
1 — Config
BAKELI_AI_URL=https://ai.bakeli.tech BAKELI_AI_KEY=ta-cle-api
<?php return [ 'url' => env('BAKELI_AI_URL', 'https://ai.bakeli.tech'), 'key' => env('BAKELI_AI_KEY'), 'model' => env('BAKELI_AI_MODEL', 'llama3.2'), 'timeout' => 120, ];
2 — AiService
<?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
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]); } }
React / Next.js
API Route sécurisée côté serveur + hook React réutilisable côté client.
ai.bakeli.tech directement depuis le browser. Utilise une API Route Next.js comme proxy.1 — Variables d'environnement
# Côté serveur uniquement (sans NEXT_PUBLIC_)
BAKELI_AI_URL=https://ai.bakeli.tech
BAKELI_AI_KEY=ta-cle-api2 — API Route (proxy sécurisé)
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
import { useState, useCallback } from 'react'; type Message = { role: 'user' | 'assistant'; content: string }; export function useAI(model = 'llama3.2') { 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
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> ); }
Migration depuis Gemini
Remplace l'API Gemini par l'IA locale Bakeli. Le format messages est identique.
Avant → Après
import google.generativeai as genai genai.configure(api_key="AIza...") model = genai.GenerativeModel("gemini-pro") text = model.generate_content("Explique Docker").text
from utils.ai_client import ask text = ask([{"role": "user", "content": "Explique Docker"}])
Checklist
| # | Action |
|---|---|
| 1 | Ajouter BAKELI_AI_URL et BAKELI_AI_KEY dans le .env |
| 2 | Ajouter les variables dans settings.py / config/ai.php |
| 3 | Copier le module client dans le projet |
| 4 | Remplacer les appels genai.* par le module client |
| 5 | Supprimer google-generativeai de requirements.txt |
Modèles disponibles
Modèles LLM installés sur le serveur. GET /v1/models pour la liste en temps réel.
| Identifiant | Taille | RAM min | Usage |
|---|---|---|---|
llama3.2 | 2.0 GB | 4 GB | Usage général, rapide ⚡ |
mistral | 4.1 GB | 8 GB | Raisonnement, texte long |
gemma3:4b | 3.3 GB | 6 GB | Multilingue, proche Gemini |
llava | 4.7 GB | 8 GB | Vision — analyse d'images 🖼️ |
phi3 | 2.3 GB | 4 GB | Code, logique |
llama3.2 — il couvre 90% des besoins.Codes d'erreur
Erreurs possibles, leurs causes et solutions.
| Code | Cause | Solution |
|---|---|---|
| 400 | JSON mal formé | Vérifier le body |
| 401 | Clé API invalide | Vérifier Authorization: Bearer ... |
| 503 | Ollama arrêté | docker start ollama |
| 502 | ai-RedTeam arrêté | docker-compose up -d ai-RedTeam |
| 504 | Timeout | Utiliser llama3.2 |
Diagnostic
curl https://ai.bakeli.tech/v1/health docker logs -f ai-RedTeam docker ps | grep -E "ollama|ai-RedTeam"