Como fazer o DeepSeek V4 Pro 0813 devolver JSON confiável com tool calling e JSON Output

Se teu agente depende de resposta em JSON, tool calling DeepSeek V4 Pro é um dos dois caminhos oficiais, e o outro é o JSON Output via response_format. O build 0813 é a versão de disponibilidade geral por trás do endpoint deepseek-v4-pro, com tool calls no formato compatível com a API da OpenAI: array tools na requisição, message.tool_calls na resposta e devolução com role: "tool". Pro JSON puro, ligue response_format com type json_object, escreva a palavra json no prompt com exemplo e ajuste max_tokens. Quem precisa de schema fechado usa o strict mode Beta, que valida o JSON Schema no servidor
Fala aí, beleza? Agente não quebra por falta de inteligência do modelo, quebra quando o json.loads recebe um "Claro! Aqui está o resultado que você pediu:" no lugar do objeto que teu código esperava 😀
O DeepSeek V4 Pro saiu do preview e o build 0813 é a versão de disponibilidade geral por trás do endpoint deepseek-v4-pro
E ele te dá dois caminhos oficiais pra saída estruturada: tool calling no formato compatível com a API da OpenAI e JSON Output ligado pelo parâmetro response_format
A ideia aqui é essa: qual dos dois usar, como desenhar a ferramenta, qual contrato de retorno a tua integração deve assumir e o que fazer quando a chamada falha (porque falha, e tem caso reportado no repositório oficial)
Bora ver na prática?
JSON Output ou tool calling: qual dos dois usar em cada cenário
Antes de sair colando código, decide o objetivo, porque os dois recursos resolvem problemas diferentes
JSON Output é pra quando você só quer o TEXTO FINAL num formato fixo
Extração de campos de um documento, classificação de ticket, resposta que vai direto pro teu endpoint
O modelo não precisa acionar nada, ele só precisa responder em objeto e não em prosa
Tool calling é pra quando o modelo precisa chamar código teu e receber o resultado de volta
Consulta a banco, busca, ação em sistema externo
É o modelo dizendo "roda essa função com esses argumentos" e esperando o retorno pra fechar a resposta
Strict mode (Beta) entra quando o schema não pode variar e você quer a validação do lado do servidor, não só na tua camada
| Recurso | Use quando | Como liga |
|---|---|---|
| JSON Output | a resposta final precisa de formato fixo | response_format: {"type": "json_object"} |
| Tool calling | o modelo precisa executar código seu | array tools na request |
| Strict mode (Beta) | o schema é requisito e não sugestão | base_url beta + "strict": true |
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
E se liga: os dois convivem na mesma integração
Dá pra ter um agente com tools pra buscar dados e, na resposta final ao usuário, exigir JSON pra alimentar tua UI
Se você ainda está decidindo qual modelo colocar em cada etapa do fluxo, essa comparação entre V4 Pro e Flash ajuda a separar o que vale rodar no caro e o que vale rodar no barato
O que você precisa antes de começar
Lista curta, sem mistério:
- chave da API DeepSeek
- um cliente compatível com a API da OpenAI (o formato das requisições é o mesmo)
- o identificador de modelo
deepseek-v4-pro - noção dos limites de janela: 1.000.000 de tokens de contexto e 384.000 de saída máxima
- pro strict mode, o
base_urlapontando pro endpoint betahttps://api.deepseek.com/beta
Tome cuidado com o nome do modelo! Os nomes legados deepseek-chat e deepseek-reasoner foram aposentados
Se tua integração antiga ainda manda um deles hardcoded, é aí que o teu primeiro 400 vai nascer
E tem os limites do array tools, que é onde muita gente escorrega sem perceber:
- máximo de 128 functions por chamada
- o único
typeaceito hoje éfunction - o nome da função só aceita
a-z,A-Z,0-9, underscore e hífen, com até 64 caracteres
Nome de função com acento, espaço ou ponto não passa, então nada de buscar.pedido, buscar pedido ou buscar_pedido_do_usuário
Se o teu interesse é mais o uso do modelo no dia a dia do que a integração, tem outro post aqui sobre onde o V4 ajuda a programar
Como ligar o JSON Output no DeepSeek V4 Pro 0813 passo a passo
Esse é o caminho mais simples e é por onde eu sugiro começar
- Envie
response_formatcomtype json_objectna requisição
from openai import OpenAI
import json
client = OpenAI(api_key="SUA_CHAVE", base_url=DEEPSEEK_BASE_URL)
system = """
Você extrai dados de pedido e responde SEMPRE em json.
Exemplo do formato esperado:
{"cliente": "Maria", "itens": 3, "total": 199.9}
"""
resp = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": "Maria comprou 3 itens e pagou 199,90"},
],
response_format={"type": "json_object"},
max_tokens=2048,
)
O erro comum deste passo: achar que ligar o parâmetro basta e ir embora feliz
- Escreva a palavra json no prompt, junto de um exemplo do formato
Isso não é superstição de blogueiro, é exigência da documentação oficial do JSON Output: a palavra json precisa aparecer no prompt de sistema ou de usuário, acompanhada de um exemplo do JSON desejado
O erro comum deste passo: escrever "responda em formato estruturado" achando que é a mesma coisa
Não é, e o exemplo do objeto no prompt faz metade do trabalho de te entregar as chaves com os nomes que você quer
- Defina
max_tokensde forma razoável
A própria documentação orienta ajustar max_tokens pro JSON não ser truncado no meio
O erro comum deste passo: deixar o limite curto demais e receber um objeto cortado no meio de uma string
Aí o parse estoura e você vai culpar o modelo, quando o problema era o teu teto
Lembra que a saída máxima do V4 Pro é 384.000 tokens, então espaço tem, o que falta normalmente é você pedir
- Valide o parse do teu lado antes de usar
raw = resp.choices[0].message.content
if not raw:
raise RuntimeError("content vazio, tratar como falha e repetir")
dados = json.loads(raw)
O erro comum deste passo: confiar cegamente no parse
A DeepSeek avisa na própria documentação que o JSON Output pode OCASIONALMENTE retornar content vazio, e a orientação oficial é ajustar o prompt
Ou seja: content vazio é falha, não é resposta, e teu código precisa saber a diferença
Como desenhar a tool e ler o tool_calls na resposta
Agora o ciclo completo de function calling, que é onde mora o agente de verdade
Se você já mexeu com function calling na API da OpenAI, aqui é bem semelhante: mesmo desenho de request e mesmos campos na resposta
- Monte o array
toolscomtype: "function"
tools = [
{
"type": "function",
"function": {
"name": "buscar_pedido",
"description": "Busca um pedido pelo id no banco interno",
"parameters": {
"type": "object",
"properties": {
"pedido_id": {
"type": "string",
"description": "ID do pedido, ex: PED-1042"
}
},
"required": ["pedido_id"]
}
}
}
]
O erro comum deste passo: nome de função fora do padrão permitido
Só a-z, A-Z, 0-9, underscore e hífen, até 64 caracteres, e no máximo 128 functions no array
- Descreva os parâmetros no JSON Schema com carinho
A description de cada campo é o que o modelo lê pra decidir o que colocar ali
Campo sem descrição é campo que vem preenchido no chute
O erro comum deste passo: schema genérico demais, tipo um params do tipo string que "aceita qualquer coisa"
- Leia
message.tool_callse confirme ofinish_reason
choice = resp.choices[0]
if choice.finish_reason == "tool_calls":
for call in choice.message.tool_calls:
args = json.loads(call.function.arguments)
# roda a tua função aqui
Quando o modelo chama uma ferramenta, o finish_reason volta como tool_calls
Os outros valores possíveis são stop, length, content_filter e insufficient_system_resource
O erro comum deste passo: tratar toda resposta como texto e nunca checar finish_reason
Esse é o campo que separa "o modelo respondeu" de "o modelo pediu pra você executar algo"
- Execute a função e devolva o resultado como mensagem com
role: "tool"
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(resultado),
})
O erro comum deste passo: esquecer de devolver a mensagem com role tool e ficar reclamando que o modelo "ignorou a ferramenta"
Ele não ignorou, ele está esperando o retorno que nunca chegou
- Mande o contexto de volta pro modelo fechar a resposta
final = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
O erro comum deste passo: remontar o histórico pela metade
A mensagem do assistant que continha o tool_calls precisa estar lá também, não só o retorno da tua função
A referência completa do ciclo está na documentação de tool calls
Strict mode: como forçar o schema com validação no servidor
Até aqui o schema é uma sugestão forte
No strict mode, que é um recurso Beta, o servidor valida o JSON Schema que você mandou
- Aponte o
base_urlpro endpoint beta
client = OpenAI(
api_key="SUA_CHAVE",
base_url="https://api.deepseek.com/beta",
)
O erro comum deste passo (e é o campeão): manter o base_url padrão, marcar strict no schema e sair achando que a validação está ativa
Não está, e você só descobre em produção 😛
- Marque
strictcomo true em cada function do arraytools
tools = [
{
"type": "function",
"function": {
"name": "buscar_pedido",
"strict": True,
"description": "Busca um pedido pelo id no banco interno",
"parameters": {
"type": "object",
"properties": {
"pedido_id": {"type": "string"}
},
"required": ["pedido_id"]
}
}
}
]
O erro comum deste passo: marcar em uma function e esquecer das outras do array
- Escreva o schema só com os tipos suportados
O strict mode aceita object, string, number, integer, boolean, array, enum e anyOf
E o schema pode usar $defs e $ref, inclusive pra estruturas recursivas (comentário aninhado, árvore de categorias, esse tipo de coisa)
O erro comum deste passo: colar um schema enorme que veio de outra stack, cheio de tipo que aqui não roda
- Trate o erro que o servidor devolve
Quando o schema não está conforme ou usa um tipo não suportado, o servidor retorna erro
Isso é bom, viu? Melhor estourar na tua esteira do que receber um argumento fora de forma no meio de uma execução
O erro comum deste passo: engolir a exceção no try e seguir a vida sem log
E um detalhe que resolve dúvida de muita gente: o strict mode funciona tanto no modo thinking quanto no non-thinking
Modo thinking: como remontar o contexto sem tomar erro 400
Esse aqui é o pega mais silencioso da integração
- Leia o campo
reasoning_content
No modo thinking, o raciocínio volta em reasoning_content, no MESMO nível do content dentro da mensagem do assistant
O erro comum deste passo: procurar o raciocínio dentro do content e concluir que "não veio nada"
- Se houve tool call, guarde e devolva esse
reasoning_contentnas requisições seguintes
A ausência dele na remontagem do contexto gera erro 400
messages.append({
"role": "assistant",
"content": choice.message.content,
"reasoning_content": choice.message.reasoning_content,
"tool_calls": choice.message.tool_calls,
})
O erro comum deste passo: montar o histórico só com role, content e tool_calls, que é exatamente o helper que todo mundo escreve no primeiro dia
A conversa funciona no turno 1 e morre no primeiro follow-up depois de uma tool
- Sem tool call no meio, o
reasoning_contentintermediário pode ficar de fora
Quando não houve tool call entre duas mensagens do usuário, o raciocínio intermediário não precisa entrar na concatenação do contexto
O erro comum deste passo: guardar TUDO pra sempre e inflar o histórico sem necessidade
A janela é de 1.000.000 de tokens, mas contexto entupido também é contexto pior
Quando a chamada falha: sintomas conhecidos e o que fazer
Agora a parte que ninguém coloca no tutorial bonitinho
Tem comportamento reportado em issue ABERTA no repositório oficial da DeepSeek, e eu não achei changelog dizendo que foi corrigido no 0813
Então o certo é você programar defensivamente, não confiar na sorte
Sintoma: a chamada de função vem como texto puro dentro do content
O finish_reason volta stop, o tool_calls volta nulo e a "chamada" está escrita ali no meio do texto, de forma intermitente
É o que descreve a issue #1244
Contorno: nunca assuma que stop significa resposta final útil
Se teu fluxo exigia tool e veio stop com tool_calls nulo, trata como falha e repete a chamada
Sintoma: tool_choice required ou de função específica retornando 400
Reportado na issue #1376 pro V4
Contorno: não faça a tua arquitetura depender de forçar a ferramenta por tool_choice
Deixa o roteamento explícito no prompt e valide o resultado depois
Sintoma: resposta vazia depois de devolver o resultado da tool
Acontece em cenário de streaming com function calling, conforme a issue #1453
Contorno: se o retorno final vier vazio, isso é erro, não é "o modelo não tinha nada a dizer"
Sintoma: content vazio no JSON Output
Esse a própria documentação avisa que pode acontecer ocasionalmente, e a orientação oficial é ajustar o prompt
Juntando tudo, o teu guardrail mínimo é esse:
def resposta_valida(choice, exige_tool=False):
if exige_tool and choice.finish_reason != "tool_calls":
return False
if choice.finish_reason == "length":
return False
if not choice.message.content and not choice.message.tool_calls:
return False
return True
Três regras que salvam a tua integração:
- valide o
finish_reasonSEMPRE, nunca só o texto - trate
contentvazio como falha, não como resposta - tenha caminho de repetição, com o prompt ajustado quando o padrão de erro se repetir
Próximo passo para colocar isso no seu agente
O contrato que a tua integração precisa assumir é bem direto: a resposta pode vir como texto, como tool call ou como falha silenciosa, e o teu código tem que distinguir os três antes de qualquer json.loads
O caminho de adoção que faz sentido é em escada
Começa pelo JSON Output com a palavra json e o exemplo no prompt, que resolve extração e classificação com pouquíssimo esforço
Sobe pra tool calling quando o agente precisar executar ação de verdade, lendo message.tool_calls e devolvendo role tool
E migra pro strict mode Beta quando o schema deixar de ser preferência e virar requisito, com validação no servidor
E tem um caminho a mais pra avaliar: além do Chat Completions compatível com OpenAI, a DeepSeek documenta uma Responses API própria
Se você está começando integração nova agora, vale ao menos ler antes de fechar a arquitetura
até o próximo post! =)
Perguntas frequentes
Como habilitar o strict mode no tool calling do DeepSeek V4 Pro?
O strict mode é um recurso Beta, então você precisa apontar o base_url para https://api.deepseek.com/beta e marcar "strict": true em cada function do array tools. Com isso ligado, o servidor valida o JSON Schema enviado e retorna erro se o schema estiver fora do padrão ou usar um tipo não suportado. Os tipos aceitos são object, string, number, integer, boolean, array, enum e anyOf, com suporte a $defs e $ref, inclusive para estruturas recursivas.
Por que a API do DeepSeek V4 Pro retorna erro 400 depois de um tool call no modo thinking?
Porque no modo thinking o raciocínio vem no campo reasoning_content, no mesmo nível de content, e esse campo precisa ser devolvido no contexto das requisições seguintes quando houve tool call. Se a integração remonta o histórico sem o reasoning_content do assistant, a API responde 400. Quando não houve tool call entre duas mensagens do usuário, esse campo intermediário pode ficar de fora da concatenação sem problema.
O que significa cada valor possível do finish_reason no tool calling do DeepSeek?
O finish_reason volta tool_calls quando o modelo decide chamar uma ferramenta, e os outros valores possíveis são stop, length, content_filter e insufficient_system_resource. Vale checar esse campo antes de tentar ler message.tool_calls, porque só faz sentido processar chamada de função quando o valor é tool_calls. Tratar qualquer outro valor como se fosse chamada de ferramenta é fonte comum de bug silencioso.
O tool_choice required funciona no DeepSeek V4 Pro?
Há uma issue aberta no repositório oficial da DeepSeek (#1376) relatando que o tool_choice required e o tool_choice apontando para uma função específica retornam erro 400. Isso é comportamento reportado, não documentação oficial confirmando suporte. Se a tua integração depende de forçar uma chamada de ferramenta específica, vale testar antes de assumir que vai funcionar em produção.
Por que o DeepSeek V4 Pro às vezes escreve a chamada de função como texto em vez de preencher tool_calls?
Existe uma issue aberta no repositório oficial da DeepSeek (#1244) relatando que o modelo, de forma intermitente, escreve a chamada de função como texto puro dentro de content, com finish_reason stop e tool_calls nulo. Ou seja, o parser não pode assumir que toda intenção de chamar ferramenta sempre vem estruturada. É um motivo a mais pra validar a resposta antes de seguir o fluxo do agente.
Function calling em streaming pode dar resposta vazia no DeepSeek V4 Pro?
Sim, há uma issue aberta no repositório oficial da DeepSeek (#1453) relatando resposta vazia do deepseek-v4-pro depois que o resultado da ferramenta é devolvido, especificamente em cenário de streaming com function calling. Se a tua aplicação depende de streaming com tools, vale ter um tratamento pra esse caso antes de colocar em produção.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
Como rodar o DeepSeek Harness com npx e abrir o Web UI no navegador
Aprenda a rodar o DeepSeek Harness npx e abrir o Web UI em http://127.0.0.1:3080 automaticamente no navegador, direto do terminal com Node.js instalado.
DeepSeek V4 Pro: o que é e quando compensa usar em vez do V4 Flash?
DeepSeek V4 Pro tem 1,6 tri de parâmetros e janela de 1 milhão de tokens. Entenda o preço, o desempenho e quando vale mais a pena que o V4 Flash.
Como rodar o DeepSeek V4 no Ollama: o passo a passo e o que checar antes de tentar
Rodar o DeepSeek V4 no Ollama hoje é via tag cloud: veja como fazer login, baixar a tag e usar via CLI ou API local, e quando vale ir de GGUF offline.
