|
2 | 2 |
|
3 | 3 | Agente de contexto local com RAG (BM25) sobre **MySQL**, interface em `templates/` e respostas em streaming via OpenRouter. |
4 | 4 |
|
| 5 | +**Modo local estrito (padrão):** desde a mitigação descrita em `.cursor/plans/rag_acl_incremental_6951b55f.plan.md`, o ACL opera por padrão em modo `strict`. A geração só acontece quando a decisão de retrieval permitir; pergunta sem base não chama LLM, `/content` não injeta mais `scope_chunks[:5]` e respostas que passam pelos gates mas falham no sanity check pós-geração são substituídas por hard stop `post_generation_misalignment`. A regra central é: na dúvida, não responder. |
| 6 | + |
5 | 7 | ## Requisitos |
6 | 8 |
|
7 | 9 | - Python 3.10+ |
@@ -85,10 +87,58 @@ O projeto usa `logging` da biblioteca padrão com loggers prefixados `kernelbots |
85 | 87 | ## Comandos no chat |
86 | 88 |
|
87 | 89 | - `/reload` — reconstrói o índice BM25 a partir do MySQL. |
88 | | -- `/content …` — força uso da base local (com fallback para os primeiros chunks se não houver hit BM25). |
89 | | -- `/doc …` — injeta o conteúdo da documentação quando disponível no índice. |
| 90 | +- `/content …` — busca global explícita; **sem fallback** de primeiros chunks. Se não houver hit forte o bastante, retorna hard stop com reformulação guiada. |
| 91 | +- `/doc …` — injeta o conteúdo da documentação quando disponível no índice; fluxo determinístico (não passa pela política de decisão). |
90 | 92 | - `/python …`, `/visualizacao-sql …`, `/projeto-bloco …`, `/planejamento-curso-carreira …` — filtra por disciplina. |
91 | 93 |
|
| 94 | +## Política de retrieval e hard stop |
| 95 | + |
| 96 | +A camada `engine/retrieval.py` separa recuperação lexical bruta (`SearchEngine.search_candidates`) da decisão de suficiência (`build_decision`). A decisão devolve `RetrievalDecision` com `allow_generation`, `reason`, `confidence` e `trace` estruturado, exposto no SSE como `[ACL_META]`. |
| 97 | + |
| 98 | +Hard stops possíveis: |
| 99 | + |
| 100 | +| `reason` | Quando dispara | |
| 101 | +|---|---| |
| 102 | +| `insufficient_context` | Sem hits ou `top_score < MIN_SCORE` | |
| 103 | +| `underspecified_query` | Menos que `MIN_TERMS` termos informativos (strict) | |
| 104 | +| `vague_but_high_risk` | Termo vago (`performance`, `erro`, `timeout`) sem domínio específico | |
| 105 | +| `ambiguous_retrieval` | `top_score - second_score < MIN_SCORE_MARGIN` | |
| 106 | +| `context_misaligned` | `coverage < MIN_COVERAGE` no melhor chunk | |
| 107 | +| `low_confidence` | Coverage ponderada baixa ou termo central ausente no modo strict | |
| 108 | +| `post_generation_misalignment` | Sanity check pós-geração detectou resposta fora dos chunks | |
| 109 | +| `provider_error` | Todos os modelos OpenRouter falharam | |
| 110 | + |
| 111 | +Thresholds configuráveis via `.env` (ver `core/config.py`): |
| 112 | + |
| 113 | +| Variável | Default | Efeito | |
| 114 | +|---|---|---| |
| 115 | +| `ACL_RETRIEVAL_MIN_SCORE` | 1.5 | Hard stop se `top_score` bruto for menor | |
| 116 | +| `ACL_RETRIEVAL_MIN_SCORE_MARGIN` | 0.15 | Margem mínima entre top1 e top2 | |
| 117 | +| `ACL_RETRIEVAL_MIN_COVERAGE` | 0.34 | Overlap mínimo de termos informativos | |
| 118 | +| `ACL_RETRIEVAL_MIN_COVERAGE_WEIGHTED` | 0.34 | Idem, com centrais valendo 2x | |
| 119 | +| `ACL_RETRIEVAL_MIN_TERMS` | 2 | Termos informativos mínimos em `strict` | |
| 120 | +| `ACL_RETRIEVAL_CANDIDATE_K` | 8 | Quantos candidatos o retrieval devolve | |
| 121 | +| `ACL_RETRIEVAL_TOP_K` | 4 | Quantos vão para o prompt | |
| 122 | +| `ACL_RETRIEVAL_MAX_CHUNKS_PER_SOURCE` | 2 | Diversidade de fonte | |
| 123 | + |
| 124 | +## Calibração e avaliação |
| 125 | + |
| 126 | +A pasta `evaluation/` contém a ferramenta que gera o artefato exigido pelo plano incremental: |
| 127 | + |
| 128 | +```bash |
| 129 | +python -m evaluation.calibration_runner --questions evaluation/all.md --out evaluation/calibration_traces.jsonl --limit 20 |
| 130 | +python -m evaluation.calibration_summary --traces evaluation/calibration_traces.jsonl |
| 131 | +``` |
| 132 | + |
| 133 | +O runner produz um JSONL com trace completo por query (scores, coverage, termos, fontes, decisão) e um placeholder `manual_label` para revisão humana. O summary imprime percentis de scores e as taxas `Stop vs Answer`, `Ambiguous Retrieval`, `Underspecified Query`, `Vague But High Risk`. |
| 134 | + |
| 135 | +## Limitações conhecidas |
| 136 | + |
| 137 | +- **BM25-only é mitigação temporária.** O módulo `engine/retrieval.py` deixa explícito (`retrieval_mode = "bm25_lexical_temporary"`). A evolução correta é busca híbrida (BM25 + embeddings + reranking), fora do escopo inicial. |
| 138 | +- **Coverage lexical** aproxima alinhamento semântico, não resolve. Por isso a Fase 3 adiciona sanity check pós-geração como último firewall. |
| 139 | +- **Calibração manual de 20 casos é bootstrap**, não prova final. A evolução prevista é 100–200 casos semi-automatizados. |
| 140 | +- **Modo `assistive`** existe como flag, mas não é ativado por padrão. A mitigação estabiliza o modo `strict` primeiro. |
| 141 | + |
92 | 142 | ## Fluxo de dados |
93 | 143 |
|
94 | 144 | ``` |
|
0 commit comments