Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
import os
|
| 6 |
from pathlib import Path
|
| 7 |
import pickle
|
|
|
|
| 8 |
|
| 9 |
import gradio as gr
|
| 10 |
import faiss
|
|
@@ -12,8 +13,6 @@ from sentence_transformers import SentenceTransformer
|
|
| 12 |
from openai import OpenAI
|
| 13 |
|
| 14 |
# ========= NVIDIA API =========
|
| 15 |
-
# Em local: defina NV_API_KEY ou NVIDIA_API_KEY no ambiente.
|
| 16 |
-
# Em Hugging Face Spaces: crie um "Repository secret" chamado NVIDIA_API_KEY.
|
| 17 |
NV_API_KEY = os.environ.get("NVIDIA_API_KEY") or os.environ.get("NV_API_KEY")
|
| 18 |
if not NV_API_KEY:
|
| 19 |
raise RuntimeError(
|
|
@@ -87,13 +86,13 @@ metadatas = emb_data["metadatas"]
|
|
| 87 |
# Mesmo modelo de embeddings usado no build_index.py
|
| 88 |
embedding_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
|
| 89 |
|
| 90 |
-
#
|
| 91 |
-
|
|
|
|
| 92 |
|
| 93 |
|
| 94 |
# ========= Recuperação de contexto =========
|
| 95 |
def retrieve_context(query: str, k: int = 4) -> str:
|
| 96 |
-
"""Busca k trechos mais relevantes no índice FAISS para a pergunta."""
|
| 97 |
if not query or not query.strip():
|
| 98 |
return ""
|
| 99 |
|
|
@@ -113,8 +112,7 @@ def retrieve_context(query: str, k: int = 4) -> str:
|
|
| 113 |
|
| 114 |
|
| 115 |
# ========= Streaming da NVIDIA =========
|
| 116 |
-
def nv_stream(messages, temperature: float, top_p: float, max_tokens: int):
|
| 117 |
-
"""Stream da resposta da NVIDIA (LLaMA 3)."""
|
| 118 |
reply = ""
|
| 119 |
stream = client.chat.completions.create(
|
| 120 |
model=CHAT_MODEL,
|
|
@@ -154,20 +152,25 @@ def chatbot(user_input: str, temperature: float, top_p: float, max_tokens: int):
|
|
| 154 |
),
|
| 155 |
}
|
| 156 |
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
messages.append({"role": "user", "content": u})
|
| 160 |
-
messages.append({"role": "assistant", "content": a})
|
| 161 |
-
messages.append({"role": "user", "content": user_input})
|
| 162 |
|
| 163 |
reply_full = ""
|
| 164 |
try:
|
| 165 |
for partial in nv_stream(messages, temperature, top_p, max_tokens):
|
| 166 |
reply_full = partial
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
except Exception as e:
|
| 169 |
reply_full = f"⚠️ Erro na API NVIDIA: {type(e).__name__}: {e}"
|
| 170 |
-
dialog_history
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
return dialog_history, ""
|
| 173 |
|
|
@@ -178,7 +181,7 @@ def clear_history():
|
|
| 178 |
return [], ""
|
| 179 |
|
| 180 |
|
| 181 |
-
# ========= CSS
|
| 182 |
custom_css = r"""
|
| 183 |
body, .gradio-container {
|
| 184 |
background: #ffffff;
|
|
@@ -258,9 +261,7 @@ body, .gradio-container {
|
|
| 258 |
|
| 259 |
|
| 260 |
# ========= Layout Gradio =========
|
| 261 |
-
# Gradio 6+: css e theme foram movidos para o launch()
|
| 262 |
with gr.Blocks(title=APP_TITLE) as demo:
|
| 263 |
-
# Header
|
| 264 |
with gr.Group(elem_id="header-box"):
|
| 265 |
gr.HTML(
|
| 266 |
f"""
|
|
@@ -276,12 +277,11 @@ with gr.Blocks(title=APP_TITLE) as demo:
|
|
| 276 |
gr.Markdown(INTRO)
|
| 277 |
|
| 278 |
with gr.Row():
|
| 279 |
-
# Coluna principal (chat)
|
| 280 |
with gr.Column(scale=3):
|
| 281 |
with gr.Group(elem_classes="card"):
|
| 282 |
gr.Markdown("### 💬 Conversa Jurídica")
|
| 283 |
|
| 284 |
-
# ✅
|
| 285 |
chatbot_ui = gr.Chatbot(
|
| 286 |
elem_id="chat-window",
|
| 287 |
label="Chatbot",
|
|
@@ -302,30 +302,15 @@ with gr.Blocks(title=APP_TITLE) as demo:
|
|
| 302 |
top_p = gr.Slider(0, 1, value=0.9, label="Top-p")
|
| 303 |
max_tokens = gr.Slider(64, 2048, value=512, step=64, label="Max Tokens")
|
| 304 |
|
| 305 |
-
btn_send.click(
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
)
|
| 310 |
-
txt.submit(
|
| 311 |
-
chatbot,
|
| 312 |
-
[txt, temperature, top_p, max_tokens],
|
| 313 |
-
[chatbot_ui, txt],
|
| 314 |
-
)
|
| 315 |
-
btn_clear.click(
|
| 316 |
-
clear_history,
|
| 317 |
-
[],
|
| 318 |
-
[chatbot_ui, txt],
|
| 319 |
-
)
|
| 320 |
-
|
| 321 |
-
# Sidebar
|
| 322 |
with gr.Column(scale=2):
|
| 323 |
with gr.Group(elem_classes="card"):
|
| 324 |
gr.Markdown("### 💡 Sugestões rápidas")
|
| 325 |
for q in SUGGESTION_QUESTIONS:
|
| 326 |
-
gr.Button(q, elem_classes="suggestion-btn").click(
|
| 327 |
-
lambda s=q: s, outputs=[txt]
|
| 328 |
-
)
|
| 329 |
|
| 330 |
gr.Markdown("---")
|
| 331 |
gr.Markdown("### 📚 Explorar por tema")
|
|
@@ -333,16 +318,13 @@ with gr.Blocks(title=APP_TITLE) as demo:
|
|
| 333 |
for theme, qs in SUGGESTIONS_THEMES.items():
|
| 334 |
with gr.Accordion(theme, open=False):
|
| 335 |
for q in qs:
|
| 336 |
-
gr.Button(q, elem_classes="suggestion-btn").click(
|
| 337 |
-
|
| 338 |
-
|
| 339 |
|
| 340 |
-
gr.Markdown(
|
| 341 |
-
'<div class="app-footer">EcoLexIA · Sistema RAG para legislação ambiental em Portugal</div>'
|
| 342 |
-
)
|
| 343 |
|
| 344 |
-
# Para Hugging Face Spaces basta que a variável `demo` exista;
|
| 345 |
-
# manter o launch permite rodar localmente.
|
| 346 |
if __name__ == "__main__":
|
|
|
|
| 347 |
demo.launch(theme=gr.themes.Soft(), css=custom_css)
|
| 348 |
|
|
|
|
|
|
| 5 |
import os
|
| 6 |
from pathlib import Path
|
| 7 |
import pickle
|
| 8 |
+
from typing import List, Dict, Any
|
| 9 |
|
| 10 |
import gradio as gr
|
| 11 |
import faiss
|
|
|
|
| 13 |
from openai import OpenAI
|
| 14 |
|
| 15 |
# ========= NVIDIA API =========
|
|
|
|
|
|
|
| 16 |
NV_API_KEY = os.environ.get("NVIDIA_API_KEY") or os.environ.get("NV_API_KEY")
|
| 17 |
if not NV_API_KEY:
|
| 18 |
raise RuntimeError(
|
|
|
|
| 86 |
# Mesmo modelo de embeddings usado no build_index.py
|
| 87 |
embedding_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
|
| 88 |
|
| 89 |
+
# ✅ Gradio atual espera "messages format":
|
| 90 |
+
# lista de dicts: {"role": "user"/"assistant", "content": "..."}
|
| 91 |
+
dialog_history: List[Dict[str, str]] = []
|
| 92 |
|
| 93 |
|
| 94 |
# ========= Recuperação de contexto =========
|
| 95 |
def retrieve_context(query: str, k: int = 4) -> str:
|
|
|
|
| 96 |
if not query or not query.strip():
|
| 97 |
return ""
|
| 98 |
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
# ========= Streaming da NVIDIA =========
|
| 115 |
+
def nv_stream(messages: List[Dict[str, str]], temperature: float, top_p: float, max_tokens: int):
|
|
|
|
| 116 |
reply = ""
|
| 117 |
stream = client.chat.completions.create(
|
| 118 |
model=CHAT_MODEL,
|
|
|
|
| 152 |
),
|
| 153 |
}
|
| 154 |
|
| 155 |
+
# Mensagens que vão para o modelo = system + histórico + user atual
|
| 156 |
+
messages: List[Dict[str, str]] = [system_msg] + dialog_history + [{"role": "user", "content": user_input}]
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
reply_full = ""
|
| 159 |
try:
|
| 160 |
for partial in nv_stream(messages, temperature, top_p, max_tokens):
|
| 161 |
reply_full = partial
|
| 162 |
+
|
| 163 |
+
# Atualiza histórico no formato messages (compatível com Gradio)
|
| 164 |
+
dialog_history = dialog_history + [
|
| 165 |
+
{"role": "user", "content": user_input},
|
| 166 |
+
{"role": "assistant", "content": reply_full},
|
| 167 |
+
]
|
| 168 |
except Exception as e:
|
| 169 |
reply_full = f"⚠️ Erro na API NVIDIA: {type(e).__name__}: {e}"
|
| 170 |
+
dialog_history = dialog_history + [
|
| 171 |
+
{"role": "user", "content": user_input},
|
| 172 |
+
{"role": "assistant", "content": reply_full},
|
| 173 |
+
]
|
| 174 |
|
| 175 |
return dialog_history, ""
|
| 176 |
|
|
|
|
| 181 |
return [], ""
|
| 182 |
|
| 183 |
|
| 184 |
+
# ========= CSS =========
|
| 185 |
custom_css = r"""
|
| 186 |
body, .gradio-container {
|
| 187 |
background: #ffffff;
|
|
|
|
| 261 |
|
| 262 |
|
| 263 |
# ========= Layout Gradio =========
|
|
|
|
| 264 |
with gr.Blocks(title=APP_TITLE) as demo:
|
|
|
|
| 265 |
with gr.Group(elem_id="header-box"):
|
| 266 |
gr.HTML(
|
| 267 |
f"""
|
|
|
|
| 277 |
gr.Markdown(INTRO)
|
| 278 |
|
| 279 |
with gr.Row():
|
|
|
|
| 280 |
with gr.Column(scale=3):
|
| 281 |
with gr.Group(elem_classes="card"):
|
| 282 |
gr.Markdown("### 💬 Conversa Jurídica")
|
| 283 |
|
| 284 |
+
# ✅ Agora o valor/retorno é messages-format (dicts role/content)
|
| 285 |
chatbot_ui = gr.Chatbot(
|
| 286 |
elem_id="chat-window",
|
| 287 |
label="Chatbot",
|
|
|
|
| 302 |
top_p = gr.Slider(0, 1, value=0.9, label="Top-p")
|
| 303 |
max_tokens = gr.Slider(64, 2048, value=512, step=64, label="Max Tokens")
|
| 304 |
|
| 305 |
+
btn_send.click(chatbot, [txt, temperature, top_p, max_tokens], [chatbot_ui, txt])
|
| 306 |
+
txt.submit(chatbot, [txt, temperature, top_p, max_tokens], [chatbot_ui, txt])
|
| 307 |
+
btn_clear.click(clear_history, [], [chatbot_ui, txt])
|
| 308 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
with gr.Column(scale=2):
|
| 310 |
with gr.Group(elem_classes="card"):
|
| 311 |
gr.Markdown("### 💡 Sugestões rápidas")
|
| 312 |
for q in SUGGESTION_QUESTIONS:
|
| 313 |
+
gr.Button(q, elem_classes="suggestion-btn").click(lambda s=q: s, outputs=[txt])
|
|
|
|
|
|
|
| 314 |
|
| 315 |
gr.Markdown("---")
|
| 316 |
gr.Markdown("### 📚 Explorar por tema")
|
|
|
|
| 318 |
for theme, qs in SUGGESTIONS_THEMES.items():
|
| 319 |
with gr.Accordion(theme, open=False):
|
| 320 |
for q in qs:
|
| 321 |
+
gr.Button(q, elem_classes="suggestion-btn").click(lambda s=q: s, outputs=[txt])
|
| 322 |
+
|
| 323 |
+
gr.Markdown('<div class="app-footer">EcoLexIA · Sistema RAG para legislação ambiental em Portugal</div>')
|
| 324 |
|
|
|
|
|
|
|
|
|
|
| 325 |
|
|
|
|
|
|
|
| 326 |
if __name__ == "__main__":
|
| 327 |
+
# Gradio 6+: css e theme no launch
|
| 328 |
demo.launch(theme=gr.themes.Soft(), css=custom_css)
|
| 329 |
|
| 330 |
+
|