# Spec: Retry Automático na Extração de Relations

## Objective

Reduzir a variância da extração de relations adicionando um retry automático quando o LLM retorna 0 relations mas existem 2+ entities — sem chamadas extras desnecessárias.

## Context

O `_extract_relations()` em `src/arandu/write/extract.py` faz uma única chamada LLM. Com `temperature=0` e `gpt-4o`, a OpenAI não garante determinismo. Em testes reais, a mesma mensagem com 19 entities gerou 0 relations num run e 21 nos outros dois. O single-pass extraction já usa retry 1x on failure (pattern existente). O multi-pass não tem retry pra relations.

## Definition of Done

1. `_extract_relations()` faz retry 1x automaticamente quando retorna lista vazia E a lista de entities tem 2+ itens.
2. O retry usa a mesma chamada LLM (mesmo prompt, mesma temperature) — sem prompt alternativo.
3. Log `logger.info("relation_extraction: 0 relations with %d entities, retrying")` antes do retry.
4. Se o retry também retornar vazio, aceita o resultado (0 relations) sem erro.
5. Teste unitário valida que o retry acontece quando relations=[] e entities≥2, e NÃO acontece quando entities<2.

## Scope

- `src/arandu/write/extract.py` — função `_extract_relations()`
- `tests/test_write_pipeline.py` — teste do retry

## Anti-scope

- NÃO mudar prompts (já foram ajustados na v0.5.3)
- NÃO adicionar lógica de retry em outras funções (entity_scan, fact_extraction)
- NÃO mudar a interface pública de `_extract_relations()` (mesmos args, mesmo return type)
- NÃO adicionar config de retry (hardcoded 1 retry é suficiente)
- NÃO mexer no reflexion pass
- NÃO mexer no single-pass extraction (já tem retry próprio)

## Technical Decisions

1. **Retry dentro de `_extract_relations()`** — não no caller. A lógica fica encapsulada onde o problema acontece. O caller (`_multi_pass_extract`) não precisa saber.
2. **Condição: `len(relations) == 0 and len(entities) >= 2`** — se tem 0 ou 1 entity, 0 relations é resultado legítimo.
3. **Retry = segunda chamada idêntica** — mesma `_call_llm()`, mesmos args. Sem prompt diferente, sem temperature diferente.
4. **Max 1 retry** — suficiente pra cobrir variância do LLM sem dobrar latência em todos os casos.

## Applicable Patterns

- `patterns/llm-interaction.md` — retry segue o padrão de fail-safe (retorna vazio, não levanta exceção)
- `patterns/error-handling.md` — retry com log warning, resultado vazio como safe default
- `patterns/testing.md` — teste com FakeLLMProvider, sem DB

## Risks

| Risco | Mitigação |
|-------|-----------|
| Retry adiciona latência (~3-5s) quando relations realmente são 0 | Só acontece com 2+ entities e 0 relations — raro em mensagens normais |
| Retry retorna resultado diferente do esperado | Mesma chamada, mesmo prompt — pior caso retorna vazio de novo |

## Budget

≤ 2 files
