DeepSeek function calling: cómo usar herramientas en tu aplicación

DeepSeek function calling permite conectar el modelo con funciones de tu aplicación. El modelo solicita una operación y genera sus argumentos; tu servidor los valida, ejecuta la función permitida y devuelve el resultado para que el modelo continúe. La implementación y los permisos de ejecución quedan en tu código, como explica la documentación de Tool Calls.

La API directa admite herramientas con deepseek-flash y deepseek-v4-pro, según la tabla oficial de modelos. Aquí usarás Chat Completions y un programa completo en Node.js para consultar un inventario de demostración. Después verás cómo controlar la selección de funciones, activar strict y adaptar el historial cuando uses Thinking.

Cómo funciona la llamada a funciones en DeepSeek

Para una consulta que requiere datos externos, el intercambio sigue esta secuencia:

  1. Tu aplicación envía la pregunta y las funciones disponibles en tools.
  2. El modelo devuelve una o varias solicitudes en message.tool_calls.
  3. Tu código verifica el nombre, analiza los argumentos y ejecuta únicamente las funciones autorizadas.
  4. Conserva la respuesta del asistente y agrega un mensaje tool por resultado, asociado al tool_call_id correspondiente.
  5. Envía el historial ampliado. El modelo puede pedir otra consulta o redactar la respuesta final.
Flujo de Tool Calls en DeepSeek: solicitud de get_weather, resultado de la herramienta y respuesta final.

Por ejemplo, “¿hay unidades disponibles?” requiere leer el inventario. En cambio, si ya tienes los datos y solo necesitas convertirlos en un objeto JSON, puedes utilizar JSON Output. Generar una respuesta con formato JSON y solicitar una función resuelven necesidades diferentes. También puedes obtener datos mediante herramientas y dar formato a la respuesta final en una etapa posterior.

El resultado de la herramienta y el texto final son dos salidas distintas. Conserva los datos devueltos por tu sistema como evidencia de la consulta; si necesitas mostrar una cantidad exacta en una interfaz, puedes presentarla directamente desde esos datos.

Ejemplo completo en Node.js: consultar inventario

Prepara la clave y el entorno

Necesitas Node.js 24 o posterior y una clave de la plataforma de DeepSeek. El programa usa fetch nativo y no requiere instalar paquetes. La guía de primera llamada indica cómo obtener la clave y cuál es la URL de la API.

Crea un archivo .env en la carpeta del proyecto y sustituye el valor de ejemplo por tu clave:

DEEPSEEK_API_KEY=tu_clave_de_deepseek

Mantén ese archivo fuera del repositorio público y agrega .env a .gitignore. Ejecuta este programa en el servidor o en tu entorno local; la clave no debe incluirse en JavaScript enviado al navegador.

Los productos DEMO-A y DEMO-B, con 8 y 0 unidades, son datos ficticios incluidos en el programa. La función solo consulta ese inventario: no reserva ni modifica existencias.

Define la herramienta y completa el ciclo

Guarda el siguiente código como stock.mjs. El contrato acepta únicamente la propiedad sku y uno de los dos identificadores de demostración. En JSON Schema, required exige la presencia del campo y additionalProperties: false rechaza propiedades adicionales; el validador local aplica esas mismas reglas.

const ENDPOINT = "https://api.deepseek.com/chat/completions";
const MAX_RONDAS = 4;
const MAX_LLAMADAS = 4;
const inventario = new Map([["DEMO-A", 8], ["DEMO-B", 0]]);

const funciones = new Map([
  ["consultar_stock", ({ sku }) => ({
    sku,
    unidades_disponibles: inventario.get(sku),
  })],
]);

const tools = [{
  type: "function",
  function: {
    name: "consultar_stock",
    description: "Consulta el inventario de demostración de un SKU.",
    parameters: {
      type: "object",
      properties: {
        sku: { type: "string", enum: ["DEMO-A", "DEMO-B"] },
      },
      required: ["sku"],
      additionalProperties: false,
    },
  },
}];

async function ejecutarLlamada(call) {
  const fn = call.function;
  if (call.type !== "function" || !funciones.has(fn?.name)) {
    return { ok: false, error: "HERRAMIENTA_NO_PERMITIDA" };
  }

  let args;
  try {
    if (typeof fn.arguments !== "string") throw new Error();
    args = JSON.parse(fn.arguments);
  } catch {
    return { ok: false, error: "JSON_INVALIDO" };
  }

  if (
    args === null || typeof args !== "object" || Array.isArray(args) ||
    Object.keys(args).length !== 1 || !Object.hasOwn(args, "sku") ||
    typeof args.sku !== "string" || !inventario.has(args.sku)
  ) {
    return { ok: false, error: "ARGUMENTOS_INVALIDOS" };
  }

  const resultado = await funciones.get(fn.name)(args);
  return { ok: true, ...resultado };
}

async function main() {
  const apiKey = process.env.DEEPSEEK_API_KEY;
  if (!apiKey) throw new Error("Falta DEEPSEEK_API_KEY");

  // Un mismo plazo para todas las peticiones de esta consulta.
  const signal = AbortSignal.timeout(45_000);
  const messages = [
    {
      role: "system",
      content: "Consulta la herramienta para conocer el stock. " +
        "No inventes cantidades. Si falla, explica que no pudiste verificarlo.",
    },
    { role: "user", content: "¿Cuántas unidades hay de DEMO-A y DEMO-B?" },
  ];
  let llamadas = 0;
  const idsVistos = new Set();

  for (let ronda = 0; ronda < MAX_RONDAS; ronda++) {
    signal.throwIfAborted();
    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: {
        Authorization: "Bearer " + apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "deepseek-flash",
        messages,
        tools,
        tool_choice: "auto",
        thinking: { type: "disabled" },
        max_tokens: 1024,
        stream: false,
      }),
      signal,
    });

    if (!response.ok) {
      await response.body?.cancel();
      throw new Error("DeepSeek respondió HTTP " + response.status);
    }

    const data = await response.json();
    signal.throwIfAborted();
    const choice = data.choices?.[0];
    const message = choice?.message;
    if (message?.role !== "assistant") {
      throw new Error("Respuesta de assistant ausente");
    }
    const calls = message.tool_calls ?? [];
    if (!Array.isArray(calls)) throw new Error("tool_calls inválido");

    if (calls.length === 0) {
      if (
        choice.finish_reason !== "stop" ||
        typeof message.content !== "string" || !message.content.trim()
      ) throw new Error("No se recibió una respuesta final completa");

      console.log(message.content);
      return;
    }

    if (choice.finish_reason !== "tool_calls") {
      throw new Error("Llamadas incompletas: no se ejecutarán");
    }
    if (
      ronda === MAX_RONDAS - 1 ||
      llamadas + calls.length > MAX_LLAMADAS
    ) throw new Error("Se alcanzó el límite local del ciclo");

    for (const call of calls) {
      if (
        typeof call?.id !== "string" || !call.id ||
        idsVistos.has(call.id)
      ) throw new Error("ID de llamada ausente o repetido");
      idsVistos.add(call.id);
    }

    // Conserva la solicitud del modelo y responde a cada llamada.
    messages.push(message);
    for (const call of calls) {
      signal.throwIfAborted();
      const resultado = await ejecutarLlamada(call);
      signal.throwIfAborted();
      messages.push({
        role: "tool",
        tool_call_id: call.id,
        content: JSON.stringify(resultado),
      });
    }
    llamadas += calls.length;
  }
}

main().catch((error) => {
  console.error(error.message);
  process.exitCode = 1;
});

Desde esa carpeta, ejecuta:

node --env-file=.env stock.mjs

La opción --env-file carga la variable en process.env. El programa realiza solicitudes a la API al ejecutarlo con una clave válida; el consumo corresponde a tu cuenta.

Comprueba el resultado y las condiciones de salida

Una respuesta final coherente con los datos de demostración sería “DEMO-A tiene 8 unidades disponibles y DEMO-B no tiene existencias”. La redacción puede variar. Ese texto es ilustrativo: no representa una respuesta capturada de una llamada real.

El programa admite que las dos consultas lleguen juntas o en rondas diferentes. Cada resultado conserva su propio identificador, aunque ambas llamadas utilicen consultar_stock. Un valor de inventario igual a cero sigue siendo un resultado válido.

La validación diferencia tres problemas: una herramienta desconocida, una cadena JSON mal formada y argumentos que incumplen el contrato. Devuelve errores controlados al modelo sin ejecutar la función rechazada. La siguiente ronda puede corregir la solicitud, siempre dentro del presupuesto local.

  • Hasta cuatro solicitudes al modelo, incluida la que debe producir la respuesta final.
  • Hasta cuatro llamadas a herramientas en total, contando también las solicitudes rechazadas por el validador.
  • Un plazo compartido de 45 segundos para las solicitudes HTTP de toda la consulta.
  • Hasta 1.024 tokens de salida por solicitud, definidos en max_tokens.

Estas cifras son decisiones del ejemplo, no límites oficiales de DeepSeek. Si la última ronda todavía solicita herramientas, el programa se detiene antes de ejecutarlas: ya no queda otra petición disponible para continuar el diálogo.

La misma señal de AbortSignal.timeout() acompaña las peticiones y la lectura de sus respuestas. Si reemplazas la consulta local por una llamada a otra API, esa función también debe manejar la cancelación y su tiempo de espera. La señal no interrumpe automáticamente cualquier función de JavaScript.

El flujo y sus validaciones se comprobaron en Node.js 24.22.1 con respuestas simuladas, incluidos errores HTTP y respuestas incompletas. No se realizaron llamadas reales a DeepSeek durante esa comprobación.

tool_choice y Thinking: cómo controlar las llamadas

Cuándo usar auto, required o una función específica

El campo tool_choice controla la selección de herramientas en Chat Completions:

ValorComportamiento
"auto"Permite responder directamente o solicitar herramientas.
"none"Impide solicitar herramientas en ese turno.
"required"Exige una o varias llamadas.
{"type":"function","function":{"name":"consultar_stock"}}Fuerza la función indicada.
Opciones de tool_choice y restricciones del modo Thinking en la API de DeepSeek.

En Chat Completions, required y la selección por nombre no se admiten con Thinking activado; esa combinación devuelve HTTP 400. Desactiva Thinking para utilizarlas.

El ejemplo usa auto, por lo que una respuesta sin herramientas es posible. Si tu proceso exige una consulta, puedes usar required en la primera ronda y volver a auto después. Mantener required en todas las rondas dificulta llegar a un turno final sin herramientas.

Exigir una llamada tampoco demuestra que se consultaron todos los datos necesarios. Para una decisión que dependa de ambos productos, verifica en tu aplicación que recibiste un resultado satisfactorio por cada SKU antes de dar por completada la tarea.

Conserva reasoning_content al continuar con herramientas

El modo Thinking está activado de forma predeterminada. El ejemplo lo desactiva explícitamente para simplificar la primera integración. Si lo habilitas, la guía oficial de Thinking exige devolver completo el reasoning_content de los turnos anteriores en las solicitudes posteriores que incluyan tools, incluso cuando un turno no haya solicitado ninguna herramienta.

Aviso de DeepSeek sobre reenviar reasoning_content en solicitudes con tools para evitar el error 400.

Guarda la respuesta original del asistente, como hace messages.push(message). Si reconstruyes los mensajes seleccionando solo role y content, puedes perder ese campo y provocar un error 400. Mantén separados el historial que necesita la API y el texto que muestras al usuario.

Cómo activar el modo strict de DeepSeek

El modo strict de Tool Calls sigue documentado como Beta y restringe los argumentos al esquema de la función. Para usarlo con el ejemplo, sustituye la declaración de ENDPOINT por:

const ENDPOINT = "https://api.deepseek.com/beta/chat/completions";

Después de definir tools, y antes de ejecutar main(), activa strict en todas las funciones:

for (const tool of tools) {
  tool.function.strict = true;
}

Cada objeto del esquema debe incluir todas sus propiedades en required y declarar additionalProperties: false. El esquema del ejemplo cumple estas condiciones. La documentación indica que strict admite Thinking y non-thinking.

Modo strict de DeepSeek en Beta: requisitos de activación y ejemplo de esquema JSON para get_weather.

El soporte se limita a un subconjunto de JSON Schema. Entre las restricciones no admitidas figuran minLength, maxLength, minItems y maxItems. Comprueba cualquier esquema nuevo contra la lista oficial y aplica localmente las restricciones que necesites.

Conserva también la validación de tu aplicación. Un SKU con el tipo correcto puede pertenecer a un catálogo al que el usuario no tiene acceso; una fecha bien formada puede incumplir una regla de negocio. La corrección de los argumentos no concede permisos ni confirma que una operación deba ejecutarse.

Streaming y Responses API: qué debes adaptar

Reúne los argumentos completos antes de ejecutar

En streaming, las llamadas llegan en fragmentos dentro de delta.tool_calls. Agrupa los fragmentos por su index, conserva el identificador y el nombre, y concatena los argumentos. La referencia de Chat Completions describe estos campos.

Espera a que termine la generación de la llamada antes de analizar el JSON y ejecutar la función. Si la conexión se corta o la salida queda truncada, descarta la solicitud incompleta, aunque una parte parezca un objeto válido. El ejemplo usa stream: false para recibir la respuesta completa de una vez.

Adapta los campos cuando utilices Responses

La Responses API también permite funciones personalizadas, pero utiliza otro contrato:

ElementoChat CompletionsResponses API
Nombre de la funcióntools[].function.nametools[].name
Solicitud generadamessage.tool_calls[]Elemento function_call en output[]
Resultado que devuelvesMensaje role: "tool", con tool_call_id y contentElemento function_call_output, con call_id y output
Campos call_id, name y arguments de una llamada a función en la Responses API de DeepSeek.

En Responses, empareja el resultado con el call_id de la llamada, no con el id del elemento. Además, debes reenviar el historial en input: previous_response_id no mantiene la conversación por ti.

La tabla de compatibilidad de Responses indica que parallel_tool_calls y max_tool_calls se ignoran. Mantén los límites en tu aplicación. Que el modelo solicite varias funciones tampoco obliga a ejecutarlas simultáneamente; el ejemplo las procesa de forma secuencial.

Errores frecuentes y cómo resolverlos

Distingue los problemas del protocolo, los argumentos de la función y los fallos HTTP. La siguiente tabla reúne comprobaciones concretas para el flujo anterior; los códigos de servicio están descritos en la referencia oficial de errores.

SíntomaQué revisar
El modelo responde sin llamar a la función.Con auto es válido. Aclara cuándo necesita la herramienta o exige una llamada en el turno apropiado.
JSON_INVALIDO o ARGUMENTOS_INVALIDOS.Inspecciona la cadena de argumentos, sus tipos y propiedades. Corrige el contrato o permite una corrección acotada; no ejecutes los datos rechazados.
HERRAMIENTA_NO_PERMITIDA.El nombre no coincide con la lista del servidor. No lo conviertas en una ejecución dinámica.
Falta un resultado o su identificador no coincide.Conserva la solicitud del asistente y responde a cada llamada con ID válido, incluidas las rechazadas por el validador, con su resultado o error controlado.
La salida termina por longitud o interrupción.Trátala como incompleta. Revisa el presupuesto de tokens y el fallo de conexión antes de intentar otra generación.
HTTP 400 al activar Thinking o strict.Comprueba el historial de razonamiento, tool_choice, la ruta Beta y la compatibilidad del esquema, según el cambio realizado.
Se alcanza el límite local del ciclo.Revisa llamadas repetidas y errores que el modelo no logra corregir antes de aumentar el presupuesto.
HTTP 401 o 402.Revisa la clave para 401 y el saldo para 402. Modificar los argumentos de la función no resuelve esos problemas.
HTTP 429 o 503.Revisa la frecuencia/concurrencia para 429 y la disponibilidad del servicio para 503. Aplica una política de reintentos limitada si corresponde.

El programa no reintenta automáticamente una petición HTTP. Si agregas reintentos, conserva el estado del ciclo y distingue entre volver a consultar al modelo y volver a ejecutar una operación. Una respuesta final fallida no demuestra que una función ejecutada anteriormente haya fallado.

Antes de conectar herramientas reales

Para reemplazar el inventario ficticio por tu base de datos o servicio, define primero qué puede hacer cada función y para quién. Las recomendaciones de OWASP sobre permisos y autonomía de herramientas ayudan a establecer ese límite en el servidor.

  • Expón operaciones específicas. Una función para consultar existencias necesita menos capacidad que una herramienta que acepta SQL o comandos arbitrarios. Mantén una lista explícita de funciones permitidas.
  • Comprueba el acceso al recurso. Obtén la identidad de la sesión autenticada y verifica sus permisos antes de consultar registros privados; no aceptes la identidad que sugiera el modelo como autorización.
  • Separa lectura y modificación. Usa credenciales con los permisos necesarios. Para acciones de alto impacto, como un cobro o una eliminación, exige la autorización prevista por tu aplicación antes de ejecutarlas.
  • Acota las respuestas de las herramientas. Devuelve los campos necesarios y errores controlados. Evita enviar credenciales, detalles internos de excepciones o grandes volúmenes de datos ajenos a la consulta.
  • Registra el flujo. Guarda identificadores, duración, resultado y consumo por solicitud, sin incluir secretos. Esto permite detectar una herramienta que falla repetidamente o consultas que consumen demasiadas rondas.

Para operaciones que modifican datos, implementa idempotencia: asocia una operación lógica con una clave estable y conserva su resultado para reconocer un reintento. El patrón se explica en la guía de AWS sobre reintentos seguros. El tool_call_id enlaza mensajes de la conversación; por sí solo no evita que el modelo vuelva a solicitar la misma operación con otro identificador.

Antes de habilitar una herramienta nueva, prueba una solicitud válida, un recurso inexistente, argumentos inválidos, falta de permisos, llamadas duplicadas y un tiempo de espera agotado. Comprueba tanto los datos que ejecuta tu aplicación como lo que termina comunicando al usuario.

Última verificación de las fuentes: 10 de noviembre de 2026.