Créer une application avec DeepSeek : Python, Node.js et sécurité

Vérification factuelle et technique : 22 juillet 2026. Ce guide utilise les endpoints, modèles, paramètres, tarifs et limites documentés par DeepSeek à cette date. DeepSeek V4 est encore présenté officiellement comme une version Preview : contrôlez les sources liées avant chaque mise en production.

Transparence : deepseek-fr.ai est un guide francophone indépendant. Il n’est ni exploité, ni approuvé, ni affilié à DeepSeek. Le site propose son propre assistant en français, mais ne fournit ni application ni clé API ; les messages envoyés dans l’assistant sont transmis à l’API DeepSeek uniquement pour générer la réponse. Les exemples ci-dessous s’exécutent uniquement dans votre propre application avec une clé officielle.

Pour créer une application avec DeepSeek, ne placez jamais la clé API dans React, Vue, une page HTML ou une application mobile. Le frontend appelle votre backend ; ce serveur authentifie l’utilisateur, limite les requêtes, valide l’entrée, appelle POST https://api.deepseek.com/chat/completions, contrôle la réponse puis renvoie uniquement le résultat utile.

Ce tutoriel construit une petite application web réutilisable : une interface envoie une demande à un backend Node.js et TypeScript, lequel appelle DeepSeek V4 et renvoie une réponse validée. Une alternative FastAPI est également fournie. La même architecture convient à la synthèse, au classement, à l’extraction, à l’aide à la rédaction ou à un assistant interne.

Créer une application DeepSeek : la réponse rapide

QuestionRéponse actuelle
Où placer la clé ?Dans une variable d’environnement du backend, jamais dans le navigateur.
Quel endpoint appeler ?POST https://api.deepseek.com/chat/completions.
Quels modèles utiliser ?deepseek-v4-flash ou deepseek-v4-pro.
Quel mode par défaut ?Thinking est activé par défaut ; rendez le choix explicite dans chaque appel.
Comment sécuriser l’accès ?Authentification, validation stricte, quotas, timeout, contrôle de sortie et logs minimaux.
Le format OpenAI est-il identique ?Non. Il est compatible avec Chat Completions, sans parité complète.

Sommaire

1. Architecture recommandée

Le chemin minimal est : navigateur ou application mobile → votre backend → API DeepSeek → votre backend → utilisateur. Le client ne contacte jamais DeepSeek avec votre secret.

CoucheResponsabilitéÀ ne pas faire
FrontendCollecter la demande, afficher le chargement et la réponse.Contenir la clé ou faire confiance à une validation uniquement côté client.
BackendAuthentifier, limiter, valider, appeler DeepSeek et contrôler la sortie.Relayer aveuglément tous les champs envoyés par le navigateur.
API DeepSeekProduire une réponse texte, JSON, streaming ou Tool Call selon le payload.Être considérée comme une source garantie de vérité.
Stockage facultatifConserver uniquement ce qui est nécessaire avec une durée définie.Journaliser par défaut prompts, réponses, clés ou données personnelles.
ObservabilitéMesurer latence, statuts, tokens, cache et finish_reason.Recopier le contenu sensible dans les logs et traces.

Cette séparation réduit le risque de vol de clé, mais elle ne suffit pas à elle seule. Votre backend doit aussi vérifier l’identité, appliquer des quotas par utilisateur et traiter les entrées comme les sorties du modèle comme des données non fiables.

2. Prérequis et protection de la clé API

  • Un compte et une clé sur la plateforme officielle DeepSeek.
  • Un solde suffisant et une surveillance de la consommation.
  • Node.js dans une version LTS maintenue, ou Python 3.11 ou version ultérieure.
  • HTTPS, authentification et gestionnaire de secrets en production.
  • Un jeu de tests représentatif avec résultats attendus.
  • Une politique de confidentialité propre à votre application avant de collecter des données personnelles.

Dans un fichier local .env non versionné :

DEEPSEEK_API_KEY=remplacez_par_votre_cle
DEEPSEEK_MODEL=deepseek-v4-flash
PORT=3000
# .gitignore
.env
node_modules/
dist/
__pycache__/

Clé déjà publiée ? Supprimer le fichier de Git ne suffit pas. Révoquez immédiatement la clé sur la plateforme, créez-en une nouvelle puis cherchez le secret dans l’historique, les logs, les artefacts CI et les builds mobiles.

3. Modèles, prix et paramètres à envoyer

Le Quick Start officiel donne actuellement l’URL de base https://api.deepseek.com et les modèles deepseek-v4-flash et deepseek-v4-pro. Utilisez GET /models pour vérifier les identifiants disponibles au moment du déploiement.

Quick Start officiel DeepSeek montrant les URL de base et les modèles V4 Flash et Pro
URL de base et modèles dans le Quick Start officiel DeepSeek, consulté le 22 juillet 2026. Cliquez sur l’image pour ouvrir la source.
ModèlePoint de départ recommandéContexteSortie maximaleConcurrence par compte
deepseek-v4-flashPrototype, extraction, classement, synthèse et volume élevé.1 million de tokens384 000 tokens dans la limite du contexte total2 500 connexions
deepseek-v4-proAnalyse plus exigeante, code et agents à comparer sur vos tests.1 million de tokens384 000 tokens dans la limite du contexte total500 connexions

Ces usages sont des points de départ éditoriaux, pas une garantie de qualité. Comparez les deux modèles sur vos propres cas, mesurez exactitude, latence et coût, puis verrouillez le modèle choisi dans la configuration. Consultez nos pages sur DeepSeek V4 Flash et Pro et les prix de l’API DeepSeek.

Alias anciens : DeepSeek annonce la dépréciation de deepseek-chat et deepseek-reasoner le 24 juillet 2026 à 15 h 59 UTC. Ne les utilisez pas dans un nouveau projet. Choisissez un identifiant V4 et contrôlez explicitement le mode Thinking.

Thinking et paramètres sans effet

Les deux modèles V4 prennent en charge Thinking et non-thinking ; Thinking est actuellement activé par défaut. Pour une extraction courte, commencez avec {"thinking":{"type":"disabled"}}. Pour une tâche complexe, activez-le et mesurez le gain réel. Les niveaux d’effort documentés sont high et max.

Formulation exacte : En mode Thinking, temperature et top_p sont ignorés. presence_penalty et frequency_penalty sont obsolètes et sans effet, quel que soit le mode. Consultez la référence API officielle Chat Completions.

La distinction est essentielle : temperature et top_p restent disponibles en non-thinking, mais ne modifient pas une réponse Thinking. presence_penalty et frequency_penalty, eux, sont marqués deprecated dans la référence générale et n’ont plus d’effet dans aucun mode. Ne les ajoutez donc pas au payload.

Référence officielle DeepSeek indiquant que frequency_penalty et presence_penalty sont deprecated et sans effet
La référence officielle marque frequency_penalty et presence_penalty comme obsolètes et sans effet, quel que soit le mode. Capture du 22 juillet 2026.

4. Tester un premier appel avec cURL

Ce test désactive Thinking et demande un objet JSON. Le prompt mentionne explicitement JSON et montre la structure attendue, conformément au guide officiel JSON Output.

curl --fail-with-body --silent --show-error \
  https://api.deepseek.com/chat/completions \
  -H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "thinking": {"type": "disabled"},
    "messages": [
      {
        "role": "system",
        "content": "Réponds uniquement en JSON valide. Format : {\"resume\":\"texte\",\"points_cles\":[\"point\"]}."
      },
      {
        "role": "user",
        "content": "Résume : La livraison du projet est prévue vendredi."
      }
    ],
    "response_format": {"type": "json_object"},
    "max_tokens": 400,
    "stream": false
  }' | jq .

Une réponse HTTP 200 ne suffit pas. Vérifiez que choices contient un élément, que finish_reason vaut stop, que message.content n’est pas vide, puis parsez et validez chaque champ. Le mode JSON vise un JSON syntaxiquement valide, mais la documentation signale qu’un contenu peut exceptionnellement être vide ; une limite max_tokens trop faible peut également interrompre la sortie.

5. Backend complet avec Node.js et TypeScript

L’exemple suivant expose POST /api/chat. Il garde la clé côté serveur, limite le corps à 16 Ko, valide strictement les champs avec Zod, applique 20 requêtes par minute et par IP, impose un timeout et ne retente que les statuts 429, 500 et 503. Pour un service public, ajoutez une authentification réelle et des quotas par compte : une limite IP seule ne suffit pas.

mkdir mon-app-deepseek && cd mon-app-deepseek
npm init -y
npm install express express-rate-limit helmet zod
npm install --save-dev typescript tsx @types/express @types/node
mkdir -p src public

# Après vérification, conservez package-lock.json dans Git.
# Exécution locale :
node --env-file=.env --import tsx src/server.ts

Créez src/app.ts :

import express, {
  type NextFunction,
  type Request,
  type Response,
} from "express";
import { rateLimit } from "express-rate-limit";
import helmet from "helmet";
import { z } from "zod";

const InputSchema = z
  .object({
    message: z.string().trim().min(1).max(4_000),
    thinking: z.boolean().default(true),
  })
  .strict();

const ModelSchema = z.enum([
  "deepseek-v4-flash",
  "deepseek-v4-pro",
]);

const DeepSeekResponseSchema = z.object({
  choices: z
    .array(
      z.object({
        finish_reason: z.string(),
        message: z.object({ content: z.string().nullable() }),
      }),
    )
    .min(1),
  usage: z
    .object({
      prompt_tokens: z.number().int().nonnegative(),
      completion_tokens: z.number().int().nonnegative(),
      total_tokens: z.number().int().nonnegative(),
    })
    .optional(),
});

type AppConfig = {
  apiKey: string;
  model: z.infer<typeof ModelSchema>;
  endpoint: string;
  fetchImpl: typeof fetch;
  sleep: (milliseconds: number) => Promise<void>;
  timeoutMs: number;
  maxRetries: number;
};

class UpstreamError extends Error {
  constructor(
    public readonly status: number,
    message: string,
  ) {
    super(message);
  }
}

function loadConfig(): AppConfig {
  const apiKey = process.env.DEEPSEEK_API_KEY;
  if (!apiKey) throw new Error("DEEPSEEK_API_KEY manquante");

  return {
    apiKey,
    model: ModelSchema.parse(
      process.env.DEEPSEEK_MODEL ?? "deepseek-v4-flash",
    ),
    endpoint: "https://api.deepseek.com/chat/completions",
    fetchImpl: fetch,
    sleep: (milliseconds) =>
      new Promise((resolve) => setTimeout(resolve, milliseconds)),
    timeoutMs: 90_000,
    maxRetries: 2,
  };
}

async function callDeepSeek(
  input: z.infer<typeof InputSchema>,
  config: AppConfig,
) {
  const requestBody = {
    model: config.model,
    messages: [
      {
        role: "system",
        content:
          "Réponds clairement en français. " +
          "N’invente pas de sources ni de faits.",
      },
      { role: "user", content: input.message },
    ],
    thinking: {
      type: input.thinking ? "enabled" : "disabled",
    },
    ...(input.thinking ? { reasoning_effort: "high" } : {}),
    max_tokens: 800,
    stream: false,
  };

  // Délai global partagé par toutes les tentatives.
  const deadline = Date.now() + config.timeoutMs;

  for (
    let attempt = 0;
    attempt <= config.maxRetries;
    attempt += 1
  ) {
    const remainingMs = deadline - Date.now();
    if (remainingMs <= 0) {
      throw new UpstreamError(504, "Délai DeepSeek dépassé");
    }

    const controller = new AbortController();
    const timer = setTimeout(
      () => controller.abort(),
      remainingMs,
    );
    let response: globalThis.Response;

    try {
      response = await config.fetchImpl(config.endpoint, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${config.apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody),
        signal: controller.signal,
      });
    } catch (error) {
      if (error instanceof Error && error.name === "AbortError") {
        throw new UpstreamError(504, "Délai DeepSeek dépassé");
      }
      // Une panne réseau après l’envoi est ambiguë : pas de retry
      // automatique, afin de limiter les appels et coûts en double.
      throw new UpstreamError(
        502,
        "DeepSeek est temporairement inaccessible",
      );
    } finally {
      clearTimeout(timer);
    }

    const retryable = [429, 500, 503].includes(response.status);
    if (retryable && attempt < config.maxRetries) {
      if (response.body) {
        await response.body.cancel();
      }
      const jitter = Math.floor(Math.random() * 200);
      const delay = 500 * 2 ** attempt + jitter;
      if (Date.now() + delay >= deadline) {
        throw new UpstreamError(504, "Délai DeepSeek dépassé");
      }
      await config.sleep(delay);
      continue;
    }

    if (!response.ok) {
      throw new UpstreamError(
        response.status,
        "Réponse DeepSeek non valide",
      );
    }

    let responseBody: unknown;
    try {
      responseBody = await response.json();
    } catch {
      throw new UpstreamError(502, "Réponse DeepSeek non JSON");
    }

    const parsed = DeepSeekResponseSchema.safeParse(responseBody);
    if (!parsed.success) {
      throw new UpstreamError(502, "Réponse DeepSeek inattendue");
    }

    const choice = parsed.data.choices[0];
    const answer = choice?.message.content?.trim();
    if (choice?.finish_reason !== "stop" || !answer) {
      throw new UpstreamError(
        502,
        "Réponse DeepSeek vide ou interrompue",
      );
    }

    return { answer, usage: parsed.data.usage };
  }

  throw new UpstreamError(
    503,
    "DeepSeek est temporairement indisponible",
  );
}

export function createApp(overrides: Partial<AppConfig> = {}) {
  const config = { ...loadConfig(), ...overrides };
  const app = express();

  app.disable("x-powered-by");
  app.use(helmet());
  app.use(express.json({ limit: "16kb" }));
  app.use(express.static("public"));

  const limiter = rateLimit({
    windowMs: 60_000,
    limit: 20,
    standardHeaders: "draft-8",
    legacyHeaders: false,
  });

  app.post(
    "/api/chat",
    limiter,
    async (
      request: Request,
      response: Response,
      next: NextFunction,
    ) => {
      try {
        const input = InputSchema.parse(request.body);
        const result = await callDeepSeek(input, config);
        response.set("Cache-Control", "no-store").json(result);
      } catch (error) {
        next(error);
      }
    },
  );

  app.use(
    (
      error: unknown,
      _request: Request,
      response: Response,
      _next: NextFunction,
    ) => {
      if (error instanceof z.ZodError) {
        response.status(400).json({
          error: "Message ou options non valides.",
        });
        return;
      }

      if (error instanceof UpstreamError) {
        console.error(`DeepSeek upstream HTTP ${error.status}`);
        const publicStatus = error.status === 504 ? 504 : 503;
        response.status(publicStatus).json({
          error:
            publicStatus === 504
              ? "La réponse a pris trop de temps."
              : "Le service IA est temporairement indisponible.",
        });
        return;
      }

      console.error(error);
      response.status(500).json({ error: "Erreur interne." });
    },
  );

  return app;
}

Créez ensuite src/server.ts :

import { createApp } from "./app.js";

const port = Number(process.env.PORT ?? 3000);

createApp().listen(port, "127.0.0.1", () => {
  console.log(`Application disponible sur http://127.0.0.1:${port}`);
});

En production derrière un reverse proxy, configurez explicitement les proxies de confiance avant de baser une limite sur l’adresse IP. Sinon, une adresse falsifiée ou l’adresse unique du proxy peut rendre la limite inefficace. Une authentification et des quotas par compte restent nécessaires.

6. Frontend : appeler uniquement votre backend

Créez public/index.html. Cette page n’inclut aucune clé et n’envoie que le message et le choix Thinking à /api/chat. La réponse est insérée avec textContent, jamais avec innerHTML, afin qu’un texte généré ne puisse pas injecter du HTML.

<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Mon assistant de synthèse</title>
  </head>
  <body>
    <main>
      <h1>Assistant de synthèse</h1>
      <form id="chat-form">
        <label for="message">Votre demande</label>
        <textarea id="message" maxlength="4000" required></textarea>

        <label>
          <input id="thinking" type="checkbox" checked />
          Activer le mode Thinking
        </label>

        <button type="submit">Envoyer</button>
      </form>

      <p id="status" role="status" aria-live="polite"></p>
      <pre id="answer"></pre>
    </main>

    <script type="module">
      const form = document.querySelector("#chat-form");
      const message = document.querySelector("#message");
      const thinking = document.querySelector("#thinking");
      const status = document.querySelector("#status");
      const answer = document.querySelector("#answer");
      const button = form.querySelector("button");

      form.addEventListener("submit", async (event) => {
        event.preventDefault();
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), 95_000);
        button.disabled = true;
        status.textContent = "Génération en cours…";
        answer.textContent = "";

        try {
          const response = await fetch("/api/chat", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              message: message.value,
              thinking: thinking.checked,
            }),
            signal: controller.signal,
          });

          const data = await response.json();
          if (!response.ok) {
            throw new Error(
              data.error ?? data.detail ?? "Erreur inconnue",
            );
          }

          answer.textContent = data.answer;
          status.textContent = "Réponse reçue.";
        } catch (error) {
          status.textContent =
            error.name === "AbortError"
              ? "La demande a expiré."
              : error.message;
        } finally {
          clearTimeout(timer);
          button.disabled = false;
        }
      });
    </script>
  </body>
</html>

Le code côté navigateur ne doit pas permettre de choisir librement le modèle, le system prompt, l’URL ou max_tokens. Ces décisions restent côté serveur, où elles peuvent être testées et plafonnées. Ajoutez aussi protection CSRF si votre authentification repose sur des cookies, contrôle d’origine et Content Security Policy adaptée.

7. Alternative Python avec FastAPI

Cette alternative fournit la même route POST /api/chat. Elle interdit les champs inconnus, limite la demande à 4 000 caractères, exécute l’appel HTTP bloquant dans un thread et renvoie des erreurs publiques génériques. Le frontend peut être servi par un reverse proxy sur la même origine.

python -m pip install fastapi "uvicorn[standard]" pydantic

# .env doit contenir DEEPSEEK_API_KEY et DEEPSEEK_MODEL
uvicorn main:app \
  --env-file .env \
  --host 127.0.0.1 \
  --port 8000 \
  --reload

Créez main.py :

import asyncio
import json
import os
import socket
from typing import Literal
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, ConfigDict, Field, ValidationError

API_KEY = os.getenv("DEEPSEEK_API_KEY")
if not API_KEY:
    raise RuntimeError("DEEPSEEK_API_KEY manquante")

ModelName = Literal["deepseek-v4-flash", "deepseek-v4-pro"]
MODEL: ModelName = os.getenv(  # type: ignore[assignment]
    "DEEPSEEK_MODEL",
    "deepseek-v4-flash",
)
if MODEL not in ("deepseek-v4-flash", "deepseek-v4-pro"):
    raise RuntimeError("DEEPSEEK_MODEL non valide")

app = FastAPI()


class ChatInput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
        str_strip_whitespace=True,
    )
    message: str = Field(min_length=1, max_length=4_000)
    thinking: bool = True


class ProviderMessage(BaseModel):
    content: str | None


class ProviderChoice(BaseModel):
    finish_reason: str
    message: ProviderMessage


class ProviderResponse(BaseModel):
    choices: list[ProviderChoice] = Field(min_length=1)


class UpstreamError(Exception):
    def __init__(self, status_code: int):
        self.status_code = status_code


def call_deepseek(body: dict) -> str:
    request = Request(
        "https://api.deepseek.com/chat/completions",
        data=json.dumps(body).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        method="POST",
    )

    try:
        with urlopen(request, timeout=90) as response:
            raw = json.loads(response.read(2_000_000))
    except HTTPError as error:
        status = 503 if error.code in (429, 500, 503) else 502
        raise UpstreamError(status) from error
    except (TimeoutError, socket.timeout) as error:
        raise UpstreamError(504) from error
    except (URLError, json.JSONDecodeError) as error:
        raise UpstreamError(502) from error

    try:
        parsed = ProviderResponse.model_validate(raw)
        choice = parsed.choices[0]
        answer = choice.message.content
    except (ValidationError, IndexError) as error:
        raise UpstreamError(502) from error

    if choice.finish_reason != "stop" or not answer or not answer.strip():
        raise UpstreamError(502)

    return answer.strip()


@app.post("/api/chat")
async def chat(data: ChatInput) -> dict[str, str]:
    body = {
        "model": MODEL,
        "messages": [
            {
                "role": "system",
                "content": "Réponds clairement en français.",
            },
            {"role": "user", "content": data.message},
        ],
        "thinking": {
            "type": "enabled" if data.thinking else "disabled"
        },
        "max_tokens": 800,
        "stream": False,
    }
    if data.thinking:
        body["reasoning_effort"] = "high"

    try:
        answer = await asyncio.to_thread(call_deepseek, body)
        return {"answer": answer}
    except UpstreamError as error:
        detail = (
            "La réponse a pris trop de temps."
            if error.status_code == 504
            else "Le service IA est temporairement indisponible."
        )
        raise HTTPException(
            status_code=error.status_code,
            detail=detail,
        ) from error

Cet exemple ne retente pas automatiquement une panne réseau ambiguë : le serveur distant peut avoir reçu et facturé la demande même si votre connexion s’est interrompue. Pour une opération métier susceptible d’avoir un effet, utilisez une clé d’idempotence dans votre propre système. En production, retirez --reload, verrouillez les dépendances et placez Uvicorn derrière un proxy HTTPS configuré.

8. JSON Output et streaming

Produire une sortie structurée

Pour une extraction, ajoutez response_format: {"type":"json_object"}, demandez explicitement du JSON dans le system prompt et montrez un exemple. Contrôlez ensuite finish_reason, refusez un contenu vide, parsez la chaîne et validez-la avec Zod, Pydantic ou JSON Schema. Le mode JSON ne garantit pas la justesse des valeurs ni leur conformité à vos règles métier.

const SummarySchema = z
  .object({
    resume: z.string().min(1).max(2_000),
    points_cles: z.array(z.string().min(1).max(300)).max(10),
  })
  .strict();

// Après avoir vérifié choices[0].finish_reason et content :
const summary = SummarySchema.parse(
  JSON.parse(content),
);

Notre guide JSON Output avec DeepSeek détaille les contrôles de structure, les sorties vides et la troncature.

Relayer le streaming SSE

Avec stream: true, DeepSeek renvoie des événements SSE jusqu’à data: [DONE]. Un dernier chunk d’utilisation peut être demandé avec stream_options: {"include_usage":true} ; il peut ne contenir aucun élément dans choices. Le flux peut également comporter des commentaires : keep-alive et des lignes vides.

  • Votre backend ouvre le flux DeepSeek et le relaie sans buffering.
  • Il ignore les lignes vides et celles qui commencent par :.
  • Il parse uniquement les lignes commençant par data: et s’arrête à [DONE].
  • En Thinking, il sépare reasoning_content de content et n’affiche que la réponse finale utile.
  • Il annule l’appel amont lorsque le navigateur se déconnecte.
  • Il ne retente jamais la génération après avoir transmis les premiers octets au client.

L’API native EventSource ouvre un GET et ne convient donc pas directement à une route POST avec corps JSON. Utilisez fetch et response.body.getReader(), ou transformez votre protocole applicatif. Ne supposez jamais qu’un chunk réseau correspond exactement à un événement SSE complet.

9. Compatibilité OpenAI : ce qu’elle signifie réellement

La documentation indique que l’API DeepSeek utilise un format compatible avec OpenAI et Anthropic. Cela facilite la réutilisation de certains SDK, mais ne signifie pas que DeepSeek reproduit tous les endpoints, rôles, paramètres et types de contenu de ces fournisseurs.

Formulation sûre : L’API DeepSeek accepte un format compatible avec OpenAI Chat Completions, mais cette compatibilité n’implique ni parité d’endpoints ni prise en charge de toutes les fonctions OpenAI.

  • Utilisez l’URL de base officielle https://api.deepseek.com sans présenter /v1 comme obligatoire.
  • Utilisez les rôles documentés par DeepSeek : system, user, assistant et tool.
  • Avec le SDK OpenAI pour Python, envoyez l’extension thinking dans extra_body.
  • Testez chaque option réellement utilisée et gardez une couche d’adaptation dans votre backend.
  • N’exposez pas les objets de réponse d’un fournisseur dans tout votre domaine métier.
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DEEPSEEK_API_KEY"],
    base_url="https://api.deepseek.com",
    timeout=120.0,
    max_retries=0,
)

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{
        "role": "user",
        "content": "Résume ce texte en français.",
    }],
    max_tokens=300,
    extra_body={"thinking": {"type": "disabled"}},
)

if not response.choices:
    raise RuntimeError("Réponse vide")
choice = response.choices[0]
content = choice.message.content
if choice.finish_reason != "stop" or not content or not content.strip():
    raise RuntimeError("Réponse vide ou interrompue")
print(content.strip())

Ce code est une voie d’accès pratique, pas une promesse de portabilité automatique. Le format Anthropic utilise https://api.deepseek.com/anthropic et reste lui aussi partiel : plusieurs paramètres et blocs de contenu sont ignorés ou non pris en charge.

10. Erreurs, retry et limites de concurrence

SignalSignification documentéeTraitement recommandé
400Format de requête invalideCorriger le payload ; ne pas retenter à l’identique.
401Authentification incorrecteFaire tourner ou vérifier la clé côté serveur.
402Solde insuffisantVérifier le compte ou activer un mode dégradé.
422Paramètre invalideComparer le corps à la référence du endpoint.
429Rythme ou concurrence dépassésMettre en file et appliquer un backoff borné avec jitter.
500Erreur serveurRetenter brièvement si l’opération le permet.
503Serveur surchargéRetenter après une attente ou utiliser un fallback testé.
finish_reason=lengthSortie tronquéeNe pas l’utiliser comme réponse complète ; ajuster la limite ou la demande.
Contenu videRéponse inexploitableÉchouer proprement et mesurer l’incident.

Les limites officielles actuelles sont exprimées en connexions concurrentes par compte : 2 500 pour V4 Flash et 500 pour V4 Pro. Une connexion reste comptée jusqu’à la fin de la réponse, y compris en streaming. Créer plusieurs clés dans le même compte ne multiplie pas la capacité.

  • Retentez seulement 429, 500 et 503, avec un nombre maximal de tentatives.
  • Si Retry-After est présent, respectez-le ; la documentation actuelle ne garantit pas ce header.
  • Ajoutez un jitter afin que toutes les instances ne retentent pas simultanément.
  • Ne retentez pas une erreur 400, 401, 402 ou 422 sans correction.
  • Après l’envoi des premiers octets d’un stream, ne redémarrez pas silencieusement la génération.
  • Une panne réseau après l’envoi est ambiguë : évitez un retry automatique lorsqu’un doublon peut coûter ou produire un effet.

Consultez les guides dédiés aux limites de concurrence DeepSeek et aux codes d’erreur de l’API.

11. Calculer le coût et mesurer le cache

Les prix suivants sont ceux publiés le 22 juillet 2026, par million de tokens. Ils peuvent changer.

ModèleEntrée, cache hitEntrée, cache missSortie
deepseek-v4-flash0,0028 $0,14 $0,28 $
deepseek-v4-pro0,003625 $0,435 $0,87 $

Pour estimer une requête, additionnez les trois composantes : tokens d’entrée en cache × prix hit, tokens d’entrée hors cache × prix miss et tokens de sortie × prix de sortie, puis divisez chaque quantité par un million. Basez la facturation interne sur les champs usage de la réponse, pas sur la longueur en caractères.

Le cache de contexte sur disque est activé automatiquement. Il fonctionne au mieux sur des préfixes répétés, sans garantir un hit, et les sorties sont générées à nouveau. Mesurez prompt_cache_hit_tokens et prompt_cache_miss_tokens. Ne présentez pas ce cache comme une mémoire permanente : les entrées inutilisées sont généralement supprimées après quelques heures à quelques jours.

  • Limitez la taille d’entrée et max_tokens par fonction.
  • Définissez un budget quotidien et par utilisateur dans votre propre système.
  • Surveillez le coût par résultat valide, pas seulement par requête HTTP.
  • Placez les instructions stables au début pour favoriser un préfixe réutilisable.
  • Ne gonflez pas l’historique : résumez ou supprimez ce qui n’est plus nécessaire.

12. Sécurité et confidentialité pour une application destinée à la France

Une clé cachée ne rend pas automatiquement l’application conforme ou sûre. Votre produit décide quelles données il collecte, ce qu’il transmet à DeepSeek, qui peut y accéder et combien de temps cela est conservé. Les conditions de l’Open Platform placent la responsabilité de l’application en aval sur son opérateur et interdisent d’exposer la clé dans le code client.

Information utilisateur : indiquez clairement que la saisie est transmise à DeepSeek afin de produire une réponse, que celle-ci est générée par une IA et qu’elle peut contenir des erreurs. Publiez votre propre politique de confidentialité et n’envoyez pas de données sensibles par défaut.

  • Minimisation : retirez noms, emails, identifiants, secrets et champs inutiles avant l’appel.
  • Données sensibles : n’envoyez pas de mots de passe, clés, données de santé ou autres données personnelles sensibles dans un prompt.
  • Base légale : identifiez la finalité et le fondement applicable avant de traiter les données de tiers.
  • Transferts : cartographiez les destinataires et transferts internationaux décrits par les politiques officielles.
  • Durée : définissez la conservation des entrées, sorties, comptes et logs ; ne conservez pas « au cas où ».
  • Droits : prévoyez un canal pour l’accès, la rectification, l’effacement et le signalement d’une erreur.
  • Décisions : ajoutez une revue humaine pour un résultat susceptible d’affecter significativement une personne.
  • Traçabilité : journalisez des identifiants techniques et métriques, pas le contenu complet par défaut.

Le terme « stateless » signifie que l’API n’injecte pas automatiquement l’historique d’une conversation : votre application renvoie les messages nécessaires. Il ne constitue pas une promesse d’absence de cache, de traitement, de journalisation ou de conservation. Consultez la politique de confidentialité officielle DeepSeek et demandez un avis juridique adapté au traitement envisagé. Cette page ne fournit pas un avis juridique.

La politique de confidentialité de deepseek-fr.ai concerne uniquement ce site éditorial, qui ne reçoit aucun prompt. Elle ne couvre pas votre application : vous devez rédiger et appliquer vos propres règles. Consultez également nos mentions d’indépendance et avertissements.

13. Tests et checklist de déploiement

Tests automatisés indispensables

  • Le frontend n’envoie que les champs autorisés ; une tentative d’injecter apiKey, model ou system est refusée.
  • Le backend ajoute seul l’Authorization Bearer et le modèle autorisé.
  • Le payload contient thinking et, si nécessaire, reasoning_effort, sans les quatre paramètres sans effet.
  • Un message vide, trop long ou comportant un champ inconnu renvoie 400 sans appeler DeepSeek.
  • Une réponse vide, mal formée ou tronquée est refusée.
  • 429, 500 et 503 déclenchent un retry borné ; les autres statuts ne bouclent pas.
  • Un timeout annule l’appel et renvoie une erreur publique sans détail secret.
  • Le résultat du modèle est affiché en texte, sans interprétation HTML.

Les exemples Node.js et FastAPI de cette page ont été vérifiés avec des réponses HTTP simulées : endpoint, modèle, en-tête d’autorisation, Thinking, validation, retry explicite et timeout. Aucun appel réel ou payant n’a été effectué.

Checklist avant production

  1. Créer une clé distincte de celle du développement et la stocker dans un coffre de secrets.
  2. Choisir Flash ou Pro avec un jeu d’évaluation, puis figer la configuration.
  3. Activer authentification, autorisation, quotas par compte et limite de taille.
  4. Ajouter HTTPS, CSP, protection CSRF selon l’authentification et règles de proxy fiables.
  5. Définir des timeouts séparés pour connexion, lecture et requête globale.
  6. Surveiller erreurs, p50/p95, tokens hit/miss/output, coût et finish_reason.
  7. Créer une alerte de dépense et un interrupteur permettant de désactiver la fonction IA.
  8. Prévoir un message de panne et, si le métier le permet, une file asynchrone ou un fallback testé.
  9. Publier les informations de confidentialité et un canal de contact.
  10. Relancer les évaluations à chaque changement de modèle, prompt, SDK ou paramètre.
  11. Verrouiller les dépendances avec un lockfile et vérifier les mises à jour en CI.
  12. Ne pas utiliser --reload ni un serveur de développement en production.

14. Faire évoluer l’application

Conversation multi-tour

L’endpoint n’ajoute pas l’historique automatiquement. Stockez ou résumez les messages utiles côté application, puis renvoyez-les à chaque tour sans dépasser la fenêtre de contexte. Pour une interface conversationnelle, consultez notre guide pour créer un chatbot avec l’API DeepSeek. Ce guide décrit votre propre produit ; deepseek-fr.ai héberge son propre assistant en français.

Tool Calls et actions métier

Un Tool Call n’exécute aucune action à lui seul. Le modèle propose un nom et des arguments ; votre backend valide le JSON, vérifie les permissions, exécute explicitement la fonction puis renvoie le résultat. Exigez une confirmation pour une action destructive ou financière. Consultez le guide Function Calling DeepSeek.

Recherche documentaire et RAG

Pour répondre à partir de documents internes, récupérez d’abord les passages autorisés, joignez uniquement les extraits nécessaires et demandez des citations vers leurs identifiants. Les contrôles d’accès doivent être appliqués avant la recherche : le modèle ne doit jamais décider seul quels documents un utilisateur peut consulter.

Travaux longs et files de tâches

Pour un document volumineux ou un traitement non interactif, placez la demande dans une file, associez-la à un utilisateur, définissez un délai et fournissez un statut. Cette architecture évite de maintenir une connexion navigateur fragile et permet de contrôler la concurrence du compte.

FAQ — créer une application avec DeepSeek

Puis-je appeler DeepSeek directement depuis React ou une application mobile ?

Non avec une clé secrète intégrée au client. Elle pourrait être extraite et utilisée pour consommer votre solde. Le client appelle un backend qui authentifie, limite et contrôle la requête.

Quel modèle choisir pour un premier MVP ?

Commencez souvent avec deepseek-v4-flash en non-thinking pour une fonction simple et économique, puis comparez Flash et Pro sur des cas réels. Choisissez celui qui atteint votre seuil de qualité, de latence et de coût.

Quels paramètres faut-il éviter ?

En Thinking, temperature et top_p sont ignorés. presence_penalty et frequency_penalty sont obsolètes et sans effet dans tous les modes. Ne les envoyez pas.

L’API DeepSeek remplace-t-elle l’API OpenAI sans modifier le code ?

Pas nécessairement. Le format Chat Completions est compatible, mais les endpoints, paramètres, rôles et fonctionnalités ne sont pas tous équivalents. Testez chaque capacité et gardez une couche d’adaptation.

Comment éviter une facture imprévue ?

Limitez les entrées et sorties, mesurez usage, appliquez des quotas par utilisateur, surveillez les erreurs et fixez des budgets par fonction. Le cache peut réduire certains coûts d’entrée, mais ses hits ne sont pas garantis.

Pourquoi une réponse JSON peut-elle rester inutilisable ?

Une syntaxe JSON correcte ne garantit pas votre schéma métier ni la vérité des valeurs. Le contenu peut aussi être vide ou interrompu. Contrôlez finish_reason, parsez le texte et validez chaque champ côté serveur.

Peut-on envoyer des données personnelles depuis la France ?

Ne le faites pas par défaut. Évaluez la finalité, la base légale, la minimisation, les transferts, l’information des personnes, les durées et les mesures de sécurité. Pour un traitement à risque, demandez l’avis de votre DPO ou d’un juriste.

deepseek-fr.ai fournit-il l’application décrite ?

Non. deepseek-fr.ai est un guide indépendant. Il ne fournit ni application, ni chatbot, ni endpoint API et ne traite aucun prompt. Vous exécutez les exemples dans votre propre infrastructure avec une clé officielle DeepSeek.

Sources officielles et méthode

Les faits attribués à DeepSeek ont été contrôlés le 22 juillet 2026 dans les sources primaires suivantes. Les recommandations d’architecture, validation, retry, tests et sécurité sont des conseils éditoriaux de deepseek-fr.ai.

Pour la référence générale, les exemples Python et les paramètres actuels, consultez aussi notre documentation française de l’API DeepSeek.