Skip to content

Commit 06de5d0

Browse files
authored
Merge branch 'main' into feat/tailwind-v4-ui
2 parents a9f099e + cb5c8bb commit 06de5d0

10 files changed

Lines changed: 789 additions & 9 deletions

File tree

.env.example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ ACL_RETRIEVAL_TOP_K=4
5151
# Máximo de chunks por fonte (slug) entre os selecionados — diversidade (1–10).
5252
ACL_RETRIEVAL_MAX_CHUNKS_PER_SOURCE=2
5353

54+
# --- Catálogo lexical de aulas (ISS JSON — ver engine/lesson_catalog.py) ---
55+
# Ativa roteamento por catálogo sem LLM (rescue, sugestões em hard stop).
56+
ACL_CATALOG_ENABLED=false
57+
# Diretório com lessons.json e search-index.json (vazio = ../ISS/content se existir).
58+
# ACL_CATALOG_JSON_DIR=/caminho/para/ISS/content
59+
# Score BM25 do catálogo mínimo para is_confident() (rescue / sugestões).
60+
ACL_CATALOG_MIN_SCORE=2.0
61+
# Margem mínima entre 1.º e 2.º match do catálogo.
62+
ACL_CATALOG_MIN_MARGIN=0.35
63+
# Limiar alto para is_strict_confident() — pré-escopo BM25 em fase futura (não relaxa retrieval).
64+
ACL_CATALOG_STRICT_THRESHOLD=4.0
65+
# Quantos matches do catálogo retornar em match().
66+
ACL_CATALOG_PROMPT_TOP_K=5
67+
5468
# --- Logging ---
5569
# text = linha legivel; json = uma linha JSON por evento ACL (grep/agregadores).
5670
# ACL_LOG_FORMAT=text

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ scripts/
1919
CLAUDE.md
2020

2121
node_modules/
22-
frontend/assets/css/output.css
22+
frontend/assets/css/output.css
23+
24+
.agent_history.md

Features.canvas

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"nodes":[
3+
{
4+
"id":"6c0d14f9d11e02ff",
5+
"type":"text",
6+
"text":"# Para hoje: \n\n## Começar o 'prompt interno' do kernelBot\n\t- Desenhar arquitetura, discutir stack e et\n\t\tVer oque é mais vantajoso, usar RAGMB35, ou o prompt interno, ou ambos somados",
7+
"styleAttributes":{},
8+
"x":-780,
9+
"y":-280,
10+
"width":500,
11+
"height":260
12+
},
13+
{
14+
"id":"ca79b034ef1e38b1",
15+
"type":"text",
16+
"text":"Basicamente: \n\nUm sistem prompt que seria 'ativado' a cada interação com o usuário e guiaria o sistema com base nas descrições e outros campos dos jsons do projeto, a escolher a aula/conteúdo que melhor se enquadra com a pergunta e/ou solicitação do usuário, para assim, não depender apenas de RAGMB35\n- Discutivel\n- Ver viabiliadade\n- Discutir se é possivel manter o RAGMB35, ou usar ambos \n",
17+
"styleAttributes":{},
18+
"x":-180,
19+
"y":-820,
20+
"width":480,
21+
"height":420
22+
},
23+
{
24+
"id":"6865db41af0d27e4",
25+
"type":"text",
26+
"text":"Basicamente casos de ambiguidade ou a falta de contexto suficiente",
27+
"styleAttributes":{},
28+
"x":500,
29+
"y":-500,
30+
"width":340,
31+
"height":100,
32+
"color":"3"
33+
},
34+
{
35+
"id":"d0bb3e87f6fc7f4d",
36+
"type":"text",
37+
"text":"Sistem prompt seria bom para os casos aonde: \n- Encontrei trechos na base, mas eles não cobrem bem a sua pergunta. Reformule incluindo termos mais específicos sobre o que quer saber.\n- Encontrei conteúdos parecidos na base e não consegui distinguir qual deles realmente responde à sua pergunta. Adicione detalhes que ajudem a desempatar, como nome do módulo, comando ou tecnologia.\n- etc",
38+
"styleAttributes":{},
39+
"x":420,
40+
"y":-840,
41+
"width":500,
42+
"height":300
43+
}
44+
],
45+
"edges":[
46+
{
47+
"id":"a61d1246554b6d96",
48+
"styleAttributes":{},
49+
"toFloating":false,
50+
"fromNode":"6c0d14f9d11e02ff",
51+
"fromSide":"right",
52+
"toNode":"ca79b034ef1e38b1",
53+
"toSide":"left"
54+
},
55+
{
56+
"id":"d4f523af1ccedba6",
57+
"styleAttributes":{},
58+
"toFloating":false,
59+
"fromNode":"ca79b034ef1e38b1",
60+
"fromSide":"right",
61+
"toNode":"d0bb3e87f6fc7f4d",
62+
"toSide":"left"
63+
},
64+
{
65+
"id":"5ba456aeac796445",
66+
"styleAttributes":{},
67+
"toFloating":false,
68+
"fromNode":"6865db41af0d27e4",
69+
"fromSide":"top",
70+
"toNode":"d0bb3e87f6fc7f4d",
71+
"toSide":"bottom"
72+
}
73+
],
74+
"metadata":{
75+
"version":"1.0-1.0",
76+
"frontmatter":{}
77+
}
78+
}

app/state.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
from __future__ import annotations
44

5-
from dataclasses import dataclass
5+
from dataclasses import dataclass, field
66

77
from engine.chat_provider import ChatProvider
88
from engine.context import ContextManager
9+
from engine.lesson_catalog import LessonCatalog
910
from engine.pinned_store import PinnedSessionStore
1011
from engine.search import SearchEngine
1112

@@ -16,3 +17,6 @@ class AppServices:
1617
context_manager: ContextManager
1718
chat_provider: ChatProvider
1819
pinned_store: PinnedSessionStore
20+
lesson_catalog: LessonCatalog | None = None
21+
indexed_lesson_keys: frozenset[str] = field(default_factory=frozenset)
22+
catalog_drift_report: dict | None = None

core/config.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ class Settings:
5858
retrieval_candidate_k: int
5959
retrieval_top_k: int
6060
retrieval_max_chunks_per_source: int
61+
# Catálogo lexical de aulas (ISS JSON) — ver engine/lesson_catalog.py
62+
catalog_enabled: bool
63+
catalog_json_dir: Path | None
64+
catalog_min_score: float
65+
catalog_min_margin: float
66+
# Limiar alto para pré-escopo BM25 (Fase futura); não relaxar retrieval BM25.
67+
catalog_strict_threshold: float
68+
catalog_prompt_top_k: int
69+
catalog_router_prompt: str
6170

6271
@property
6372
def openrouter_headers(self) -> dict[str, str]:
@@ -88,6 +97,7 @@ def load(cls) -> Settings:
8897
prompts_dir = Path(__file__).resolve().parent / "systemPrompt"
8998
system_prompt_file = prompts_dir / "system_prompt.txt"
9099
sticky_instruction_file = prompts_dir / "sticky_instruction.txt"
100+
catalog_router_file = prompts_dir / "catalog_router.txt"
91101

92102
if not system_prompt_file.exists():
93103
raise RuntimeError(
@@ -99,9 +109,15 @@ def load(cls) -> Settings:
99109
f"Arquivo de instrução sticky não encontrado: {sticky_instruction_file}. "
100110
"Crie o arquivo core/systemPrompt/sticky_instruction.txt com o template de contexto fixado."
101111
)
112+
if not catalog_router_file.exists():
113+
raise RuntimeError(
114+
f"Arquivo de prompt do catálogo não encontrado: {catalog_router_file}. "
115+
"Crie o arquivo core/systemPrompt/catalog_router.txt com as instruções de contexto do catálogo."
116+
)
102117

103118
system_prompt = system_prompt_file.read_text(encoding="utf-8").strip()
104119
sticky_instruction = sticky_instruction_file.read_text(encoding="utf-8").strip()
120+
catalog_router_prompt = catalog_router_file.read_text(encoding="utf-8").strip()
105121

106122
raw_global = (os.getenv("ACL_GLOBAL_CONTEXT") or "geral").strip().lower()
107123
if raw_global == "geral":
@@ -161,6 +177,30 @@ def _env_int(name: str, default: int, lo: int, hi: int) -> int:
161177
"ACL_RETRIEVAL_MAX_CHUNKS_PER_SOURCE", 2, 1, 10
162178
)
163179

180+
raw_catalog_enabled = (os.getenv("ACL_CATALOG_ENABLED") or "false").strip().lower()
181+
catalog_enabled = raw_catalog_enabled in ("1", "true", "yes", "on")
182+
183+
catalog_json_dir: Path | None = None
184+
raw_catalog_dir = (os.getenv("ACL_CATALOG_JSON_DIR") or "").strip()
185+
if raw_catalog_dir:
186+
raw_path = Path(raw_catalog_dir).expanduser()
187+
catalog_json_dir = (
188+
raw_path.resolve()
189+
if raw_path.is_absolute()
190+
else (project_root / raw_path).resolve()
191+
)
192+
else:
193+
iss_fallback = project_root.parent / "ISS" / "content"
194+
if iss_fallback.is_dir():
195+
catalog_json_dir = iss_fallback.resolve()
196+
197+
catalog_min_score = _env_float("ACL_CATALOG_MIN_SCORE", 2.0, 0.0, 100.0)
198+
catalog_min_margin = _env_float("ACL_CATALOG_MIN_MARGIN", 0.35, 0.0, 50.0)
199+
catalog_strict_threshold = _env_float(
200+
"ACL_CATALOG_STRICT_THRESHOLD", 4.0, 0.0, 100.0
201+
)
202+
catalog_prompt_top_k = _env_int("ACL_CATALOG_PROMPT_TOP_K", 5, 1, 20)
203+
164204
""" !Credenciais do banco! """
165205

166206
db_host = _normalize_db_host(os.getenv("DB_HOST") or "")
@@ -205,4 +245,11 @@ def _env_int(name: str, default: int, lo: int, hi: int) -> int:
205245
retrieval_candidate_k=retrieval_candidate_k,
206246
retrieval_top_k=retrieval_top_k,
207247
retrieval_max_chunks_per_source=retrieval_max_chunks_per_source,
248+
catalog_enabled=catalog_enabled,
249+
catalog_json_dir=catalog_json_dir,
250+
catalog_min_score=catalog_min_score,
251+
catalog_min_margin=catalog_min_margin,
252+
catalog_strict_threshold=catalog_strict_threshold,
253+
catalog_prompt_top_k=catalog_prompt_top_k,
254+
catalog_router_prompt=catalog_router_prompt,
208255
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Contexto do catálogo de aulas (ativado quando há correspondência lexical provável):
2+
3+
- O assistente está respondendo no contexto de uma aula provável do catálogo acadêmico.
4+
- Fundamente a resposta APENAS nos trechos RAG fornecidos abaixo deste bloco.
5+
- Não invente conteúdo, exemplos ou definições a partir só dos metadados do catálogo (título, slug, resumo).
6+
- Se os trechos forem insuficientes para responder com segurança, diga explicitamente que a base não cobre a pergunta e peça reformulação com termos mais específicos (módulo, comando, tecnologia).

0 commit comments

Comments
 (0)