-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroutes.py
More file actions
119 lines (99 loc) · 4.09 KB
/
Copy pathroutes.py
File metadata and controls
119 lines (99 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""Rotas GET / e POST /chat."""
from __future__ import annotations
import logging
import re
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import HTMLResponse, StreamingResponse
from collections.abc import AsyncGenerator
log = logging.getLogger("kernelbots.api.chat")
router = APIRouter()
_SESSION_ID_RE = re.compile(r"^[A-Za-z0-9_-]{8,128}$")
@router.get("/", response_class=HTMLResponse)
async def home(request: Request) -> HTMLResponse:
client_ip = request.client.host if request.client else "desconhecido"
log.info(f"🌐 Interface carregada — cliente: {client_ip}")
templates = request.app.state.templates
return templates.TemplateResponse(request=request, name="index.html")
@router.post("/chat")
async def chat(request: Request) -> StreamingResponse:
client_ip = request.client.host if request.client else "desconhecido"
services = request.app.state.services
try:
data = await request.json()
except Exception:
log.warning(f"⚠ Requisição inválida de {client_ip} — corpo não é JSON válido")
raise HTTPException(status_code=400, detail="JSON inválido no corpo da requisição.")
user_message: str = (data.get("message") or "").strip()
if not user_message:
log.warning(f"⚠ Requisição de {client_ip} com campo 'message' ausente ou vazio")
raise HTTPException(status_code=400, detail="Campo 'message' ausente ou vazio.")
raw_discipline = data.get("discipline")
discipline: str | None
if raw_discipline is None:
discipline = None
elif isinstance(raw_discipline, str):
discipline = raw_discipline.strip() or None
else:
log.warning(f"⚠ Requisição de {client_ip} — campo 'discipline' com tipo inválido")
raise HTTPException(
status_code=400,
detail="Campo 'discipline' deve ser string ou omitido.",
)
raw_session = data.get("session_id")
session_id: str | None
if raw_session is None:
session_id = None
elif isinstance(raw_session, str):
s = raw_session.strip()
if not s:
session_id = None
elif not _SESSION_ID_RE.match(s):
log.warning(f"⚠ Requisição de {client_ip} — session_id com formato inválido")
raise HTTPException(
status_code=400,
detail="Campo 'session_id' inválido (use 8–128 caracteres: letras, dígitos, _ ou -).",
)
else:
session_id = s
else:
log.warning(f"⚠ Requisição de {client_ip} — campo 'session_id' com tipo inválido")
raise HTTPException(
status_code=400,
detail="Campo 'session_id' deve ser string ou omitido.",
)
if user_message.strip().lower() == "/reload":
log.info("🔄 Comando /reload recebido — reconstruindo índice BM25...")
services.search_engine.rebuild()
chunk_count = len(services.search_engine.chunks)
silo_count = len(services.search_engine.discipline_ids)
status = (
f"Índice reconstruído: {chunk_count} chunk(s) total "
f"({silo_count} silo(s) do MySQL)."
)
log.info("✅ /reload concluído — %s", status)
async def _reload_stream() -> AsyncGenerator[str, None]:
yield f"data: {status}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(
_reload_stream(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no", "Connection": "keep-alive"},
)
built = services.context_manager.build_messages(
user_message,
discipline_filter=discipline,
session_id=session_id,
)
return StreamingResponse(
services.chat_provider.stream_response(
built.messages,
trace=built.trace,
decision=built.decision,
),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
},
)