Skip to content

Commit 52045e7

Browse files
committed
chore: remove example environment file
This commit deletes the `.env.exemple` file, streamlining the project by removing unnecessary example configuration that is no longer needed.
1 parent 95d7267 commit 52045e7

3 files changed

Lines changed: 191 additions & 1 deletion

File tree

.env.exemple

Whitespace-only changes.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<p>
1616
<strong>Se você é um recrutador:</strong> O código fala mais que mil palavras. Olhe a pasta <code>engine/</code>.<br />
17-
<strong>Se você é um curioso:</strong> Volte em breve. Ou dê um <code>python main.py</code> e descubra.
17+
<strong>Se você é um curioso:</strong> Comece pelo arquivo de documentação: <a href="./documentation.md"><code>documentation.md</code></a>.
1818
</p>
1919

2020
<br clear="all" />

documentation.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
## Propósito do sistema
2+
3+
O **ACL (KernelBot)** é um chatbot com **RAG lexical (BM25)** que responde via **LLM (OpenRouter)**, mas com uma regra explícita de segurança: **se o retrieval não tiver confiança suficiente, ele não chama o modelo** (hard stop).
4+
5+
Na prática, o sistema transforma o conteúdo de uma tabela MySQL chamada **`knowledge`** (aulas por disciplina) em um índice BM25 **em memória**, particionado por **silos** (disciplinas). A cada pergunta, ele escolhe um escopo (global, disciplina, `/doc`) e só gera resposta quando os gates de retrieval (score, coverage, ambiguidade) permitem.
6+
7+
Fora de escopo (neste repo, no estado atual): pipeline de ingestão, schema SQL versionado e suíte de testes automatizados. Este `documentation.md` documenta **o que está implementado no código hoje**, sem inferir arquivos/pastas inexistentes.
8+
9+
## Arquitetura
10+
11+
### Stack
12+
13+
| Camada | Tecnologia |
14+
|---|---|
15+
| Servidor HTTP | FastAPI + Uvicorn |
16+
| Retrieval | BM25Okapi (`rank-bm25`) — in-memory |
17+
| Dados | MySQL (lido via `PyMySQL`) |
18+
| Gateway LLM | OpenRouter (streaming via `httpx`) |
19+
| Frontend | HTML estático (`templates/index.html`) + JS modular em `frontend/src/` |
20+
| Streaming | SSE (`text/event-stream`) |
21+
| Logs | `logging` stdlib + payload estruturado (texto/JSON) |
22+
23+
### Visão de componentes
24+
25+
```mermaid
26+
flowchart TD
27+
UI["Browser UI\n(templates/index.html + frontend/src)"] -->|POST /chat (JSON)| API["FastAPI /chat (SSE)"]
28+
API --> CM["ContextManager.build_messages()"]
29+
CM --> SE["SearchEngine.search_candidates()\nBM25 por silo"]
30+
SE --> DB["MySQL\nknowledge WHERE active=1"]
31+
CM -->|messages + decision + trace| CP["ChatProvider.stream_response()\nOpenRouter streaming"]
32+
CP -->|SSE tokens + ACL_META| UI
33+
```
34+
35+
### Estrutura do repositório (real no workspace)
36+
37+
```
38+
KernelBot/
39+
├── main.py # Entry point: carrega Settings, monta serviços e roda Uvicorn (127.0.0.1:8001)
40+
├── app/
41+
│ ├── factory.py # create_app(): templates, static mounts, lifespan, routers
42+
│ └── state.py # AppServices (injeção de dependências via app.state)
43+
├── api/
44+
│ └── routes.py # GET / (UI) e POST /chat (SSE)
45+
├── core/
46+
│ ├── config.py # Settings.load() a partir do .env + prompts + thresholds
47+
│ ├── logging_config.py # configure_logging() (text/json)
48+
│ ├── structured_log.py # log_event() + formatter com payload ACL
49+
│ └── systemPrompt/ # system_prompt.txt + sticky_instruction.txt (obrigatórios)
50+
├── engine/
51+
│ ├── search.py # SearchEngine: rebuild, silos BM25, candidatos (raw_score) + whitelist de discipline
52+
│ ├── database.py # SELECT no MySQL e chunking por janela de palavras
53+
│ ├── retrieval.py # gates/decisão (hard stop vs allow_generation) + sanity pós-geração
54+
│ ├── context.py # roteamento (/doc, /content, /python...) + pin por sessão + hard stop
55+
│ ├── chat_provider.py # streaming OpenRouter com fallback + ACL_META + override pós-geração
56+
│ ├── pinned_store.py # PinnedSessionStore em memória (session_id → chunks)
57+
│ └── watcher.py # legado (não integrado ao fluxo atual)
58+
├── templates/
59+
│ └── index.html # Shell da UI (carrega /src/main.js)
60+
├── frontend/
61+
│ ├── src/ # UI JS (ChatService, render markdown, histórico, sessão)
62+
│ └── assets/ # CSS e imagens
63+
├── content/ # existe, mas o engine atual não lê arquivos daqui
64+
├── SQL/ # existe no workspace, mas sem artefatos versionados (neste estado)
65+
├── requirements.txt
66+
├── .env / .env.example
67+
└── documentation.md
68+
```
69+
70+
### Decisões de design (trade-offs)
71+
72+
- **BM25 (lexical) em vez de embeddings**: simples, barato e rápido; trade-off é recall semântico menor e dependência de termos na query.
73+
- **Hard stop no modo strict**: reduz alucinação e custo de tokens; trade-off é mais “recusa” quando a pergunta é vaga/ambígua.
74+
- **Pin por sessão (server-side em memória)**: melhora follow-ups sem re-busca constante; trade-off é que o pin expira por turnos e some ao reiniciar o processo.
75+
76+
## APIs
77+
78+
### Base URL
79+
80+
Por padrão, o servidor sobe em:
81+
82+
```bash
83+
http://127.0.0.1:8001
84+
```
85+
86+
Sem autenticação.
87+
88+
### Endpoints
89+
90+
| Método | Caminho | Descrição |
91+
|---|---|---|
92+
| `GET` | `/` | Serve a UI (`templates/index.html`) |
93+
| `POST` | `/chat` | Recebe mensagem e retorna SSE (`text/event-stream`) |
94+
95+
### `POST /chat`
96+
97+
**Request body (JSON):**
98+
99+
```json
100+
{
101+
"message": "string (obrigatório)",
102+
"discipline": "string (opcional)",
103+
"session_id": "string (opcional; 8–128 [A-Za-z0-9_-])"
104+
}
105+
```
106+
107+
Notas:
108+
109+
- **`discipline` (JSON)**: se fornecido, passa por whitelist em `SearchEngine.normalize_discipline()` (só aceita valores presentes no DB em `SELECT DISTINCT discipline ...`).
110+
- **`session_id`**: habilita **contexto fixado (pin)**; o frontend gera e persiste em `sessionStorage`.
111+
- **Comando `/reload`**: se `message == "/reload"` (case-insensitive após trim), o backend reconstrói o índice BM25 e retorna um stream curtinho com status.
112+
113+
**Response (`text/event-stream`)**:
114+
115+
- `data: [ACL_META]{...}\n\n` — metadados (sempre no começo do stream; e pode reaparecer no override pós-geração)
116+
- `data: <chunk>\n\n` — tokens/trechos (com `\n` escapado como `\\n`)
117+
- `data: [DONE]\n\n` — fim
118+
- `data: [ERROR] ...\n\n` — erro amigável de provider (quando todos os modelos falham)
119+
120+
**Campos relevantes em `ACL_META` (v=2)** (emitidos por `engine/chat_provider.py`):
121+
122+
- `label`: rótulo de escopo (ex.: “Python”, “Base geral”, “Documentação (doc)”)
123+
- `sources`: lista de fontes (ex.: `db:python/slug-da-aula`)
124+
- `pinned_active` / `pinned_display`: status do pin da sessão
125+
- `mode`: `strict` (no estado atual)
126+
- `decision`: `answer` ou `hard_stop`
127+
- `reason`: motivo (ex.: `ok`, `insufficient_context`, `context_misaligned`, `provider_error`, `post_generation_misalignment`)
128+
- `confidence`: `high|medium|low`
129+
- `llm_called`: boolean
130+
- `tokens_used`: contador aproximado (incrementa por delta recebido)
131+
132+
## Fluxos
133+
134+
### Fluxo 1 — Inicialização do servidor
135+
136+
1. `main.py` chama `configure_logging()`.
137+
2. `Settings.load()``.env` e valida pré-requisitos:
138+
- `OPENROUTER_API_KEY` é obrigatório.
139+
- `core/systemPrompt/system_prompt.txt` e `core/systemPrompt/sticky_instruction.txt` são obrigatórios.
140+
3. `SearchEngine(...).rebuild()` tenta carregar chunks do MySQL (`engine/database.py`). Se DB não estiver configurado ou estiver inacessível, o índice fica vazio e logs avisam.
141+
4. `create_app()` registra templates, monta estáticos (`/assets`, `/src`) e inclui rotas.
142+
5. Uvicorn sobe em `127.0.0.1:8001`.
143+
144+
### Fluxo 2 — Chat (ponta a ponta)
145+
146+
```text
147+
UI (frontend/src/ui.js) → POST /chat { message, session_id } → SSE stream
148+
```
149+
150+
1. O usuário envia uma mensagem.
151+
2. A UI (`frontend/src/api.js`) faz `fetch("/chat")` e lê `res.body.getReader()` processando linhas `data: ...`.
152+
3. O backend (`api/routes.py`) valida JSON, `message`, `discipline` e `session_id`.
153+
4. `ContextManager.build_messages()` decide o escopo e tenta retrieval:
154+
- Comandos de escopo no texto (prefixos): `/doc`, `/content`, `/python`, `/visualizacao-sql`, `/projeto-bloco`, `/planejamento-curso-carreira`.
155+
- Sem comando, ele pode usar o **pin** da sessão para sugerir escopo efetivo (se existir e não conflitar).
156+
5. `SearchEngine.search_candidates()` retorna candidatos BM25 (com `raw_score` e `matched_terms`).
157+
6. `engine/retrieval.build_decision()` aplica gates (score absoluto, margem, coverage, termos mínimos, “vague but high risk”).
158+
7. Se **`allow_generation=False`**: o backend envia **hard stop** via SSE (sem chamar LLM).
159+
8. Se **`allow_generation=True`**: o backend chama OpenRouter em streaming e re-emite tokens via SSE.
160+
9. No final, o provider roda um **sanity check pós-geração** (`post_generation_flags`). Se detectar desalinhamento em modo `strict`, ele emite um `ACL_META` atualizado e anexa uma mensagem de hard stop (`post_generation_misalignment`).
161+
10. A UI renderiza Markdown incremental (via `marked@12` + `highlight.js`) e mostra breadcrumbs a partir de `meta.sources`.
162+
163+
### Fluxo 3 — Contexto fixado (pin) por sessão
164+
165+
- O frontend gera um `session_id` (UUID sem hífens) e salva em `sessionStorage` (`frontend/src/utils/sessionId.js`).
166+
- O backend salva os chunks selecionados no `PinnedSessionStore` (em memória), com:
167+
- `turns_left` (expira a cada turno via `begin_turn()`),
168+
- `scope_key` (ex.: `discipline:python`, `doc`, `content`).
169+
- Se o usuário enviar `/reset` ou `/limpar` no começo da mensagem, o backend limpa o pin daquela sessão.
170+
171+
### Fluxo 4 — Rebuild manual do índice (`/reload`)
172+
173+
1. Usuário envia `/reload`.
174+
2. `api/routes.py` chama `services.search_engine.rebuild()`.
175+
3. O endpoint retorna um stream curto com:
176+
- `data: Índice reconstruído: ...\n\n`
177+
- `data: [DONE]\n\n`
178+
179+
## Glossário e referências (opcional)
180+
181+
- **Silo**: partição lógica do índice por `discipline` (ex.: `python`).
182+
- **Chunk**: janela de ~500 palavras com overlap de 50 (ver `engine/database.py`).
183+
- **Retrieval candidate**: item retornado por `SearchEngine.search_candidates()` com `raw_score` (BM25 cru).
184+
- **Hard stop**: decisão de não chamar o LLM e responder com uma mensagem de reformulação (modo `strict`).
185+
- **Pin**: contexto fixado por sessão em memória (`PinnedSessionStore`).
186+
187+
Referências no código:
188+
189+
- Backend: `main.py`, `api/routes.py`, `app/factory.py`, `engine/context.py`, `engine/retrieval.py`, `engine/chat_provider.py`
190+
- Frontend: `templates/index.html`, `frontend/src/api.js`, `frontend/src/ui.js`

0 commit comments

Comments
 (0)