结合GPT-4,Langchain和Graph RAG打开Python网站智能对话新次元

作者:Sienna

审核:Los

图1|Graph RAG示意©️【深蓝AI】编译

1. 内容概览

本文将深入解析GitHub上备受瞩目的LLM开源项目,详细指导如何运用Graph RAG(Retriever-Augmented Generator with Graph)技术,结合Langchain框架与GPT-4o(或类似LLM模型)的能力,构建一个能够提供精准、连贯信息的本地聊天机器人。本文不仅提供了详尽的步骤说明与代码示例,还涵盖了从安装Python库、配置Neo4j图数据库,到利用OpenAI API处理自然语言并提取结构化信息的全过程。此外,我们深入探讨了Graph RAG如何通过图形数据库技术高效管理复杂信息关系,显著提升搜索与回答的准确性。最后,通过实际查询演示了GraphCypherQAChain在知识图谱中的应用,并对比了Graph RAG与向量数据库的优势。

2. GraphRAGs技术解析

2.1 定义与优势

GraphRAGs,即图形表示的检索增强生成器,是一种创新的信息检索与生成技术。它利用图形数据库来构建信息之间的关联网络,使得即便是抽象或复杂的查询也能基于图形结构快速找到相关且准确的答案。相较于传统的向量RAG,GraphRAG能够更深入地理解信息间的上下文关系,从而提供更加人性化的回答。

2.2 工作原理

GraphRAG的工作机制融合了图形数据库的高效查询能力与RAG的语义理解能力。在GraphRAG中,每一条信息都被视为图形中的一个节点,而信息之间的关系则通过边来表示。这种结构不仅直观展现了数据的内在联系,还允许系统根据查询需求灵活遍历图形,找到最相关的节点和路径。

2.3 实例解析

以《火影忍者》中的Sakura为例,当使用GraphRAG处理“Sakura有什么特点?”这类抽象查询时,系统会根据已有的图形数据库信息,快速定位到“Sakura”节点,并沿着与之相连的边(如“长发”、“淡绿色眼睛” 等特征)检索相关信息。这样,即便原始数据并未直接提及“特点”二字,GraphRAG也能通过图形结构推理出合适的答案,实现语义上的准确匹配。

图2|Sakura相关检索图©️【深蓝AI】编译

3. GraphRAG与向量数据库的比较

GraphRAG侧重于利用图形数据库来建模和查询实体间的复杂关系。它通过构建节点和边的图形结构,有效地表示了和存储信息之间的关联。这种结构不仅支持基于图形的操作,如遍历关系、查找路径和识别模式,还能在处理具有丰富上下文和关系的数据时展现出强大的能力。GraphRAG特别适用于需要深入理解信息间关系、进行复杂推理或生成结构化答案的场景。

向量数据库则专注于高效执行相似性搜索和最近邻查询。它们利用向量化技术将非结构化数据(如文本、图像等)转换为高维空间中的向量,并通过专门的索引技术和算法快速检索与给定查询向量最相似的向量。向量数据库在处理大规模数据集、进行快速相似性匹配或推荐系统等领域表现出色。然而,它们在处理需要深入理解语义关系和进行复杂推理的任务时可能显得力不从心。

4. 用Python构建图形GraphRAG系统

在Python中构建GraphRAG系统,通常需要结合多个库和框架,如LangChain、Neo4j、OpenAI等。以下是一个简化的示例,展示了如何使用这些工具来构建基本的GraphRAG系统。

首先,确保安装了必要的Python库,包括LangChain及其相关依赖。然后,可以创建Neo4j图数据库实例,并配置相应的连接信息。接下来,利用OpenAI API从自然语言中提取结构化信息,并将其转换为图形数据库中的节点和边。

import os  
from langchain.chat_models import ChatOpenAI  
from langchain_community.graphs import Neo4jGraph  
from neo4j import GraphDatabase  
  
# 设置 OpenAI API 密钥  
os.environ["OPENAI_API_KEY"] = 'Your_API_Key'  
  
# 创建 OpenAI 聊天模型实例  
model = ChatOpenAI()  
  
# 配置 Neo4j 数据库连接  
url = "bolt://localhost:7687"  
username = "neo4j"  
password = "your_password"  
driver = GraphDatabase.driver(url, auth=(username, password))  
  
graph = Neo4jGraph(driver=driver)  
  
print(graph)

OpenAI的函数展现了其卓越的能力,擅长从自然语言文本中提炼出结构化的信息。这一功能的核心理念在于引导LLM生成一个精心设计的JSON对象,该对象内部充满了通过处理文本获得的详尽数据。为了表示和构建知识图谱,项目中精心设计了几个核心的数据结构类,它们各自承载着不同的属性:属性类(用于定义具体的键值对)、节点类(表示知识图谱中的实体)、关系类(定义节点间的连接)以及知识图谱类(作为整体的容器)。

# 导入必要的库和基类  
from langchain_community.graphs.graph_document import (  
    Node as BaseNode,  # 导入BaseNode类并重命名为Node以避免与下面的Node类冲突
    Relationship as BaseRelationship,  
    GraphDocument, 
)  
from langchain.schema import Document  
from typing import List, Dict, Any, Optional  
from langchain_core.pydantic_v1 import BaseModel, Field  
  
# 定义属性类,用于存储键值对  
class Property(BaseModel):  
    """表示单个属性,包含键和值"""  
    key: str = Field(..., description="属性的键")  
    value: str = Field(..., description="属性的值")  
  
# 定义节点类,继承自BaseNode,并添加属性列表  
class Node(BaseNode):  # 如果BaseNode已有足够的属性,这里的继承可能不需要,或需要根据实际情况调整  
    """表示知识图谱中的节点,可以包含多个属性"""  
    properties: Optional[List[Property]] = Field(  
        None, description="节点的属性列表"  
    )  
  
# 定义关系类,继承自BaseRelationship,并添加属性列表  
class Relationship(BaseRelationship):  
    """表示知识图谱中节点间的关系,可以包含多个属性"""  
    properties: Optional[List[Property]] = Field(  
        None, description="关系的属性列表"  
    )  
  
# 定义知识图谱类,作为整个知识图谱的容器  
class KnowledgeGraph(BaseModel):  
    """用于生成包含实体和关系的知识图谱"""  
    nodes: List[Node] = Field(  
        ..., description="知识图谱中所有节点的列表"  
    )  
    rels: List[Relationship] = Field(  # 通常,我们使用更标准的术语如'relationships'或'edges'来代表关系  
        ..., description="知识图谱中所有关系的列表"  
    )

为了突破API的限制,需要将属性值重构为属性类对象的列表形式,摒弃了传统的字典结构。鉴于API接口的限制,即只能传递单一对象,通过设计一个名为KnowledgeGraph的类,它将节点与关系合并为一个整体,实现了高效的数据封装。

接下来,我们定义了一系列实用的函数,用于优化和简化知识图谱中节点与关系的操作过程。

●format_property_key:此函数负责格式化属性的键,确保键的命名符合特定的风格要求。

●props_to_dict:该函数将属性列表转换为字典格式,便于后续处理与存储。

●map_to_base_node:此函数将自定义的节点对象映射到基础节点类BaseNode,确保节点数据的一致性与兼容性。

●map_to_base_relationship:与map_to_base_node类似,此函数负责将自定义的关系对象映射到基础关系类BaseRelationship。

def format_property_key(s: str) -> str:  
    """  
    格式化属性的键,将首字母小写,其余单词首字母大写。  
      
    Args:  
        s (str): 原始的属性键字符串。  
          
    Returns:  
        str: 格式化后的属性键字符串。  
    """  
    words = s.split()  
    if not words:  
        return s  
    first_word = words[0].lower()  
    capitalized_words = [word.capitalize() for word in words[1:]]  
    return "".join([first_word] + capitalized_words)  
  
def props_to_dict(props) -> dict:  
    """  
    将属性列表转换为字典。  
      
    Args:  
        props (List[Property]): 属性列表。  
          
    Returns:  
        dict: 属性字典。  
    """  
    properties = {}  
    if not props:  
        return properties  
    for p in props:  
        properties[format_property_key(p.key)] = p.value  
    return properties  
  
def map_to_base_node(node: Node) -> BaseNode:  
    """  
    将自定义的节点映射到基础节点类。  
      
    Args:  
        node (Node): 自定义节点对象。  
          
    Returns:  
        BaseNode: 映射后的基础节点对象。  
    """  
    properties = props_to_dict(node.properties) if node.properties else {}  
    # 为Cypher语句生成添加名称属性  
    properties["name"] = node.id.title()  
    return BaseNode(  
        id=node.id.title(),   
        type=node.type.capitalize(),   
        properties=properties  
    )  
  
def map_to_base_relationship(rel: Relationship) -> BaseRelationship:  
    """  
    将自定义的关系映射到基础关系类。  
      
    Args:  
        rel (Relationship): 自定义关系对象。  
          
    Returns:  
        BaseRelationship: 映射后的基础关系对象。  
    """  
    source = map_to_base_node(rel.source)  
    target = map_to_base_node(rel.target)  
    properties = props_to_dict(rel.properties) if rel.properties else {}  
    return BaseRelationship(  
        source=source,   
        target=target,   
        type=rel.type,   
        properties=properties  
    )

定义一个名为get_extraction_chain的函数,该函数旨在构建一条专门用于从文本数据中精确提取知识图谱的链条。该函数提供了两个灵活的可选参数:allowed_nodes和allowed_rels,它们分别扮演着指定允许节点标签和关系类型的角色。

通过明确界定这些参数,我们可以显著提升信息提取的准确性和针对性,有效减少类似于Sakura示例中所描述的模糊和歧义情况。尽管在构建提示语时复杂,我们会尝试内置消歧逻辑,但在某些或特定场景下,直接指定allowed_nodes和allowed_rels将成为优化提取结果的关键步骤。

最终,get_extraction_chain函数将调用create_structured_output_chain方法,利用这些定制化的参数和预设的提示语,结合 LLM的强大能力,构建出高效且精准的知识图谱提取链。这样的设计既保证了功能的灵活性,又兼顾了结果的准确性和实用性。

# 导入必要的模块和函数  
from langchain.chains.openai_functions import create_structured_output_chain  # 从langchain库中导入用于创建结构化输出链的函数  
from langchain_core.prompts import ChatPromptTemplate  # 从langchain_core库中导入用于生成聊天提示模板的类  
  
# 假设已经有一个大型语言模型实例,这里用model代替  
llm = model  
  
# 定义一个函数,用于获取知识图谱提取链  
def get_extraction_chain(  
    allowed_nodes: Optional[List[str]] = None,  # 可选参数,用于指定允许的节点标签列表,默认为None  
    allowed_rels: Optional[List[str]] = None   # 可选参数,用于指定允许的关系类型列表,默认为None  
    ):  
    # 使用ChatPromptTemplate.from_messages方法构建一个提示模板  
    # 该模板为GPT-4或其他LLM提供了关于如何构建知识图谱的详细指令  
    prompt = ChatPromptTemplate.from_messages(
        [(
          "system",
          f"""# Knowledge Graph Instructions for GPT-4
## 1. Overview
You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph.
- **Nodes** represent entities and concepts. They're akin to Wikipedia nodes.
- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience.
## 2. Labeling Nodes
- **Consistency**: Ensure you use basic or elementary types for node labels.
  - For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist".
- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text.
{'- **Allowed Node Labels:**' + ", ".join(allowed_nodes) if allowed_nodes else ""}
{'- **Allowed Relationship Types**:' + ", ".join(allowed_rels) if allowed_rels else ""}
## 3. Handling Numerical Data and Dates
- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes.
- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes.
- **Property Format**: Properties must be in a key-value format.
- **Quotation Marks**: Never use escaped single or double quotes within property values.
- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`.
## 4. Coreference Resolution
- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency.
If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"),
always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID.
Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial.
## 5. Strict Compliance
Adhere to the rules strictly. Non-compliance will result in termination.
          """),
            ("human", "Use the given format to extract information from the following input: {input}"),
            ("human", "Tip: Make sure to answer in the correct format"),
        ])
    return create_structured_output_chain(KnowledgeGraph, llm, prompt, verbose=False)

通过允许用户明确指定希望从文本中提取的节点或关系类型,极大地增强了信息提取的灵活性和精确度。这一功能使得信息提取过程更加高效和定制化。结合Neo4j数据库的强大连接与LLM的精准提示,成功地将复杂的信息提取流程封装成了一个简洁的函数,实现了从数据输入到图数据库存储的一站式处理。这种设计不仅简化了操作流程,还让我们能够轻松聚焦于关键信息的提取,从而全面提升工作效率与数据质量。

def extract_and_store_graph(  
    document: Document,  
    nodes: Optional[List[str]] = None,  
    rels: Optional[List[str]] = None  
) -> None:  
    """  
    从文档中提取并存储图结构信息到Neo4j数据库。  
      
    Args:  
        document (Document): 待处理的文档对象。  
        nodes (Optional[List[str]]): 可选的节点类型列表,用于限制提取的节点类型。  
        rels (Optional[List[str]]): 可选的关系类型列表,用于限制提取的关系类型。  
      
    Raises:  
        TypeError: 如果document不是Document类型的实例。  
    """  
    if not isinstance(document, Document):  
        raise TypeError(f"Expected document to be an instance of Document, got {type(document)}")  
      
    # 使用自定义的提取链获取图数据  
    extract_chain = get_extraction_chain(nodes, rels)  
    extracted_data = extract_chain.invoke(document.page_content)['function']  
      
    # 构建图文档对象  
    graph_document = GraphDocument(  
        nodes=[map_to_base_node(node) for node in extracted_data.nodes],  
        relationships=[map_to_base_relationship(rel) for rel in extracted_data.rels],  
        source=document  
    )  
      
    # 打印图文档信息  
    print(graph_document)  
      
    # 将图文档信息存储到Neo4j数据库中  
    graph.add_graph_documents([graph_document], True) 

使用较大的分块大小值可以尽可能多地包含每个句子的上下文。这有助于更好地进行核心参照解析。重要的是要记住,核心参照步骤只有在实体及其参照出现在同一语块中时才会起作用;否则,LLM就没有足够的信息将两者联系起来。

# 使用更大的分块大小和重叠来增强上下文理解能力  
from langchain_community.document_loaders import WebBaseLoader  
from langchain_text_splitters import TokenTextSplitter  
raw_documents = WebBaseLoader("https://blog.langchain.dev/what-is-an-agent/").load()  
text_splitter = TokenTextSplitter(chunk_size=2048, chunk_overlap=24)  
  
# 仅处理第一个原始文档作为示例  
documents = text_splitter.split_documents(raw_documents[:1])  
对于文档列表中的每个文档,通过调用 extractandstore_graph 函数来提取知识图谱,并将其保存到 Neo4j 数据库中。此外,可以使用 tqdm 库来显示处理进度。
# 遍历文档列表,提取并存储知识图谱  
from tqdm import tqdm  
  
for i, d in tqdm(enumerate(documents), total=len(documents)):  
    print(f"Processing chunk {i+1}: {d}")  
    extract_and_store_graph(d)  
    print("Graph stored successfully.")  
  
# 使用GraphCypherQAChain浏览知识图谱中的信息,类似于关系数据库中的SQL查询  
# (此部分代码假设GraphCypherQAChain已正确定义并可用)  
# 例如:查询特定节点及其关系  
# query_chain = GraphCypherQAChain(...)  
# result = query_chain.run("MATCH (n:Person)-[r]->(m) RETURN n, r, m")  
# print(result)

最后,我们可以借助GraphCypherQAChain类,通过精心构建Cypher查询语句,深入探索并浏览了知识图谱中的丰富信息。这一过程与我们在关系数据库中利用SQL语言进行数据检索的方式不谋而合,展现了跨领域技术的相通之处。

# 在RAG应用中查询知识图谱  
from langchain.chains import GraphCypherQAChain  
  
# 刷新图数据库模式以确保最新状态  
graph.refresh_schema()  
print(graph.schema)  
  
# 初始化GraphCypherQAChain,集成大型语言模型以增强Cypher查询能力  
cypher_chain = GraphCypherQAChain.from_llm(  
    graph=graph,  
    cypher_llm=model,  # 用于生成和优化Cypher查询的LLM  
    qa_llm=model,      # 用于回答查询结果的LLM  
    validate_cypher=True,  # 验证关系方向,确保查询准确性  
    # return_intermediate_steps=True,  # 可选,返回查询的中间步骤  
    verbose=True        # 输出详细日志,便于调试  
)

尽管已尝试多种查询方式,但在将自然语言问题转换为Cypher查询语句的过程中,仍然面临不确定性。

例如,使用以下自然语言查询来探索知识图谱:

cypher_chain.invoke({"query": "what is Ai Agent?"})

查询结果如下⬇️

尽管结果中并未直接显示名为“musings”的节点,这说明当前系统可能在处理某些类型查询时存在局限。期待通过优化查询策略或改进数据模型,使系统能够更智能地处理此类复杂查询。
目前公认最有效的策略之一是先利用矢量搜索定位相关信息的大致范围,随后再精确检索具体细节。不过,当前处理的示例并不完全理想,这在一定程度上削弱了立即寻找替代解决方案的急迫。

5. 结论

综上所述,知识图谱的探索之旅既充满乐趣也伴随着诸多技术挑战。尽管目前仍有许多未知领域有待探索,但通过融合知识图谱与RAG(检索-增强生成)技术,将开启无限可能。展望未来,图形RAG有望成为该领域的全球标准,引领我们迈向更加智能化、高效化的信息处理新时代。

参考:

https://github.com/tomasonjo/blogs/blob/master/llm/openaifunctionconstructinggraph.ipynb

https://github.com/tomasonjo/blogs/blob/master/llm/openaifunctionconstructinggraph.ipynb

https://www.linkedin.com/pulse/graph-databases-vs-vector-constantin-a-alexander/

https://pub.towardsai.net/langchain-graph-rag-gpt-4o-python-project-easy-ai-chat-for-your-website-46a46e24f161

本文首发于微信公众号【深蓝AI】,移步公众号【深蓝AI】,第一时间获取自动驾驶、人工智能与机器人行业最新最前沿论文和科技动态👇
深蓝AI·赋能人工智能+自动驾驶+机器人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值