Cómo crear un chatbot con la API de DeepSeek

Última verificación documental y de código: 29 de julio de 2026. Los ejemplos se revisaron contra la API vigente, pero no se ejecutaron porque no había una API key con saldo. Para publicar resultados de funcionamiento, latencia y coste se necesita ejecutar el proyecto y conservar las respuestas y métricas brutas.

Un chatbot útil no es solo un cuadro de texto conectado a un modelo. Necesita una interfaz, un servidor que proteja la clave, gestión del historial, límites de uso, controles de seguridad y una forma de observar errores y costes. En esta guía construiremos una base funcional con Node.js, Express y la API compatible con OpenAI de DeepSeek. El navegador hablará únicamente con nuestro servidor; la clave nunca llegará al JavaScript público.

El resultado admite conversaciones de varias rondas, selección explícita entre DeepSeek-V4-Flash y DeepSeek-V4-Pro, respuestas en streaming, validación básica y un límite de historial. Después añadiremos un ejemplo de tool calling para mostrar la diferencia entre que el modelo solicite una función y que el servidor la ejecute de verdad.

DeepSeek-Español.chat es un sitio independiente y no está afiliado, autorizado ni respaldado por DeepSeek. Este tutorial describe una integración de terceros que utiliza la API de DeepSeek; no es documentación oficial ni una aplicación oficial de DeepSeek.

Qué ofrece la API oficial en la fecha de verificación

ElementoValor documentado
URL base OpenAIhttps://api.deepseek.com
Modelos principalesdeepseek-v4-flash y deepseek-v4-pro
Contexto1 millón de tokens
Salida máxima384K tokens
ModosThinking y Non-Thinking; Thinking está activado por defecto
Concurrencia por cuenta2.500 para Flash y 500 para Pro
EntradaTexto; V4 no es un modelo de visión

DeepSeek anunció la retirada de deepseek-chat y deepseek-reasoner para el 24 de julio de 2026 a las 15:59 UTC. A 29 de julio, la documentación operativa solo enumera los IDs V4 explícitos. No uses los aliases en código nuevo ni supongas que representan V3 o R1; su respuesta actual no se probó con una clave válida.

Los precios verificados por millón de tokens son: Flash, 0,0028 USD por entrada con caché, 0,14 USD sin caché y 0,28 USD por salida; Pro, 0,003625 USD con caché, 0,435 USD sin caché y 0,87 USD por salida. La caché de contexto en disco está activada por defecto, pero un acierto depende de reutilizar un prefijo compatible. No presentes una estimación de caché como ahorro garantizado.

Arquitectura mínima y segura

  • Navegador: envía el mensaje y muestra fragmentos de la respuesta.
  • Servidor propio: autentica al usuario, valida el cuerpo, añade instrucciones y llama a DeepSeek.
  • Almacenamiento opcional: guarda conversaciones solo si existe una necesidad y una política de retención claras.
  • Proveedor del modelo: procesa el prompt y devuelve texto o una solicitud de herramienta.

Esta separación evita exponer la clave API y permite aplicar cuotas por usuario. Una aplicación totalmente estática no puede guardar de forma segura una clave compartida. Ofrecer un campo para que cada visitante introduzca su propia clave es otro producto, con riesgos de phishing y soporte distintos; no lo mezcles con este diseño.

1. Preparar el proyecto

Necesitas Node.js 20 o posterior y una clave creada en la plataforma oficial. Instala Express, el SDK de OpenAI, validación con Zod y variables de entorno:

mkdir deepseek-chatbot
cd deepseek-chatbot
npm init -y
npm install express openai zod dotenv helmet express-rate-limit
npm pkg set type=module

Crea una variable DEEPSEEK_API_KEY en el entorno del servidor. No la escribas en el repositorio, en una plantilla HTML ni en una variable cuyo nombre empiece por VITE_, NEXT_PUBLIC_ o equivalente. Añade el archivo local de secretos a .gitignore y configura el secreto mediante el panel de tu proveedor al desplegar.

DEEPSEEK_API_KEY=tu_clave_del_servidor
PORT=3000

2. Crear un endpoint de chat

El siguiente servidor acepta como máximo 24 mensajes, limita cada contenido a 8.000 caracteres y restringe el modelo a una lista permitida. Estos límites no sustituyen el cálculo real de tokens, pero evitan cuerpos arbitrariamente grandes. En producción, autentica antes de consumir una cuota pagada.

import "dotenv/config";
import express from "express";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import OpenAI from "openai";
import { z } from "zod";

if (!process.env.DEEPSEEK_API_KEY) {
  throw new Error("Falta DEEPSEEK_API_KEY");
}

const app = express();
app.use(helmet());
app.use(express.json({ limit: "128kb" }));
app.use("/api/", rateLimit({ windowMs: 60_000, limit: 30 }));

const client = new OpenAI({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com"
});

const messageSchema = z.object({
  role: z.enum(["user", "assistant"]),
  content: z.string().min(1).max(8_000)
});

const requestSchema = z.object({
  model: z.enum(["deepseek-v4-flash", "deepseek-v4-pro"])
    .default("deepseek-v4-flash"),
  messages: z.array(messageSchema).min(1).max(24)
});

app.post("/api/chat", async (req, res) => {
  const parsed = requestSchema.safeParse(req.body);
  if (!parsed.success) {
    return res.status(400).json({ error: "Solicitud no válida" });
  }

  try {
    const completion = await client.chat.completions.create({
      model: parsed.data.model,
      messages: [
        { role: "system", content: "Responde en español con claridad. Si no sabes algo, dilo." },
        ...parsed.data.messages
      ],
      thinking: { type: "disabled" },
      stream: false
    });

    return res.json({
      answer: completion.choices[0]?.message?.content ?? "",
      usage: completion.usage ?? null
    });
  } catch (error) {
    console.error("DeepSeek request failed", error?.status ?? error?.message);
    return res.status(502).json({ error: "El proveedor no respondió correctamente" });
  }
});

app.listen(process.env.PORT || 3000);

Desactivamos Thinking para un chat rápido. Para tareas complejas puedes enviar thinking: { type: "enabled" } y reasoning_effort: "high" o "max". Los parámetros temperature y top_p pueden utilizarse en Non-Thinking, pero se ignoran cuando Thinking está habilitado. En cambio, presence_penalty y frequency_penalty están obsoletos en la API actual y no producen efecto en ningún modo. No construyas una función de producto ni controles de interfaz basados en parámetros que el modo o la API ignoran.

3. Añadir streaming sin revelar secretos

Para reducir la espera percibida, el servidor puede reenviar fragmentos como texto plano. Este ejemplo conserva la clave en el backend y cancela la petición si el navegador cierra la conexión:

app.post("/api/chat/stream", async (req, res) => {
  const parsed = requestSchema.safeParse(req.body);
  if (!parsed.success) return res.status(400).end("Solicitud no válida");

  res.setHeader("Content-Type", "text/plain; charset=utf-8");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  const controller = new AbortController();
  res.on("close", () => {
    if (!res.writableEnded) controller.abort();
  });

  try {
    const stream = await client.chat.completions.create({
      model: parsed.data.model,
      messages: parsed.data.messages,
      thinking: { type: "disabled" },
      stream: true
    }, { signal: controller.signal });

    for await (const part of stream) {
      const text = part.choices[0]?.delta?.content;
      if (text) res.write(text);
    }
    res.end();
  } catch {
    if (!res.headersSent) res.status(502);
    res.end();
  }
});

En el frontend, usa fetch, comprueba response.ok y lee response.body con un lector. Renderiza el contenido como texto por defecto. Si conviertes Markdown en HTML, sanitiza el resultado con una biblioteca mantenida; insertar directamente HTML generado por un modelo abre la puerta a XSS.

4. Gestionar el historial sin desbordar el contexto

El endpoint es sin estado: el cliente debe enviar las rondas necesarias en cada solicitud. Un contexto de un millón de tokens no significa que debas reenviar todo indefinidamente. Más entrada aumenta coste, latencia y exposición de datos. Mantén las últimas rondas relevantes, resume conversaciones largas en el servidor y conserva hechos estructurados por separado.

  • No confíes únicamente en caracteres; usa el campo usage para observar tokens reales.
  • No mezcles conversaciones de usuarios diferentes ni reutilices un identificador compartido.
  • Si envías user_id, usa un identificador opaco, sin correo, teléfono o nombre.
  • Define una caducidad para cualquier historial persistente y permite borrarlo.
  • Evita guardar el razonamiento interno salvo que exista una necesidad legítima y documentada.

5. Implementar tool calling correctamente

DeepSeek puede devolver el nombre y los argumentos de una función, pero no ejecuta esa función. Tu servidor debe validar los argumentos, comprobar permisos, ejecutar una operación permitida y devolver el resultado como mensaje tool. Nunca conviertas cualquier nombre sugerido por el modelo en una llamada dinámica al sistema.

const tools = [{
  type: "function",
  function: {
    name: "consultar_estado_pedido",
    description: "Consulta un pedido que pertenece al usuario autenticado",
    parameters: {
      type: "object",
      properties: { pedidoId: { type: "string", pattern: "^[A-Z0-9-]{6,24}$" } },
      required: ["pedidoId"],
      additionalProperties: false
    }
  }
}];

const first = await client.chat.completions.create({
  model: "deepseek-v4-flash",
  messages,
  tools,
  thinking: { type: "disabled" }
});

const call = first.choices[0]?.message?.tool_calls?.[0];
if (call?.function?.name === "consultar_estado_pedido") {
  const args = z.object({
    pedidoId: z.string().regex(/^[A-Z0-9-]{6,24}$/)
  }).parse(JSON.parse(call.function.arguments));
  const result = await consultarPedidoAutorizado(req.user.id, args.pedidoId);
  messages.push(first.choices[0].message);
  messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });

  const final = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages,
    thinking: { type: "disabled" }
  });
  const answer = final.choices[0]?.message?.content ?? "";
  // Devuelve `answer` desde la ruta que contiene este flujo.
}

Una operación de lectura también necesita autorización. Para cancelar pedidos, emitir reembolsos, enviar mensajes o modificar cuentas, exige confirmación humana y controles deterministas. El modelo no debe decidir por sí solo que una acción irreversible está autorizada.

6. Privacidad, seguridad y moderación

Si tu chatbot usa la API, el prompt sale del navegador y se entrega al proveedor para generar una respuesta. Guardar el historial localmente no convierte el procesamiento en local. Explica el flujo en tu política de privacidad, identifica a los proveedores, documenta la base jurídica aplicable y evita pedir datos sensibles que el caso de uso no necesita.

  • Autentica usuarios antes de permitir consumo significativo.
  • Aplica límites por cuenta, IP y organización, no solo el límite global del proveedor.
  • Elimina secretos, contraseñas y números completos de documentos antes del prompt.
  • Registra métricas y códigos de error, no el contenido completo por defecto.
  • Protege las herramientas contra prompt injection mediante listas permitidas y autorización en código.
  • Informa de que la salida puede contener errores y facilita revisión humana en usos de impacto.

Consulta también nuestra política de privacidad, la página de seguridad y los términos del sitio. Esos documentos deben describir tu implementación real y no copiar afirmaciones de la aplicación oficial.

7. Controlar coste y calidad

Usa Flash para soporte frecuente, clasificación y conversación rápida; reserva Pro para problemas que realmente se beneficien de mayor capacidad. Esta es una decisión de producto, no una garantía universal de calidad. Mide respuestas aceptadas, escalados humanos, latencia, tokens de entrada y salida, aciertos de caché y errores por modelo.

Define un presupuesto por sesión: máximo de rondas, máximo de tokens de salida y cuota diaria. Calcula el gasto con los tokens reales devueltos por API y los precios verificados. Nuestra página de precios de DeepSeek explica la diferencia entre caché y no caché, mientras que la guía de API reúne endpoints y parámetros.

8. Pruebas antes de publicar

  1. Prueba solicitudes vacías, demasiado grandes y con roles no permitidos.
  2. Simula 401, 402, 429, timeout y respuestas interrumpidas.
  3. Comprueba que la clave no aparece en bundles, mapas de código, logs ni respuestas.
  4. Evalúa conversaciones en español real, incluidas faltas ortográficas y ambigüedad.
  5. Intenta hacer que el modelo ignore reglas, invoque herramientas inexistentes o acceda a pedidos ajenos.
  6. Verifica que el chatbot admite “no lo sé” y ofrece escalado humano.
  7. Configura alertas por aumento de errores, latencia o gasto.

Un test que solo pregunta “hola” no valida un chatbot. Crea un conjunto de casos representativos con criterios objetivos, vuelve a ejecutarlo cuando cambie el modelo y conserva la versión de instrucciones evaluada. Las respuestas son probabilísticas; evita aserciones basadas en una frase exacta salvo en campos estructurados validados.

9. Diseñar una experiencia clara y recuperable

La interfaz debe distinguir entre “enviando”, “generando”, “completado” y “error”. Durante el streaming muestra un indicador visible y conserva el texto ya recibido, pero no presentes una respuesta cortada como terminada. Añade un botón de detener que cancele también la petición en el servidor; ocultar el texto en pantalla no detiene el consumo.

Permite copiar, volver a intentar y empezar una conversación nueva. Explica qué parte del historial se enviará y ofrece borrado. Para accesibilidad, anuncia las respuestas nuevas mediante una región adecuada, conserva el foco del teclado, no uses solo color para los estados y permite leer el contenido sin animaciones. Un chatbot eficaz debe seguir funcionando cuando el lector de pantalla o la conexión son lentos.

No muestres una cifra inventada de “confianza”. Es mejor presentar fuentes aportadas por el propio usuario, separar hechos de sugerencias y proporcionar un camino de escalado. En soporte, el traspaso humano debe incluir únicamente el contexto autorizado, el motivo del escalado y la respuesta incompleta, sin duplicar todo el historial por defecto.

10. Preparar operación e incidentes

Versiona el prompt de sistema y el esquema de herramientas. Despliega cambios a una fracción de usuarios, compara calidad, latencia y coste, y conserva una forma rápida de volver a la versión anterior. Configura un interruptor para desactivar herramientas o nuevas conversaciones sin borrar datos ni bloquear el acceso al historial existente.

Define quién responde ante una clave filtrada, un incremento de gasto, contenido peligroso o una caída del proveedor. El procedimiento debe cubrir rotación de secretos, pausa de tráfico, aviso interno, revisión de logs mínimos y comunicación honesta al usuario. Si cambias de modelo, vuelve a ejecutar las evaluaciones: compartir una interfaz de API no garantiza respuestas equivalentes.

Preguntas frecuentes

¿Puedo llamar a DeepSeek directamente desde el navegador?

No con una clave compartida. Cualquier secreto incluido en JavaScript público puede extraerse y utilizarse a tu cargo. Realiza la llamada desde un servidor o una función backend.

¿Debo usar deepseek-chat o deepseek-reasoner?

No. El plazo de retirada anunciado ya pasó y la lista operativa actual solo muestra los IDs V4 explícitos. Usa deepseek-v4-flash o deepseek-v4-pro y declara thinking de forma explícita.

¿La API acepta imágenes?

La API V4 documentada es de texto. Para imágenes necesitas un modelo de visión separado y debes explicar ese proveedor y flujo. DeepSeek-VL y OCR son familias descargables, no IDs de la API V4 oficial.

¿Tool calling significa navegación web automática?

No. El modelo propone una llamada estructurada; tu aplicación proporciona y ejecuta la herramienta. Una búsqueda web requiere implementar o contratar un servicio de búsqueda y validar sus resultados.

¿Un millón de tokens elimina la necesidad de resumir?

No. Un contexto grande sigue teniendo coste, latencia y riesgos de privacidad. Conserva únicamente lo necesario para la tarea y prueba la calidad de tus resúmenes.

Fuentes oficiales