Memgraph는 동적 분석 환경에 최적화된 오픈소스 그래프 데이터베이스이며 Neo4j와 호환됩니다. 데이터베이스를 쿼리하기 위해 Memgraph는 Cypher를 사용합니다 - 속성 그래프 데이터베이스를 위한 가장 널리 채택되고, 완전히 명세화되고, 개방된 쿼리 언어입니다. 이 노트북은 자연어로 Memgraph 쿼리하기와 비구조화된 데이터로부터 지식 그래프 구축하기를 보여줍니다. 하지만 먼저, 모든 것을 설정해야 합니다.

Setting up

이 가이드를 진행하려면 DockerPython 3.x가 설치되어 있어야 합니다. 처음으로 Memgraph Platform (Memgraph 데이터베이스 + MAGE 라이브러리 + Memgraph Lab)을 빠르게 실행하려면 다음을 수행하세요: Linux/MacOS에서:
curl https://install.memgraph.com | sh
Windows에서:
iwr https://windows.memgraph.com | iex
두 명령 모두 Docker Compose 파일을 시스템에 다운로드하고, 두 개의 별도 컨테이너에서 memgraph-magememgraph-lab Docker 서비스를 빌드하고 시작하는 스크립트를 실행합니다. 이제 Memgraph가 실행 중입니다! 설치 프로세스에 대한 자세한 내용은 Memgraph 문서를 참조하세요. LangChain을 사용하려면 필요한 모든 패키지를 설치하고 import하세요. 적절한 권한을 보장하기 위해 패키지 매니저 pip--user 플래그를 사용합니다. Python 3.4 이상 버전을 설치했다면 pip는 기본적으로 포함되어 있습니다. 다음 명령을 사용하여 필요한 모든 패키지를 설치할 수 있습니다:
pip install langchain langchain-openai langchain-memgraph --user
이 노트북에서 제공된 코드 블록을 실행하거나 별도의 Python 파일을 사용하여 Memgraph와 LangChain을 실험할 수 있습니다.

Natural language querying

Memgraph의 LangChain 통합에는 자연어 쿼리가 포함됩니다. 이를 활용하려면 먼저 필요한 모든 import를 수행하세요. 코드에 나타나는 대로 설명하겠습니다. 먼저 MemgraphGraph를 인스턴스화하세요. 이 객체는 실행 중인 Memgraph 인스턴스에 대한 연결을 보유합니다. 모든 환경 변수를 올바르게 설정해야 합니다.
import os

from langchain_core.prompts import PromptTemplate
from langchain_memgraph.chains.graph_qa import MemgraphQAChain
from langchain_memgraph.graphs.memgraph import MemgraphLangChain
from langchain_openai import ChatOpenAI

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

graph = MemgraphLangChain(
    url=url, username=username, password=password, refresh_schema=False
)
refresh_schema는 데이터베이스에 아직 데이터가 없고 불필요한 데이터베이스 호출을 피하고자 하기 때문에 초기에 False로 설정됩니다.

Populating the database

데이터베이스를 채우려면 먼저 비어 있는지 확인하세요. 가장 효율적인 방법은 인메모리 분석 스토리지 모드로 전환하고, 그래프를 삭제한 다음 인메모리 트랜잭션 모드로 돌아가는 것입니다. Memgraph의 스토리지 모드에 대해 자세히 알아보세요. 데이터베이스에 추가할 데이터는 다양한 플랫폼에서 사용 가능한 여러 장르의 비디오 게임과 퍼블리셔에 관한 것입니다.
# Drop graph
graph.query("STORAGE MODE IN_MEMORY_ANALYTICAL")
graph.query("DROP GRAPH")
graph.query("STORAGE MODE IN_MEMORY_TRANSACTIONAL")

# Creating and executing the seeding query
query = """
    MERGE (g:Game {name: "Baldur's Gate 3"})
    WITH g, ["PlayStation 5", "Mac OS", "Windows", "Xbox Series X/S"] AS platforms,
            ["Adventure", "Role-Playing Game", "Strategy"] AS genres
    FOREACH (platform IN platforms |
        MERGE (p:Platform {name: platform})
        MERGE (g)-[:AVAILABLE_ON]->(p)
    )
    FOREACH (genre IN genres |
        MERGE (gn:Genre {name: genre})
        MERGE (g)-[:HAS_GENRE]->(gn)
    )
    MERGE (p:Publisher {name: "Larian Studios"})
    MERGE (g)-[:PUBLISHED_BY]->(p);
"""

graph.query(query)
[]
graph 객체가 query 메서드를 보유하고 있는 것을 주목하세요. 이 메서드는 Memgraph에서 쿼리를 실행하며 MemgraphQAChain에서도 데이터베이스를 쿼리하는 데 사용됩니다.

Refresh graph schema

Memgraph에 새로운 데이터가 생성되었으므로 스키마를 새로 고쳐야 합니다. 생성된 스키마는 MemgraphQAChain이 LLM에게 더 나은 Cypher 쿼리를 생성하도록 지시하는 데 사용됩니다.
graph.refresh_schema()
데이터를 숙지하고 업데이트된 그래프 스키마를 확인하려면 다음 문을 사용하여 출력할 수 있습니다:
print(graph.get_schema)
Node labels and properties (name and type) are:
- labels: (:Platform)
  properties:
    - name: string
- labels: (:Genre)
  properties:
    - name: string
- labels: (:Game)
  properties:
    - name: string
- labels: (:Publisher)
  properties:
    - name: string

Nodes are connected with the following relationships:
(:Game)-[:HAS_GENRE]->(:Genre)
(:Game)-[:PUBLISHED_BY]->(:Publisher)
(:Game)-[:AVAILABLE_ON]->(:Platform)

Querying the database

OpenAI API와 상호 작용하려면 API 키를 환경 변수로 구성해야 합니다. 이는 요청에 대한 적절한 권한을 보장합니다. API 키 획득에 대한 자세한 정보는 여기에서 찾을 수 있습니다. API 키를 구성하려면 Python os 패키지를 사용할 수 있습니다:
os.environ["OPENAI_API_KEY"] = "your-key-here"
Jupyter 노트북 내에서 코드를 실행하는 경우 위의 코드 스니펫을 실행하세요. 다음으로, 그래프 데이터를 기반으로 한 질문-답변 프로세스에 활용될 MemgraphQAChain을 생성하세요. temperature parameter는 예측 가능하고 일관된 답변을 보장하기 위해 0으로 설정됩니다. 쿼리 생성에 관한 더 자세한 메시지를 받으려면 verbose 매개변수를 True로 설정할 수 있습니다.
chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    graph=graph,
    model_name="gpt-4-turbo",
    allow_dangerous_requests=True,
)
이제 질문을 시작할 수 있습니다!
response = chain.invoke("Which platforms is Baldur's Gate 3 available on?")
print(response["result"])
MATCH (:Game{name: "Baldur's Gate 3"})-[:AVAILABLE_ON]->(platform:Platform)
RETURN platform.name
Baldur's Gate 3 is available on PlayStation 5, Mac OS, Windows, and Xbox Series X/S.
response = chain.invoke("Is Baldur's Gate 3 available on Windows?")
print(response["result"])
MATCH (:Game{name: "Baldur's Gate 3"})-[:AVAILABLE_ON]->(:Platform{name: "Windows"})
RETURN "Yes"
Yes, Baldur's Gate 3 is available on Windows.

Chain modifiers

체인의 동작을 수정하고 더 많은 컨텍스트나 추가 정보를 얻으려면 체인의 매개변수를 수정할 수 있습니다.

Return direct query results

return_direct 수정자는 실행된 Cypher 쿼리의 직접 결과를 반환할지 또는 처리된 자연어 응답을 반환할지를 지정합니다.
# Return the result of querying the graph directly
chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    graph=graph,
    return_direct=True,
    allow_dangerous_requests=True,
    model_name="gpt-4-turbo",
)

response = chain.invoke("Which studio published Baldur's Gate 3?")
print(response["result"])
MATCH (g:Game {name: "Baldur's Gate 3"})-[:PUBLISHED_BY]->(p:Publisher)
RETURN p.name
[{'p.name': 'Larian Studios'}]

Return query intermediate steps

return_intermediate_steps 체인 수정자는 초기 쿼리 결과에 더하여 쿼리의 중간 단계를 포함하여 반환된 응답을 향상시킵니다.
# Return all the intermediate steps of query execution
chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    graph=graph,
    allow_dangerous_requests=True,
    return_intermediate_steps=True,
    model_name="gpt-4-turbo",
)

response = chain.invoke("Is Baldur's Gate 3 an Adventure game?")
print(f"Intermediate steps: {response['intermediate_steps']}")
print(f"Final response: {response['result']}")
MATCH (:Game {name: "Baldur's Gate 3"})-[:HAS_GENRE]->(:Genre {name: "Adventure"})
RETURN "Yes"
Intermediate steps: [{'query': 'MATCH (:Game {name: "Baldur\'s Gate 3"})-[:HAS_GENRE]->(:Genre {name: "Adventure"})\nRETURN "Yes"'}, {'context': [{'"Yes"': 'Yes'}]}]
Final response: Yes.

Limit the number of query results

top_k 수정자는 쿼리 결과의 최대 개수를 제한하고자 할 때 사용할 수 있습니다.
# Limit the maximum number of results returned by query
chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    graph=graph,
    top_k=2,
    allow_dangerous_requests=True,
    model_name="gpt-4-turbo",
)

response = chain.invoke("What genres are associated with Baldur's Gate 3?")
print(response["result"])
MATCH (:Game {name: "Baldur's Gate 3"})-[:HAS_GENRE]->(g:Genre)
RETURN g.name;
Adventure, Role-Playing Game

Advanced querying

솔루션의 복잡성이 증가함에 따라 신중한 처리가 필요한 다양한 사용 사례를 접할 수 있습니다. 애플리케이션의 확장성을 보장하는 것은 원활한 사용자 흐름을 유지하는 데 필수적입니다. 체인을 다시 인스턴스화하고 사용자가 잠재적으로 물어볼 수 있는 몇 가지 질문을 시도해 봅시다.
chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    graph=graph,
    model_name="gpt-4-turbo",
    allow_dangerous_requests=True,
)

response = chain.invoke("Is Baldur's Gate 3 available on PS5?")
print(response["result"])
MATCH (:Game{name: "Baldur's Gate 3"})-[:AVAILABLE_ON]->(:Platform{name: "PS5"})
RETURN "Yes"
I don't know the answer.
생성된 Cypher 쿼리는 괜찮아 보이지만 응답으로 어떤 정보도 받지 못했습니다. 이는 LLM을 사용할 때 흔히 발생하는 문제를 보여줍니다 - 사용자가 쿼리를 표현하는 방식과 데이터가 저장되는 방식 간의 불일치입니다. 이 경우 사용자 인식과 실제 데이터 저장 간의 차이가 불일치를 야기할 수 있습니다. 프롬프트 개선, 즉 이러한 차이를 더 잘 파악하도록 모델의 프롬프트를 다듬는 프로세스는 이 문제를 해결하는 효율적인 솔루션입니다. 프롬프트 개선을 통해 모델은 정확하고 관련성 있는 쿼리를 생성하는 데 있어 숙련도가 향상되어 원하는 데이터를 성공적으로 검색할 수 있습니다.

Prompt refinement

이를 해결하기 위해 QA 체인의 초기 Cypher 프롬프트를 조정할 수 있습니다. 이는 사용자가 특정 플랫폼(예: PS5)을 참조하는 방법에 대한 지침을 LLM에 추가하는 것을 포함합니다. LangChain PromptTemplate을 사용하여 수정된 초기 프롬프트를 생성합니다. 이 수정된 프롬프트는 개선된 MemgraphQAChain 인스턴스에 인수로 제공됩니다.
MEMGRAPH_GENERATION_TEMPLATE = """Your task is to directly translate natural language inquiry into precise and executable Cypher query for Memgraph database.
You will utilize a provided database schema to understand the structure, nodes and relationships within the Memgraph database.
Instructions:
- 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.
- 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."
Schema:
{schema}

With all the above information and instructions, generate Cypher query for the
user question.
If the user asks about PS5, Play Station 5 or PS 5, that is the platform called PlayStation 5.

The question is:
{question}"""

MEMGRAPH_GENERATION_PROMPT = PromptTemplate(
    input_variables=["schema", "question"], template=MEMGRAPH_GENERATION_TEMPLATE
)

chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    cypher_prompt=MEMGRAPH_GENERATION_PROMPT,
    graph=graph,
    model_name="gpt-4-turbo",
    allow_dangerous_requests=True,
)

response = chain.invoke("Is Baldur's Gate 3 available on PS5?")
print(response["result"])
MATCH (:Game{name: "Baldur's Gate 3"})-[:AVAILABLE_ON]->(:Platform{name: "PlayStation 5"})
RETURN "Yes"
Yes, Baldur's Gate 3 is available on PS5.
이제 플랫폼 명명에 대한 지침이 포함된 수정된 초기 Cypher 프롬프트를 사용하여 사용자 쿼리와 더 밀접하게 일치하는 정확하고 관련성 있는 결과를 얻고 있습니다. 이 접근 방식을 통해 QA 체인을 더욱 개선할 수 있습니다. 체인에 추가 프롬프트 개선 데이터를 손쉽게 통합하여 앱의 전반적인 사용자 경험을 향상시킬 수 있습니다.

Constructing knowledge graph

비구조화된 데이터를 구조화된 데이터로 변환하는 것은 쉽거나 간단한 작업이 아닙니다. 이 가이드는 LLM을 활용하여 이를 돕는 방법과 Memgraph에서 지식 그래프를 구축하는 방법을 보여줍니다. 지식 그래프가 생성되면 GraphRAG 애플리케이션에 사용할 수 있습니다. 텍스트로부터 지식 그래프를 구축하는 단계는 다음과 같습니다:

Extracting structured information from text

설정 섹션의 모든 import 외에도 텍스트에서 구조화된 정보를 추출하는 데 사용될 LLMGraphTransformerDocument를 import하세요.
from langchain_core.documents import Document
from langchain_experimental.graph_transformers import LLMGraphTransformer
다음은 지식 그래프가 구축될 Charles Darwin에 관한 예제 텍스트입니다 (출처).
text = """
    Charles Robert Darwin was an English naturalist, geologist, and biologist,
    widely known for his contributions to evolutionary biology. His proposition that
    all species of life have descended from a common ancestor is now generally
    accepted and considered a fundamental scientific concept. In a joint
    publication with Alfred Russel Wallace, he introduced his scientific theory that
    this branching pattern of evolution resulted from a process he called natural
    selection, in which the struggle for existence has a similar effect to the
    artificial selection involved in selective breeding. Darwin has been
    described as one of the most influential figures in human history and was
    honoured by burial in Westminster Abbey.
"""
다음 단계는 원하는 LLM에서 LLMGraphTransformer를 초기화하고 문서를 그래프 구조로 변환하는 것입니다.
llm = ChatOpenAI(temperature=0, model_name="gpt-4-turbo")
llm_transformer = LLMGraphTransformer(llm=llm)
documents = [Document(page_content=text)]
graph_documents = llm_transformer.convert_to_graph_documents(documents)
내부적으로 LLM은 텍스트에서 중요한 엔티티를 추출하고 노드와 관계 목록으로 반환합니다. 다음과 같이 보입니다:
print(graph_documents)
[GraphDocument(nodes=[Node(id='Charles Robert Darwin', type='Person', properties={}), Node(id='English', type='Nationality', properties={}), Node(id='Naturalist', type='Profession', properties={}), Node(id='Geologist', type='Profession', properties={}), Node(id='Biologist', type='Profession', properties={}), Node(id='Evolutionary Biology', type='Field', properties={}), Node(id='Common Ancestor', type='Concept', properties={}), Node(id='Scientific Concept', type='Concept', properties={}), Node(id='Alfred Russel Wallace', type='Person', properties={}), Node(id='Natural Selection', type='Concept', properties={}), Node(id='Selective Breeding', type='Concept', properties={}), Node(id='Westminster Abbey', type='Location', properties={})], relationships=[Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='English', type='Nationality', properties={}), type='NATIONALITY', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Naturalist', type='Profession', properties={}), type='PROFESSION', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Geologist', type='Profession', properties={}), type='PROFESSION', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Biologist', type='Profession', properties={}), type='PROFESSION', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Evolutionary Biology', type='Field', properties={}), type='CONTRIBUTION', properties={}), Relationship(source=Node(id='Common Ancestor', type='Concept', properties={}), target=Node(id='Scientific Concept', type='Concept', properties={}), type='BASIS', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Alfred Russel Wallace', type='Person', properties={}), type='COLLABORATION', properties={}), Relationship(source=Node(id='Natural Selection', type='Concept', properties={}), target=Node(id='Selective Breeding', type='Concept', properties={}), type='COMPARISON', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Westminster Abbey', type='Location', properties={}), type='BURIAL', properties={})], source=Document(metadata={}, page_content='\n    Charles Robert Darwin was an English naturalist, geologist, and biologist,\n    widely known for his contributions to evolutionary biology. His proposition that\n    all species of life have descended from a common ancestor is now generally\n    accepted and considered a fundamental scientific concept. In a joint\n    publication with Alfred Russel Wallace, he introduced his scientific theory that\n    this branching pattern of evolution resulted from a process he called natural\n    selection, in which the struggle for existence has a similar effect to the\n    artificial selection involved in selective breeding. Darwin has been\n    described as one of the most influential figures in human history and was\n    honoured by burial in Westminster Abbey.\n'))]

Storing into Memgraph

GraphDocument 형식, 즉 노드와 관계 형식으로 데이터가 준비되면 add_graph_documents 메서드를 사용하여 Memgraph로 가져올 수 있습니다. 이 메서드는 graph_documents 목록을 Memgraph에서 실행해야 하는 적절한 Cypher 쿼리로 변환합니다. 완료되면 지식 그래프가 Memgraph에 저장됩니다.
# Empty the database
graph.query("STORAGE MODE IN_MEMORY_ANALYTICAL")
graph.query("DROP GRAPH")
graph.query("STORAGE MODE IN_MEMORY_TRANSACTIONAL")

# Create KG
graph.add_graph_documents(graph_documents)
그래프 구축 프로세스는 비결정적입니다. 비구조화된 데이터에서 노드와 관계를 생성하는 데 사용되는 LLM이 비결정적이기 때문입니다.

Additional options

또한 요구 사항에 따라 추출할 특정 유형의 노드와 관계를 정의할 수 있는 유연성이 있습니다.
llm_transformer_filtered = LLMGraphTransformer(
    llm=llm,
    allowed_nodes=["Person", "Nationality", "Concept"],
    allowed_relationships=["NATIONALITY", "INVOLVED_IN", "COLLABORATES_WITH"],
)
graph_documents_filtered = llm_transformer_filtered.convert_to_graph_documents(
    documents
)

print(f"Nodes:{graph_documents_filtered[0].nodes}")
print(f"Relationships:{graph_documents_filtered[0].relationships}")
Nodes:[Node(id='Charles Robert Darwin', type='Person', properties={}), Node(id='English', type='Nationality', properties={}), Node(id='Evolutionary Biology', type='Concept', properties={}), Node(id='Natural Selection', type='Concept', properties={}), Node(id='Alfred Russel Wallace', type='Person', properties={})]
Relationships:[Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='English', type='Nationality', properties={}), type='NATIONALITY', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Evolutionary Biology', type='Concept', properties={}), type='INVOLVED_IN', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Natural Selection', type='Concept', properties={}), type='INVOLVED_IN', properties={}), Relationship(source=Node(id='Charles Robert Darwin', type='Person', properties={}), target=Node(id='Alfred Russel Wallace', type='Person', properties={}), type='COLLABORATES_WITH', properties={})]
그래프는 더 빠른 검색을 위해 인덱싱될 모든 노드에 __Entity__ 레이블을 가질 수도 있습니다.
# Drop graph
graph.query("STORAGE MODE IN_MEMORY_ANALYTICAL")
graph.query("DROP GRAPH")
graph.query("STORAGE MODE IN_MEMORY_TRANSACTIONAL")

# Store to Memgraph with Entity label
graph.add_graph_documents(graph_documents, baseEntityLabel=True)
그래프에서 얻은 정보의 출처를 포함하는 옵션도 있습니다. 이를 위해 include_sourceTrue로 설정하면 소스 문서가 저장되고 MENTIONS 관계를 사용하여 그래프의 노드에 연결됩니다.
# Drop graph
graph.query("STORAGE MODE IN_MEMORY_ANALYTICAL")
graph.query("DROP GRAPH")
graph.query("STORAGE MODE IN_MEMORY_TRANSACTIONAL")

# Store to Memgraph with source included
graph.add_graph_documents(graph_documents, include_source=True)
소스의 내용이 저장되고 문서에 id가 없었기 때문에 id 속성이 생성되는 것을 주목하세요. __Entity__ 레이블과 문서 소스를 모두 가질 수 있습니다. 그러나 특히 콘텐츠의 긴 문자열로 인해 소스가 포함된 경우 둘 다 메모리를 차지한다는 점에 유의하세요. 마지막으로 이전 섹션에서 설명한 대로 지식 그래프를 쿼리할 수 있습니다:
chain = MemgraphQAChain.from_llm(
    ChatOpenAI(temperature=0),
    graph=graph,
    model_name="gpt-4-turbo",
    allow_dangerous_requests=True,
)
print(chain.invoke("Who Charles Robert Darwin collaborated with?")["result"])
MATCH (:Person {id: "Charles Robert Darwin"})-[:COLLABORATION]->(collaborator)
RETURN collaborator;
Alfred Russel Wallace

Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.
I