# app.py β€” GraphRAG Portfolio Agent | GPT-4o-mini + Neo4j import streamlit as st import os st.set_page_config( page_title="GraphRAG β€” Portfolio Agent", page_icon="πŸ€–", layout="wide", initial_sidebar_state="expanded" ) # ── SESSION STATE ───────────────────────────────────────────── for k, v in { 'messages': [], 'neo4j': None, 'neo4j_ok': False, 'agent': None, 'schema_ok': False, 'openai_key': '', 'graph_stats': None, }.items(): if k not in st.session_state: st.session_state[k] = v # ── HELPERS ─────────────────────────────────────────────────── def get_neo4j_config(): cfg = {} try: s = st.secrets if 'NEO4J_URI' in s: return {'uri': s['NEO4J_URI'], 'username': s['NEO4J_USERNAME'], 'password': s['NEO4J_PASSWORD'], 'database': s.get('NEO4J_DATABASE', 'neo4j')} if 'neo4j' in s: n = s['neo4j'] return {'uri': n.get('uri',''), 'username': n.get('username',''), 'password': n.get('password',''), 'database': n.get('database','neo4j')} except Exception: pass return {'uri': os.getenv('NEO4J_URI',''), 'username': os.getenv('NEO4J_USERNAME',''), 'password': os.getenv('NEO4J_PASSWORD',''), 'database': os.getenv('NEO4J_DATABASE','neo4j')} def get_openai_key(): try: if 'OPENAI_API_KEY' in st.secrets: return st.secrets['OPENAI_API_KEY'] except Exception: pass return os.getenv('OPENAI_API_KEY', st.session_state.openai_key) def conectar_neo4j(): if st.session_state.neo4j is not None: return st.session_state.neo4j try: from neo4j import GraphDatabase cfg = get_neo4j_config() if not all([cfg['uri'], cfg['username'], cfg['password']]): return None driver = GraphDatabase.driver(cfg['uri'], auth=(cfg['username'], cfg['password'])) with driver.session(database=cfg['database']) as s: s.run('RETURN 1') st.session_state.neo4j = (driver, cfg['database']) st.session_state.neo4j_ok = True return st.session_state.neo4j except Exception as e: st.session_state.neo4j_ok = False return None def criar_agente(openai_key): try: from graph_agent import GraphRAGAgent conn = st.session_state.neo4j if conn is None: return None driver, database = conn return GraphRAGAgent(openai_key, driver, database) except Exception as e: st.error(f"Erro ao criar agente: {e}") return None # ── SIDEBAR ─────────────────────────────────────────────────── with st.sidebar: st.title("πŸ€– GraphRAG Agent") st.caption("GPT-4o-mini Β· Neo4j Β· Cypher") st.divider() # Neo4j status conn = conectar_neo4j() if st.session_state.neo4j_ok: st.success("πŸ—„οΈ Neo4j Conectado") else: st.error("Neo4j offline") st.info("Configure NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD nos secrets do HF Space.") st.divider() # OpenAI Key st.markdown("#### πŸ”‘ OpenAI API Key") key_input = st.text_input( "Chave", type="password", value=st.session_state.openai_key, placeholder="sk-...", help="Ou configure OPENAI_API_KEY nos secrets do HF" ) if key_input: st.session_state.openai_key = key_input openai_key = get_openai_key() if openai_key: st.success("βœ… OpenAI Key configurada") else: st.warning("OpenAI Key nΓ£o configurada") st.divider() # Popular grafo st.markdown("#### 🧬 Base de Conhecimento") if st.session_state.neo4j_ok: if st.button("πŸ“₯ Popular Grafo Neo4j", use_container_width=True, type="primary"): try: from graph_knowledge import popular_neo4j, verificar_schema driver, database = st.session_state.neo4j with st.spinner("Populando Neo4j com projetos GNN..."): n, erros = popular_neo4j(driver, database) nos, arestas = verificar_schema(driver, database) st.session_state.graph_stats = {'nos': nos, 'arestas': arestas} st.session_state.schema_ok = True st.success(f"βœ… {n} statements executados!") if erros: st.warning(f"{len(erros)} avisos (normal em re-execuΓ§Γ΅es)") except Exception as e: st.error(f"Erro: {e}") if st.session_state.graph_stats: st.markdown("**NΓ³s no grafo:**") for item in st.session_state.graph_stats['nos']: col1, col2 = st.columns([3,1]) col1.caption(item['tipo']) col2.markdown(f"**{item['total']}**") total_ar = sum(a['total'] for a in st.session_state.graph_stats['arestas']) st.caption(f"Total de arestas: **{total_ar}**") else: st.info("Conecte o Neo4j primeiro") st.divider() if st.button("πŸ—‘οΈ Limpar Chat", use_container_width=True): st.session_state.messages = [] st.rerun() # ── MAIN ────────────────────────────────────────────────────── st.title("πŸ€– Portfolio GraphRAG Agent") st.markdown("Pergunte qualquer coisa sobre os **5 projetos de GNN** β€” o agente consulta o Neo4j com Cypher gerado pelo GPT e responde.") st.divider() # Alertas de configuraΓ§Γ£o if not get_openai_key(): st.warning("⬅️ Configure sua OpenAI API Key na sidebar para usar o agente.") if not st.session_state.neo4j_ok: st.error("Neo4j nΓ£o conectado. Configure as credenciais na sidebar.") if st.session_state.neo4j_ok and not st.session_state.schema_ok: st.info("⬅️ Clique em **Popular Grafo Neo4j** na sidebar para carregar a base de conhecimento.") # ── COMO FUNCIONA ───────────────────────────────────────────── if not st.session_state.messages: with st.expander("ℹ️ Como funciona o GraphRAG?", expanded=True): c1, c2, c3 = st.columns(3) with c1: st.markdown("**1. VocΓͺ pergunta**") st.markdown("Em linguagem natural, sobre qualquer projeto, tecnologia ou conceito do portfΓ³lio.") with c2: st.markdown("**2. GPT gera Cypher**") st.markdown("O GPT-4o-mini analisa o schema do grafo e gera uma query Cypher para o Neo4j.") with c3: st.markdown("**3. Neo4j responde**") st.markdown("A query Γ© executada, o contexto Γ© passado de volta ao GPT que formula a resposta final.") st.markdown("### πŸ’‘ SugestΓ΅es de perguntas") sugestoes = [ "Quais projetos usam PyTorch Geometric?", "Qual projeto tem maior AUC?", "Me explique o DOMINANT", "Qual a diferenΓ§a entre HetGNN e GraphSAGE?", "Quais papers sΓ£o referenciados?", "Que conceitos o TGN implementa?", "Qual projeto usa dado real?", "Como funciona Inductive Learning?", ] cols = st.columns(4) for i, sug in enumerate(sugestoes): with cols[i % 4]: if st.button(sug, key=f"sug_{i}", use_container_width=True): st.session_state.pending_question = sug st.rerun() # ── CHAT HISTORY ───────────────────────────────────────────── for msg in st.session_state.messages: with st.chat_message(msg["role"], avatar="πŸ§‘" if msg["role"]=="user" else "πŸ€–"): if msg["role"] == "assistant" and msg.get("cypher"): with st.expander(f"πŸ” Cypher gerado ({msg.get('n_resultados', 0)} resultados)", expanded=False): st.code(msg["cypher"], language="cypher") st.markdown(msg["content"]) # ── INPUT ───────────────────────────────────────────────────── # Processa pergunta pendente (de botΓ£o de sugestΓ£o) pergunta_pendente = st.session_state.pop("pending_question", None) pergunta = st.chat_input("Pergunte sobre os projetos de GNN...") or pergunta_pendente if pergunta and get_openai_key() and st.session_state.neo4j_ok: # Mostra mensagem do usuΓ‘rio with st.chat_message("user", avatar="πŸ§‘"): st.markdown(pergunta) st.session_state.messages.append({"role": "user", "content": pergunta}) # Cria agente e responde agent = criar_agente(get_openai_key()) if agent: with st.chat_message("assistant", avatar="πŸ€–"): with st.spinner("Consultando grafo Neo4j..."): try: resultado = agent.responder(pergunta) with st.expander( f"πŸ” Cypher gerado ({len(resultado['resultados'])} resultados)", expanded=False ): st.code(resultado["cypher"], language="cypher") st.markdown(resultado["resposta"]) st.session_state.messages.append({ "role": "assistant", "content": resultado["resposta"], "cypher": resultado["cypher"], "n_resultados": len(resultado["resultados"]), }) except Exception as e: msg_erro = f"Erro ao processar: {e}" st.error(msg_erro) st.session_state.messages.append({ "role": "assistant", "content": msg_erro}) else: st.error("NΓ£o foi possΓ­vel criar o agente. Verifique as configuraΓ§Γ΅es.") elif pergunta and not get_openai_key(): st.warning("Configure a OpenAI API Key na sidebar.") elif pergunta and not st.session_state.neo4j_ok: st.error("Neo4j nΓ£o conectado.")