Skip to Content
IAAPI LLM

Utilisation des API LLM — OpenAI & Anthropic

Chaque appel à un LLM suit un ensemble de patterns récurrents : construction de messages, gestion du streaming, appel d’outils, suivi des tokens. Cette page regroupe ces patterns en code Python fonctionnel pour les deux fournisseurs les plus utilisés.

Installation des SDK

pip install openai anthropic

Variables d’environnement requises :

VariableFournisseur
OPENAI_API_KEYOpenAI
ANTHROPIC_API_KEYAnthropic

Initialisation :

import os from openai import OpenAI import anthropic openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) anthropic_client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

Piège : ne jamais coder la clé en dur. Si un .env n’existe pas, utiliser python-dotenv localement : from dotenv import load_dotenv; load_dotenv().

Chat completions — Sync

Appel basique à envoi unique, attente de la réponse complète.

OpenAI

response = openai_client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "Tu es un assistant technique."}, {"role": "user", "content": "Explique le principe de l'injection de dépendances en Python."}, ], temperature=0.7, max_tokens=1024, ) print(response.choices[0].message.content) # Accès au token usage : print(response.usage.prompt_tokens, response.usage.completion_tokens)

Anthropic

response = anthropic_client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[ {"role": "user", "content": "Explique le principe de l'injection de dépendances en Python."}, ], ) # Claude renvoie une liste de blocs (text, tool_use, ...) print(response.content[0].text) print(response.usage.input_tokens, response.usage.output_tokens)

Différence clé : OpenAI renvoie un seul bloc de texte, Claude renvoie une liste de blocs hétérogènes. Toujours indexer [0] ou itérer selon le besoin.

Chat completions — Async

Pour les appels concurrents (parallélisme, UI non-bloquante).

OpenAI

import asyncio from openai import AsyncOpenAI async def main(): client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) response = await client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "Bonjour, donne-moi une recette de crêpes."}], ) print(response.choices[0].message.content) asyncio.run(main())

Anthropic

import asyncio import anthropic async def main(): client = anthropic.AsyncAnthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) async with client.messages.stream( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[{"role": "user", "content": "Bonjour, donne-moi une recette de crêpes."}], ) as stream: async for text in stream.text_stream: print(text, end="", flush=True) print() # fin de ligne asyncio.run(main())

Streaming de réponses

Indispensable pour le UX (typing effect) et pour les longues réponses.

OpenAI — Server-Sent Events

stream = openai_client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Écris un poème sur les microservices."}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta if delta.content: print(delta.content, end="", flush=True) print()

Anthropic — Stream natif

with anthropic_client.messages.stream( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[{"role": "user", "content": "Écris un poème sur les microservices."}], ) as stream: for text in stream.text_stream: print(text, end="", flush=True) print()

Attention streaming : avec le streaming, l’objet usage final contient les tokens totaux. Ne pas l’ignorer si vous comptabilisez les coûts.

Tool Calling

Le pattern tool/function calling est documenté en détail dans Structured Output. Cette section présente le squelette de contrôle récurrent.

# Squelette OpenAI response = openai_client.chat.completions.create( model="gpt-4o", messages=messages, tools=tools, tool_choice="auto", ) message = response.choices[0].message if message.tool_calls: for tc in message.tool_calls: args = json.loads(tc.function.arguments) result = dispatch(tc.function.name, args) messages.append(message) messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(result)}) # rappeler le modèle avec le résultat en contexte

Pour le parcours complet avec OpenAI, Claude et Gemini, consulter la page Structured Output qui couvre les schémas de confirmation, validation et gestion des erreurs outil.

Sortie structurée (JSON)

Pour forcer un format JSON prévisible, cf. Structured Output. Le code minimal :

response = openai_client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Extrais : nom, âge, ville de Marie, 30 ans, Lyon."}], response_format={"type": "json_schema", "json_schema": { "name": "profil", "schema": {"type": "object", "properties": { "nom": {"type": "string"}, "age": {"type": "integer"}, "ville": {"type": "string"} }} }}, ) data = json.loads(response.choices[0].message.content)

Gestion d’erreurs et retry

Les API LLM retournent des erreurs transitoires (rate limit, timeout, 5xx). Un retry avec backoff exponentiel est nécessaire.

import time import openai from openai import APIError, RateLimitError, APITimeoutError def call_llm_with_retry(client, model, messages, max_retries=5, backoff=2.0, **kwargs): for attempt in range(max_retries): try: return client.chat.completions.create( model=model, messages=messages, **kwargs ) except RateLimitError: wait = backoff * (2 ** attempt) + 0.5 # jitter print(f"Rate limit — attente de {wait:.1f}s (essai {attempt+1}/{max_retries})") time.sleep(wait) except APITimeoutError: wait = backoff * (attempt + 1) print(f"Timeout — attente de {wait:.1f}s") time.sleep(wait) except APIError as e: # 5xx = probable erreur serveur passagère if 500 <= e.status_code < 600: wait = backoff * (2 ** attempt) print(f"Erreur serveur ({e.status_code}) — attente de {wait:.1f}s") time.sleep(wait) else: # Erreur côté client (paramètres invalides) : pas de retry raise raise RuntimeError(f"Échec après {max_retries} tentatives pour {model}")

Pièges :

  • Ne jamais retry sur erreur 400/401/403 — c’est un bug dans les arguments ou la clé.
  • Le backoff RateLimitError d’OpenAI inclut un header x-ratelimit-reset. Consulter ce header pour une attente précise.
  • Pour Anthropic, capturer anthropic.RateLimitError avec le même schéma.

Comptabilisation des tokens

Chaque fournisseur expose le coût par appel via usage. Voici comment suivre la consommation cumulée.

TOKEN_COST = { "gpt-4o": {"input": 0.0025/1000, "output": 0.01/1000}, "gpt-4o-mini": {"input": 0.00015/1000, "output": 0.0006/1000}, "claude-sonnet-4-20250514": {"input": 3.0/1_000_000, "output": 15.0/1_000_000}, } def estimate_cost(response, model: str) -> float: usage = response.usage cost_table = TOKEN_COST[model] return usage.prompt_tokens * cost_table["input"] + usage.completion_tokens * cost_table["output"] # Usage cumulé sur plusieurs appels total_cost = 0.0 for resp in responses: total_cost += estimate_cost(resp, resp.model) print(f"Coût total estimé : {total_cost:.4f} $")
ModèlePrix input (/$/1M tokens)Prix output (/$/1M tokens)
gpt-4o2,5010,00
gpt-4o-mini0,150,60
claude-sonnet-4-202505143,0015,00

Coût tête de série : les modèles les moins chers ne sont pas toujours les plus adaptés. Un gpt-4o-mini qui ne comprend pas le tool calling forcera des allers-retours plus nombreux — le coût total peut être supérieur.

Comparaison rapide OpenAI vs Anthropic

CritèreOpenAIAnthropic
Classe clienteOpenAI() / AsyncOpenAI()Anthropic() / AsyncAnthropic()
Point d’entréeclient.chat.completions.create()client.messages.create()
Retour de messageresponse.choices[0].messageresponse.content[0].text
Tokens usageresponse.usage.prompt_tokens / .completion_tokensresponse.usage.input_tokens / .output_tokens
Streamingstream=True + itération sur chunksclient.messages.stream() avec contexte manager
Tool callingtools=... + tool_choice="auto"tools=... + blocs tool_use dans content
ErreursRateLimitError, APITimeoutError, APIErrorRateLimitError, APIConnectionError, APIStatusError

Références : OpenAI Python SDK , Anthropic Python SDK , OpenAI API Reference , Anthropic Messages API