以编程方式使用文档

Memgraph 是一个开源图数据库,针对动态分析环境进行了优化,并与 Neo4j 兼容。要查询数据库,Memgraph 使用 Cypher——属性图数据库中采用最广泛、完全规范化的开源查询语言。

本笔记本将向您展示如何 使用自然语言查询 Memgraph 以及如何 从您的非结构化数据构建知识图谱。 首先,请确保

完成所有设置 设置.

要学习本指南,您需要

Docker Python 3.x 要快速运行 installed.

Memgraph 平台 **(Memgraph 数据库 + MAGE 库 + Memgraph Lab)首次运行,请执行以下操作:** 在 Windows 上:

On Linux/MacOS:

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

两个命令都会运行一个脚本,下载 Docker Compose 文件到您的系统,构建并启动

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

memgraph-mage Docker 服务分别在两个容器中运行。现在 Memgraph 已启动并运行!了解更多关于安装过程的信息,请访问 memgraph-lab Memgraph 文档 要使用 LangChain,请安装并导入所有必要的包。我们将使用包管理器.

pip 以及标志以确保正确的权限。如果您已安装 Python 3.4 或更高版本, --user 默认已包含。您可以使用以下命令安装所有必需的包: pip 您可以在本笔记本中运行提供的代码块,也可以使用单独的 Python 文件来尝试 Memgraph 和 LangChain。

pip install langchain langchain-openai langchain-memgraph --user

自然语言查询

Memgraph 与 LangChain 的集成包括自然语言查询功能。要使用它,首先进行所有必要的导入。我们将在代码中出现时讨论它们。

首先,实例化

。此对象保持与运行中的 Memgraph 实例的连接。请确保正确设置所有环境变量。 MemgraphGraph

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 填充数据库

要填充数据库,首先确保它为空。最有效的方法是切换到内存分析存储模式,删除图,然后返回内存事务模式。了解更多关于 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 方法。该方法在 Memgraph 中执行查询,也被 query 用于查询数据库。 MemgraphQAChain 刷新图谱模式

Memgraph 集成

由于新数据是在 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)

查询数据库

要与 OpenAI API 交互,您必须将 API 密钥配置为环境变量。这确保了您的请求获得适当的授权。您可以在 OpenAI 帮助中心中找到有关获取 API 密钥的更多信息。要配置 API 密钥,您可以使用 Python os package:

os.environ["OPENAI_API_KEY"] = "your-key-here"

如果您在 Jupyter notebook 中运行代码,请运行上面的代码片段。

接下来,创建 MemgraphQAChain,它将在基于图谱数据的问答过程中被使用。该 temperature parameter 设置为零以确保可预测且一致的答案。您可以将 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.

链修饰符

要修改链的行为并获取更多上下文或附加信息,您可以修改链的参数。

返回直接查询结果

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_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.

限制查询结果数量

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

高级查询

随着解决方案的复杂性增加,您可能会遇到需要谨慎处理的不同用例。确保应用程序的可扩展性对于维护流畅的用户体验至关重要,不能有任何纰漏。

让我们再次实例化链,并尝试提出用户可能潜在会问的一些问题。

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 合作时的常见挑战——用户提问方式与数据存储方式之间的不对齐。在这种情况下,用户感知与实际数据存储之间的差异可能导致不匹配。提示词优化是解决此问题的有效方法,它是完善模型提示词以更好地理解这些差异的过程。通过提示词优化,模型在生成精确且相关的查询方面变得更加熟练,从而成功检索到所需的数据。

提示词优化

为了解决这个问题,我们可以调整 QA 链的初始 Cypher 提示词。这涉及向 LLM 添加关于用户如何引用特定平台(如我们的案例中的 PS5)的指导。我们使用 LangChain PromptTemplate 来实现这一点,创建一个修改后的初始提示词。然后将这个修改后的提示词作为参数提供给我们优化后的 MemgraphQAChain instance.

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 链。您可以轻松地将额外的提示词优化数据集成到您的链中,从而增强应用程序的整体用户体验。

构建知识图谱

将非结构化数据转换为结构化数据并非易事或简单任务。本指南将展示如何利用LLM来帮助我们完成这项工作,以及如何在Memgraph中构建知识图谱。创建知识图谱后,您可以将它用于GraphRAG应用。

从文本构建知识图谱的步骤如下:

从文本中提取结构化信息

除了设置部分中的所有导入外 设置部分,导入 LLMGraphTransformerDocument,这些将用于从文本中提取结构化信息。

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

以下是关于查尔斯·达尔文的示例文本(来源),将从该文本构建知识图谱。

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.
"""

下一步是初始化 LLMGraphTransformer 从所需的LLM并将文档转换为图结构。

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'))]

存储到Memgraph

一旦您准备好格式为 GraphDocument的数据,即节点和关系,您可以使用 add_graph_documents 方法将其导入Memgraph。该方法将 graph_documents 列表转换为适当的Cypher查询,这些查询需要在Memgraph中执行。完成后,知识图谱就存储在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_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_source to True ,然后源文档被存储并使用 MENTIONS relationship.

# 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