Pular para o conteúdo

Problemas comuns

Esta unidade acrescentou duas coisas ao agente: um campo grande demais e uma fronteira entre processos. Cada uma trouxe os próprios modos de falha, e quase nenhum deles aparece como erro no lugar onde a causa está.

Todos os erros abaixo aconteceram na máquina do curso, na ordem em que estão aqui. As mensagens são as originais.

Failed to parse JSONRPC message from server
pydantic_core._pydantic_core.ValidationError: Invalid JSON

Causa. Um print() no servidor MCP.

No transporte stdio, a saída padrão é o fio do protocolo. Cliente e servidor trocam mensagens JSON-RPC por ali. Qualquer coisa que você escreva com print entra no meio dessa conversa, e o cliente tenta interpretar a sua mensagem de depuração como protocolo.

O erro não diz "você deu um print". Diz que o JSON é inválido — que é verdade, do ponto de vista de quem está lendo.

Solução. Depure para o stderr, que não é o fio:

import sys
print("consultando o CNJ...", file=sys.stderr)
Atenção

Isto vale para tudo que o servidor importa, não só para o código que você escreveu. Uma biblioteca que imprima um aviso de boas-vindas na importação quebra o servidor do mesmo jeito, e o rastro não vai apontar para ela.

Se o servidor quebrar logo no handshake e você não achar o print, comente as importações uma a uma.

returned multiple content, using the first one

tool listar_assuntos returned multiple content, using the first one

Causa. A Tool devolveu uma list.

Uma lista de cinco itens vira cinco blocos de conteúdo na resposta MCP, e o adaptador do smolagents fica com o primeiro. Os outros quatro são descartados.

Repare no que este erro tem de pior: é um aviso, não uma exceção. A execução segue, o agente recebe um item, e responde com ele. Você não vai ver falha nenhuma — vai ver uma resposta curta demais.

Solução. Embrulhe num dict:

# Errado: 5 itens viram 5 blocos, o cliente fica com 1 {: #errado-5-itens-viram-5-blocos-o-cliente-fica-com-1 }
return [{"numero_processo": p, ...} for p in processos]

# Certo: 1 bloco, com os 5 dentro {: #certo-1-bloco-com-os-5-dentro }
return {"processos": [{"numero_processo": p, ...} for p in processos]}

É a metade do servidor da regra do capítulo O agente que não tem Tool nenhuma. A outra metade é o problema seguinte.

string indices must be integers — de novo, por outro motivo

InterpreterError: Could not index {
  "assuntos": [
    "Decorrente de Violência Doméstica",
    "Violência Doméstica Contra a Mulher"
  ]
} with 'assuntos': TypeError: string indices must be integers, not 'str'

Causa. Faltou structured_output=True no cliente.

Leia a mensagem com atenção: ela imprime um dicionário, com as chaves certas e os dados certos, e diz que não dá para indexá-lo. Porque não é um dicionário — é o texto de um dicionário. Sem o parâmetro, o adaptador entrega ao modelo o campo de texto do bloco de conteúdo.

Solução.

with MCPClient(parametros, structured_output=True) as ferramentas:
Nota

Este mesmo TypeError: string indices must be integers, not 'str' já apareceu duas vezes neste curso, por três causas sem relação nenhuma entre si: mudança de API do smolagents na Unidade 0, Tool devolvendo json.dumps na Unidade 2, e o structured_output aqui.

A mensagem de erro diz onde quebrou. Nunca diz por quê.

FutureWarning: Parameter 'structured_output' was not specified

FutureWarning: Parameter 'structured_output' was not specified. Currently it
defaults to False, but in version 1.25, the default will change to True.

Causa. Você não passou o parâmetro, e hoje o padrão é False.

Solução. Passe explicitamente — mesmo que o padrão futuro seja o que você quer. Um padrão que muda é uma linha de código que funciona hoje, quebra em seis meses e não aparece em nenhum diff seu.

does not support multiple positional arguments

Code execution failed at line 'violencia_domestica_assuntos =
listar_assuntos('violência doméstica')' due to: ValueError: tool
listar_assuntos does not support multiple positional arguments or combined
positional and keyword arguments

Causa. O adaptador MCP só aceita chamada por nome. Uma string posicional cai no raise, mesmo sendo o único argumento.

E o modelo escreveu o que qualquer pessoa escreveria: a assinatura que ele leu era def listar_assuntos(termo: string).

Solução. Seis linhas no cliente, e o modelo não precisa saber de nada:

def aceitar_posicional(ferramenta):
    nomes = list(ferramenta.inputs)
    original = ferramenta.forward

    def forward(*args, **kwargs):
        kwargs.update(dict(zip(nomes, args)))
        return original(**kwargs)

    ferramenta.forward = forward
    return ferramenta

A alternativa — pedir na pergunta "chame as Tools com argumentos nomeados" — também funciona, e foi medida: zero erros na rodada seguinte. Mas depende de o modelo obedecer, e gasta contexto em toda pergunta.

Input should be a valid string [input_value=None]

Error executing tool buscar_processos: 1 validation error for buscar_processosArguments
assunto
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]

Causa. O modelo escreveu .get(0) num dicionário cujas chaves são strings, recebeu None e mandou None para a Tool.

Não é defeito da fronteira. É o qwen2:7b navegando um nível de estrutura a mais do que ele navega bem — o embrulho {"assuntos": [...]} que existe justamente para o dado atravessar inteiro.

Solução. Diga a forma na pergunta:

"As Tools devolvem dicionários: listar_assuntos devolve {'assuntos': [...]} "
"e buscar_processos devolve {'processos': [...]}. "

Uma linha no enunciado, não uma reescrita da Tool. Vale registrar sem eufemismo: parte do que se chama engenharia de prompt é compensar um modelo pequeno.

ImportError: cannot import name 'FastMCP'

Causa. Você instalou o SDK mcp versão 2, que renomeou FastMCP para MCPServer. O mcpadapt, que o smolagents usa por baixo, ainda fala a versão 1.

Solução. Fixe a versão:

pip install "mcp<2"

E, no mesmo pedido, o que o mcpadapt importa sem declarar:

pip install websockets

O sintoma desse segundo é um ModuleNotFoundError: No module named 'websockets' num arquivo que não é seu, para um transporte que você não usa.

O servidor não sobe, e não diz nada

Sintoma: o with MCPClient(...) trava ou morre falando de módulo que não existe, e você não vê nenhum log do servidor.

Causa mais comum. command="python" em vez de command=sys.executable.

O "python" é o primeiro Python do PATH, que pode ser o do sistema — sem smolagents, sem mcp, sem nada. O processo filho morre na importação, num terminal que você não está vendo.

Solução.

import sys
parametros = StdioServerParameters(command=sys.executable, args=[SERVIDOR])

E o caminho do servidor absoluto: o processo filho nasce no diretório do cliente, não no seu.

Para diagnosticar, rode o servidor sozinho antes de conectar:

python servidor_mcp.py

Ele deve ficar parado, esperando. Se ele imprimir um traceback, o problema é dele e você acabou de vê-lo — o que dentro do with teria virado silêncio.

HTTP 504 Gateway Time-out na agregação

HTTP 504 Gateway Time-out
... NSX LB

Causa. Uma agregação com script sobre o índice inteiro. O CNJ tem um balanceador na frente, com um tempo máximo, e cálculo por documento sobre 23 milhões de documentos não cabe nele.

Repare que a resposta nem é do Elasticsearch: NSX LB é o balanceador dizendo que desistiu de esperar.

Solução. Restrinja antes de agregar — bool/filter primeiro, agregação depois — e tire o script. Se a conta não couber em terms + date_histogram, ela não é para ser feita na API: traga os baldes e faça a conta em Python.

Datas que não existem

Dois casos, e os dois vieram do dado real.

Ano 995 num movimento:

{"dataHora": "0995-09-12T00:00:00.000Z", "codigo": 26, "nome": "Distribuição"}

Ano 2610 numa agregação por ano de ajuizamento: baldes em 2608, 2609, 2610, 2611, 2612.

Causa. No segundo caso, dataAjuizamento tem dois formatos no índice: o ISO (2020-12-29T00:00:00.000Z) e um compacto (20260617161919). O compacto é lido como epoch em milissegundos, e vira ano 2610.

Não é raro: em Violência Doméstica, 10.779 de 14.728 registros — 73,2% — estão no formato compacto.

Atenção

A "solução" intuitiva é a armadilha. Um range filtrando 2015 a 2026 remove os baldes absurdos e produz uma série limpa — que mostra 1.284 processos em 2021 caindo para 4 em 2025.

Essa queda de 99% não existe. O filtro removeu exatamente os anos recentes, porque são eles que estão no formato compacto. A consulta "consertada" é mais perigosa que a quebrada: a quebrada tem ano 2610 e ninguém acredita; a consertada tem uma tendência plausível.

Não filtre a data até saber quantos registros estão em cada formato. Conte primeiro.

Para o ano 995 no movimento, a regra é a do capítulo O campo que não cabe: ordene pelo campo ISO cru, guarde-o como _ordem, e apague-o antes de devolver. Ordenar pela data já formatada em dd/mm/aaaa ordena texto, e texto põe 06/08 antes de 14/07.

5 C�MARA CRIMINAL

"orgaoJulgador": {"codigo": 12345, "nome": "5 C�MARA CRIMINAL"}

Causa. A codificação está quebrada no dado, não na sua tela. Não adianta chcp 65001 nem reconfigure(encoding="utf-8") — isso conserta o que você imprime, não o que chegou torto da origem.

Solução. Agrupe por codigo, exiba por nome.

O código é um inteiro e não tem acento para estragar. Duas grafias diferentes do mesmo órgão viram dois baldes numa agregação por nome, e um só numa agregação por código. O nome torto continua feio na tela — mas a contagem fica certa, e é a contagem que responde à pergunta.

O agente falhou em tudo e respondeu mesmo assim

O pior problema desta unidade não produz mensagem de erro nenhuma.

RESPOSTA FINAL:
Apesar de ter falhado ao tentar obter os processos mais recentes sobre
violência doméstica através da ferramenta 'listar_assuntos', posso fornecer
uma resposta geral para o seu pedido.

Os três processos mais recentes sobre violência doméstica podem ser:

1. **Processo X**: Este processo foi iniciado recentemente e está focado na
   violência doméstica. (...)

Nenhuma chamada tinha dado certo. Seis passos, todos com o mesmo ValueError. E a resposta tem estrutura, vocabulário jurídico correto e aparência de trabalho feito.

Não há conserto de código para isto. Há um hábito:

Número de processo é verificável. Prosa não é.

Peça ao agente o número, e confira o número — na tela do sistema, ou com o validar_cnj da Unidade 1. Um número inventado por um LLM quase sempre reprova no dígito verificador.

A resposta muda entre execuções idênticas

Duas execuções da mesma pergunta, com o mesmo código e temperature=0:

itens devolvidos passos tempo
1ª execução 6 2 125,2 s
2ª execução 3 2 145,7 s

Causa. A pergunta era ambígua — três processos de cada assunto, ou três no total? O agente resolveu a ambiguidade sozinho, de um jeito diferente a cada vez, sem dizer qual escolheu.

temperature=0 reduz a variação do texto. Não elimina a variação da decisão.

Solução. Peça o total, e peça a ordem, quando eles importam. E conte o que voltou.

O handshake demora

Não é problema, e vale saber para não procurar defeito onde não há:

Operação Tempo
Handshake (subir o servidor + listar Tools) 2,23 s
list_tools isolado 0,017 s
buscar_processos no script 0,73 s
buscar_processos via MCP 0,30 s
ultimos_movimentos no script 0,20 s
ultimos_movimentos via MCP 0,17 s
Erro na Tool, via MCP 0,007 s

Os 2,23 segundos são uma vez por sessão, não por chamada. A travessia em si não custa nada mensurável — as diferenças acima estão dentro do ruído da rede do CNJ.

Se a sua sessão está lenta, o custo está no modelo, não na fronteira.

Fixando o capítulo

Q1: O servidor MCP quebra no handshake com pydantic_core ValidationError: Invalid JSON. Qual a causa mais provável?


Q2: Uma agregação por ano de ajuizamento devolve baldes em 2608, 2609 e 2610. Você acrescenta um range de 2015 a 2026 e a série fica limpa, mostrando queda de 1.284 para 4 processos. O que fazer?


Q3: O agente terminou sem exceção e devolveu três processos bem descritos, com linguagem jurídica correta. Qual é o primeiro passo?


Próximo: Quiz final da Unidade 3.