Spaces:
Sleeping
Sleeping
File size: 10,275 Bytes
f5eb34f | 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | # 🔒 Setup Multi-Usuário
Guia para implementar isolamento de dados por usuário no RAG Template.
---
## Situação Atual
**Comportamento:**
- Todos os documentos são compartilhados entre todos os usuários
- Qualquer usuário pode buscar em todos os documentos
- Apenas o histórico de chat é isolado por sessão
**Tabelas atuais:**
```sql
documents (
id,
title,
content,
embedding,
created_at
) -- SEM user_id!
```
---
## Opções de Implementação
### Opção 1: Isolamento por Sessão (Simples)
**Quando usar:**
- App pessoal (1 usuário por instância)
- Prototipagem rápida
- Demos e testes
**Implementação:**
- Adicionar `session_id` na tabela `documents`
- Filtrar documentos por `session_id` atual
- Problema: Sessão se perde ao recarregar página
### Opção 2: Autenticação com Hugging Face Spaces (Recomendado)
**Quando usar:**
- Deploy público no Hugging Face Spaces
- Múltiplos usuários
- Dados privados por usuário
**Implementação:**
- Usar autenticação integrada do Spaces
- Capturar `user_id` do Gradio
- Adicionar coluna `user_id` nas tabelas
### Opção 3: Autenticação Custom (Avançado)
**Quando usar:**
- Deploy standalone
- Sistema de autenticação próprio
- Controle total
**Implementação:**
- Sistema de login/registro
- JWT ou sessions
- Tabela de usuários
---
## Implementação Recomendada: Opção 2
### 1. Modificar Schema do Banco
```sql
-- Adicionar coluna user_id em documents
ALTER TABLE documents ADD COLUMN user_id TEXT;
-- Adicionar índice para performance
CREATE INDEX idx_documents_user_id ON documents(user_id);
-- Adicionar coluna user_id em chats
ALTER TABLE chats ADD COLUMN user_id TEXT;
-- Adicionar coluna user_id em query_metrics
ALTER TABLE query_metrics ADD COLUMN user_id TEXT;
```
### 2. Atualizar database.py
```python
# src/database.py
def insert_document(
self,
title: str,
content: str,
embedding: List[float],
user_id: str # NOVO parâmetro
) -> Optional[int]:
"""Insere documento no banco com user_id"""
conn = self.connect()
if not conn:
return None
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO documents (title, content, embedding, user_id)
VALUES (%s, %s, %s::vector, %s)
RETURNING id
""",
(title, content, embedding, user_id)
)
row = cur.fetchone()
return row[0] if row else None
except Exception as e:
self.last_error = f"Falha ao inserir documento: {str(e)}"
return None
def search_similar(
self,
query_embedding: List[float],
k: int = 4,
user_id: str = None # NOVO parâmetro
) -> List[Dict[str, Any]]:
"""Busca documentos similares, opcionalmente filtrados por user_id"""
conn = self.connect()
if not conn:
return []
try:
with conn.cursor() as cur:
if user_id:
# Busca apenas documentos do usuário
cur.execute(
"""
SELECT id, title, content, 1 - (embedding <=> %s::vector) as score
FROM documents
WHERE user_id = %s
ORDER BY embedding <=> %s::vector
LIMIT %s
""",
(query_embedding, user_id, query_embedding, k)
)
else:
# Busca em todos (modo público)
cur.execute(
"""
SELECT id, title, content, 1 - (embedding <=> %s::vector) as score
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT %s
""",
(query_embedding, query_embedding, k)
)
rows = cur.fetchall()
return [
{
"id": r[0],
"title": r[1],
"content": r[2],
"score": float(r[3])
}
for r in rows
]
except Exception as e:
self.last_error = f"Falha na busca: {str(e)}"
return []
```
### 3. Capturar user_id no Gradio
```python
# app.py
def create_app():
"""Cria aplicação Gradio com todas as abas"""
# Inicializa gerenciadores
db_manager = DatabaseManager()
embedding_manager = EmbeddingManager()
generation_manager = GenerationManager()
# Inicializa schema do banco
db_ok = db_manager.init_schema()
# Interface Gradio
with gr.Blocks(title="RAG Template", css=CUSTOM_CSS) as demo:
# Captura user_id (funciona no Hugging Face Spaces)
def get_user_id(request: gr.Request):
# No Spaces com autenticação habilitada
if hasattr(request, 'username') and request.username:
return request.username
# Fallback: usar session
return str(uuid.uuid4())
# Estado global do usuário
user_id_state = gr.State(value=None)
# Header
with gr.Row():
gr.Markdown("""
# RAG Template
Template interativo de Retrieval-Augmented Generation com PostgreSQL + pgvector
Explore cada etapa do processo RAG de forma visual e educativa.
""")
# Inicializar user_id ao carregar
demo.load(
fn=get_user_id,
outputs=[user_id_state]
)
# Abas principais
with gr.Tabs():
# Passar user_id_state para as abas
create_ingestion_tab(db_manager, embedding_manager, user_id_state)
create_exploration_tab(db_manager, embedding_manager, user_id_state)
create_chat_tab(db_manager, embedding_manager, generation_manager, user_id_state)
# ...
return demo
```
### 4. Atualizar Abas para Usar user_id
```python
# ui/ingestion_tab.py
def create_ingestion_tab(
db_manager: DatabaseManager,
embedding_manager: EmbeddingManager,
user_id_state: gr.State # NOVO
):
"""Cria aba de ingestão de documentos"""
with gr.Tab("Ingestão de Documentos"):
# ... UI components ...
def ingest_documents(files, strategy, chunk_size_val, chunk_overlap_val, user_id):
# ... processamento ...
# Inserir com user_id
for chunk_text, embedding_vec in zip(chunks, embeddings):
emb_list = embedding_vec.tolist()
doc_id = db_manager.insert_document(
filename,
chunk_text,
emb_list,
user_id # NOVO
)
# Conecta evento com user_id
ingest_btn.click(
fn=ingest_documents,
inputs=[file_upload, chunk_strategy, chunk_size, chunk_overlap, user_id_state], # NOVO
outputs=[...]
)
```
---
## Habilitar Autenticação no Hugging Face Spaces
### 1. No Space Settings
```yaml
# No arquivo README.md do Space (YAML header)
---
title: RAG Template
emoji: 📚
colorFrom: blue
colorTo: purple
sdk: gradio
sdk_version: 4.36.0
app_file: app.py
pinned: true
hf_oauth: true # ADICIONAR ESTA LINHA
hf_oauth_scopes: # ADICIONAR ESTAS LINHAS
- read-repos
- write-repos
---
```
### 2. Capturar Username
```python
import gradio as gr
def create_app():
with gr.Blocks() as demo:
@demo.load(inputs=None, outputs=None)
def on_load(request: gr.Request):
if request:
username = request.username or "anonymous"
print(f"User logged in: {username}")
return username
return "anonymous"
```
---
## Alternativa Simples: Modo "Workspace"
Se não quiser autenticação completa, pode usar um sistema de "workspaces":
```python
# app.py
with gr.Blocks() as demo:
# Seletor de workspace
workspace_input = gr.Textbox(
label="Nome do Workspace",
placeholder="Digite um nome único para seu workspace",
value=""
)
workspace_state = gr.State(value="default")
def set_workspace(workspace_name):
if not workspace_name:
return "default"
# Hash simples para consistência
import hashlib
return hashlib.md5(workspace_name.encode()).hexdigest()[:16]
workspace_input.change(
fn=set_workspace,
inputs=[workspace_input],
outputs=[workspace_state]
)
```
**Vantagens:**
- Sem autenticação
- Simples de implementar
- Usuário escolhe nome do workspace
**Desvantagens:**
- Qualquer um com o nome do workspace pode acessar
- Sem segurança real
---
## Recomendação Final
**Para uso pessoal/demo:**
- Use isolamento por sessão (Opção 1)
- Rápido e simples
**Para deploy público no Spaces:**
- Use autenticação HF OAuth (Opção 2)
- Seguro e integrado
**Para produção empresarial:**
- Use autenticação custom (Opção 3)
- Controle total
---
## Migration Script
Script para migrar banco existente:
```python
# scripts/add_user_id.py
import os
from dotenv import load_dotenv
import psycopg
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
def migrate():
conn = psycopg.connect(DATABASE_URL, autocommit=True)
with conn.cursor() as cur:
# Adicionar colunas
cur.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS user_id TEXT")
cur.execute("ALTER TABLE chats ADD COLUMN IF NOT EXISTS user_id TEXT")
cur.execute("ALTER TABLE query_metrics ADD COLUMN IF NOT EXISTS user_id TEXT")
# Setar user_id padrão para documentos existentes
cur.execute("UPDATE documents SET user_id = 'legacy' WHERE user_id IS NULL")
cur.execute("UPDATE chats SET user_id = 'legacy' WHERE user_id IS NULL")
# Criar índices
cur.execute("CREATE INDEX IF NOT EXISTS idx_documents_user_id ON documents(user_id)")
cur.execute("CREATE INDEX IF NOT EXISTS idx_chats_user_id ON chats(user_id)")
print("✅ Migração concluída!")
conn.close()
if __name__ == "__main__":
migrate()
```
---
**Quer que eu implemente alguma dessas opções agora?**
|