Spaces:
Sleeping
Sleeping
A newer version of the Gradio SDK is available:
6.6.0
🔒 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:
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_idna tabeladocuments - Filtrar documentos por
session_idatual - 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_iddo Gradio - Adicionar coluna
user_idnas 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
-- 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
# 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
# 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
# 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
# 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
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":
# 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:
# 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?