Danielfonseca1212 commited on
Commit
256e811
Β·
verified Β·
1 Parent(s): 2744df3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -152
app.py CHANGED
@@ -1,152 +1,246 @@
1
- # graph_agent.py β€” GraphRAG Agent: GPT-4o-mini + Neo4j Cypher
2
- from openai import OpenAI
3
- import re
4
-
5
- SYSTEM_PROMPT = """VocΓͺ Γ© um agente especialista em Graph Neural Networks para detecΓ§Γ£o de fraude.
6
- VocΓͺ tem acesso a uma base de conhecimento em grafo Neo4j com 5 projetos de GNN.
7
-
8
- PROJETOS DISPONÍVEIS:
9
- 1. Sistema Imune Digital β€” Deep RL (DQN Dueling), 3 agentes especialistas
10
- 2. HetGNN Fraud β€” Grafo heterogΓͺneo, HGTConv, 5 tipos de nΓ³
11
- 3. TGN Fraud Detection β€” Temporal GNN, memΓ³ria GRU, stream e-commerce
12
- 4. DOMINANT β€” Anomaly detection sem labels (IJCAI 2019)
13
- 5. GraphSAGE Elliptic β€” Dataset real Bitcoin, inductive learning
14
-
15
- SCHEMA DO GRAFO:
16
- NΓ³s: Projeto, Tecnologia, Conceito, Paper, Metrica
17
- Arestas:
18
- (Projeto)-[:USA]->(Tecnologia)
19
- (Projeto)-[:IMPLEMENTA]->(Conceito)
20
- (Projeto)-[:REFERENCIA]->(Paper)
21
- (Projeto)-[:TEM_METRICA]->(Metrica)
22
- (Projeto)-[:DIFERENTE_DE]->(Projeto)
23
-
24
- PROPRIEDADES:
25
- Projeto: nome, descricao, paradigma, dado, url, emoji, ano
26
- Tecnologia: nome
27
- Conceito: nome, descricao
28
- Paper: titulo, autores, venue, modelo
29
- Metrica: projeto, tipo, valor, dataset
30
-
31
- Sua tarefa:
32
- 1. Gerar uma query Cypher para buscar informaΓ§Γ£o relevante no grafo
33
- 2. A query deve ser eficiente e especΓ­fica Γ  pergunta
34
- 3. Retornar APENAS o Cypher, sem explicaΓ§Γ£o, dentro de ```cypher ... ```
35
-
36
- Exemplos:
37
- Pergunta: "Quais projetos usam PyTorch Geometric?"
38
- ```cypher
39
- MATCH (p:Projeto)-[:USA]->(t:Tecnologia {nome: 'PyTorch Geometric'})
40
- RETURN p.nome, p.descricao, p.url
41
- ```
42
-
43
- Pergunta: "Qual projeto tem maior AUC?"
44
- ```cypher
45
- MATCH (p:Projeto)-[:TEM_METRICA]->(m:Metrica {tipo: 'AUC'})
46
- RETURN p.nome, m.valor, m.dataset
47
- ORDER BY m.valor DESC
48
- ```
49
-
50
- Pergunta: "Me explique o conceito de Inductive Learning"
51
- ```cypher
52
- MATCH (c:Conceito {nome: 'Inductive Learning'})<-[:IMPLEMENTA]-(p:Projeto)
53
- RETURN c.nome, c.descricao, collect(p.nome) AS projetos
54
- ```"""
55
-
56
- ANSWER_PROMPT = """VocΓͺ Γ© Daniel Fonseca, ML Engineer especialista em Graph Neural Networks para detecΓ§Γ£o de fraude.
57
- Responda de forma tΓ©cnica, clara e entusiasmada sobre seus projetos.
58
-
59
- Contexto do grafo Neo4j:
60
- {context}
61
-
62
- Pergunta do usuΓ‘rio: {question}
63
-
64
- InstruΓ§Γ΅es:
65
- - Responda em portuguΓͺs
66
- - Seja especΓ­fico e tΓ©cnico
67
- - Cite os projetos relevantes com seus emojis
68
- - Se tiver URL de projeto, mencione que pode ser acessado no Hugging Face
69
- - MΓ‘ximo 4 parΓ‘grafos
70
- - Finalize com uma frase que convide o usuΓ‘rio a explorar mais"""
71
-
72
-
73
- class GraphRAGAgent:
74
- def __init__(self, openai_api_key: str, neo4j_driver, neo4j_database: str):
75
- self.client = OpenAI(api_key=openai_api_key)
76
- self.driver = neo4j_driver
77
- self.database = neo4j_database
78
- self.model = "gpt-4o-mini"
79
-
80
- def gerar_cypher(self, pergunta: str) -> str:
81
- """GPT gera Cypher a partir da pergunta em linguagem natural."""
82
- resp = self.client.chat.completions.create(
83
- model=self.model,
84
- messages=[
85
- {"role": "system", "content": SYSTEM_PROMPT},
86
- {"role": "user", "content": pergunta}
87
- ],
88
- temperature=0.1,
89
- max_tokens=300,
90
- )
91
- texto = resp.choices[0].message.content
92
- # Extrai o Cypher do bloco de cΓ³digo
93
- match = re.search(r'```cypher\s*(.*?)\s*```', texto, re.DOTALL)
94
- if match:
95
- return match.group(1).strip()
96
- # Fallback: tenta extrair qualquer bloco de cΓ³digo
97
- match = re.search(r'```\s*(.*?)\s*```', texto, re.DOTALL)
98
- if match:
99
- return match.group(1).strip()
100
- return texto.strip()
101
-
102
- def executar_cypher(self, cypher: str) -> list:
103
- """Executa Cypher no Neo4j e retorna resultados."""
104
- try:
105
- with self.driver.session(database=self.database) as session:
106
- result = session.run(cypher)
107
- return [dict(record) for record in result]
108
- except Exception as e:
109
- return [{"erro": str(e)}]
110
-
111
- def formatar_contexto(self, resultados: list) -> str:
112
- """Formata resultados do Neo4j em texto para o LLM."""
113
- if not resultados:
114
- return "Nenhum resultado encontrado no grafo."
115
- if len(resultados) == 1 and "erro" in resultados[0]:
116
- return f"Erro na query: {resultados[0]['erro']}"
117
- linhas = []
118
- for r in resultados[:10]: # max 10 resultados
119
- linha = " | ".join(f"{k}: {v}" for k, v in r.items() if v is not None)
120
- linhas.append(linha)
121
- return "\n".join(linhas)
122
-
123
- def gerar_resposta(self, pergunta: str, contexto: str) -> str:
124
- """GPT gera resposta final com base no contexto do grafo."""
125
- prompt = ANSWER_PROMPT.format(context=contexto, question=pergunta)
126
- resp = self.client.chat.completions.create(
127
- model=self.model,
128
- messages=[{"role": "user", "content": prompt}],
129
- temperature=0.7,
130
- max_tokens=600,
131
- )
132
- return resp.choices[0].message.content
133
-
134
- def responder(self, pergunta: str) -> dict:
135
- """
136
- Pipeline completo:
137
- 1. Gera Cypher
138
- 2. Executa no Neo4j
139
- 3. Formata contexto
140
- 4. Gera resposta
141
- """
142
- cypher = self.gerar_cypher(pergunta)
143
- resultados = self.executar_cypher(cypher)
144
- contexto = self.formatar_contexto(resultados)
145
- resposta = self.gerar_resposta(pergunta, contexto)
146
-
147
- return {
148
- "cypher": cypher,
149
- "resultados": resultados,
150
- "contexto": contexto,
151
- "resposta": resposta,
152
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py β€” GraphRAG Portfolio Agent | GPT-4o-mini + Neo4j
2
+ import streamlit as st
3
+ import os
4
+
5
+ st.set_page_config(
6
+ page_title="GraphRAG β€” Portfolio Agent",
7
+ page_icon="πŸ€–",
8
+ layout="wide",
9
+ initial_sidebar_state="expanded"
10
+ )
11
+
12
+ # ── SESSION STATE ─────────────────────────────────────────────
13
+ for k, v in {
14
+ 'messages': [], 'neo4j': None, 'neo4j_ok': False,
15
+ 'agent': None, 'schema_ok': False,
16
+ 'openai_key': '', 'graph_stats': None,
17
+ }.items():
18
+ if k not in st.session_state:
19
+ st.session_state[k] = v
20
+
21
+ # ── HELPERS ───────────────────────────────────────────────────
22
+ def get_neo4j_config():
23
+ cfg = {}
24
+ try:
25
+ s = st.secrets
26
+ if 'NEO4J_URI' in s:
27
+ return {'uri': s['NEO4J_URI'], 'username': s['NEO4J_USERNAME'],
28
+ 'password': s['NEO4J_PASSWORD'],
29
+ 'database': s.get('NEO4J_DATABASE', 'neo4j')}
30
+ if 'neo4j' in s:
31
+ n = s['neo4j']
32
+ return {'uri': n.get('uri',''), 'username': n.get('username',''),
33
+ 'password': n.get('password',''), 'database': n.get('database','neo4j')}
34
+ except Exception:
35
+ pass
36
+ return {'uri': os.getenv('NEO4J_URI',''), 'username': os.getenv('NEO4J_USERNAME',''),
37
+ 'password': os.getenv('NEO4J_PASSWORD',''), 'database': os.getenv('NEO4J_DATABASE','neo4j')}
38
+
39
+ def get_openai_key():
40
+ try:
41
+ if 'OPENAI_API_KEY' in st.secrets:
42
+ return st.secrets['OPENAI_API_KEY']
43
+ except Exception:
44
+ pass
45
+ return os.getenv('OPENAI_API_KEY', st.session_state.openai_key)
46
+
47
+ def conectar_neo4j():
48
+ if st.session_state.neo4j is not None:
49
+ return st.session_state.neo4j
50
+ try:
51
+ from neo4j import GraphDatabase
52
+ cfg = get_neo4j_config()
53
+ if not all([cfg['uri'], cfg['username'], cfg['password']]):
54
+ return None
55
+ driver = GraphDatabase.driver(cfg['uri'], auth=(cfg['username'], cfg['password']))
56
+ with driver.session(database=cfg['database']) as s:
57
+ s.run('RETURN 1')
58
+ st.session_state.neo4j = (driver, cfg['database'])
59
+ st.session_state.neo4j_ok = True
60
+ return st.session_state.neo4j
61
+ except Exception as e:
62
+ st.session_state.neo4j_ok = False
63
+ return None
64
+
65
+ def criar_agente(openai_key):
66
+ try:
67
+ from graph_agent import GraphRAGAgent
68
+ conn = st.session_state.neo4j
69
+ if conn is None:
70
+ return None
71
+ driver, database = conn
72
+ return GraphRAGAgent(openai_key, driver, database)
73
+ except Exception as e:
74
+ st.error(f"Erro ao criar agente: {e}")
75
+ return None
76
+
77
+ # ── SIDEBAR ───────────────────────────────────────────────────
78
+ with st.sidebar:
79
+ st.title("πŸ€– GraphRAG Agent")
80
+ st.caption("GPT-4o-mini Β· Neo4j Β· Cypher")
81
+ st.divider()
82
+
83
+ # Neo4j status
84
+ conn = conectar_neo4j()
85
+ if st.session_state.neo4j_ok:
86
+ st.success("πŸ—„οΈ Neo4j Conectado")
87
+ else:
88
+ st.error("Neo4j offline")
89
+ st.info("Configure NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD nos secrets do HF Space.")
90
+
91
+ st.divider()
92
+
93
+ # OpenAI Key
94
+ st.markdown("#### πŸ”‘ OpenAI API Key")
95
+ key_input = st.text_input(
96
+ "Chave", type="password",
97
+ value=st.session_state.openai_key,
98
+ placeholder="sk-...",
99
+ help="Ou configure OPENAI_API_KEY nos secrets do HF"
100
+ )
101
+ if key_input:
102
+ st.session_state.openai_key = key_input
103
+
104
+ openai_key = get_openai_key()
105
+ if openai_key:
106
+ st.success("βœ… OpenAI Key configurada")
107
+ else:
108
+ st.warning("OpenAI Key nΓ£o configurada")
109
+
110
+ st.divider()
111
+
112
+ # Popular grafo
113
+ st.markdown("#### 🧬 Base de Conhecimento")
114
+ if st.session_state.neo4j_ok:
115
+ if st.button("πŸ“₯ Popular Grafo Neo4j", use_container_width=True, type="primary"):
116
+ try:
117
+ from graph_knowledge import popular_neo4j, verificar_schema
118
+ driver, database = st.session_state.neo4j
119
+ with st.spinner("Populando Neo4j com projetos GNN..."):
120
+ n, erros = popular_neo4j(driver, database)
121
+ nos, arestas = verificar_schema(driver, database)
122
+ st.session_state.graph_stats = {'nos': nos, 'arestas': arestas}
123
+ st.session_state.schema_ok = True
124
+ st.success(f"βœ… {n} statements executados!")
125
+ if erros:
126
+ st.warning(f"{len(erros)} avisos (normal em re-execuΓ§Γ΅es)")
127
+ except Exception as e:
128
+ st.error(f"Erro: {e}")
129
+
130
+ if st.session_state.graph_stats:
131
+ st.markdown("**NΓ³s no grafo:**")
132
+ for item in st.session_state.graph_stats['nos']:
133
+ col1, col2 = st.columns([3,1])
134
+ col1.caption(item['tipo'])
135
+ col2.markdown(f"**{item['total']}**")
136
+ total_ar = sum(a['total'] for a in st.session_state.graph_stats['arestas'])
137
+ st.caption(f"Total de arestas: **{total_ar}**")
138
+ else:
139
+ st.info("Conecte o Neo4j primeiro")
140
+
141
+ st.divider()
142
+ if st.button("πŸ—‘οΈ Limpar Chat", use_container_width=True):
143
+ st.session_state.messages = []
144
+ st.rerun()
145
+
146
+ # ── MAIN ──────────────────────────────────────────────────────
147
+ st.title("πŸ€– Portfolio GraphRAG Agent")
148
+ st.markdown("Pergunte qualquer coisa sobre os **5 projetos de GNN** β€” o agente consulta o Neo4j com Cypher gerado pelo GPT e responde.")
149
+ st.divider()
150
+
151
+ # Alertas de configuraΓ§Γ£o
152
+ if not get_openai_key():
153
+ st.warning("⬅️ Configure sua OpenAI API Key na sidebar para usar o agente.")
154
+
155
+ if not st.session_state.neo4j_ok:
156
+ st.error("Neo4j nΓ£o conectado. Configure as credenciais na sidebar.")
157
+
158
+ if st.session_state.neo4j_ok and not st.session_state.schema_ok:
159
+ st.info("⬅️ Clique em **Popular Grafo Neo4j** na sidebar para carregar a base de conhecimento.")
160
+
161
+ # ── COMO FUNCIONA ─────────────────────────────────────────────
162
+ if not st.session_state.messages:
163
+ with st.expander("ℹ️ Como funciona o GraphRAG?", expanded=True):
164
+ c1, c2, c3 = st.columns(3)
165
+ with c1:
166
+ st.markdown("**1. VocΓͺ pergunta**")
167
+ st.markdown("Em linguagem natural, sobre qualquer projeto, tecnologia ou conceito do portfΓ³lio.")
168
+ with c2:
169
+ st.markdown("**2. GPT gera Cypher**")
170
+ st.markdown("O GPT-4o-mini analisa o schema do grafo e gera uma query Cypher para o Neo4j.")
171
+ with c3:
172
+ st.markdown("**3. Neo4j responde**")
173
+ st.markdown("A query Γ© executada, o contexto Γ© passado de volta ao GPT que formula a resposta final.")
174
+
175
+ st.markdown("### πŸ’‘ SugestΓ΅es de perguntas")
176
+ sugestoes = [
177
+ "Quais projetos usam PyTorch Geometric?",
178
+ "Qual projeto tem maior AUC?",
179
+ "Me explique o DOMINANT",
180
+ "Qual a diferenΓ§a entre HetGNN e GraphSAGE?",
181
+ "Quais papers sΓ£o referenciados?",
182
+ "Que conceitos o TGN implementa?",
183
+ "Qual projeto usa dado real?",
184
+ "Como funciona Inductive Learning?",
185
+ ]
186
+ cols = st.columns(4)
187
+ for i, sug in enumerate(sugestoes):
188
+ with cols[i % 4]:
189
+ if st.button(sug, key=f"sug_{i}", use_container_width=True):
190
+ st.session_state.pending_question = sug
191
+ st.rerun()
192
+
193
+ # ── CHAT HISTORY ─────────────────────────────────────────────
194
+ for msg in st.session_state.messages:
195
+ with st.chat_message(msg["role"], avatar="πŸ§‘" if msg["role"]=="user" else "πŸ€–"):
196
+ if msg["role"] == "assistant" and msg.get("cypher"):
197
+ with st.expander(f"πŸ” Cypher gerado ({msg.get('n_resultados', 0)} resultados)", expanded=False):
198
+ st.code(msg["cypher"], language="cypher")
199
+ st.markdown(msg["content"])
200
+
201
+ # ── INPUT ─────────────────────────────────────────────────────
202
+ # Processa pergunta pendente (de botΓ£o de sugestΓ£o)
203
+ pergunta_pendente = st.session_state.pop("pending_question", None)
204
+
205
+ pergunta = st.chat_input("Pergunte sobre os projetos de GNN...") or pergunta_pendente
206
+
207
+ if pergunta and get_openai_key() and st.session_state.neo4j_ok:
208
+ # Mostra mensagem do usuΓ‘rio
209
+ with st.chat_message("user", avatar="πŸ§‘"):
210
+ st.markdown(pergunta)
211
+ st.session_state.messages.append({"role": "user", "content": pergunta})
212
+
213
+ # Cria agente e responde
214
+ agent = criar_agente(get_openai_key())
215
+ if agent:
216
+ with st.chat_message("assistant", avatar="πŸ€–"):
217
+ with st.spinner("Consultando grafo Neo4j..."):
218
+ try:
219
+ resultado = agent.responder(pergunta)
220
+
221
+ with st.expander(
222
+ f"πŸ” Cypher gerado ({len(resultado['resultados'])} resultados)",
223
+ expanded=False
224
+ ):
225
+ st.code(resultado["cypher"], language="cypher")
226
+
227
+ st.markdown(resultado["resposta"])
228
+
229
+ st.session_state.messages.append({
230
+ "role": "assistant",
231
+ "content": resultado["resposta"],
232
+ "cypher": resultado["cypher"],
233
+ "n_resultados": len(resultado["resultados"]),
234
+ })
235
+ except Exception as e:
236
+ msg_erro = f"Erro ao processar: {e}"
237
+ st.error(msg_erro)
238
+ st.session_state.messages.append({
239
+ "role": "assistant", "content": msg_erro})
240
+ else:
241
+ st.error("NΓ£o foi possΓ­vel criar o agente. Verifique as configuraΓ§Γ΅es.")
242
+
243
+ elif pergunta and not get_openai_key():
244
+ st.warning("Configure a OpenAI API Key na sidebar.")
245
+ elif pergunta and not st.session_state.neo4j_ok:
246
+ st.error("Neo4j nΓ£o conectado.")