API Python DeepSeek V4 : Exemples de Code Minimalistes avec Streaming

API Python DeepSeek V4 : Exemples de Code Minimalistes avec Streaming

Bonjour à tous ! C’est Dora ici. Tout a commencé par une petite irritation : je n’arrêtais pas de copier le même passe-partout de complétion de chat entre les projets, en changeant les URL de base et les noms de modèles comme des étiquettes sur des bocaux. Ce n’était pas un travail difficile, juste celui qui ajoute du grain à votre journée. J’avais vu DeepSeek apparaître suffisamment souvent pour être curieuse, j’ai donc consacré quelques matinées fin janvier 2026 à intégrer leur API « V4 » dans ma pile Python et voir comment c’était en utilisation réelle.

Je n’étais pas à la chasse aux benchmarks. Je voulais savoir : le client reste-t-il hors de mon chemin, puis-je faire du streaming de manière fiable, et les erreurs échouent-elles d’une manière facile à comprendre ? Voici ce que j’ai essayé, ce qui m’a bloquée, et ce qui a fonctionné discrètement. Allons-y !

Configuration de l’environnement

Dépendances

J’ai gardé la configuration simple sur macOS avec Python 3.11. Vous pouvez faire cela avec la bibliothèque standard, mais trois petits paquets ont rendu la vie plus facile :

  • requests (HTTP simple : suffisant pour la plupart des cas)
  • httpx (async et timeouts qui se comportent bien)
  • python-dotenv (pour ne pas coller des clés partout) Si vous prévoyez du streaming avec Server-Sent Events, vous pouvez utiliser requests et analyser les lignes vous-même (ce que j’ai fait), ou apporter un assistant comme sseclient-py. Je m’en suis tenu à requests, moins de pièces mobiles.

Installation

pip install requests httpx python-dotenv

J’ai également créé un environnement virtuel minimal par projet. C’est un conseil ennuyeux, mais cela vous économise de la dérive des dépendances lorsque vous revisitez ceci dans trois mois.

Configuration de la clé API

J’ai stocké la clé dans une variable d’environnement. Rien de fantaisiste :

# .env
DEEPSEEK_API_KEY=your_key_here

Puis en Python :

from dotenv import load_dotenv
import os

load_dotenv()

API_KEY = os.getenv("DEEPSEEK_API_KEY")

if not API_KEY:
    raise RuntimeError("Missing DEEPSEEK_API_KEY")

Deux petites notes de configuration :

  • L’URL de base et les noms de modèles changent plus souvent que vous ne le pensez. J’ai vérifié la documentation officielle de l’API DeepSeek avant chaque exécution pour confirmer les chemins et les modèles disponibles.
  • J’ai gardé les timeouts explicites. C’est une habitude qui paie une fois que vous avez atteint les limites de débit ou du bruit réseau.

Demande de chat basique

Le modèle mental est familier si vous avez utilisé des complétions de chat ailleurs. DeepSeek expose un point de terminaison de chat avec messages=[{"role": "...", "content": "..."}]. C’est utile car je n’ai pas eu à reformuler mes invites.
Voici la demande minimale que j’ai utilisée avec requests. Les noms de modèles varient selon le compte et la région, pendant mes tests j’ai vu des références comme deepseek-chat et deepseek-reasoner. Si votre documentation mentionne une chaîne de modèle « V4 », utilisez-la. Sinon, choisissez le modèle de chat polyvalent le plus proche listé dans votre console.

import os
import requests

API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"

payload = {
    "model": "deepseek-chat", # check docs/console for the exact model
    "messages": [
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "Give me two bullet points on the value of clear commit messages."}
    ],
    "temperature": 0.3,
    "max_tokens": 200
}

resp = requests.post(
    BASE_URL,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    },
    json=payload,
    timeout=30
)

resp.raise_for_status()

data = resp.json()
content = data["choices"][0]["message"]["content"]
print(content)

Notes de terrain

  • La première exécution s’est déroulée sans problème (un soulagement). La structure correspondait à ce que je m’attendais, ce qui a rendu la migration d’une petite bibliothèque de prompts rapide.
  • J’ai gardé la température basse pour des réponses reproductibles. Cela semble évident, mais je l’oublie quand même lors du dépannage.
  • Si vous avez besoin de runs déterministes, épinglez également top_p et seed si l’API le supporte. Quand la documentation est silencieuse, je suppose non-déterministe.

Si vous comparez les fournisseurs, l’avantage ici est une friction faible. L’inconvénient est que les différences se cachent dans les bords, les charges utiles d’erreur, la comptabilité des tokens et la forme du streaming. Ce sont les bords où votre intégration soit se sent solide, soit ennuyeuse.

Exemple de génération de code

Je ne demande pas aux modèles d’écrire des modules complets. C’est devenu un travail de nettoyage. Mais pour de petits assistants, comme « analyser ce format d’horodatage » ou « rédiger le SQL avec des espaces réservés », c’est pratique.
J’ai utilisé une invite étroite, un contrat clair et de petites limites de sortie. Cela a empêché le modèle de vagabonder et a rendu les diffs faciles à examiner.

import requests, os

API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"

messages = [
    {"role": "system", "content": (
        "You generate small, safe Python helpers. "
        "Return only code inside one block."
    )},
    {"role": "user", "content": (
        "Write a Python function `parse_yyyymmdd` that takes a string like '2026-01-31' "
        "and returns a datetime.date. If invalid, return None. No external deps."
    )}
]

resp = requests.post(
    BASE_URL,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    },
    json={
        "model": "deepseek-chat", # or your V4-capable model
        "messages": messages,
        "temperature": 0,
        "max_tokens": 250
    },
    timeout=30
)

resp.raise_for_status()
code = resp.json()["choices"][0]["message"]["content"]
print(code)

Ce qui a aidé en pratique

  • Je lui dis toujours de retourner uniquement du code. Si je saute ça, je reçois des phrases de conclusion que je n’ai pas besoin.
  • La température 0 réduit les éditions délicates.
  • Je parcours la logique de toute façon. À ma course, elle a géré ValueError, mais j’ai quand même ajouté un test supplémentaire pour les espaces blancs. Deux minutes de plus maintenant économisent des heures de surprise plus tard.

Ce n’a pas économisé de temps au premier essai. Après trois ou quatre petits assistants, j’ai remarqué que cela réduisait l’effort mental : moins de commutateurs d’onglets, moins de moments « quel est le code strptime exact ? ». C’est suffisant pour moi.

Réponses en streaming

J’aime le streaming pour toute invite qui pourrait grandir. Cela me permet de sortir tôt si la réponse s’éloigne, et cela rend les réponses longues moins lourdes.
Le streaming de DeepSeek utilisait le modèle habituel dans mes tests : définissez stream=true et lisez les lignes de données jusqu’à [DONE]. Je n’avais pas besoin d’un client spécial, requests avec iter_lines était suffisant.

import os, json, requests

API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"

payload = {
    "model": "deepseek-chat",
    "messages": [
        {"role": "system", "content": "Be brief."},
        {"role": "user", "content": "Summarize this: Streaming keeps the UI responsive and lets me stop early."}
    ],
    "stream": True,
    "temperature": 0.2,
}

with requests.post(
    BASE_URL,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    },
    json=payload,
    stream=True,
    timeout=60
) as r:
    r.raise_for_status()
    for line in r.iter_lines(decode_unicode=True):
        if not line:
            continue
        if line.startswith("data: "):
            chunk = line[len("data: "):]
            if chunk == "[DONE]":
                break
            try:
                obj = json.loads(chunk)
                delta = obj["choices"][0]["delta"].get("content", "")
                if delta:
                    print(delta, end="", flush=True)
            except json.JSONDecodeError:
                # I keep a small log when this happens: usually network blips
                pass

print()

Deux petits comportements que j’ai aimés :

  • Les premiers tokens sont arrivés rapidement (une seconde ou deux sur une connexion propre). Pas scientifique, juste assez pour se sentir rapide quand je l’ai intégré à un outil CLI.
  • Le marqueur [DONE] a montré fiablement. Cela semble trivial jusqu’à ce que ce ne le soit pas, les terminateurs manquants font planter les interfaces utilisateur.

Si vous avez besoin de streaming dans une application web, je mettrais une couche serveur mince entre pour normaliser les événements. C’est une étape supplémentaire, mais cela garde votre interface simple.

Server-Sent Events

Sous le capot, vous lisez essentiellement Server-Sent Events. Si vous préférez un assistant, sseclient-py fonctionne, mais rouler la vôtre ici est bien tant que vous vous protégez contre les lignes partielles et les timeouts. La page de documentation sur le streaming dans la documentation de l’API DeepSeek était suffisante pour que cela s’exécute sans surprises.

Gestion des erreurs

La plupart de mes erreurs étaient prévisibles : clé manquante, mauvais nom de modèle ou timeouts quand j’ai limité mon réseau pour simuler le Wi-Fi en déplacement.
Un petit modèle que je réutilise :

import httpx, time, os

API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"

RETRIABLE = {408, 409, 425, 429, 500, 502, 503, 504}

async def chat_once(client, messages):
    resp = await client.post(
        BASE_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "model": "deepseek-chat",
            "messages": messages,
            "temperature": 0.2,
            "max_tokens": 300,
        },
        timeout=30,
    )
    if resp.status_code == 401:
        raise RuntimeError("Unauthorized. Check DEEPSEEK_API_KEY and account access.")
    if resp.status_code == 404:
        raise RuntimeError("Endpoint or model not found. Confirm model name in console/docs.")
    if resp.status_code in RETRIABLE:
        raise RuntimeError(f"Retryable status: {resp.status_code}")
    resp.raise_for_status()
    return resp.json()

async def chat_with_retries(messages, attempts=4):
    backoff = 0.5
    async with httpx.AsyncClient() as client:
        for i in range(attempts):
            try:
                return await chat_once(client, messages)
            except RuntimeError as e:
                msg = str(e)
                if "Retryable status" in msg and i < attempts - 1:
                    time.sleep(backoff)
                    backoff *= 2
                    continue
                raise

Quelques notes pratiques :

  • Limites de débit : j’ai vu 429 quand j’ai lancé des tests parallèles. La sauvegarde exponentielle a aidé, mais j’ai aussi ajouté de petites variations (50–150ms aléatoires) pour éviter les troupeaux de tonnerre.
  • Hygiène des timeouts : j’ai défini des timeouts plus courts de connexion/lecture pour les vérifications rapides (5–10s) et plus longs pour les gros invites. Les timeouts ne doivent pas tous être 30s par défaut : cela cache les problèmes.
  • Charges utiles d’erreur : quand les choses ont échoué, le corps JSON incluait un message que je pouvais faire remonter à un journal. Je l’enveloppe toujours dans mes propres exceptions pour contrôler ce qui atteint l’interface utilisateur.

Si votre base de code parle déjà du schéma de style OpenAI, c’est gérable : même forme de message, bords légèrement différents. L’essentiel est d’être strict sur les noms de modèles et de enregistrer le corps de réponse complet sur non-2xx pour ne pas deviner.
En termes de documentation, j’ai compté sur la documentation officielle de l’API DeepSeek pour les noms de paramètres et la forme du streaming. Chaque fois qu’un fournisseur utilise des points de terminaison familiers, il est tentant de supposer la parité. J’ai appris à vérifier la documentation en premier et à copier moins entre les clients que je ne pense pouvoir le faire.

Qui pourrait aimer ceci

  • Si vous avez un wrapper Python existant pour les complétions de chat, le chemin de migration est doux.
  • Si vous vous souciez du streaming et des tentatives simples, il se comporte de manière prévisible.
  • Si vous avez besoin d’outils très spécifiques (schémas d’appels de fonction, tokens de raisonnement ou travaux par lots), vous voudrez lire la documentation attentivement et créer un prototype avec une tâche étroite avant de vous engager.

Je n’ai pas essayé d’orchestrer de longs agents multi-étapes ici. Je me suis concentrée sur de petites invites d’usage quotidien, le genre qui grignotent les frictions. C’est là que l’API DeepSeek V4 avec Python se sentait assez stable pour garder.