Implementazione del grafo RAG utilizzando knowledge graphs

La retrieval-augmented generation di grafi (Graph RAG) sta emergendo come una potente tecnica per le applicazioni di AI generativa che utilizza conoscenze specifiche del dominio e informazioni pertinenti. La Graph RAG è un'alternativa ai metodi di ricerca vettoriale che utilizzano un database vettoriale

I knowledge graph sono sistemi di conoscenza in cui database a grafi come Neo4j o Amazon Neptune possono rappresentare dati strutturati. In un knowledge graph, le relazioni tra i punti dati, chiamate edge, sono significative quanto le connessioni tra i punti dati, chiamate vertici o talvolta nodi. Un knowledge graph semplifica l'attraversamento di una rete e l'elaborazione di domande complesse sui dati connessi. I knowledge graph sono particolarmente adatti per casi d'uso che coinvolgono chatbot, risoluzione delle identità, analisi di rete, motori di raccomandazione, customer 360 e rilevamento delle frodi.

Un approccio Graph RAG utilizza la natura strutturata dei database a grafo per fornire maggiore profondità e contesto alle informazioni recuperate su reti o relazioni complesse.  Con un database a grafo abbinato a un modello linguistico di grandi dimensioni (LLM), uno sviluppatore può automatizzare parti significative del processo di creazione di grafici a partire da dati non strutturati come il testo. Un LLM può elaborare dati di testo e identificare entità, comprenderne le relazioni e rappresentarle in una struttura grafica.

Esistono molti modi per creare un'applicazione Graph RAG, ad esempio GraphRag di Microsoft o l'abbinamento di GPT4 con LlamaIndex. Per questo tutorial utilizzerai Memgraph, una soluzione di database di grafi open source per creare un sistema rag utilizzando Llama-3 di Meta su watsonx. Memgraph utilizza Cypher, un linguaggio di query dichiarativo. Condivide alcune somiglianze con SQL ma si concentra su nodi e relazioni piuttosto che su tabelle e righe. Farai in modo che Llama 3 crei e compili il suo database grafico a partire da testo non strutturato e informazioni sulle query presenti nel database.

Fase 1

Sebbene sia possibile scegliere tra diversi strumenti, questo tutorial illustra come configurare un account IBM per utilizzare uno Jupyter Notebook.

Accedi a watsonx.ai utilizzando il tuo account IBM® Cloud.

Crea un progetto watsonx.ai.

Puoi ottenere l'ID del tuo progetto dall'interno del tuo progetto. Clicca sulla scheda Gestisci. Quindi, copia l'ID del progetto dalla sezione Dettagli della pagina Generali. Per questo tutorial è necessario questo ID progetto.

A seguire, associa il tuo progetto a watsonx.ai Runtime

a.  Crea un'istanza di servizio watsonx.ai Runtime (scegli il piano Lite, che è un'istanza gratuita).

b.  Genera una chiave API in watsonx.ai tempo di esecuzione. Salva questa chiave API per utilizzarla in questo tutorial.

c.  Vai al tuo progetto e seleziona la scheda Gestisci

d.  Nella scheda a sinistra, seleziona Servizi e integrazioni

e.  Seleziona i servizi IBM

f. Seleziona Associa servizio e scegli watsonx.ai Runtime.

Associa il servizio watsonx.ai Runtime al progetto che hai creato in watsonx.ai.

Fase 2

Ora dovrai installare Docker.

Dopo aver installato Docker, installa Memgraph utilizzando il loro container Docker. Su OSX o Linux, puoi utilizzare questo comando in un terminale:

curl https://install.memgraph.com | sh

Su un computer Windows, utilizza:

iwr https://windows.memgraph.com | iex

Segui i passaggi di installazione per far funzionare il motore Memgraph e il laboratorio Memgraph.

Sul tuo computer, crea un nuovo virtualenv per questo progetto:

virtualenv kg_rag --python=python3.12

Nell'ambiente Python per il tuo notebook, installa le seguenti librerie Python:

./kg_rag/bin/pip install langchain langchain-openai langchain_experimental langchain-community==0.3.15 neo4j langchain_ibm jupyterlab json-repair getpass4

Ora sei pronto a collegarti a Memgraph.

Fase 3

Se hai configurato Memgraph per utilizzare un nome utente e una password, impostali qui. In alternativa, puoi utilizzare i valori predefiniti e non impostare nessuno dei due. Non è una buona pratica per un database di produzione ma, per un ambiente di sviluppo locale che non memorizza dati sensibili, non è un problema.

import os
from langchain_community.chains.graph_qa.memgraph import MemgraphQAChain
from langchain_community.graphs import MemgraphGraph

url = os.environ.get("MEMGRAPH_URI", "bolt://localhost:7687")
username = os.environ.get("MEMGRAPH_USERNAME", "")
password = os.environ.get("MEMGRAPH_PASSWORD", "")

#initialize memgraph connection
graph = MemgraphGraph(
    url=url, username=username, password=password, refresh_schema=True
)

Ora crea una stringa di esempio che descriva un set di dati di relazioni da utilizzare per testare le capacità di generazione di grafi del tuo sistema LLM. È possibile utilizzare origini dati più complesse, ma questo semplice esempio ci aiuta a dimostrare l'algoritmo.

graph_text = “””
John’s title is Director of the Digital Marketing Group.
John works with Jane whose title is Chief Marketing Officer.
Jane works in the Executive Group.
Jane works with Sharon whose title is the Director of Client Outreach.
Sharon works in the Sales Group.
“””

Inserisci la chiave API watsonx che ha creato nel primo passaggio:

from getpass import getpass

watsonx_api_key = getpass()
os.environ[“WATSONX_APIKEY”] = watsonx_api_key
watsonx_project_id = getpass()
os.environ[“WATSONX_PROJECT_ID”] = watsonx_project_id

Ora configura un'istanza WatsonxLLM per generare testo. Per incoraggiare il modello a generare quanti più dettagli possibile senza entità o relazioni allucinanti che non sono presenti, la temperatura deve essere piuttosto bassa e il numero di token elevato.

from langchain_ibm import WatsonxLLM
from ibm_watsonx_ai.metanames import GenTextParamsMetaNames

graph_gen_parameters = {   
    GenTextParamsMetaNames.DECODING_METHOD: “sample”,
    GenTextParamsMetaNames.MAX_NEW_TOKENS: 1000,
    GenTextParamsMetaNames.MIN_NEW_TOKENS: 1,
    GenTextParamsMetaNames.TEMPERATURE: 0.3,
    GenTextParamsMetaNames.TOP_K: 10,
    GenTextParamsMetaNames.TOP_P: 0.8
}
watsonx_llm = WatsonxLLM(
    model_id=”meta-llama/llama-3-3-70b-instruct”,
    url=”https://us-south.ml.cloud.ibm.com”,
    project_id=os.getenv(“WATSONX_PROJECT_ID”),
    params=graph_gen_parameters,
)

LLMGraphTransformer ti consente di impostare i tipi di nodi e relazioni che desideri che l'LLM generi. Nel tuo caso, il testo descrive i dipendenti di un'azienda, i gruppi in cui lavorano e le loro qualifiche. Limitando l'LLM solo a queste entità, è più probabile ottenere una buona rappresentazione della conoscenza in un grafo.

La chiamata a convert_to_graph_documents fa sì che LLMGraphTransformer crei un grafo di conoscenza dal testo. Questo passaggio genera la sintassi Neo4j corretta per inserire le informazioni nel database grafico per rappresentare il contesto pertinente e le entità pertinenti.

from langchain_experimental.graph_transformers.llm import LLMGraphTransformer
from langchain_core.documents import Document

llm_transformer = LLMGraphTransformer(
    llm=watsonx_llm,
    allowed_nodes=[“Person”, “Title”, “Group”],
    allowed_relationships=[“TITLE”, “COLLABORATES”, “GROUP”]
)
documents = [Document(page_content=graph_text)]
graph_documents = llm_transformer.convert_to_graph_documents(documents)

Ora cancella tutti i vecchi dati dal database Memgraph e inserisci i nuovi nodi e edge.

# make sure the database is empty
graph.query(“STORAGE MODE IN_MEMORY_ANALYTICAL”)
graph.query(“DROP GRAPH”)
graph.query(“STORAGE MODE IN_MEMORY_TRANSACTIONAL”)

# create knowledge graph
graph.add_graph_documents(graph_documents)

La sintassi Cypher generata viene memorizzata negli oggetti graph_documents. Puoi verificarlo semplicemente stampandolo come stringa.

print(f”{graph_documents}”)

Lo schema e i tipi di dati creati da Cypher sono visibili nella proprietà "get_schema" del grafico.

graph.refresh_schema()
print(graph.get_schema)

Il risultato sarà:

Node labels and properties (name and type) are:
- labels: (:Title)
properties:
- id: string
- labels: (:Group)
properties:
- id: string
- labels: (:Person)
properties:
- id: string

Nodes are connected with the following relationships:
(:Person)-[:COLLABORATES]->(:Person)
(:Person)-[:GROUP]->(:Group)
(:Person)-[:TITLE]->(:Title)

È inoltre possibile visualizzare la struttura del grafo nel visualizzatore Memgraph labs:

 

Un'immagine grafica della rete che mostra nodi e edge La rete Memgraph generata dal testo di input

L'LLM ha svolto un lavoro ragionevole nel creare i nodi e le relazioni corretti. Ora è il momento di interrogare il knowledge graph.

Fase 4

Sollecitare correttamente l'LLM richiede un po' di prompt engineering. LangChain fornisce un FewShotPromptTemplate che può essere utilizzato per fornire esempi all'LLM nel prompt per assicurarsi che scriva una sintassi Cypher corretta e concisa. Il codice seguente fornisce diversi esempi di domande e quesiti che il LLM dovrebbe utilizzare. Mostra anche la limitazione dell'output del modello solo alla query. Un LLM troppo loquace potrebbe aggiungere informazioni aggiuntive che porterebbero a query Cypher non valide, quindi il prompt chiede al modello di output solo la query stessa.

Aggiungere un prefisso istruttivo aiuta anche a limitare il comportamento del modello e rende più probabile che l'LLM output la sintassi Cypher corretta.

from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

examples = [
{
“question”: “<|begin_of_text|>What group is Charles in?<|eot_id|>“,
“query”: “<|begin_of_text|>MATCH (p:Person {{id: ‘Charles’}})-[:GROUP]->(g:Group) RETURN g.id<|eot_id|>“,
},
{
“question”: “<|begin_of_text|>Who does Paul work with?<|eot_id|>“,
“query”: “<|begin_of_text|>MATCH (a:Person {{id: ‘Paul’}})-[:COLLABORATES]->(p:Person) RETURN p.id<|eot_id|>“,
},
{
“question”: “What title does Rico have?<|eot_id|>“,
“query”: “<|begin_of_text|>MATCH (p:Person {{id: ‘Rico’}})-[:TITLE]->(t:Title) RETURN t.id<|eot_id|>“,
}
]

example_prompt = PromptTemplate.from_template(
“<|begin_of_text|>{query}<|eot_id|>“
)

prefix = “””
Instructions:
- Respond with ONE and ONLY ONE query.
- Use provided node and relationship labels and property names from the
schema which describes the database’s structure. Upon receiving a user
question, synthesize the schema to craft a precise Cypher query that
directly corresponds to the user’s intent.
- Generate valid executable Cypher queries on top of Memgraph database.
Any explanation, context, or additional information that is not a part
of the Cypher query syntax should be omitted entirely.
- Use Memgraph MAGE procedures instead of Neo4j APOC procedures.
- Do not include any explanations or apologies in your responses. Only answer the question asked.
- Do not include additional questions. Only the original user question.
- Do not include any text except the generated Cypher statement.
- For queries that ask for information or functionalities outside the direct
generation of Cypher queries, use the Cypher query format to communicate
limitations or capabilities. For example: RETURN “I am designed to generate Cypher queries based on the provided schema only.”

Here is the schema information

{schema}

With all the above information and instructions, generate Cypher query for the
user question.

The question is:

{question}

Below are a number of examples of questions and their corresponding Cypher queries.”””

cypher_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix=prefix,
    suffix=”User input: {question}\nCypher query: “,
    input_variables=[“question”, “schema”],
)

Successivamente, creiamo un prompt per controllare il modo in cui l'LLM risponde alla domanda con le informazioni restituite da Memgraph. Forniremo all'LLM diversi esempi e istruzioni su come rispondere una volta che avrà ricevuto informazioni contestuali dal database grafico.

 

qa_examples = [
    {
        “question”: “<|begin_of_text|>What group is Charles in?<|eot_id|>“,
        “context”: “[{{‘g.id’: ‘Executive Group’}}]”,
        “response”: “Charles is in the Executive Group<|eot_id|>“
    },
    {
        “question”: “<|begin_of_text|>Who does Paul work with?<|eot_id|>“,
        “context”: “[{{‘p.id’: ‘Greg’}}, {{‘p2.id’: ‘Norma’}}]”,
        “response”: “Paul works with Greg and Norma<|eot_id|>“
    },
    {
        “question”: “What title does Rico have?<|eot_id|>“,
        “context”: “[{{‘t.id’: ‘Vice President of Sales’}}]”,
        “response”: “Vice President of Sales<|eot_id|>“
    }
]

qa_template = “””
Use the provided question and context to create an answer.Question: {question}

Context: {context}
Use only names departments or titles contained within {question} and {context}.
“””
qa_example_prompt = PromptTemplate.from_template(“”)

qa_prompt = FewShotPromptTemplate(
    examples=qa_examples,
    prefix=qa_template,
    input_variables=[“question”, “context”],
    example_prompt=qa_example_prompt,
    suffix=” “
)

Ora è il momento di creare la catena di risposta alle domande. MemGraphQAchain ti consente di impostare quale LLM desidera utilizzare, lo schema di grafo da utilizzare e le informazioni sul debugging. L'uso di una temperatura di 0 e una penalità di lunghezza incoraggia l'LLM a mantenere il prompt Cypher breve e diretto.

query_gen_parameters = {
    GenTextParamsMetaNames.DECODING_METHOD: “sample”,
    GenTextParamsMetaNames.MAX_NEW_TOKENS: 100,
    GenTextParamsMetaNames.MIN_NEW_TOKENS: 1,
    GenTextParamsMetaNames.TEMPERATURE: 0.0,
    GenTextParamsMetaNames.TOP_K: 1,
    GenTextParamsMetaNames.TOP_P: 0.9,
    GenTextParamsMetaNames.LENGTH_PENALTY: {‘decay_factor’: 1.2, ‘start_index’: 20}
}

chain = MemgraphQAChain.from_llm(
        llm = WatsonxLLM(
        model_id=”meta-llama/llama-3-3-70b-instruct”,
        url=”https://us-south.ml.cloud.ibm.com”,
        project_id=”dfe8787b-1f6f-4e18-b36a-e22c00f141d1”,
        params=query_gen_parameters
    ),
    graph = graph,
    allow_dangerous_requests = True,
    verbose = True,
    return_intermediate_steps = True, # for debugging
    cypher_prompt=cypher_prompt,
    qa_prompt=qa_prompt
)

Ora puoi invocare la catena con una domanda in linguaggio naturale (ricorda che le tue risposte potrebbero essere leggermente diverse perché gli LLM non sono puramente deterministici).

chain.invoke(“What is Johns title?”)

Questo produrrà:

> Inserire la nuova catena MemgraphQAChain...
Cypher generato:
 MATCH (p:Person {id: 'John'})-[:TITLE]->(t:Title) RETURN t.id
Full Context:
[{'t.id': 'Director of the Digital Marketing Group'}]

> Finished chain.
{'query': 'What is Johns title?',
 'result': ' \nAnswer: Director of the Digital Marketing Group.',
 'intermediate_steps': [{'query': " MATCH (p:Person {id: 'John'})-[:TITLE]->(t:Title) RETURN t.id"},
 {'context': [{'t.id': 'Director of the Digital Marketing Group'}]}]}

Nella domanda successiva, poni alla catena una domanda leggermente più complessa:

chain.invoke(“Who does John collaborate with?”)

Il risultato dovrebbe essere:

> Entering new MemgraphQAChain chain...
Generated Cypher:
MATCH (p:Person {id: ‘John’})-[:COLLABORATES]->(c:Person) RETURN c
Full Context:
[{‘c’: {‘id’: ‘Jane’}}]

> Finished chain.
{‘query’: ‘Who does John collaborate with?’,
‘result’: ‘ \nAnswer: John collaborates with Jane.’,
‘intermediate_steps’: [{‘query’: “ MATCH (p:Person {id: ‘John’})-[:COLLABORATES]->(c:Person) RETURN c”},
{‘context’: [{‘c’: {‘id’: ‘Jane’}}]}]}

La risposta corretta è contenuta nella risposta. In alcuni casi, potrebbe esserci del testo aggiuntivo da rimuovere prima di restituire la risposta a un utente finale.

È possibile chiedere alla catena Memgraph informazioni sulle relazioni di gruppo:

chain.invoke(“What group is Jane in?”)

Il risultato sarà:

> Entering new MemgraphQAChain chain...
Generated Cypher:
MATCH (p:Person {id: ‘Jane’})-[:GROUP]->(g:Group) RETURN g.id
Full Context:
[{‘g.id’: ‘Executive Group’}]

> Finished chain.
{‘query’: ‘What group is Jane in?’,
‘result’: ‘Jane is in Executive Group.’,
‘intermediate_steps’: [{‘query’: “ MATCH (p:Person {id: ‘Jane’})-[:GROUP]->(g:Group) RETURN g.id”},
{‘context’: [{‘g.id’: ‘Executive Group’}]}]}

Questa è la risposta corretta.

Infine, poni alla catena una domanda con due output:

chain.invoke(“Who does Jane collaborate with?”)

Il risultato sarà:

> Entering new MemgraphQAChain chain...
Generated Cypher:
MATCH (p:Person {id: ‘Jane’})-[:COLLABORATES]->(c:Person) RETURN c
Full Context:
[{‘c’: {‘id’: ‘Sharon’}}]

> Finished chain.
{‘query’: ‘Who does Jane collaborate with?’,
‘result’: ‘ Jane collaborates with Sharon.’,
‘intermediate_steps’: [{‘query’: “ MATCH (p:Person {id: ‘Jane’})-[:COLLABORATES]->(c:Person) RETURN c”},
{‘context’: [{‘c’: {‘id’: ‘Sharon’}}]}]}

La catena identifica correttamente entrambi i collaboratori.

Conclusione

In questo tutorial, hai creato un'applicazione Graph RAG usando Memgraph e watsonx per generare le strutture di dati del grafico e interrogarle. Utilizzando un LLM tramite watsonx estrae le informazioni sui nodi e sugli edge dal testo sorgente in linguaggio naturale e genera la sintassi delle query Cypher per popolare un database a grafi. Ha poi usato watsonx per trasformare le domande in linguaggio naturale su quel testo sorgente in query Cypher che hanno estratto informazioni dal database di grafi. Utilizzando la Prompt Engineering, l'LLM ha trasformato i risultati del database Memgraph in risposte in linguaggio naturale.

Soluzioni correlate
IBM® watsonx.ai

Addestra, convalida, adatta e implementa le funzionalità di AI generativa, foundation model e machine learning con IBM watsonx.ai, uno studio aziendale di nuova generazione per builder AI. Crea applicazioni AI in tempi ridotti e una minima quantità di dati.

Esplora watsonx.ai
Soluzioni di intelligenza artificiale

Metti l'AI al servizio della tua azienda con l'esperienza leader di settore e il portfolio di soluzioni di IBM nel campo dell'AI.

Esplora le soluzioni AI
Consulenza e servizi per l'intelligenza artificiale (AI)

I servizi di AI di IBM Consulting aiutano a reinventare il modo in cui le aziende lavorano con l'AI per la trasformazione.

Esplora i servizi AI
Prossimi passi

Attraverso l'AI, IBM Concert scopre insight di importanza chiave sulle operazioni e fornisce raccomandazioni specifiche per migliorare le applicazioni. Scopri come Concert può migliorare il tuo business.

Esplora Concert Esplora le soluzioni di automazione dei processi aziendali