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
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)
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:
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.
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.