---
name: albert-api
description: >-
  Albert API — API d'inférence et d'IA générative interministérielle de l'État
  (OpenGateLLM, hébergement SecNumCloud). Compatible OpenAI : chat completions
  et embeddings. Plus rerank, transcription audio, OCR de PDF et d'images, et un
  pipeline RAG souverain complet (collections, documents, recherche sémantique/
  hybride/lexicale). À utiliser pour appeler Albert, intégrer un LLM souverain
  dans un produit ou un script de l'administration, faire du RAG sur corpus
  interne, transcrire de l'audio ou extraire du texte de PDF/images via Albert,
  ou gérer ses clés et quotas Albert. Déclencheurs : Albert, Albert API,
  OpenGateLLM, LLM de l'État, IA générative souveraine, base_url Albert.
---

# Albert API — Référence consolidée

API d'inférence de l'État français, opérée par le département IAE de la DINUM (commun numérique **OpenGateLLM**). **Partiellement compatible OpenAI** : pour `chat/completions` et `embeddings`, réutiliser le SDK `openai` (ou tout client OpenAI) en changeant simplement `base_url` et la clé. Les autres surfaces — `rerank`, RAG, audio, OCR — n'existent pas dans le SDK OpenAI et s'appellent en HTTP direct.

**Base URL :** `https://albert.api.etalab.gouv.fr`
**Spec OpenAPI (fait foi) :** `https://albert.api.etalab.gouv.fr/openapi.json`
**Doc humaine :** https://doc.incubateur.net/alliance/albert-api

---

## Comment utiliser cette skill

- **Consommation d'abord.** L'usage courant = appeler les modèles (chat, embeddings) et le RAG. Les écritures (créer collection, uploader documents, gérer des clés) ne se font que sur intention explicite de l'utilisateur.
- **Compatibilité OpenAI, mais partielle.** Pointer un client OpenAI sur `https://albert.api.etalab.gouv.fr/v1` évite de réécrire du code pour `chat/completions` et `embeddings`, dont les schémas de requête/réponse suivent OpenAI (Albert ajoute des paramètres : `search`, `search_args`). **Le SDK s'arrête là** : `rerank`, `audio/transcriptions`, `ocr` et le RAG n'ont aucune méthode côté SDK — ne jamais générer de `client.rerank(...)`, `client.ocr(...)` ou `client.search(...)`, ces appels échouent avant même de partir. Les faire en HTTP direct (`requests`, `httpx`, `curl`).
- **Ne jamais logger ni afficher le token.** Le Bearer token est un secret. Ne pas l'écho dans les réponses, les logs, les exemples copiés. Le lire depuis une variable d'environnement (`ALBERT_API_KEY`).
- **L'OpenAPI fait autorité.** En cas de divergence entre ce fichier et la spec récupérée en live, **suivre la spec**. Son champ `info.version` change sans préavis (`v0.4.7`, puis `latest`, puis `0.6.0post1` à ce jour) : c'est le contenu qui fait foi, pas un semver — `bin/check_drift.py` détecte les écarts d'endpoints et d'enums.
- **Ne pas inventer d'ID de modèle, et ne jamais coder un alias.** Lister les modèles via `GET /v1/models` et utiliser le champ **`id`**, jamais une valeur du tableau `aliases` (cf. § Modèles).

---

## Authentification & accès

- **Header :** `Authorization: Bearer <token>` sur tous les endpoints `/v1/*`.
- **Public :** réservé aux agents de la fonction publique. Demande d'accès en ligne (voir la doc humaine). L'accès distingue un mode **expérimentation** (quotas réduits) d'un mode **production** (sur dossier).
- **Connaître ses droits :** `GET /v1/me` renvoie l'identité, les quotas et limites du compte. `GET /v1/usage` renvoie la consommation.
- **Gérer ses clés :** `GET/POST /v1/keys`, `DELETE /v1/keys/{key_id}`. Créer une clé dédiée par usage/produit plutôt que réutiliser la clé personnelle.

> ⚠️ **Créer une clé : piège vérifié en live (2026-10-16) sur la route canonique `POST /v1/keys`.**
> 1. `POST /v1/keys` (corps `{"name": "...", "expires": <ts>}`) renvoie **201** avec une réponse dont le champ exploitable porte le nom **`value`** (champs renvoyés : `created`, `expires`, `id`, `name`, `object`, `user_id`, `value`). La spec 0.8.0 (`KeyResponse`) déclare ce champ `value` correctement — contrairement au constat du 08/09 où elle annonçait `token`/`key`. Lire `resp["token"]` lève toujours un `KeyError` : c'est `resp["value"]` qu'il faut utiliser.
> 2. Le champ `value` d'un élément du listing `GET /v1/keys` est **masqué** : tout appel avec renvoie `401 {"detail":"Invalid API key."}` (vérifié sur `GET /v1/models`, code HTTP 200 avec la clé fraîchement créée, 401 avec celle du listing). **La valeur exploitable n'existe qu'à la création** — pas récupérable ensuite. La perdre = clé orpheline, à supprimer et recréer.
>
> Corollaire : **pas de renommage possible**. `/v1/keys/{key_id}` n'expose que `get` et `delete`, il n'y a pas de `PATCH` (confirmé dans la spec 0.8.0) — renommer = créer + réinstaller la nouvelle valeur partout + supprimer l'ancienne.
>
> Diagnostiquer une clé suspecte avec **`GET /v1/models`** ou `GET /v1/me` : pour une clé invalide (`"invalid-test-key"`, test 2026-10-16) les deux renvoient **401** — pas de distinction `/v1/models` vs `/v1/me` à exploiter ; un `403` signe alors un quota/compte plutôt qu'une clé invalide.

- **Erreurs :** `401` = token absent/invalide ; `403` = droits ou quota insuffisants ; `404` = ressource/ID inexistant ; `429` = rate limit. Sur 401/403, distinguer *token manquant* de *quota dépassé* avant de réessayer.

---

## Choisir un endpoint (intention → surface)

| Objectif | Endpoint |
|----------|----------|
| Générer du texte, chat, RAG-in-one-call, tool calling | `POST /v1/chat/completions` |
| Vectoriser du texte (embeddings) | `POST /v1/embeddings` |
| Reclasser des documents par pertinence | `POST /v1/rerank` |
| Transcrire de l'audio | `POST /v1/audio/transcriptions` |
| Extraire le texte d'un PDF/image (OCR) | `POST /v1/ocr` |
| Indexer un corpus puis chercher dedans (RAG) | `/v1/collections` → `/v1/documents` → `POST /v1/search` |
| Lister les modèles et leurs types | `GET /v1/models` |
| Quotas, usage, clés | `/v1/me`, `/v1/usage`, `/v1/keys` |
| Santé du service | `GET /health`, `GET /health/models` |

---

## Modèles

Lister en live : `GET /v1/models` (champ `data[]`). `GET /v1/models/{model}` pour le détail d'un modèle.

### `id` ou alias ? Toujours l'`id`

Chaque entrée du catalogue expose **deux** façons de désigner le modèle, et elles n'ont pas le même statut. La spec OpenAPI les décrit ainsi :

| Champ | Description dans la spec | Statut |
|-------|--------------------------|--------|
| `id` | « The model identifier, which can be **referenced in the API endpoints** » | **Identifiant contractuel**, c'est celui à coder |
| `aliases` | « Aliases of the model. It will be used to **identify the model by users** » | Commodité d'affichage, **non contractuelle** |

> ⚠️ **Coder un alias est un bug différé.** Les alias ont déjà churné sans préavis : en juillet 2026, `deepseek-ai/DeepSeek-V4-Flash` a été re-versionné en `deepseek-ai/DeepSeek-V4-Flash-0731` et `Qwen/Qwen3.6-27B` a disparu du catalogue. Toutes les intégrations qui les avaient codés en dur sont tombées en `404 {"detail":"Model not found."}`. Les `id` canoniques, eux, n'ont pas bougé.

Les `id` canoniques sont en **minuscules avec des tirets** (`deepseek-v4-flash`), alors que les alias reprennent souvent la nomenclature HuggingFace (`deepseek-ai/DeepSeek-V4-Flash-0731`). Certains modèles portent aussi un alias de **rôle** stable (`openweight-code`, `openweight-small`…), pratique mais réservé aux modèles ouverts : les modèles propriétaires n'en ont pas, donc ce schéma ne peut pas servir de politique uniforme.

```bash
# Le bon réflexe : récupérer les id, filtrés par type
curl -s https://albert.api.etalab.gouv.fr/v1/models \
  -H "Authorization: Bearer $ALBERT_API_KEY" \
  | jq -r '.data[] | select(.type=="text-generation") | .id'
```

### Instantané du catalogue

Relevé le **2026-08-26**, revérifié le **2026-09-03** (conforme au catalogue live), **à revérifier via `GET /v1/models`** : le catalogue évolue et cette table sera périmée.

| `type` | Usage | `id` canoniques |
|--------|-------|-----------------|
| `text-generation` | Chat / complétion | `deepseek-v4-flash`, `mistral-medium-2508`, `qwen3-coder-30b-A3b-instruct`, `openai/gpt-oss-120b` |
| `image-text-to-text` | Vision (image + texte) | `mistral-medium-3-5-128b`, `gemma-4-31b-it`, `mistral-small-3-2-24b-instruct-2506`, `ministral-3-8b-instruct-2512`, `lightonocr-2-1b` |
| `text-embeddings-inference` | Embeddings | `bge-m3`, `qwen3-vl-embedding-8b`, `mistral-embed-2312` |
| `text-classification` | Rerank | `bge-reranker-v2-m3` |
| `automatic-speech-recognition` | Transcription audio | `whisper-large-v3` |
| `image-to-text` | OCR | `mistral-ocr-2512` |

### Vérifier qu'un modèle répond avant de s'appuyer dessus

`GET /health/models` (authentifié) renvoie un statut par modèle :

```bash
curl -s https://albert.api.etalab.gouv.fr/health/models \
  -H "Authorization: Bearer $ALBERT_API_KEY" | jq '.data[] | select(.status!="green")'
```

Un modèle présent dans `GET /v1/models` mais `red` dans `/health/models` répond **`502 Bad Gateway`**, pas `404`. Distinguer les deux évite de chercher une erreur de nommage là où il n'y a qu'une panne amont : `404` = identifiant inconnu (vérifier l'`id`), `502` = modèle indisponible (attendre ou basculer).

---

## Endpoints

### Inférence — compatibles OpenAI

| Méthode | Chemin | Notes |
|---------|--------|-------|
| POST | `/v1/chat/completions` | `messages*`, `model*` ; options : `temperature`, `top_p`, `max_completion_tokens`, `stream`, `stop`, `n`, `seed`, `response_format`, `tools`, `tool_choice`. **Spécifique Albert :** `search: true` + `search_args` pour activer le RAG dans l'appel. |
| POST | `/v1/embeddings` | `input*` (string ou array), `model*` ; `dimensions`, `encoding_format` (`float`\|`base64`). |
| GET | `/v1/models` · `/v1/models/{model}` | Catalogue des modèles. |

### Reranking — HTTP direct (hors SDK OpenAI)

| Méthode | Chemin | Corps |
|---------|--------|-------|
| POST | `/v1/rerank` | `query*`, `documents*` (array de strings), `model*`, `top_n`. Renvoie `results` : une liste de `{index, relevance_score}` triée par pertinence — **des indices dans `documents`, pas les textes** ; c'est à l'appelant de re-mapper. |

Le SDK `openai` n'expose pas de ressource `rerank` : cet endpoint s'appelle en HTTP direct (cf. § Exemples).

### Audio & documents

| Méthode | Chemin | Corps | Notes |
|---------|--------|-------|-------|
| POST | `/v1/audio/transcriptions` | `multipart` : `file*`, `model*` ; `language`, `prompt`, `response_format` (`json`\|`text`\|`verbose_json`\|`diarized_json`\|`srt`\|`vtt`), `temperature` | `diarized_json` = transcription avec séparation des locuteurs. |
| POST | `/v1/ocr` | `json` : `document*` (**objet**, pas une URL nue : `{"type":"document_url","document_url":"…"}` pour un PDF, ou `{"type":"image_url","image_url":"…"}` pour une image), `model`, `pages`, `image_limit`, `include_image_base64` | OCR d'un PDF/image accessible par URL. |

### RAG — collections, documents, recherche

| Méthode | Chemin | Corps / notes |
|---------|--------|---------------|
| GET/POST | `/v1/collections` | POST : `name*`, `description`, `visibility` (`private`\|`public`). |
| GET/PATCH/DELETE | `/v1/collections/{collection_id}` | Lire / modifier / supprimer une collection. |
| GET/POST | `/v1/documents` | POST `multipart` : `file` (le fichier à indexer ; ou `name` pour un document sans fichier), `collection_id` (collection cible) ; chunking paramétrable (`chunk_size`, `chunk_overlap`, `separators`, `preset_separators`, `disable_chunking`). |
| GET/DELETE | `/v1/documents/{document_id}` | Métadonnées / suppression d'un document. |
| GET/POST | `/v1/documents/{document_id}/chunks` | Lister / ajouter des chunks. |
| GET/DELETE | `/v1/documents/{document_id}/chunks/{chunk_id}` | Lire / supprimer un chunk. |
| POST | `/v1/search` | `query`, `collection_ids`, `document_ids`, `method` (`hybrid`\|`semantic`\|`lexical`), `limit`, `score_threshold`, `metadata_filters`. Renvoie les chunks les plus pertinents. |

### Compte & supervision

| Méthode | Chemin |
|---------|--------|
| GET/PATCH | `/v1/me` |
| GET | `/v1/usage` |
| GET/POST | `/v1/keys` |
| GET/DELETE | `/v1/keys/{key_id}` |
| GET/PATCH · GET · DELETE | `/v1/me/info` · `/v1/me/usage` · `/v1/me/keys` · `/v1/me/keys/{key_id}` — **dépréciées, à ne plus utiliser** |
| GET | `/health` · `/health/models` · `/metrics` |

---

## Workflow RAG (la valeur cœur d'Albert)

Deux approches :

**A. RAG explicite** (contrôle total) :
1. Créer une collection — `POST /v1/collections`.
2. Uploader les documents dedans — `POST /v1/documents` (chunking automatique).
3. Chercher les passages pertinents — `POST /v1/search` (`method: hybrid` par défaut conseillé).
4. Injecter les chunks récupérés dans le `messages` d'un `POST /v1/chat/completions`.

**B. RAG intégré** (une seule requête) : passer `search: true` + `search_args` (collections, méthode, limite) directement à `POST /v1/chat/completions` — Albert fait la recherche et la génération en un appel.

---

## Exemples

> Lire `ALBERT_API_KEY` depuis l'environnement ; ne jamais coller le token en clair. Vérifier les `id` de modèle via `GET /v1/models` (le champ `id`, pas un alias).

### Chat (SDK OpenAI)

```python
from openai import OpenAI
import os

client = OpenAI(
    base_url="https://albert.api.etalab.gouv.fr/v1",
    api_key=os.environ["ALBERT_API_KEY"],
)

resp = client.chat.completions.create(
    model="mistral-medium-2508",  # un id de type text-generation (cf. GET /v1/models)
    messages=[{"role": "user", "content": "Explique le RGPD en une phrase."}],
    temperature=0.2,
)
print(resp.choices[0].message.content)
```

### Lister les modèles / chat (curl)

```bash
curl -s https://albert.api.etalab.gouv.fr/v1/models \
  -H "Authorization: Bearer $ALBERT_API_KEY" | jq '.data[] | {id, type, aliases}'

curl -s https://albert.api.etalab.gouv.fr/v1/chat/completions \
  -H "Authorization: Bearer $ALBERT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"mistral-medium-2508","messages":[{"role":"user","content":"Bonjour"}]}'
```

### Embeddings

```python
emb = client.embeddings.create(model="bge-m3", input=["texte à vectoriser"])
vector = emb.data[0].embedding
```

### Reranking (requests — pas de méthode SDK)

```python
import os, requests

docs = ["texte du chunk 1", "texte du chunk 2", "texte du chunk 3"]

r = requests.post(
    "https://albert.api.etalab.gouv.fr/v1/rerank",
    headers={"Authorization": f"Bearer {os.environ['ALBERT_API_KEY']}"},
    json={
        "model": "bge-reranker-v2-m3",  # un id de type text-classification
        "query": "Quelles sont les obligations de publication ?",
        "documents": docs,
        "top_n": 2,
    },
).json()

# `results` contient des index dans `docs`, pas les textes : re-mapper soi-même
top = [(docs[x["index"]], x["relevance_score"]) for x in r["results"]]
```

### Transcription audio

```bash
curl -s https://albert.api.etalab.gouv.fr/v1/audio/transcriptions \
  -H "Authorization: Bearer $ALBERT_API_KEY" \
  -F "file=@reunion.mp3" \
  -F "model=whisper-large-v3" \
  -F "response_format=text"
```

### RAG explicite (requests)

```python
import os, requests

BASE = "https://albert.api.etalab.gouv.fr/v1"
H = {"Authorization": f"Bearer {os.environ['ALBERT_API_KEY']}"}

# 1) collection
col = requests.post(f"{BASE}/collections", headers=H,
                    json={"name": "ma-doc", "visibility": "private"}).json()

# 2) document
with open("guide.pdf", "rb") as f:
    requests.post(f"{BASE}/documents", headers=H,
                  data={"collection_id": col["id"]},
                  files={"file": f})

# 3) recherche
hits = requests.post(f"{BASE}/search", headers=H, json={
    "query": "Quelles sont les obligations de publication ?",
    "collection_ids": [col["id"]],
    "method": "hybrid",
    "limit": 5,
}).json()

# 4) injecter les chunks dans un chat completion (cf. exemple chat)
```

---

## Albert en ligne de commande (CLI)

Albert n'a **pas de CLI dédiée** — et n'en a pas besoin pour l'usage courant : étant compatible OpenAI, **n'importe quel client CLI OpenAI fonctionne** en pointant la base URL sur Albert et la clé sur `ALBERT_API_KEY`. Deux options éprouvées :

### `llm` (Simon Willison) — recommandé pour le terminal

Enregistrer Albert comme modèle OpenAI-compatible dans `~/.config/io.datasette.llm/extra-openai-models.yaml` :

```yaml
- model_id: albert-mistral
  model_name: mistral-medium-2508   # un id renvoyé par GET /v1/models
  api_base: https://albert.api.etalab.gouv.fr/v1
  api_key_name: albert
```

Puis :

```bash
llm keys set albert            # colle le token (stocké hors du shell, jamais en clair dans l'historique)
llm -m albert-mistral "Résume le RGPD en une phrase."
cat rapport.txt | llm -m albert-mistral "Fais-en une synthèse en 3 points."
```

### CLI `openai` officielle

```bash
export OPENAI_BASE_URL="https://albert.api.etalab.gouv.fr/v1"
export OPENAI_API_KEY="$ALBERT_API_KEY"   # déjà exporté côté env, pas en dur

openai api models.list
openai api chat.completions.create -m mistral-medium-2508 -g user "Bonjour"
```

> Pour les capacités propres à Albert (RAG, OCR, gestion des collections/clés), il n'y a pas de sous-commande CLI standard : utiliser `curl` (cf. exemples ci-dessus) ou le SDK. Ne jamais passer le token en argument de commande (visible dans `ps` et l'historique) : le lire depuis l'environnement ou un gestionnaire de clés (`llm keys`).

---

## Garanties & doctrine

- **Hébergement SecNumCloud** (Outscale) : qualification ANSSI.
- **Pas de rétention des conversations**, pas d'envoi de données sur Internet ouvert.
- Adapté aux usages avec données internes de l'administration, dans le respect du cadre RGPD applicable.

---

## Maintenance de cette skill

La source d'autorité est l'OpenAPI live (`/openapi.json`). Pour vérifier que cette skill n'a pas dérivé de la spec :

```bash
python3 bin/check_drift.py            # compare la spec live au snapshot committé
python3 bin/check_drift.py --update   # régénère le snapshot après mise à jour de SKILL.md
```

Le script (sans dépendance) compare version, endpoints et enums au fichier `openapi.snapshot.json`. En cas d'écart il sort en code 1 et liste les différences — signal qu'il faut relire et corriger ce fichier. À lancer périodiquement ou avant toute revue ; idéalement adossé à une GitHub Action qui ouvre une issue quand la version d'Albert bouge.

---

## Références

- **Doc Albert API :** https://doc.incubateur.net/alliance/albert-api
- **OpenAPI (autoritatif) :** https://albert.api.etalab.gouv.fr/openapi.json
- **OpenGateLLM (commun numérique sous-jacent) :** notebooks et tutoriels RAG/OCR.
- **Assistant IA** (interface chat grand public adossée à Albert) : https://assistant.numerique.gouv.fr/
