File size: 5,556 Bytes
2744df3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# graph_agent.py — GraphRAG Agent: GPT-4o-mini + Neo4j Cypher
from openai import OpenAI
import re

SYSTEM_PROMPT = """Você é um agente especialista em Graph Neural Networks para detecção de fraude.
Você tem acesso a uma base de conhecimento em grafo Neo4j com 5 projetos de GNN.

PROJETOS DISPONÍVEIS:
1. Sistema Imune Digital — Deep RL (DQN Dueling), 3 agentes especialistas
2. HetGNN Fraud — Grafo heterogêneo, HGTConv, 5 tipos de nó
3. TGN Fraud Detection — Temporal GNN, memória GRU, stream e-commerce
4. DOMINANT — Anomaly detection sem labels (IJCAI 2019)
5. GraphSAGE Elliptic — Dataset real Bitcoin, inductive learning

SCHEMA DO GRAFO:
Nós: Projeto, Tecnologia, Conceito, Paper, Metrica
Arestas: 
  (Projeto)-[:USA]->(Tecnologia)
  (Projeto)-[:IMPLEMENTA]->(Conceito)
  (Projeto)-[:REFERENCIA]->(Paper)
  (Projeto)-[:TEM_METRICA]->(Metrica)
  (Projeto)-[:DIFERENTE_DE]->(Projeto)

PROPRIEDADES:
  Projeto: nome, descricao, paradigma, dado, url, emoji, ano
  Tecnologia: nome
  Conceito: nome, descricao
  Paper: titulo, autores, venue, modelo
  Metrica: projeto, tipo, valor, dataset

Sua tarefa: 
1. Gerar uma query Cypher para buscar informação relevante no grafo
2. A query deve ser eficiente e específica à pergunta
3. Retornar APENAS o Cypher, sem explicação, dentro de ```cypher ... ```

Exemplos:
Pergunta: "Quais projetos usam PyTorch Geometric?"
```cypher
MATCH (p:Projeto)-[:USA]->(t:Tecnologia {nome: 'PyTorch Geometric'})
RETURN p.nome, p.descricao, p.url
```

Pergunta: "Qual projeto tem maior AUC?"
```cypher
MATCH (p:Projeto)-[:TEM_METRICA]->(m:Metrica {tipo: 'AUC'})
RETURN p.nome, m.valor, m.dataset
ORDER BY m.valor DESC
```

Pergunta: "Me explique o conceito de Inductive Learning"
```cypher
MATCH (c:Conceito {nome: 'Inductive Learning'})<-[:IMPLEMENTA]-(p:Projeto)
RETURN c.nome, c.descricao, collect(p.nome) AS projetos
```"""

ANSWER_PROMPT = """Você é Daniel Fonseca, ML Engineer especialista em Graph Neural Networks para detecção de fraude.
Responda de forma técnica, clara e entusiasmada sobre seus projetos.

Contexto do grafo Neo4j:
{context}

Pergunta do usuário: {question}

Instruções:
- Responda em português
- Seja específico e técnico
- Cite os projetos relevantes com seus emojis
- Se tiver URL de projeto, mencione que pode ser acessado no Hugging Face
- Máximo 4 parágrafos
- Finalize com uma frase que convide o usuário a explorar mais"""


class GraphRAGAgent:
    def __init__(self, openai_api_key: str, neo4j_driver, neo4j_database: str):
        self.client   = OpenAI(api_key=openai_api_key)
        self.driver   = neo4j_driver
        self.database = neo4j_database
        self.model    = "gpt-4o-mini"

    def gerar_cypher(self, pergunta: str) -> str:
        """GPT gera Cypher a partir da pergunta em linguagem natural."""
        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user",   "content": pergunta}
            ],
            temperature=0.1,
            max_tokens=300,
        )
        texto = resp.choices[0].message.content
        # Extrai o Cypher do bloco de código
        match = re.search(r'```cypher\s*(.*?)\s*```', texto, re.DOTALL)
        if match:
            return match.group(1).strip()
        # Fallback: tenta extrair qualquer bloco de código
        match = re.search(r'```\s*(.*?)\s*```', texto, re.DOTALL)
        if match:
            return match.group(1).strip()
        return texto.strip()

    def executar_cypher(self, cypher: str) -> list:
        """Executa Cypher no Neo4j e retorna resultados."""
        try:
            with self.driver.session(database=self.database) as session:
                result = session.run(cypher)
                return [dict(record) for record in result]
        except Exception as e:
            return [{"erro": str(e)}]

    def formatar_contexto(self, resultados: list) -> str:
        """Formata resultados do Neo4j em texto para o LLM."""
        if not resultados:
            return "Nenhum resultado encontrado no grafo."
        if len(resultados) == 1 and "erro" in resultados[0]:
            return f"Erro na query: {resultados[0]['erro']}"
        linhas = []
        for r in resultados[:10]:  # max 10 resultados
            linha = " | ".join(f"{k}: {v}" for k, v in r.items() if v is not None)
            linhas.append(linha)
        return "\n".join(linhas)

    def gerar_resposta(self, pergunta: str, contexto: str) -> str:
        """GPT gera resposta final com base no contexto do grafo."""
        prompt = ANSWER_PROMPT.format(context=contexto, question=pergunta)
        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,
            max_tokens=600,
        )
        return resp.choices[0].message.content

    def responder(self, pergunta: str) -> dict:
        """
        Pipeline completo:
        1. Gera Cypher
        2. Executa no Neo4j
        3. Formata contexto
        4. Gera resposta
        """
        cypher     = self.gerar_cypher(pergunta)
        resultados = self.executar_cypher(cypher)
        contexto   = self.formatar_contexto(resultados)
        resposta   = self.gerar_resposta(pergunta, contexto)

        return {
            "cypher":     cypher,
            "resultados": resultados,
            "contexto":   contexto,
            "resposta":   resposta,
        }