# šŸ”’ 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?**