Pular para o conteúdo

As cinco formas de pergunta

Até aqui você escreveu — ou leu escrever — quatro Tools: listar_assuntos, buscar_processos, contar_processos e ultimos_movimentos. Todas sobre temas que eu escolhi.

Isso não serve. Quem vai usar isto tem tema próprio: um juiz de Violência Doméstica, um de Execução Fiscal, um de Infância, um de Fazenda Pública. Nenhum curso consegue entregar uma Tool pronta por assunto — e se conseguisse, entregaria a coisa errada, porque a Tool útil é a que responde à pergunta que aquele magistrado faz.

A saída não é escrever mais Tools. É perceber uma coisa sobre as perguntas.

A observação que muda tudo

Os temas são muitos. As formas de perguntar são cinco.

Um juiz criminal pergunta "quantos processos de tráfico eu tenho"; um de Fazenda pergunta "quantas execuções fiscais estão em tramitação". Temas opostos, mesma forma: contar. Troque o nome do assunto e a consulta é idêntica.

Testei as cinco contra a API do CNJ, cronometrando e medindo o retorno:

# Forma A pergunta em português Peça de Query DSL Tempo Resposta
1 Contar "quantos?" size: 0 + track_total_hits 6,9 s 218 bytes
2 Listar "quais?" size: N + sort + _source 5,7 s 805 bytes
3 Agrupar "onde? por quem?" aggs + terms 1,9 s 601 bytes
4 Evoluir "quando? aumentou?" aggs + date_histogram 1,7 s 2.490 bytes
5 Detalhar "e este processo aqui?" term no numeroProcesso 1,7 s 599 bytes

Olhe a última coluna antes de qualquer outra coisa. Nenhuma passa de 2,5 KB.

O capítulo anterior gastou-se inteiro para trazer 613 movimentos a um tamanho publicável. Aqui, cinco perguntas de verdade, contra um índice de milhões de processos, e a maior resposta cabe numa página. Não é sorte: é que perguntas bem formadas devolvem respostas, não dados.

Forma 1 — Contar

{
    "size": 0,                    # não me traga documento nenhum
    "track_total_hits": True,     # mas conte todos, sem parar em 10.000
    "query": {"bool": {"filter": [
        {"term": {"assuntos.nome.keyword": assunto}},
        {"range": {"nivelSigilo": {"lte": 0}}},
    ]}},
}

size: 0 é a linha inteira do capítulo. Você está dizendo ao Elasticsearch: resolva isto do seu lado e me mande só o número.

Rodando com "Dívida Ativa":

assuntos que contêm 'dívida ativa':
  - Dívida Ativa (Execução Fiscal)
  - Dívida Ativa não-tributária

TOTAL de processos: 5.985.465
resposta inteira em bytes: 1519

por assunto:
   5965757  Dívida Ativa (Execução Fiscal)
     20731  Dívida Ativa não-tributária

Quase seis milhões de processos, respondidos em 1.519 bytes.

Atenção

Aqui mora o erro mais comum de quem começa — e ele não parece erro.

Você pergunta ao agente "quais processos de dívida ativa estão em tramitação?". Ele chama buscar_processos, que devolve 20, e responde com 20. Você conclui que há 20, ou que a ferramenta está limitada, ou que faltou token.

Nada disso. Há 5.985.465. O agente devolveu 20 porque você fez uma pergunta da forma 2 — "quais" — e a forma 2 lista. Se a pergunta é "quantos", a forma é a 1, e a resposta é exata.

Contar e listar são perguntas diferentes. O LIMITE_MAXIMO das nossas Tools nunca teve nada a ver com a contagem: ele limita quantos documentos você recebe, não quantos existem. Um agente sem uma Tool de contar é um agente que só sabe responder "quais", e vai responder "quais" mesmo quando lhe perguntam "quantos".

Forma 2 — Listar

{
    "size": 3,
    "query": {"bool": {"filter": [...]}},
    "sort": [{"dataAjuizamento": {"order": "desc"}}],
    "_source": ["numeroProcesso", "dataAjuizamento"],
}

Três peças, e cada uma responde a uma pressão diferente:

sort é a peça que os iniciantes esquecem. Sem ele, o Elasticsearch devolve por relevância interna, que para uma consulta de filter puro não significa nada — você recebe três processos arbitrários e o modelo os apresenta como se fossem os três.

Guarde esta forma; ela volta na forma 4 com um problema.

Forma 3 — Agrupar

{
    "size": 0,
    "query": {"bool": {"filter": [...]}},
    "aggs": {"por_orgao": {
        "terms": {"field": "orgaoJulgador.nome.keyword", "size": 5}
    }},
}

De novo size: 0: os documentos não interessam, a distribuição sim. Violência Doméstica, por órgão julgador:

    698  LEOPOLDINA REGIONAL VI JUI VIO DOM FAM C MULHER
    616  SAO GONCALO JUI VIO DOM FAM
    528  QUEIMADOS J VIO E ESP ADJ CRIM
    377  5 C�MARA CRIMINAL
    373  1 C�MARA CRIMINAL

601 bytes para ranquear centenas de órgãos.

E repare nas duas últimas linhas. Não é o seu terminal: os caracteres quebrados estão no dado, como o CNJ o devolve. "5 CÂMARA CRIMINAL" perdeu o  em algum ponto entre o tribunal de origem e o índice, e chegou até aqui assim.

Nota

Lição prática, que vale para qualquer agrupamento:

Agrupe por codigo quando precisar de identidade; use nome só para exibir. Nomes de órgão vêm abreviados sem padrão, em maiúsculas, às vezes com encoding quebrado. orgaoJulgador.codigo é estável; orgaoJulgador.nome.keyword é apresentação.

E quando o nome for para os olhos de um magistrado, vale a Tool higienizar o que sabe estar quebrado, em vez de repassar ruído ao modelo.

Forma 4 — Evoluir

Aqui a coisa fica séria. É a forma mais pedida — "aumentou ou diminuiu?" — e a única das cinco em que a API do CNJ, hoje, devolve uma resposta plausível e falsa.

{
    "size": 0,
    "query": {"bool": {"filter": [...]}},
    "aggs": {"por_ano": {"date_histogram": {
        "field": "dataAjuizamento",
        "calendar_interval": "year",
        "min_doc_count": 1,
    }}},
}

Consulta correta. Resultado:

2608   735
2609  1851
2610  4963
2611  2441
2612   517

Ano 2610.

Por que 2610

Inspecionando o _source cru, o mesmo campo dataAjuizamento aparece em dois formatos diferentes no índice:

"dataAjuizamento": "2020-12-29T00:00:00.000Z"   ← ISO, o esperado
"dataAjuizamento": "20260617161919"             ← compacto, yyyyMMddHHmmss

O segundo é uma data legítima — 17/06/2026, 16h19 — escrita sem separadores. Mas o Elasticsearch, ao ler um campo de data que contém só dígitos, interpreta o número como milissegundos desde 1970. A conta:

20260617161919  →  lido como data:  2026-06-17
                →  lido como epoch_millis:  2612-01-13

Confere com os baldes 2608–2612. Não há dado inventado nem processo do futuro: há um campo com dois formatos, e um deles sendo lido como outra coisa.

Quantos? No assunto Violência Doméstica: 10.779 de 14.728 registros — 73,2%.

O conserto óbvio, e por que ele é pior

A reação natural é acrescentar um range no período real:

{"range": {"dataAjuizamento": {"gte": "2015-01-01", "lte": "2026-09-05"}}}

Agora a série sai limpa, em 1.180 bytes:

2015    25      2020   756      2024   144
2016    46      2021  1284      2025     4
2017    26      2022   938
2018    16      2023   583
2019    77

Um gráfico bonito. Pico em 2021, queda contínua, quase nada em 2025.

E é falso. Essa série diz que os ajuizamentos de Violência Doméstica contra a Mulher despencaram — de 1.284 em 2021 para 4 em 2025.

Os registros descartados não são aleatórios. O número do processo carrega o ano de ajuizamento nos dígitos 10 a 13, então dá para conferir sem depender do campo quebrado. Em amostras de 500 de cada grupo:

Ano no número do processo Com data ISO Com data compacta
2021 185 83
2022 73 74
2023 22 38
2024 11 67
2025 0 50
2026 0 14

Os processos recentes estão quase todos do lado compacto. O range que "saneou" a série removeu justamente os anos recentes — e produziu uma queda que não existe.

Atenção

Pare um instante no que isso significaria fora do exercício.

Um agente com uma Tool de série temporal, escrita corretamente, com um filtro de período que qualquer revisor aprovaria, produziria um gráfico afirmando que a violência doméstica contra a mulher caiu 99% no estado do Rio de Janeiro.

Ninguém mentiu. A consulta está certa, o filtro está certo, o gráfico está certo. O dado é que tem dois formatos.

É por isto que este curso mede tudo o que afirma, e é por isto que a forma 4 é a única com um aviso deste tamanho. Antes de publicar qualquer série temporal do DataJud:

  1. conte o total do assunto sem filtro de data;
  2. conte com o filtro;
  3. se a diferença for grande, a sua série está incompleta — e você precisa saber quem ficou de fora antes de mostrar o gráfico a alguém.

Uma Tool honesta devolve a série e quantos registros ficaram fora dela. Deixar o modelo apresentar só a série é entregar a ele um número que ele não tem como desconfiar.

O que isso faz com a forma 2

Volte à forma 2. Ela ordenava por dataAjuizamento desc e chamava o resultado de "os mais recentes".

Os três que saíram foram:

0803392-61.2026.8.19.0045
0823630-25.2026.8.19.0038
0809832-02.2026.8.19.0004

São de 2026, então parece certo. Mas eles subiram ao topo por terem sido lidos como ano 2612 — não por serem os mais recentes. Qualquer processo de 2026 gravado em ISO fica abaixo de qualquer processo gravado em compacto, mesmo que o compacto seja de 2018.

Ou seja: sort por data neste campo não devolve os mais recentes. Devolve os de formato compacto primeiro, e só depois os outros.

Enquanto o CNJ não uniformizar o campo, uma Tool que promete "os mais recentes" está prometendo o que não entrega. As saídas honestas são duas: ordenar por um campo confiável, ou dizer no docstring e no retorno que a ordenação é aproximada. Nunca a terceira, que é deixar como está e não contar.

Forma 5 — Detalhar

{
    "size": 10,
    "query": {"bool": {"filter": [
        {"term": {"numeroProcesso": digitos}},
        {"range": {"nivelSigilo": {"lte": 0}}},
    ]}},
    "_source": {"includes": ["numeroProcesso", "grau", "classe.nome"]},
}

É a forma da ultimos_movimentos, sem os movimentos. Saída:

G1  Procedimento Especial da Lei Antitóxicos
G2  Apelação Criminal

599 bytes — e a lembrança de que um número devolve um documento por grau, com classes diferentes em cada um.

O size: 10 continua sendo pelos graus, nunca pelos processos: o term no número é exato.

O molde

As cinco formas, vistas de cima, têm o mesmo esqueleto. Toda Tool deste curso — e toda que você vier a escrever — cabe nele:

def minha_tool(parametro: str, quantidade: int = 5) -> list:
    """Uma frase dizendo o que devolve. O modelo lê isto para decidir."""

    # 1. GUARDA — o que dá para recusar sem gastar rede
    quantidade = max(1, min(int(quantidade), LIMITE_MAXIMO))
    if not parametro:
        raise ValueError("mensagem que ensina o próximo argumento")

    # 2. CONSULTA — uma das cinco formas, e sempre com o filtro de sigilo
    resposta = consultar({
        "size": ...,
        "query": {"bool": {"filter": [
            ...,
            {"range": {"nivelSigilo": {"lte": TETO_SIGILO}}},
        ]}},
    })

    # 3. CORTE — o que não vai voltar
    ...

    # 4. RETORNO TIPADO — list ou dict de verdade, nunca str
    return resultado

Só o bloco 2 muda entre as cinco formas. Os blocos 1, 3 e 4 são idênticos, sempre, e é neles que mora tudo o que as Unidades 2 e 3 ensinaram:

Escolhendo a forma

Na prática, a forma sai da própria frase do magistrado:

Se a pergunta começa com... A forma é
"quantos", "qual o total", "quantas vezes" 1 — contar
"quais", "me mostre", "liste os últimos" 2 — listar
"onde", "em que vara", "por quem", "quais os principais" 3 — agrupar
"quando", "aumentou", "por ano", "desde" 4 — evoluir
"e o processo tal", "o que houve em" 5 — detalhar

Duas armadilhas conhecidas:

"Quais são os principais assuntos" parece forma 2 e é forma 3. "Principais" é ranking, e ranking é aggs. Listar mil processos para contar assuntos no Python é o caminho caro e errado — foi assim que a listar_assuntos da Unidade 2 nasceu como agregação.

"Quais processos estão em dívida ativa" parece forma 2 e frequentemente quer a forma 1. Quando a resposta plausível tem seis dígitos, ninguém quer a lista: quer o número, e talvez uma amostra. Um bom agente tem as duas Tools e escolhe.

A lição

Os assuntos do TJRJ são milhares. As formas de perguntar são cinco. Aprender as cinco é o que separa usar as Tools deste curso de escrever as suas.

E o corolário, que a forma 4 cobrou caro:

Toda Tool nova precisa ser medida antes de ser usada. A consulta certa sobre um dado que você não mediu produz uma resposta errada com cara de certa.

Exercício

Este é o exercício mais importante do curso, e é sobre a sua vara.

  1. Escolha um assunto seu. Rode listar_assuntos com uma palavra do seu tema e pegue o nome exato como o CNJ o escreve — nome aproximado devolve zero, e devolve em silêncio.

  2. Escreva três perguntas que você realmente faria sobre esse tema. Não perguntas de exercício: as que você faria numa reunião de gestão da vara.

  3. Classifique cada uma nas cinco formas.

  4. Implemente a mais útil, usando o molde. Só o bloco 2 é trabalho novo.

  5. Meça antes de confiar. Rode a consulta crua uma vez e olhe o retorno: quantos bytes, e algum campo com cara estranha? Se escolheu a forma 4, faça as três contagens do aviso acima antes de acreditar em qualquer série.

Quem terminar este exercício não precisa mais deste curso para escrever Tools — precisa só de tempo.

No próximo capítulo, as Tools saem do script: viram um servidor MCP rodando na sua máquina, e o mesmo ultimos_movimentos passa a estar disponível para o seu agente local sem estar dentro dele.