如何有效地将用户输入映射到图数据库

57 阅读4分钟

引言

在现代应用中,图数据库因其强大的查询能力和自然模型而变得越来越受欢迎。然而,当我们希望通过图数据库进行问答时,如何将用户的自然语言输入映射到数据库中的结构化数据变得尤为重要。在本文中,我们将探讨一种策略,通过将用户输入的值映射到图数据库中,来改进图数据库查询生成。

主要内容

1. 环境设置

首先,我们需要获取必要的软件包并设置环境变量。以下是安装和设置的基本步骤:

%pip install --upgrade --quiet langchain langchain-community langchain-openai neo4j

接下来,定义Neo4j的凭据来连接和操作数据库。

import os
import getpass

os.environ["OPENAI_API_KEY"] = getpass.getpass()
os.environ["NEO4J_URI"] = "bolt://localhost:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "password"

在这里我们使用OpenAI模型作为默认选择,但可以根据需求更换为其他模型。

2. 数据库初始化和数据导入

利用以下代码初始化Neo4j数据库并导入电影相关的数据:

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)  # 使用API代理服务提高访问稳定性

3. 实体检测

为了从用户输入中提取实体(如电影名称或人名),我们可以使用以下代码:

from typing import List
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

class Entities(BaseModel):
    names: List[str] = Field(..., description="所有在文本中出现的人名或电影名")

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你会从文本中提取人名和电影名字。"),
        ("human", "请使用以下格式从输入中提取信息:{question}")
    ]
)

entity_chain = prompt | llm.with_structured_output(Entities)

entities = entity_chain.invoke({"question": "Who played in Casino movie?"})
# Output: Entities(names=['Casino'])

4. 将实体映射到数据库

为了将提取的实体映射到数据库,我们使用以下函数:

match_query = """MATCH (p:Person|Movie)
WHERE p.name CONTAINS $value OR p.title CONTAINS $value
RETURN coalesce(p.name, p.title) AS result, labels(p)[0] AS type
LIMIT 1
"""

def map_to_database(entities: Entities) -> Optional[str]:
    result = ""
    for entity in entities.names:
        response = graph.query(match_query, {"value": entity})
        try:
            result += f"{entity} maps to {response[0]['result']} {response[0]['type']} in database\n"
        except IndexError:
            pass
    return result

map_to_database(entities)  # Output: 'Casino maps to Casino Movie in database\n'

代码示例

下面是完整的代码示例阐述如何将自然语言查询转换为Cypher查询,并通过数据库获取答案。

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

cypher_template = """根据以下Neo4j图模式,编写一个Cypher查询来回答用户问题:
{schema}
问题中的实体映射到以下数据库值:
{entities_list}
问题:{question}
Cypher查询:"""

cypher_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "将输入问题转换为Cypher查询。"),
        ("human", cypher_template),
    ]
)

cypher_response = (
    RunnablePassthrough.assign(names=entity_chain)
    | RunnablePassthrough.assign(
        entities_list=lambda x: map_to_database(x["names"]),
        schema=lambda _: graph.get_schema,
    )
    | cypher_prompt
    | llm.bind(stop=["\nCypherResult:"])
    | StrOutputParser()
)

cypher = cypher_response.invoke({"question": "Who played in Casino movie?"})
# Output: 'MATCH (:Movie {title: "Casino"})<-[:ACTED_IN]-(actor)\nRETURN actor.name'

from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema

corrector_schema = [
    Schema(el["start"], el["type"], el["end"])
    for el in graph.structured_schema.get("relationships")
]
cypher_validation = CypherQueryCorrector(corrector_schema)

response_template = """根据问题、Cypher查询和Cypher响应,写出自然语言响应:
问题:{question}
Cypher查询:{query}
Cypher响应:{response}"""

response_prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "将输入问题和Cypher响应,转换为自然语言答案。",
        ),
        ("human", response_template),
    ]
)

chain = (
    RunnablePassthrough.assign(query=cypher_response)
    | RunnablePassthrough.assign(
        response=lambda x: graph.query(cypher_validation(x["query"])),
    )
    | response_prompt
    | llm
    | StrOutputParser()
)

chain.invoke({"question": "Who played in Casino movie?"})
# Output: 'Robert De Niro, James Woods, Joe Pesci, and Sharon Stone played in the movie "Casino".'

常见问题和解决方案

  1. 地图实体不匹配:这可能是由于数据库中的拼写差异或用户输入不一致造成的。建议使用模糊搜索或全文索引来提高匹配率。

  2. 网络访问问题:由于某些地区的网络限制,API调用可能不稳定。可以考虑使用API代理服务来提高访问的稳定性。

总结和进一步学习资源

通过本文的探讨,我们学习了如何将自然语言输入有效地映射到图数据库中,并利用该映射生成准确的查询。如果希望深入了解类似技术,可以关注以下资源:

参考资料

  1. Neo4j 官方文档
  2. Langchain 使用指南

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

---END---