DeepSeek Tool Calls permite que o modelo solicite funções definidas pela sua aplicação. O modelo pode pedir uma consulta de pedido, cálculo de frete ou abertura de ticket, mas não executa a função. Seu backend decide se a ferramenta existe, valida os argumentos, verifica a permissão do usuário, executa o código permitido e devolve o resultado com o tool_call_id correspondente.
Regra de segurança: o modelo propõe; o backend autoriza e executa. JSON válido, strict mode ou uma descrição convincente nunca substituem autenticação, autorização e validação das regras do negócio.
Status em 19 de julho de 2026: os IDs hospedados com Tool Calls são
deepseek-v4-flashedeepseek-v4-pro. Os aliasesdeepseek-chatedeepseek-reasonerserão retirados em 24 de julho de 2026, às 15:59 UTC. Não os use em novas implementações.
Última verificação técnica: 19 de julho de 2026. Este site e este guia são independentes, sem afiliação, autorização ou endosso da DeepSeek.
Resumo da implementação correta
- declare funções no array
toolscom nome, descrição e JSON Schema; - envie a pergunta e as ferramentas para
/chat/completions; - aceite uma chamada de ferramenta somente com
finish_reason: "tool_calls"; - valide nome, ID e argumentos antes de executar;
- verifique a autorização do usuário dentro da função;
- devolva exatamente um resultado para cada chamada, usando o mesmo
tool_call_id; - repita o loop até uma resposta com
finish_reason: "stop"ou até o limite definido; - em Thinking Mode, preserve integralmente
reasoning_contentnas mensagens de assistente que envolvem ferramentas; - não envie
tool_choiceno Thinking Mode V4; - não exponha API key, shell, banco ou cliente HTTP genérico ao modelo.
Esta página trata especificamente do loop de ferramentas. Para autenticação, endpoint e parâmetros gerais, consulte DeepSeek API. Para o objeto completo de requisição e resposta, use Chat Completions.
Como Tool Calls funciona
| Etapa | Responsável | Ação |
|---|---|---|
| 1 | Usuário | Pede uma informação ou ação |
| 2 | Aplicação | Envia messages e tools à API |
| 3 | Modelo | Responde diretamente ou retorna tool_calls |
| 4 | Backend | Valida função, argumentos, identidade e autorização |
| 5 | Backend | Executa somente a função permitida |
| 6 | Aplicação | Adiciona uma mensagem role="tool" por chamada |
| 7 | Aplicação | Envia novamente todo o contexto necessário |
| 8 | Modelo | Solicita outra ferramenta ou conclui com stop |
Uma interação pode exigir várias rodadas. Por exemplo, o modelo pode consultar a data, depois o estoque e só então responder. Um código que executa apenas a primeira chamada e espera texto final falha em fluxos multi-step. O loop precisa ser limitado para evitar custo ou repetição sem fim.
Objetos e campos essenciais
| Campo | Função | Validação necessária |
|---|---|---|
tools | Lista de funções apresentadas ao modelo | Somente ferramentas necessárias; máximo documentado de 128 |
function.name | Identificador da função | Allowlist exata; até 64 caracteres no formato aceito |
function.parameters | JSON Schema dos argumentos | Validar novamente no backend |
tool_choice | Controla escolha em Non-Thinking | Não enviar em Thinking V4 |
tool_calls | Chamadas propostas pelo modelo | Array, IDs únicos, tipo function e nome permitido |
function.arguments | String que pretende conter JSON | Limitar tamanho, fazer parse e aplicar schema |
tool_call_id | Liga o resultado à chamada | Copiar exatamente o ID recebido |
reasoning_content | Raciocínio retornado em Thinking | Preservar nas rodadas com ferramentas |
finish_reason | Indica por que a geração parou | Executar apenas em tool_calls; concluir apenas em stop |
A API aceita apenas funções como tipo de ferramenta e documenta até 128 funções por requisição. Isso é um limite técnico, não uma recomendação para declarar 128 ferramentas. Uma lista curta, bem descrita e específica reduz seleções erradas e facilita autorização.
Exemplo completo em Node.js
O exemplo consulta um pedido fictício. Ele usa Node.js 20 ou superior, fetch nativo e Zod. A função é read-only, mas ainda verifica se o pedido pertence ao usuário autenticado. Em uma aplicação real, authContext deve vir da sessão validada no backend, nunca de um argumento escolhido pelo modelo.
mkdir deepseek-tools-demo
cd deepseek-tools-demo
npm init -y
npm install zod
npm pkg set type=module
Salve o código como tool-calls.mjs:
import { z } from "zod";
const API_KEY = process.env.DEEPSEEK_API_KEY;
const MODEL = process.env.DEEPSEEK_MODEL || "deepseek-v4-flash";
const THINKING_ENABLED = process.env.DEEPSEEK_THINKING === "enabled";
const MAX_TOOL_ROUNDS = 8;
if (!API_KEY) {
throw new Error("DEEPSEEK_API_KEY não configurada.");
}
if (!["deepseek-v4-flash", "deepseek-v4-pro"].includes(MODEL)) {
throw new Error("DEEPSEEK_MODEL deve ser deepseek-v4-flash ou deepseek-v4-pro.");
}
const tools = [
{
type: "function",
function: {
name: "get_order_status",
description:
"Consulta um pedido pelo ID. Use somente quando o usuário pedir o status de um pedido específico.",
parameters: {
type: "object",
properties: {
order_id: {
type: "string",
description: "ID no formato BR seguido por cinco dígitos, por exemplo BR12345.",
pattern: "^BR[0-9]{5}$",
},
},
required: ["order_id"],
additionalProperties: false,
},
},
},
];
const orderArgsSchema = z.object({
order_id: z.string().regex(/^BR[0-9]{5}$/),
}).strict();
const orders = new Map([
[
"BR12345",
{
ownerId: "user_42",
status: "em_transporte",
previsaoEntrega: "2026-07-22",
},
],
[
"BR99999",
{
ownerId: "user_77",
status: "aguardando_pagamento",
previsaoEntrega: null,
},
],
]);
function getOrderStatus(args, authContext) {
const order = orders.get(args.order_id);
if (!order || order.ownerId !== authContext.userId) {
return { found: false };
}
return {
found: true,
order_id: args.order_id,
status: order.status,
previsao_entrega: order.previsaoEntrega,
};
}
const toolRegistry = new Map([
[
"get_order_status",
{
schema: orderArgsSchema,
execute: getOrderStatus,
},
],
]);
class DeepSeekApiError extends Error {
constructor(status) {
super(`Falha na DeepSeek API: ${status}`);
this.name = "DeepSeekApiError";
this.status = status;
}
}
async function callDeepSeek(messages, apiUserId) {
const payload = {
model: MODEL,
messages,
tools,
thinking: { type: THINKING_ENABLED ? "enabled" : "disabled" },
max_tokens: 1200,
stream: false,
user_id: apiUserId,
};
if (THINKING_ENABLED) {
payload.reasoning_effort = "high";
} else {
// tool_choice é usado somente no modo Non-Thinking.
payload.tool_choice = "auto";
}
const response = await fetch(
"https://api.deepseek.com/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(90_000),
},
);
if (!response.ok) {
throw new DeepSeekApiError(response.status);
}
const data = await response.json();
const choice = data?.choices?.[0];
if (!choice?.message || typeof choice.finish_reason !== "string") {
throw new Error("Resposta inválida da DeepSeek API.");
}
return choice;
}
function normalizeAssistantMessage(message, hasToolCalls) {
const normalized = {
role: "assistant",
content: typeof message.content === "string" ? message.content : "",
};
if (hasToolCalls) {
normalized.tool_calls = message.tool_calls;
}
if (THINKING_ENABLED && hasToolCalls) {
if (typeof message.reasoning_content !== "string") {
throw new Error("reasoning_content ausente em Tool Call com Thinking.");
}
normalized.reasoning_content = message.reasoning_content;
}
return normalized;
}
function executeToolCall(toolCall, authContext, seenToolCallIds) {
if (
!toolCall ||
toolCall.type !== "function" ||
typeof toolCall.id !== "string" ||
!toolCall.id ||
typeof toolCall.function?.name !== "string" ||
typeof toolCall.function?.arguments !== "string"
) {
throw new Error("Tool Call malformada.");
}
if (seenToolCallIds.has(toolCall.id)) {
throw new Error("tool_call_id duplicado.");
}
seenToolCallIds.add(toolCall.id);
const registeredTool = toolRegistry.get(toolCall.function.name);
if (!registeredTool) {
throw new Error("Função não permitida.");
}
if (toolCall.function.arguments.length > 4096) {
throw new Error("Argumentos da ferramenta excedem o limite local.");
}
let rawArguments;
try {
rawArguments = JSON.parse(toolCall.function.arguments);
} catch {
throw new Error("Argumentos da ferramenta não são JSON válido.");
}
const parsedArguments = registeredTool.schema.safeParse(rawArguments);
if (!parsedArguments.success) {
throw new Error("Argumentos da ferramenta foram rejeitados pelo schema local.");
}
const result = registeredTool.execute(parsedArguments.data, authContext);
return {
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result),
};
}
async function runToolLoop({ userMessage, authContext, apiUserId }) {
const messages = [
{
role: "system",
content:
"Você é um assistente de pedidos. Use ferramentas para consultar dados. Nunca invente status.",
},
{ role: "user", content: userMessage },
];
const seenToolCallIds = new Set();
for (let round = 1; round <= MAX_TOOL_ROUNDS; round += 1) {
const choice = await callDeepSeek(messages, apiUserId);
const toolCalls = Array.isArray(choice.message.tool_calls)
? choice.message.tool_calls
: [];
if (choice.finish_reason === "stop") {
if (toolCalls.length !== 0) {
throw new Error("Resposta stop contém Tool Calls inesperadas.");
}
const finalContent = choice.message.content;
if (typeof finalContent !== "string" || !finalContent.trim()) {
throw new Error("Resposta final vazia.");
}
return finalContent;
}
if (choice.finish_reason !== "tool_calls") {
throw new Error(`Geração não concluída: ${choice.finish_reason}`);
}
if (toolCalls.length === 0) {
throw new Error("finish_reason=tool_calls sem chamadas de ferramenta.");
}
const assistantMessage = normalizeAssistantMessage(
choice.message,
true,
);
messages.push(assistantMessage);
const toolResults = toolCalls.map((toolCall) =>
executeToolCall(toolCall, authContext, seenToolCallIds),
);
if (toolResults.length !== toolCalls.length) {
throw new Error("Nem todas as Tool Calls receberam resultado.");
}
messages.push(...toolResults);
}
throw new Error(`Limite de ${MAX_TOOL_ROUNDS} rodadas de ferramentas atingido.`);
}
const authContext = {
// Em produção, derive da sessão autenticada no servidor.
userId: "user_42",
};
const answer = await runToolLoop({
userMessage: "Qual é o status do meu pedido BR12345?",
authContext,
apiUserId: "demo_user_42",
});
console.log(answer);
Execute em Non-Thinking:
DEEPSEEK_API_KEY="sua_chave" \
DEEPSEEK_MODEL="deepseek-v4-flash" \
DEEPSEEK_THINKING="disabled" \
node tool-calls.mjs
Para Thinking, altere somente o ambiente:
DEEPSEEK_API_KEY="sua_chave" \
DEEPSEEK_MODEL="deepseek-v4-flash" \
DEEPSEEK_THINKING="enabled" \
node tool-calls.mjs
Controles aplicados pelo exemplo
- aceita apenas os dois IDs V4 atuais;
- mantém a API key no ambiente do servidor;
- limita o loop a oito respostas do modelo;
- executa apenas nomes registrados em
toolRegistry; - limita o tamanho da string de argumentos antes de fazer parse;
- valida argumentos novamente com Zod em modo estrito;
- verifica o proprietário do pedido dentro da função;
- recusa IDs duplicados e cria um resultado para cada chamada;
- preserva
reasoning_contentnas mensagens de ferramentas com Thinking; - conclui somente quando
finish_reasonéstope há conteúdo; - rejeita
length,content_filter,insufficient_system_resourcee valores desconhecidos.
A base falsa existe apenas para tornar o exemplo reproduzível. Troque-a por uma camada de serviço que use credenciais de baixo privilégio, queries parametrizadas, timeout e auditoria. Não permita que o modelo escreva SQL, URL ou comando de shell e os execute diretamente.
Thinking Mode com Tool Calls
DeepSeek V4 suporta ferramentas em Thinking Mode, mas há dois requisitos de compatibilidade que mudam a implementação:
- Não envie
tool_choice: as notas oficiais de integração informam que V4 Thinking rejeita esse parâmetro. Deixe a escolha automática nesse modo. - Preserve
reasoning_content: quando a mensagem do assistente contém Tool Calls, o campo precisa voltar integralmente em todas as requisições subsequentes relevantes. Omissão pode causar erro 400. - Mantenha
contentpresente: normalize valor nulo para string vazia na mensagem de assistente com ferramentas.
O exemplo faz isso ao adicionar tool_choice: "auto" somente quando Thinking está desativado e ao reconstruir a mensagem de assistente com content, reasoning_content e tool_calls. Não tente resumir ou alterar reasoning_content no meio do loop.
No Thinking Mode, os parâmetros temperature, top_p, presence_penalty e frequency_penalty não têm efeito. O exemplo não os envia. O esforço documentado é high ou max.
Para um guia dedicado a conversas, streaming e parâmetros de raciocínio, consulte DeepSeek Thinking Mode.
Raw HTTP e OpenAI SDK usam posições diferentes
No payload HTTP enviado diretamente por fetch, thinking e user_id são campos de primeiro nível, como no exemplo Node.js. Ao usar o OpenAI SDK para Python, a documentação orienta colocar esses campos específicos em extra_body; reasoning_effort permanece como argumento da chamada.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": "Consulte meu pedido."}],
tools=tools,
reasoning_effort="high",
extra_body={
"thinking": {"type": "enabled"},
"user_id": "user_pseudonimo_42",
},
)
O trecho acima demonstra apenas a posição dos parâmetros. Um aplicativo Python ainda precisa implementar o mesmo loop limitado, preservar a mensagem completa do assistente, validar todas as chamadas e encerrar somente em stop.
tool_choice em Non-Thinking
As opções abaixo pertencem ao formato OpenAI em Non-Thinking. Não copie esses exemplos para Thinking V4.
| Valor | Comportamento | Quando usar |
|---|---|---|
auto | O modelo responde ou chama uma ou mais funções | Perguntas que podem precisar de dados externos |
none | Impede chamadas de ferramentas | Resposta exclusivamente textual |
required | Exige uma ou mais chamadas | Fluxo no qual toda resposta depende de verificação externa |
| Função específica | Força o nome declarado | Fluxo em que a aplicação já determinou a única ferramenta possível |
{
"tool_choice": {
"type": "function",
"function": { "name": "get_order_status" }
}
}
Forçar uma ferramenta não autoriza sua execução. O backend continua responsável por confirmar identidade, permissão, estado do recurso e validade semântica dos argumentos.
Strict mode Beta
Strict mode tenta fazer a saída da chamada cumprir o JSON Schema declarado. É um recurso Beta e requer:
- usar
https://api.deepseek.com/betacomo base URL; - definir
strict: trueem todas as funções do array; - fornecer um schema aceito pelo servidor.
{
"type": "function",
"function": {
"name": "get_order_status",
"strict": true,
"description": "Consulta um pedido pelo ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"pattern": "^BR[0-9]{5}$"
}
},
"required": ["order_id"],
"additionalProperties": false
}
}
}
Os tipos listados pela documentação Beta incluem object, string, number, integer, boolean, array, enum e anyOf. Em cada objeto, todas as propriedades precisam ser obrigatórias e additionalProperties deve ser false. Para strings, minLength e maxLength não são suportados; para arrays, minItems e maxItems também não são.
Strict mode controla formato, não intenção. Um order_id pode obedecer ao regex e pertencer a outra pessoa. Uma quantia pode ser número e ainda exceder o saldo permitido. Valide novamente com sua biblioteca, aplique regras do negócio e verifique permissão antes de qualquer efeito.
Como tratar finish_reason
| Valor | Ação segura |
|---|---|
tool_calls | Exigir chamadas válidas, executar todas as permitidas e continuar o loop |
stop | Exigir ausência de Tool Calls e conteúdo final não vazio; então encerrar |
length | Tratar como resposta incompleta; não executar nem exibir como conclusão |
content_filter | Interromper e aplicar a política da aplicação |
insufficient_system_resource | Tratar como falha transitória controlada |
| Ausente ou desconhecido | Falhar fechado e registrar apenas metadados seguros |
Não use apenas a presença de message.content para decidir que terminou. O modelo pode retornar texto junto com uma solicitação de ferramenta. O contrato do loop deve se basear em finish_reason e na consistência entre esse valor e tool_calls.
Controles de segurança para produção
1. Allowlist de ferramentas
Mapeie nomes estáticos para funções conhecidas. Nunca use eval, import dinâmico baseado no nome gerado ou reflexão que permita chamar qualquer método do processo.
2. Autorização fora do prompt
O prompt não é uma política de acesso. A função deve receber um contexto autenticado criado pelo servidor e verificar tenant, usuário, papel e recurso. Não permita que o modelo forneça user_id, tenant_id ou permissões como argumentos confiáveis.
3. Separar leitura de escrita
Consultas read-only podem seguir um fluxo automático após autorização. Cancelar, reembolsar, cobrar, enviar, apagar ou alterar precisa de confirmação explícita, proteção contra repetição e auditoria. A confirmação deve ocorrer na interface e ser associada à ação exata, não inferida de uma frase anterior.
4. Idempotência e concorrência
Operações de escrita devem aceitar uma idempotency key criada pelo backend. Grave o estado da ação antes de fazer retry e controle concorrência para impedir duas execuções simultâneas. Se houver timeout após o envio, verifique o estado real antes de repetir.
5. Reduzir a superfície de cada função
Prefira get_order_status(order_id) a ferramentas genéricas como run_sql(query), fetch_url(url) ou run_command(command). Interfaces estreitas permitem validação, autorização e logs compreensíveis.
6. Tratar resultados como não confiáveis
Resultados de CRM, páginas e documentos podem conter instruções maliciosas. Serialize apenas campos necessários e diga ao modelo que dados de ferramentas são conteúdo, não comandos. Prompt injection não deve alterar a allowlist ou permissões.
7. Logs com minimização
Registre request ID, ferramenta, decisão de autorização, duração, resultado e código de erro. Evite gravar chave, raciocínio completo, prompt, documento, token de sessão ou resposta integral sem necessidade e retenção definida.
Matriz mínima de testes
| Caso | Resultado esperado |
|---|---|
| Função desconhecida | Falhar sem executar |
| Argumentos que não são JSON | Falhar antes da função |
| Campo extra | Schema local rejeita |
| Pedido de outro usuário | Não revelar existência ou dados |
tool_call_id duplicado | Interromper o loop |
| Duas Tool Calls na mesma resposta | Um resultado correspondente para cada ID |
| Mais de oito rodadas | Encerrar sem nova chamada |
finish_reason=length | Não apresentar resposta parcial |
Thinking sem reasoning_content | Falhar antes da próxima requisição |
| Prompt injection no resultado | Não ampliar ferramentas ou permissões |
| Retry de ação de escrita | Idempotência impede duplicação |
Quando usar Tool Calls
- consultar estoque, pedido ou status de serviço;
- obter dados recentes de um sistema autorizado;
- calcular frete ou elegibilidade com uma função determinística;
- criar tickets ou tarefas após confirmação e autorização;
- orquestrar etapas específicas com contratos de entrada estreitos.
Não use Tool Calls quando uma resposta textual é suficiente, quando não existe função real, quando a aplicação ainda não tem autenticação ou quando uma decisão humana obrigatória não pode ser automatizada. JSON Output estrutura texto; Tool Calls solicita execução externa. São recursos diferentes.
Privacidade e transparência
O operador do sistema downstream é responsável pelos dados dos próprios usuários. Envie à API apenas os campos necessários, não inclua dados pessoais em user_id, não retorne um registro inteiro quando três campos bastam e defina retenção para logs de ferramentas.
- informe que a saída é gerada por IA e pode conter erros;
- explique quais fornecedores e sistemas recebem dados;
- obtenha base legal e consentimento quando aplicáveis;
- implemente acesso, correção e exclusão conforme a jurisdição;
- não apresente seu aplicativo como produto oficial ou endossado.
Perguntas frequentes
O modelo executa a função?
Não. Ele gera uma proposta estruturada. A aplicação valida, autoriza e executa a função real.
Quais modelos hospedados aceitam Tool Calls?
deepseek-v4-flash e deepseek-v4-pro, conforme o catálogo verificado.
Posso usar tool_choice no Thinking Mode?
Não no DeepSeek V4 Thinking pelo endpoint OpenAI-compatible. As notas oficiais de integração informam que o parâmetro é rejeitado. Omita-o e deixe a seleção automática.
Strict mode elimina a validação local?
Não. Ele ajuda no formato do JSON Schema, mas não verifica autorização, propriedade, estado do recurso ou regra de negócio.
Por que preservar reasoning_content?
Em Thinking com ferramentas, a DeepSeek exige que esse campo retorne nas requisições subsequentes relevantes. Removê-lo pode gerar erro 400 e quebrar a continuidade do raciocínio.
Quantas rodadas devo permitir?
Defina um limite de acordo com o fluxo e o orçamento. O exemplo usa oito como política local, não como limite oficial. Também monitore número de chamadas, tempo e tokens.
Conclusão
Uma implementação confiável de DeepSeek Tool Calls é um loop de controle, não uma execução automática de qualquer JSON produzido pelo modelo. Declare ferramentas estreitas, valide nome e argumentos, autorize com contexto do servidor, associe cada resultado ao tool_call_id e encerre somente em finish_reason: "stop".
Ao ativar Thinking, omita tool_choice e preserve reasoning_content. Mesmo com strict mode, mantenha validação local, limites de rodadas, confirmação para ações sensíveis e idempotência. Esses controles transformam a solicitação do modelo em uma operação que o seu sistema consegue explicar, testar e interromper.
Fontes oficiais
- Guia oficial de Tool Calls e strict mode
- Referência oficial de Chat Completions
- Thinking Mode e preservação de reasoning_content
- Notas oficiais de compatibilidade V4 para ferramentas em Thinking
- Release V4 e retirada dos aliases antigos
- Termos da DeepSeek Open Platform
Este conteúdo é independente e educacional. A DeepSeek não revisou, autorizou ou endossou este código ou guia.
