目录
- 1. 案例目标
- 2. 技术栈与核心依赖
- 3. 环境配置
- 4. 案例实现
- 5. 案例效果
- 6. 案例实现思路
- 7. 扩展建议
- 8. 总结
1. 案例目标
本案例旨在构建一个基于Neo4j图数据库的电影问答系统,使用LangChain框架将自然语言查询转换为Cypher查询语句,从而实现对电影数据的智能问答。该系统能够理解用户关于电影的自然语言问题,自动生成相应的Cypher查询,从Neo4j数据库中检索相关信息,并以自然语言形式返回答案。
核心功能:
- 自然语言到Cypher查询的自动转换
- 基于电影和关键词的复杂查询处理
- 支持多条件组合查询(如电影类型与流行度组合)
- 支持聚合查询(如计算平均流行度)
2. 技术栈与核心依赖
本案例使用了以下主要技术和依赖:
| 技术/库 | 版本 | 用途 |
|---|---|---|
| Python | 3.11.11 | 编程语言 |
| langchain | 0.1.0 | 核心框架,提供链式处理能力 |
| langchain-openai | 0.0.5 | OpenAI模型集成 |
| langchain-neo4j | 0.0.1 | Neo4j图数据库集成 |
| neo4j | 5.15.0 | Neo4j Python驱动 |
| kagglehub | 0.2.5 | 从Kaggle下载数据集 |
| openai | 1.6.1 | OpenAI API客户端 |
| tiktoken | 0.5.2 | OpenAI的token计算工具 |
注意:本案例使用了GPT-4模型作为语言模型,需要配置有效的OpenAI API密钥。
3. 环境配置
在运行本案例之前,需要进行以下环境配置:
3.1 安装依赖包
%pip install -qU langchain langchain-openai langchain-neo4j neo4j kagglehub3.2 配置API密钥
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"] = getpass.getpass()3.3 Neo4j数据库配置
本案例假设已在本地安装并运行了Neo4j数据库,默认端口为7687。需要提供用户名和密码进行连接。
警告:确保Neo4j数据库服务已启动,并且提供的用户名和密码正确无误。
4. 案例实现
4.1 连接Neo4j数据库
首先,创建与Neo4j数据库的连接:
from langchain_neo4j import Neo4jGraph graph = Neo4jGraph() # 清除现有数据 graph.query("MATCH (n) DETACH DELETE n")4.2 加载示例电影数据
使用提供的示例数据创建电影和人物节点及其关系:
# 创建电影节点 movies_query = """ MERGE (m:Movie {title: $movie.title}) ON CREATE SET m.released = $movie.released, m.tagline = $movie.tagline """ # 创建人物节点和关系 actors_query = """ MERGE (p:Person {name: $person.name}) ON CREATE SET p.born = $person.born WITH p MERGE (m:Movie {title: $movie.title}) MERGE (p)-[r:ACTED_IN]->(m) ON CREATE SET r.roles = $person.roles """ directors_query = """ MERGE (p:Person {name: $person.name}) ON CREATE SET p.born = $person.born WITH p MERGE (m:Movie {title: $movie.title}) MERGE (p)-[:DIRECTED]->(m) """ # 执行查询 for movie, actors, director in zip(movies, actors, directors): graph.query(movies_query, params={"movie": movie}) for actor in actors: graph.query(actors_query, params={"movie": movie, "person": actor}) graph.query(directors_query, params={"movie": movie, "person": director})4.3 定义图模式
查看和定义图数据库的模式结构:
graph.refresh_schema() print(graph.schema)节点属性:
Movie {tagline: STRING, title: STRING, released: INTEGER}
Person {born: INTEGER, name: STRING}
关系属性:
ACTED_IN {roles: LIST}
关系:
(:Person)-[:ACTED_IN]->(:Movie)
(:Person)-[:DIRECTED]->(:Movie)
4.4 创建GraphCypherQAChain
使用LangChain的GraphCypherQAChain将自然语言转换为Cypher查询:
from langchain_neo4j import GraphCypherQAChain from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o", temperature=0) chain = GraphCypherQAChain.from_llm( graph=graph, llm=llm, verbose=True, allow_dangerous_requests=True )4.5 执行查询
使用创建的链执行自然语言查询:
response = chain.invoke({"query": "Who was the director of the movie Forrest Gump?"}) response4.6 使用Kaggle电影数据集
案例还展示了如何从Kaggle下载并导入更大的电影数据集:
import kagglehub # 下载数据集 path = kagglehub.dataset_download("rounakbanik/the-movies-dataset") # 移动文件到Neo4j导入目录 import shutil import os neo4j_import_dir = "/var/lib/neo4j/import/" # Linux路径 # Windows路径可能是: "C:\\Users\\Neo4j\\.Neo4jDesktop\\relate-data\\dbmss\\dbms-xxx\\import\\" shutil.copy(os.path.join(path, "movies_metadata.csv"), neo4j_import_dir) shutil.copy(os.path.join(path, "keywords.csv"), neo4j_import_dir)4.7 导入Kaggle数据到Neo4j
# 导入电影数据 movies_query = """ LOAD CSV WITH HEADERS FROM 'file:///movies_metadata.csv' AS row WITH row WHERE row.id IS NOT NULL MERGE (m:Movie {id: row.id}) SET m.title = row.title, m.popularity = toFloat(row.popularity) """ # 导入关键词数据 keywords_query = """ CALL apoc.periodic.iterate( "LOAD CSV WITH HEADERS FROM 'file:///keywords.csv' AS row RETURN row", "WITH row, apoc.convert.fromJsonList(row.keywords) AS keywords UNWIND keywords AS keyword MERGE (k:Keyword {name: keyword.name}) MERGE (m:Movie {id: row.id}) MERGE (m)-[:HAS_KEYWORD]->(k)", {batchSize: 1000, parallel: true} ) """ graph.query(movies_query) graph.query(keywords_query)4.8 使用增强模式
使用增强模式获取更详细的图结构信息:
enhanced_graph = Neo4jGraph(enhanced_schema=True) print(enhanced_graph.schema)节点属性:
-Movie
- `id`: STRING 示例: "862"
- `title`: STRING 示例: "Toy Story"
- `popularity`: FLOAT 示例: "21.946943"
-Keyword
- `name`: STRING 示例: "terror"
关系:
(:Movie)-[:HAS_KEYWORD]->(:Keyword)
4.9 执行复杂查询
使用增强图数据执行更复杂的查询:
llm = ChatOpenAI(model="gpt-4o", temperature=0) chain = GraphCypherQAChain.from_llm( graph=enhanced_graph, llm=llm, verbose=True, allow_dangerous_requests=True ) # 查询示例1 response = chain.invoke({ "query": "What movies have a popularity score above 8.0 and are tagged with 'comedy'?" }) # 查询示例2 response = chain.invoke({ "query": "What is the average popularity score of movies tagged with 'romance'?" }) # 查询示例3 response = chain.invoke({ "query": "What movies are tagged with both 'comedy' and 'romance'?" })5. 案例效果
本案例实现了以下效果:
5.1 自然语言查询转换
系统能够将自然语言问题自动转换为Cypher查询语句,例如:
- 输入:"Who was the director of the movie Forrest Gump?"
- 生成的Cypher查询:
cypher MATCH (p:Person)-[:DIRECTED]->(m:Movie {title: 'Forrest Gump'}) RETURN p.name - 输出:"Robert Zemeckis was the director of the movie Forrest Gump."
5.2 复杂条件查询
系统能够处理包含多个条件的复杂查询:
- 输入:"What movies have a popularity score above 8.0 and are tagged with 'comedy'?"
- 生成的Cypher查询:
cypher MATCH (m:Movie)-[:HAS_KEYWORD]->(k:Keyword {name: 'comedy'}) WHERE m.popularity > 8.0 RETURN m.title - 输出:"Ratchet & Clank, Kick-Ass, Jackass 3D, Aliens in the Attic, A Dog's Will have a popularity score above 8.0 and are tagged with 'comedy'."
5.3 聚合查询
系统能够处理需要聚合计算的查询:
- 输入:"What is the average popularity score of movies tagged with 'romance'?"
- 生成的Cypher查询:
cypher MATCH (m:Movie)-[:HAS_KEYWORD]->(k:Keyword {name: 'romance'}) RETURN avg(m.popularity) AS average_popularity - 输出:"The average popularity score of movies tagged with 'romance' is 12.390850747422673."
5.4 多关系查询
系统能够处理涉及多个关系的查询:
- 输入:"What movies are tagged with both 'comedy' and 'romance'?"
- 生成的Cypher查询:
cypher MATCH (m:Movie)-[:HAS_KEYWORD]->(k1:Keyword {name: 'comedy'}), (m)-[:HAS_KEYWORD]->(k2:Keyword {name: 'romance'}) RETURN m.title - 输出:"Lisbela and the Prisoner, Love Hina Christmas Special: Silent Eve, Love Is in the Air are tagged with both 'comedy' and 'romance'."
6. 案例实现思路
本案例的实现思路可以分为以下几个关键步骤:
6.1 数据模型设计
案例采用了图数据库的数据模型,将电影、人物和关键词表示为节点,将它们之间的关系表示为边:
- 电影节点(Movie):包含标题、发布年份、标语和流行度等属性
- 人物节点(Person):包含姓名和出生年份等属性
- 关键词节点(Keyword):包含关键词名称属性
- 关系:ACTED_IN(出演)、DIRECTED(导演)、HAS_KEYWORD(拥有关键词)
6.2 自然语言到Cypher的转换
案例的核心是使用LangChain的GraphCypherQAChain将自然语言转换为Cypher查询:
- 接收用户自然语言查询
- 利用图数据库模式信息生成提示词
- 将提示词发送给LLM(如GPT-4)生成Cypher查询
- 在Neo4j数据库中执行生成的Cypher查询
- 将查询结果转换为自然语言回答
6.3 增强模式的使用
案例使用了Neo4j的增强模式功能,提供更详细的图结构信息:
- 包含属性的数据类型信息
- 提供属性值的示例
- 帮助LLM更准确地生成Cypher查询
6.4 大规模数据处理
案例展示了如何处理大规模数据集:
- 使用KaggleHub下载大型电影数据集
- 使用Neo4j的LOAD CSV命令批量导入数据
- 使用APOC过程的periodic.iterate进行并行处理
注意:在导入大规模数据时,可能会遇到死锁问题,如案例中所示。可以通过调整批处理大小和并行度来缓解此类问题。
7. 扩展建议
基于本案例的实现,以下是一些可能的扩展方向:
7.1 增强查询能力
- 支持更复杂的查询:扩展系统以支持更复杂的自然语言查询,如时间序列分析、路径查询等
- 多轮对话:实现多轮对话功能,允许用户基于之前的回答进行追问
- 模糊查询:支持模糊匹配和容错查询,提高用户体验
- 查询优化:实现查询优化策略,提高复杂查询的执行效率
7.2 扩展数据模型
- 添加更多实体类型:如导演、演员、制片公司、奖项等
- 丰富关系类型:如"合作"、"获奖"、"评价"等
- 时间维度:添加时间维度,支持历史数据查询和趋势分析
- 用户数据:集成用户评分、评论等数据,支持个性化推荐
7.3 改进用户体验
- 可视化界面:开发Web界面,提供可视化查询和结果展示
- 查询建议:基于用户输入提供查询建议和自动补全
- 结果解释:提供查询结果的解释和可视化展示
- 多语言支持:支持多种自然语言的查询和回答
7.4 技术优化
- 缓存机制:实现查询结果缓存,提高常用查询的响应速度
- 模型微调:针对特定领域微调LLM,提高查询转换的准确性
- 分布式处理:实现分布式图数据库和查询处理,支持更大规模数据
- 实时更新:支持数据的实时更新和增量导入
7.5 应用场景扩展
- 推荐系统:基于图数据构建电影推荐系统
- 知识图谱:扩展为更广泛的电影知识图谱
- 情感分析:集成用户评论的情感分析结果
- 跨领域查询:支持电影与其他领域(如音乐、书籍)的关联查询
8. 总结
本案例成功展示了如何使用LangChain和Neo4j构建一个电影问答系统。该系统通过将自然语言查询转换为Cypher查询语句,实现了对电影数据的智能问答。案例涵盖了从环境配置、数据导入、模式定义到查询执行的完整流程,并展示了如何处理小规模示例数据和大规模真实数据集。
主要成就:
- 成功实现了自然语言到Cypher查询的自动转换
- 展示了图数据库在复杂关系查询中的优势
- 演示了LangChain框架与Neo4j图数据库的集成方法
- 提供了处理大规模图数据的实践经验
技术亮点:
- 使用增强模式提供更详细的图结构信息,提高查询转换准确性
- 利用APOC过程实现高效的数据导入和处理
- 结合GPT-4模型实现高质量的自然语言理解和查询生成
- 展示了从简单到复杂查询的渐进式实现方法
该案例为构建基于图数据库的智能问答系统提供了完整的参考实现,可以作为其他领域问答系统开发的基础模板。通过适当的调整和扩展,该方法可以应用于各种需要复杂关系查询的场景,如社交网络分析、生物信息学研究、金融风控等领域。