|
1 | | -<p align="center"> |
2 | | - <img src="frontend/assets/images/KernelBanner.webp" alt="KernelBot" width="100%" /> |
3 | | -</p> |
4 | | - |
5 | | -# KernelBot |
6 | | - |
7 | | -**Chatbot RAG local-first que transforma Markdown em base de conhecimento consultável via chat.** |
8 | | - |
9 | | ---- |
10 | | - |
11 | | -## O que é |
12 | | - |
13 | | -O KernelBot — internamente chamado **ACL (Agente de Contexto Local)** — é um sistema que indexa arquivos Markdown de um diretório local (`content/`) usando BM25, recupera trechos relevantes e os injeta como contexto para um LLM via OpenRouter. A resposta chega em streaming (SSE) com rastreabilidade: você sabe de onde veio cada trecho usado. |
14 | | - |
15 | | -Sem banco de dados, sem fila, sem infra pesada. Índice e estado ficam na memória do processo. |
16 | | - |
17 | | ---- |
18 | | - |
19 | | -## Problema que resolve |
20 | | - |
21 | | -Alunos precisam revisar gravações de aulas de ~2h para tirar dúvidas simples. Sem o KernelBot, a alternativa é reassistir o material bruto ou esperar resposta da secretaria/professores. |
22 | | - |
23 | | -O sistema usa resumos estruturados das aulas como corpus RAG, permitindo perguntas diretas com respostas ancoradas no conteúdo real — não em "achismo" do modelo. |
24 | | - |
25 | | ---- |
26 | | - |
27 | | -## Como funciona |
28 | | - |
29 | | -``` |
30 | | -content/*.md ──► SearchEngine (BM25 por silo) ──► ContextManager ──► ChatProvider ──► SSE |
31 | | - ▲ │ |
32 | | - │ ▼ |
33 | | - Watchdog OpenRouter API |
34 | | -(rebuild automático) (LLM com fallback) |
35 | | -``` |
36 | | - |
37 | | -**Três etapas:** |
38 | | - |
39 | | -1. **Indexação** — `SearchEngine` lê `content/**/*.md`, divide por headers em chunks e cria um índice BM25 separado por silo (subpasta). O Watchdog monitora alterações e reconstrói o índice automaticamente (~1.5s de debounce). |
40 | | - |
41 | | -2. **Decisão de contexto** — `ContextManager.build_messages()` interpreta comandos (`/python`, `/doc`, `/content`), campo `discipline` do JSON e pinned context por sessão para decidir quais chunks injetar no system prompt. |
42 | | - |
43 | | -3. **Geração** — `ChatProvider` chama o LLM via OpenRouter em streaming SSE. Suporta fallback automático entre modelos. Antes dos tokens, emite metadados (`[ACL_META]`) com rótulo do silo e fontes usadas. |
44 | | - |
45 | | ---- |
46 | | - |
47 | | -## Diferenciais |
48 | | - |
49 | | -| Recurso | O que faz | |
50 | | -|---------|-----------| |
51 | | -| **Silos BM25** | Cada subpasta de `content/` é um domínio isolado com índice próprio — evita mistura de contextos entre disciplinas | |
52 | | -| **Comandos de escopo** | `/python`, `/doc`, `/content` etc. forçam onde a busca acontece | |
53 | | -| **Pinned context** | Mantém contexto entre mensagens da mesma sessão para follow-ups curtos não "perderem o fio" | |
54 | | -| **File watching** | Editou um `.md` em `content/`? Em ~1.5s o índice já reflete a mudança | |
55 | | -| **Rastreabilidade** | Respostas incluem breadcrumbs com os arquivos fonte usados (via `[ACL_META]` no SSE) | |
56 | | -| **Zero infra** | Sem banco, sem Redis, sem Docker obrigatório — `python main.py` e pronto | |
57 | | - |
58 | | ---- |
59 | | - |
60 | | -## Estrutura de pastas |
61 | | - |
62 | | -``` |
63 | | -KernelBot/ |
64 | | -├── main.py # Orquestração: serviços, watchdog, app FastAPI |
65 | | -├── core/ # Settings (.env), logging centralizado |
66 | | -├── engine/ # SearchEngine, ContentWatcher, ContextManager, ChatProvider, PinnedSessionStore |
67 | | -├── api/ # Rotas FastAPI (GET /, POST /chat) |
68 | | -├── app/ # create_app(), estado injetado (AppServices) |
69 | | -├── content/ # Base de conhecimento — silos por subpasta |
70 | | -│ ├── python/ # 16 aulas |
71 | | -│ ├── visualizacao-sql/# 17 aulas |
72 | | -│ ├── projeto-bloco/ # 9 aulas |
73 | | -│ ├── planejamento-curso-carreira/ # 7 aulas |
74 | | -│ └── doc/ # Documentação interna (silo /doc) |
75 | | -├── frontend/ # Assets CSS e módulos JS da UI |
76 | | -├── templates/ # index.html (Jinja2) |
77 | | -├── tests/ # Smoke tests (pytest) |
78 | | -└── requirements.txt |
79 | | -``` |
80 | | - |
81 | | ---- |
82 | | - |
83 | | -## Como rodar |
84 | | - |
85 | | -### Pré-requisitos |
86 | | - |
87 | | -- Python 3.10+ |
88 | | -- Chave de API do [OpenRouter](https://openrouter.ai/) |
89 | | - |
90 | | -### Instalação |
91 | | - |
92 | | -```bash |
93 | | -git clone https://github.com/seu-usuario/KernelBot.git |
94 | | -cd KernelBot |
95 | | -pip install -r requirements.txt |
96 | | -``` |
97 | | - |
98 | | -### Configuração |
99 | | - |
100 | | -Crie um arquivo `.env` na raiz: |
101 | | - |
102 | | -``` |
103 | | -OPENROUTER_API_KEY=sua-chave-aqui |
104 | | -``` |
105 | | - |
106 | | -Variáveis opcionais: |
107 | | - |
108 | | -| Variável | Default | Descrição | |
109 | | -|----------|---------|-----------| |
110 | | -| `ACL_GLOBAL_CONTEXT` | `geral` | Escopo BM25 sem filtro: `geral` (só silo geral) ou `all` (todos os silos) | |
111 | | -| `ACL_PINNED_MAX_TURNS` | `5` | Turnos antes do pinned context expirar | |
112 | | -| `ACL_PINNED_MAX_CHARS` | `24000` | Limite de caracteres no pinned context | |
113 | | -| `ACL_PINNED_WEAK_SCORE` | `0.4` | Score BM25 abaixo do qual se reutiliza o pin | |
114 | | - |
115 | | -### Executar |
116 | | - |
117 | | -```bash |
118 | | -python main.py |
119 | | -``` |
120 | | - |
121 | | -Ou com reload para desenvolvimento: |
122 | | - |
123 | | -```bash |
124 | | -uvicorn main:app --host 127.0.0.1 --port 8000 --reload |
125 | | -``` |
126 | | - |
127 | | -Acesse `http://127.0.0.1:8000`. |
128 | | - |
129 | | ---- |
130 | | - |
131 | | -## Comandos do chat |
132 | | - |
133 | | -| Comando | Efeito | |
134 | | -|---------|--------| |
135 | | -| `/python <pergunta>` | Busca RAG restrita ao silo `python` | |
136 | | -| `/visualizacao-sql <pergunta>` | Busca RAG restrita ao silo `visualizacao-sql` | |
137 | | -| `/projeto-bloco <pergunta>` | Busca RAG restrita ao silo `projeto-bloco` | |
138 | | -| `/planejamento-curso-carreira <pergunta>` | Busca RAG restrita ao silo `planejamento-curso-carreira` | |
139 | | -| `/doc <pergunta>` | Injeta todo o conteúdo de `content/doc/` no prompt | |
140 | | -| `/content <pergunta>` | Força RAG no escopo global (com fallback para os primeiros chunks se não houver hit) | |
141 | | -| `/reset` ou `/limpar` | Limpa o pinned context da sessão | |
142 | | - |
143 | | -Sem comando, o sistema usa o campo `discipline` do JSON (se enviado) ou o escopo global configurado. |
144 | | - |
145 | | ---- |
146 | | - |
147 | | -## Testes |
148 | | - |
149 | | -```bash |
150 | | -python -m pytest tests/ -v |
151 | | -``` |
152 | | - |
153 | | ---- |
154 | | - |
155 | | -## Limitações |
156 | | - |
157 | | -- **BM25 é lexical, não semântico** — se o vocabulário da pergunta divergir do corpus, o retrieval pode falhar. Use `/content` para forçar contexto. |
158 | | -- **Sem autenticação** — adequado para uso local (`127.0.0.1`). Não expor em rede sem proteção adicional. |
159 | | -- **Pinned context em RAM** — reiniciar o servidor perde o estado de sessão. |
160 | | -- **Comandos de silo não são automáticos** — novas subpastas em `content/` indexam automaticamente, mas o atalho `/nome` precisa ser adicionado manualmente em `_DISCIPLINE_COMMAND_PREFIXES` (`engine/context.py`). |
161 | | -- **Versões não fixadas** — `requirements.txt` sem pins exatos; risco de drift em novas instalações. |
162 | | - |
163 | | ---- |
164 | | - |
165 | | -## Por que existe |
166 | | - |
167 | | -Criado para resolver uma dor real: alunos do INFNET gastavam horas revisando gravações de aula para tirar dúvidas pontuais. O KernelBot transforma resumos estruturados dessas aulas em uma base pesquisável via chat — com controle de escopo, rastreabilidade e zero dependência de infra complexa. |
168 | | - |
169 | | ---- |
170 | | - |
171 | | -## Stack |
172 | | - |
173 | | -| Camada | Tecnologia | |
174 | | -|--------|------------| |
175 | | -| Servidor HTTP | FastAPI + Uvicorn | |
176 | | -| Índice de busca | BM25Okapi (`rank-bm25`) | |
177 | | -| File watching | Watchdog | |
178 | | -| LLM gateway | OpenRouter API (`httpx` async) | |
179 | | -| Frontend | HTML/CSS/JS puro (Jinja2) | |
180 | | -| Streaming | Server-Sent Events (SSE) | |
181 | | - |
182 | | ---- |
183 | | - |
184 | | -## Licença |
185 | | - |
186 | | -Veja o arquivo [LICENSE](LICENSE). |
| 1 | +<div align="center"> |
| 2 | + <img src="frontend/assets/images/KernelBanner.webp" alt="Banner" width="100%" /> |
| 3 | +</div> |
| 4 | + |
| 5 | +<br /> |
| 6 | + |
| 7 | +<div> |
| 8 | + <img align="right" width="50%" src="frontend/assets/images/spiderMan.webp" alt="Work in Progress" /> |
| 9 | + |
| 10 | + <strong><h3>Em obras (ou quase isso)</h3></strong> |
| 11 | + |
| 12 | + <p>O código já está performando mais que muito sênior por aí, mas a documentação ainda está sendo "indexada" pela minha produtividade.</p> |
| 13 | + |
| 14 | + <p> |
| 15 | + <strong>Se você é um recrutador:</strong> O código fala mais que mil palavras. Olhe a pasta <code>engine/</code>.<br /> |
| 16 | + <strong>Se você é um curioso:</strong> Volte em breve. Ou dê um <code>python main.py</code> e descubra. |
| 17 | + </p> |
| 18 | + |
| 19 | + <br clear="all" /> |
| 20 | +</div> |
0 commit comments