# Tool `analisar` — Guia Completo de Integracao

Documentacao tecnica da ferramenta `analisar` do MCP Server TecJustica, destinada ao desenvolvedor que vai integra-la em um agente consumidor.

**Versao:** v4.0.0 | **Ultima atualizacao:** 2026-03-10

---

## 1. O que e

A `analisar` e a **unica ferramenta de analise com LLM** do servidor. Ela recebe uma pergunta em linguagem natural e responde com base nos documentos reais do processo judicial (sentencas, peticoes, laudos, decisoes, etc.).

Internamente, ela:
1. Busca todos os documentos do processo no Supabase (ou apenas os IDs especificados)
2. Detecta se o processo e civel ou criminal
3. Envia o conteudo ao **Gemini 2.5 Flash** (via OpenRouter) com a pergunta do usuario
4. Se o volume de texto exceder ~800K chars, usa estrategia **map-reduce** automatica
5. Retorna a resposta do LLM + metadados da execucao

---

## 2. Assinatura

```
analisar(
    numero_processo: str,                                          # Obrigatorio
    pergunta: str = "Faca uma analise completa deste processo",    # Opcional
    document_ids: str | None = None,                               # Opcional
    perspectiva: str | None = None,                                # Opcional
)
```

### Parametros

| Parametro | Tipo | Obrigatorio | Descricao |
|-----------|------|:-----------:|-----------|
| `numero_processo` | `str` | Sim | Numero CNJ do processo (`NNNNNNN-DD.AAAA.J.TR.OOOO`) |
| `pergunta` | `str` | Nao | Pergunta ou instrucao em linguagem natural. Default: analise completa |
| `document_ids` | `str \| None` | Nao | UUIDs separados por virgula para analisar docs especificos |
| `perspectiva` | `str \| None` | Nao | Orienta o LLM a analisar sob um ponto de vista (ver tabela abaixo) |

### Perspectivas

| Valor | Tipo de processo | Descricao |
|-------|:----------------:|-----------|
| `autor` | Civel | Polo ativo |
| `reu` | Civel | Polo passivo |
| `terceiro` | Civel | Terceiro interessado |
| `acusado` | Criminal | Reu criminal |
| `ministerio_publico` | Criminal | MP como acusacao |
| `defensor` | Criminal | Advogado de defesa |
| `vitima` | Criminal | Vitima ou assistente |
| `juiz` | Neutro | Magistrado |
| `assessor` | Neutro | Assessor juridico |

---

## 3. Dois caminhos internos

A tool segue dois caminhos mutuamente exclusivos:

### Caminho 1: Documentos especificos (`document_ids` informado)

```
document_ids="uuid1,uuid2,uuid3"
```

1. Faz split por virgula e strip nos IDs
2. Para cada ID, chama `client.ler_documento(id)` — busca `SELECT *` na tabela `documents`
3. Descarta docs sem campo `conteudo` (podem existir docs sem texto extraido)
4. Se 2+ docs: injeta instrucao de comparacao automatica na pergunta
5. Envia ao map-reduce (que decide se precisa dividir em lotes ou nao)

**Quando usar:** O agente ja sabe quais documentos quer analisar (por ex., so a sentenca e o recurso).

### Caminho 2: Processo completo (`document_ids` omitido)

1. Chama `client.listar_documentos(numero_processo, limite=5000, incluir_preview=True)`
   - Isso faz `SELECT id, sequencia, nome, tipo_nome, data_juntada, tamanho_texto, paginas, conteudo` na tabela `documents`
   - Ordenado por `sequencia DESC`, limite 5000 docs
2. Filtra docs sem conteudo
3. Se zero docs com conteudo: retorna erro instrutivo
4. Envia ao map-reduce

**Quando usar:** O agente quer uma visao geral ou nao sabe quais docs sao relevantes.

---

## 4. Estrategias de processamento (map-reduce)

A funcao `analisar_map_reduce()` em `tools/_helpers.py` decide automaticamente:

### Contexto unico (`estrategia: "contexto_unico"`)

**Condicao:** Todos os documentos cabem em 1 lote (< 800K chars total).

```
[docs] → formatar contexto → 1 chamada ao Gemini → resposta final
```

- Monta contexto formatado com headers por documento:
  ```
  ## Nome do Documento
  **Tipo:** Sentenca | **Data:** 2024-01-15

  [conteudo textual completo]

  ---

  ## Proximo Documento
  ...
  ```
- Adiciona prefixo: `TIPO DE PROCESSO: CIVEL` + perspectiva (se informada)
- 1 unica chamada ao Gemini Flash
- Custo minimo, latencia minima

### Map-reduce (`estrategia: "map_reduce"`)

**Condicao:** Total de chars dos documentos > 800K (BATCH_SIZE_CHARS).

```
[docs] → dividir em lotes → MAP (paralelo) → resumos parciais → REDUCE → resposta final
```

**Fase MAP:**
- Divide docs em lotes de ~800K chars cada (respeitando limites por doc)
- Para cada lote, envia prompt de resumo em **paralelo** (`asyncio.gather`)
- Prompt MAP: "Resuma pontos importantes: partes, datas, valores, decisoes, pedidos, prazos"
- Cada resumo tem `max_tokens=4000`

**Fase REDUCE:**
- Concatena resumos parciais: `## Resumo do Lote 1/N`, `## Resumo do Lote 2/N`, ...
- Envia com a pergunta original do usuario + perspectiva
- `max_tokens=4000` na resposta final

**Calculos de lotes:**
- 1 doc de 2M chars → 3 lotes (800K + 800K + 400K)
- 500 docs de 2K chars cada (1M total) → 2 lotes
- 10 docs de 50K chars cada (500K total) → 1 lote (contexto unico, sem map-reduce)

---

## 5. Modelo LLM

| Propriedade | Valor |
|-------------|-------|
| Modelo | Gemini 2.5 Flash |
| OpenRouter ID | `google/gemini-2.5-flash` |
| Contexto | ~1M tokens (~3.2M chars) |
| Custo | $0.15/1M input, $0.60/1M output |
| Temperature | 0.3 (query) / 0.2 (resumo) |
| max_tokens | 4000 (resumo e resposta final) |

**Modelo unico:** Nao ha selecao automatica entre modelos. Todos os campos `model` nos metodos do `LLMService` sao ignorados — sempre usa `MODEL_ID = "google/gemini-2.5-flash"`.

### System prompt

O LLM recebe um system prompt fixo (em `LLMService.SYSTEM_PROMPT`):
- Assistente juridico especializado em processos brasileiros
- Respostas baseadas APENAS no contexto fornecido
- Valores em R$, formato brasileiro
- Nao inventa dados
- Mencionar que prazos devem ser verificados com advogado

---

## 6. Retorno

### Sucesso (processo completo)

```json
{
    "sucesso": true,
    "numero_processo": "0000105-15.2017.8.06.0203",
    "tipo_processo": "civel",
    "modelo": "gemini-flash",
    "resposta": "O processo trata de acao de cobranca... [texto longo da analise]",
    "estrategia": "contexto_unico",
    "lotes": 1,
    "docs_total": 47,
    "contexto_chars": 350000,
    "tokens_prompt": 87500,
    "tokens_completion": 1200
}
```

### Sucesso (documentos especificos)

```json
{
    "sucesso": true,
    "numero_processo": "0000105-15.2017.8.06.0203",
    "tipo_processo": "civel",
    "modelo": "gemini-flash",
    "estrategia": "contexto_unico",
    "documentos_analisados": ["Sentenca", "Peticao Inicial"],
    "resposta": "Comparando a sentenca com a peticao inicial...",
    "lotes": 1,
    "docs_total": 2,
    "contexto_chars": 25000,
    "tokens_prompt": 6250,
    "tokens_completion": 800
}
```

### Sucesso (map-reduce)

```json
{
    "sucesso": true,
    "numero_processo": "0000105-15.2017.8.06.0203",
    "tipo_processo": "civel",
    "modelo": "gemini-flash",
    "resposta": "Analise consolidada do processo...",
    "estrategia": "map_reduce",
    "lotes": 3,
    "docs_total": 250,
    "total_chars_originais": 2100000,
    "contexto_reduce_chars": 15000,
    "duration_ms": 12500
}
```

### Erro

```json
{
    "sucesso": false,
    "erro": "Processo nao encontrado. Verifique o numero...",
    "dica": "Use listar_processos() para ver processos disponiveis."
}
```

### Campos do retorno

| Campo | Tipo | Presente em | Descricao |
|-------|------|:-----------:|-----------|
| `sucesso` | `bool` | Sempre | `true` se a analise foi concluida |
| `numero_processo` | `str` | Sempre | Numero CNJ |
| `tipo_processo` | `str` | Sucesso | `"civel"` ou `"criminal"` (detectado automaticamente) |
| `modelo` | `str` | Sucesso | Sempre `"gemini-flash"` |
| `resposta` | `str` | Sucesso | Texto da analise (pode ser longo) |
| `estrategia` | `str` | Sucesso | `"contexto_unico"` ou `"map_reduce"` |
| `lotes` | `int` | Contexto unico | Sempre `1` |
| `docs_total` | `int` | Contexto unico | Numero de documentos processados |
| `contexto_chars` | `int` | Contexto unico | Chars enviados ao LLM |
| `tokens_prompt` | `int \| None` | Contexto unico | Tokens de input (OpenRouter) |
| `tokens_completion` | `int \| None` | Contexto unico | Tokens de output |
| `total_chars_originais` | `int` | Map-reduce | Total de chars antes da divisao |
| `contexto_reduce_chars` | `int` | Map-reduce | Chars na fase REDUCE |
| `duration_ms` | `float` | Map-reduce | Tempo total em ms |
| `documentos_analisados` | `list[str]` | Docs especificos | Nomes dos docs analisados |
| `erro` | `str` | Erro | Descricao do erro |
| `dica` | `str` | Erro | Sugestao de como resolver |

---

## 7. Como obter IDs de documentos

O agente tem **duas formas** de obter IDs para usar com `document_ids`:

### Via `visao_geral_processo` (recomendado)

Desde v4.0.0, `visao_geral_processo` retorna o campo `documentos` com catalogo completo (ate 200 docs):

```json
{
    "numero_processo": "0000105-15.2017.8.06.0203",
    "classe_descricao": "Procedimento Comum Civel",
    "partes": [...],
    "stats_documentos": {...},
    "documentos": [
        {
            "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "nome": "Sentenca",
            "tipo_nome": "Sentenca",
            "data_juntada": "2024-01-15",
            "tamanho_texto": 15000,
            "fonte": "api",
            "confianca": null
        },
        {
            "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
            "nome": "Peticao Inicial",
            "tipo_nome": "Peticao Inicial",
            "data_juntada": "2023-06-10",
            "tamanho_texto": 8500,
            "fonte": "api",
            "confianca": null
        }
    ],
    "ultimas_movimentacoes": [...]
}
```

O agente pode inspecionar o catalogo, identificar os documentos relevantes e chamar:

```json
{"name": "analisar", "arguments": {
    "numero_processo": "0000105-15.2017.8.06.0203",
    "pergunta": "Compare a sentenca com os pedidos da inicial",
    "document_ids": "a1b2c3d4-e5f6-7890-abcd-ef1234567890,b2c3d4e5-f6a7-8901-bcde-f12345678901"
}}
```

### Via `ls_documentos`

Retorna lista paginada com mais campos (sequencia, tamanho_bytes, nivel_sigilo, metodo_extracao, signatarios, tipo_arquivo). Util quando:
- O processo tem mais de 200 documentos (limite do catalogo em `visao_geral_processo`)
- Precisa filtrar por `fonte` (`"api"` vs `"segmentation"`)
- Precisa paginacao

---

## 8. Fluxo recomendado para o agente

### Fluxo basico (mais comum)

```
1. visao_geral_processo(cnj)
   → Recebe: metadados + partes + catalogo de docs com IDs

2. analisar(cnj, "Qual a situacao atual?")
   → Analise geral do processo inteiro

3. analisar(cnj, "Analise a sentenca em detalhes", document_ids="uuid-da-sentenca")
   → Analise focada em documento especifico
```

### Fluxo avancado (analise multi-perspectiva)

```
1. visao_geral_processo(cnj)
2. analisar(cnj, "Analise de risco completa", perspectiva="autor")
3. analisar(cnj, "Quais as chances de sucesso na defesa?", perspectiva="reu")
4. analisar(cnj, "Viabilidade de acordo: valor justo e argumentos", perspectiva="juiz")
```

### Fluxo com busca + analise cirurgica

```
1. visao_geral_processo(cnj)
2. grep_documentos("dano moral", numero_processo=cnj)
   → Encontra docs que mencionam "dano moral"
3. analisar(cnj, "Qual o entendimento sobre dano moral nestes documentos?",
           document_ids="id1,id2,id3")
```

---

## 9. Deteccao civel vs criminal

A funcao `detectar_tipo_processo()` em `services/supabase_client.py` analisa a `classe_descricao` do processo na tabela `processos`. O resultado (`"civel"` ou `"criminal"`) e injetado no prompt como `TIPO DE PROCESSO: CIVEL/CRIMINAL`, o que adapta automaticamente o vocabulario e a analise do LLM.

---

## 10. Logging e observabilidade

Toda chamada gera logs em duas tabelas Supabase:

### `tool_call_logs`
- `tool_name`: `"analisar"`
- `duration_ms`: tempo total da chamada
- `user_id`, `user_email`: usuario autenticado
- `params`: parametros da chamada (numero_processo, document_ids, estrategia)
- `success`: se a chamada foi bem-sucedida

### `llm_usage_logs`
- **Contexto unico:** 1 registro com `call_type="query"`
- **Map-reduce:** N registros MAP (`call_type="query"`, tool_name `"analisar:map_1"`, `"analisar:map_2"`, ...) + 1 REDUCE (`"analisar:reduce"`) + 1 consolidado (`call_type="map_reduce"`)
- Campos: `model_id`, `tokens_prompt`, `tokens_completion`, `context_chars`, `duration_ms`

---

## 11. Tratamento de erros

| Cenario | Erro | Campo `dica` |
|---------|------|:------------:|
| Processo nao encontrado | `"Processo nao encontrado..."` | `"Use listar_processos()..."` |
| Processo sem documentos com conteudo | `"Processo X nao encontrado ou sem documentos..."` | `"Verifique o numero..."` |
| `document_ids` vazio apos split | `"Nenhum document_id valido fornecido."` | `"Use ls_documentos()..."` |
| Nenhum doc encontrado por ID | `"Nenhum dos documentos foi encontrado ou tem conteudo."` | `"Verifique os IDs..."` |
| Erro generico (timeout, LLM, etc.) | Mensagem do exception | `"Reduza o escopo com document_ids."` |

Todos os erros seguem o formato `erro_instrutivo`:

```json
{"sucesso": false, "erro": "...", "dica": "..."}
```

O campo `dica` foi projetado para que o agente consumidor saiba automaticamente qual tool chamar em seguida.

---

## 12. Limites e consideracoes

| Aspecto | Valor | Nota |
|---------|-------|------|
| Docs por chamada (processo completo) | 5000 | Limite do `listar_documentos` |
| Docs por chamada (especificos) | Sem limite hard | Limitado pelo tempo de fetch sequencial |
| Tamanho max de contexto por lote | ~800K chars | `BATCH_SIZE_CHARS` em `_helpers.py` |
| Tamanho max total (processo completo) | Sem limite | Map-reduce divide automaticamente |
| Tokens max de resposta | 4000 | `max_tokens_final` em `analisar_map_reduce` |
| Tokens max de resumo (MAP) | 4000 | `max_tokens_resumo` |
| Catalogo em `visao_geral_processo` | 200 docs | Se o processo tiver mais, usar `ls_documentos` |
| Busca de docs especificos | Sequencial | Um `ler_documento()` por ID (nao paralelo) |

### Custos estimados (Gemini Flash)

| Cenario | Input chars | Lotes | Custo estimado |
|---------|:-----------:|:-----:|:--------------:|
| Processo pequeno (50K chars) | 50K | 1 | ~$0.002 |
| Processo medio (500K chars) | 500K | 1 | ~$0.02 |
| Processo grande (2M chars) | 2M | 3 | ~$0.08 (MAP) + ~$0.003 (REDUCE) |
| 2 docs especificos (30K chars) | 30K | 1 | ~$0.001 |

---

## 13. Arquivos-fonte

| Arquivo | Conteudo |
|---------|----------|
| `tools/analise.py` | Tool `analisar`, caminhos 1 e 2, dispatch |
| `tools/_helpers.py` | `analisar_map_reduce()`, `_dividir_em_lotes()`, `_montar_contexto_documentos()`, constantes |
| `services/llm_service.py` | `LLMService.query()`, `LLMService.resumir_texto()`, system prompt |
| `services/supabase_client.py` | `ler_documento()`, `listar_documentos()`, `detectar_tipo_processo()` |
| `tools/navegacao.py` | `visao_geral_processo()` (fonte de IDs para `document_ids`) |

---

## 14. Exemplo completo via MCP (curl)

```bash
# Setup: obter token e session ID (ver CLAUDE.md para detalhes)
TOKEN="tk_..."
SID="..."  # obtido do initialize

# 1. Visao geral (obter IDs dos documentos)
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
    "name":"visao_geral_processo",
    "arguments":{"numero_processo":"0000105-15.2017.8.06.0203"}
  }}'
# → Resposta inclui "documentos": [{"id": "uuid1", "nome": "Sentenca", ...}, ...]

# 2. Analise do processo inteiro
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{
    "name":"analisar",
    "arguments":{
      "numero_processo":"0000105-15.2017.8.06.0203",
      "pergunta":"Qual a situacao atual do processo?"
    }
  }}'

# 3. Analise cirurgica de docs especificos
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{
    "name":"analisar",
    "arguments":{
      "numero_processo":"0000105-15.2017.8.06.0203",
      "pergunta":"Analise a fundamentacao da sentenca",
      "document_ids":"uuid-da-sentenca",
      "perspectiva":"juiz"
    }
  }}'
```
