第四章 基于本地部署的大语言模型OLlama&Neo4j图数据库的知识图谱搭建

目录

一、部署本地大语言模型Ollama

二、安装Neo4j数据库

三、应用本地大语言模型搭建知识图谱

1、导入依赖库

2、连接数据库

3、加载CSV文档

4、初始化(载入)大模型

5、编写实体关系识别函数

6、处理文档生成知识图谱

7、将大模型识别出的关系写入数据库

8、设置QA链实现关于电影的问答功能

9、实现电影推荐

改进与反思:

解决方法:

最终结果:


一、部署本地大语言模型Ollama

进入官网:Ollama

下载Ollama

安装Ollama到本地

安装完毕,打开命令行输入`ollama --help`检查是否安装成功。

选择合适的模型,并使用命令行指令`ollama run llama3.2:3b`进行本地安装(由于设备受限,我选择了参数大小为3b的llama3.2模型,本地存储占用2GB.)

安装完成后即可在本地进行对话

对话时只要输入运行相应模型的指令"ollama run 模型名称"即可。

二、安装Neo4j数据库

参考以下教程:

neo4j入门到精通------1、安装部署_neo4j需要jdk在系统环境中吗?-CSDN博客

Neo4j安装教程_51CTO博客_neo4j教程

NEO4J指定JDK路径_elasticsearch_K歌、之王-华为开发者空间

Java 11 / JDK 11 下载-CSDN博客

Neo4j启动指令:`neo4j.bat console`,停止指令:`neo4j stop`

默认用户名:`neo4j`,密码:`neo4j`

可视化网址:http://localhost:7474/

图数据库用户名:neo4j,密码:your_password

注意:项目中的代码需要适配neo4j最新版,并配置相应的apoch包。

安装过程容易发生的错误:

Neo.ClientError.Statement.SyntaxError Unknown function 'apoc.version' (line 1, column 8 (offset: 7)) "RETURN apoc.version()"

  1. 检查 APOC 插件是否已安装

    如果您已经安装了 Neo4j 5.x,并且遇到这个错误,可能是因为 APOC 插件未安装或未启用。在 Neo4j 5.x 版本中,APOC 插件需要手动安装。

    您可以执行以下步骤来安装和配置 APOC 插件:

    a. 安装 APOC 插件

    • 访问 APOC Releases 页面,下载与您的 Neo4j 版本相匹配的 APOC 插件 .jar 文件。
    • 将下载的 .jar 文件放置到 Neo4j 的 plugins 目录中:
      • 如果您是手动安装的 Neo4j,plugins 目录通常位于 Neo4j 安装路径下。
      • 如果您使用 Docker 部署 Neo4j,请将 .jar 文件挂载到 Docker 容器中的 /var/lib/neo4j/plugins 目录。

    b. 配置 neo4j.conf 文件

    在 Neo4j 的配置文件 neo4j.conf 中,确保以下设置项存在并已启用(没有就自己添加):

  2. dbms.security.procedures.unrestricted=apoc.* dbms.security.procedures.allowlist=apoc.*

    • dbms.security.procedures.unrestricted 允许使用 APOC 中的所有过程。
    • dbms.security.procedures.allowlist 列出了允许的 APOC 过程。

    修改配置文件后,重启 Neo4j 服务以使设置生效。

  3. 确认 APOC 是否有效

    重新启动 Neo4j 后,您可以再次尝试查询 APOC 版本:

    在cypher输入指令:

    RETURN apoc.version()

    如果成功返回版本信息,说明 APOC 插件已安装并正确配置。

  4. 查看日志

    如果安装和配置后仍然无法使用 APOC,您可以查看 Neo4j 日志文件,通常位于 logs 目录下的 debug.logneo4j.log 文件,查看是否有错误或警告信息,帮助进一步排查问题。

完成这些步骤后,应该能够成功调用 APOC 函数。如果问题仍然存在,请提供更多详细的日志信息。

三、应用本地大语言模型搭建知识图谱

1、导入依赖库

python 复制代码
from langchain_core.runnables import  RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
from langchain_core.output_parsers import StrOutputParser
from langchain_community.graphs import Neo4jGraph
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.chat_models import ChatOllama
from langchain_experimental.graph_transformers import LLMGraphTransformer
from neo4j import GraphDatabase
from yfiles_jupyter_graphs import GraphWidget
from langchain_community.vectorstores import Neo4jVector
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores.neo4j_vector import remove_lucene_chars
from langchain_ollama import OllamaEmbeddings
import os
from langchain_experimental.llms.ollama_functions import OllamaFunctions
from neo4j import  Driver

from dotenv import load_dotenv

load_dotenv()

load_dotenv()函数用来加载本地环境,在项目文件夹中创建.env文件,写入相关代码,此函数能将文件内容链入程序中。

python 复制代码
NEO4J_URI=neo4j://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=12345678
python 复制代码
import langchain
print(langchain.__version__)

0.3.7

2、连接数据库

python 复制代码
# graph = Neo4jGraph()
graph = Neo4jGraph(
    url="neo4j://localhost:7687",  # 替换为您的 Neo4j 地址
    username="neo4j",              # 替换为您的用户名
    password="12345678",       # 替换为您的密码
    #database="my_database"         # 替换为您希望连接的数据库(默认是neo4j)
)

3、加载CSV文档

python 复制代码
from langchain_community.document_loaders import CSVLoader
from langchain_core.documents import Document

# 加载 CSV 文档
loader = CSVLoader(
    file_path="./data/douban_movies.csv",
    encoding='utf-8',  # 如果有中文,确保使用正确的编码
    csv_args={
        'delimiter': ',',  # CSV 分隔符,默认为逗号
    }
)
rows = loader.load()

# 将每行数据作为一个文档
documents = [Document(page_content=row.page_content) for row in rows]

# 检查文档数量
print(f"文档数量: {len(documents)}")
for i, doc in enumerate(documents[:5]):  # 打印前5个文档内容
    print(f"文档 {i+1}:")
    print(doc.page_content)
    print("---")

4、初始化(载入)大模型

python 复制代码
llm = OllamaFunctions(
    model="llama3.1:8b",  
    temperature=0,
    base_url="http://localhost:11434",
    format="json"
)
llm_transformer = LLMGraphTransformer(llm=llm)

5、编写实体关系识别函数

python 复制代码
from tqdm.auto import tqdm
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
from typing import List
import json

# 定义关系模式
class MovieRelation(BaseModel):
    movie_name: str = Field(description="电影名称")
    director: List[str] = Field(description="导演列表")
    screenwriter: List[str] = Field(description="编剧列表")
    actors: List[str] = Field(description="主演列表")
    relationships: List[dict] = Field(description="实体之间的关系列表")

# 创建提示模板
prompt_template = """
请分析以下电影信息,识别出电影、导演、编剧和演员之间的关系:

{text}

请以JSON格式输出以下信息:
1. 电影名称
2. 导演列表
3. 编剧列表
4. 主演列表(仅包含前5名主演)
5. 实体之间的关系(如:导演-执导->电影,演员-主演->电影,编剧-编写->电影)

请确保输出格式符合以下结构:
{
  
  {
    "movie_name": "电影名",
    "director": ["导演1", "导演2"],
    "screenwriter": ["编剧1", "编剧2"],
    "actors": ["演员1", "演员2"],
    "relationships": [
        {
  
  {"source": "导演名", "relation": "执导", "target": "电影名"}},
        {
  
  {"source": "编剧名", "relation": "编写", "target": "电影名"}},
        {
  
  {"source": "演员名", "relation": "主演", "target": "电影名"}}
    ]
}}
"""
# 创建提示对象
prompt = ChatPromptTemplate.from_template(prompt_template)

6、处理文档生成知识图谱

python 复制代码
# 处理文档并生成知识图谱
def process_movie_relations(doc, llm):
    response = None  # 初始化response变量
    try:
        # 构建完整的提示
        chain = prompt | llm
        
        # 获取模型响应
        response = chain.invoke({"text": doc.page_content})
        
        # 添加调试信息
        print("原始响应类型:", type(response))
        print("原始响应内容:", response)
        
        # 尝试解析JSON响应
        if isinstance(response, str):
            try:
                parsed_response = json.loads(response)
            except json.JSONDecodeError as e:
                print(f"JSON解析错误: {str(e)}")
                print(f"问题响应: {response}")
                return None
        elif hasattr(response, 'content'):
            try:
                parsed_response = json.loads(response.content)
            except json.JSONDecodeError as e:
                print(f"JSON解析错误: {str(e)}")
                print(f"问题响应: {response.content}")
                return None
        elif isinstance(response, dict):
            parsed_response = response
        else:
            raise ValueError(f"无法处理的响应类型: {type(response)}")
            
        # 验证必要的字段
        required_fields = ['movie_name', 'director', 'screenwriter', 'actors', 'relationships']
        if all(field in parsed_response for field in required_fields):
            return parsed_response
        else:
            missing_fields = [field for field in required_fields if field not in parsed_response]
            raise ValueError(f"响应缺少必要字段: {missing_fields}")
        
    except Exception as e:
        print(f"处理错误: {str(e)}")
        if response:
            print(f"问题响应: {response}")
        return None

打印输出

处理全部文档内容

python 复制代码
# 处理所有文档并显示进度
results = []
failed_docs = []

print(f"\n开始处理 {len(documents)} 部电影数据...")

with tqdm(total=len(documents), desc="处理电影数据", unit="部") as pbar:
    for i, doc in enumerate(documents):
        try:
            result = process_movie_relations(doc, llm)
            if result:
                results.append(result)
                movie_name = result.get('movie_name', 'Unknown')
                tqdm.write(f"[{i+1}/{len(documents)}] ✓ 成功: {movie_name}")
            else:
                failed_docs.append(doc)
                tqdm.write(f"[{i+1}/{len(documents)}] ✗ 失败: 解析错误")
        except Exception as e:
            failed_docs.append(doc)
            tqdm.write(f"[{i+1}/{len(documents)}] ✗ 失败: {str(e)}")
        finally:
            pbar.update(1)

# 打印详细统计
print("\n处理统计:")
print(f"总数据量: {len(documents)} 部电影")
print(f"成功处理: {len(results)} 部电影")
print(f"处理失败: {len(failed_docs)} 部电影")
print(f"成功率: {len(results)/len(documents)*100:.2f}%")

# 保存成功的结果
with open('movie_relations.json', 'w', encoding='utf-8') as f:
    json.dump(results, f, ensure_ascii=False, indent=2)
print("\n成功的结果已保存到 movie_relations.json")

# 保存失败的文档信息供后续分析
if failed_docs:
    with open('failed_movies.txt', 'w', encoding='utf-8') as f:
        for doc in failed_docs:
            f.write(f"---\n{doc.page_content}\n")
    print("失败的文档已保存到 failed_movies.txt")

成功率达到51.20%,正确转化成知识图谱的电影条数为128条,说明该模型转换能力还有待提高。

模型选取方面:

qwen2.5-coder:7b------直解析错误

llama3.1:8b ------正确处理51.20%

可见,不同的模型对这种数据的解析能力是不同的,如果采用参数更大更适合实体关系抽取的模型效果会更好!由于本地电脑内存限制,我将继续使用已成功转化的128条数据进行图谱构建,以便给读者留下改进空间。

7、将大模型识别出的关系写入数据库

插入数据的时候,实体之间的关系是否符合:导演-执导->电影,演员-主演->电影,编剧-编写->电影

python 复制代码
from langchain_community.graphs import Neo4jGraph
import json
from tqdm import tqdm

def import_to_neo4j(graph: Neo4jGraph, json_file: str):
    """
    从JSON文件逐条导入电影关系数据到Neo4j数据库
    """
    # 读取JSON文件
    print(f"正在读取文件: {json_file}")
    with open(json_file, 'r', encoding='utf-8') as f:
        movie_data = json.load(f)
    
    print(f"成功读取 {len(movie_data)} 条电影数据")
    
    try:
        # 清空数据库
        print("清空数据库...")
        graph.query("""
            MATCH (n)
            DETACH DELETE n
        """)
        
        # 创建索引
        print("创建索引...")
        graph.query("""
            CREATE INDEX IF NOT EXISTS FOR (m:Movie) ON (m.name)
        """)
        graph.query("""
            CREATE INDEX IF NOT EXISTS FOR (p:Person) ON (p.name)
        """)
        
        # 逐条处理数据
        print(f"\n开始导入数据,共 {len(movie_data)} 条记录...")
        
        success_count = 0
        error_count = 0
        
        with tqdm(total=len(movie_data), desc="导入进度") as pbar:
            for movie in movie_data:
                try:
                    # 创建电影节点
                    graph.query("""
                        MERGE (m:Movie {name: $movie_name})
                    """, {"movie_name": movie["movie_name"]})
                    
                    # 处理每个关系
                    for rel in movie["relationships"]:
                        # 创建人物节点
                        graph.query("""
                            MERGE (p:Person {name: $person_name})
                        """, {"person_name": rel["source"]})
                        
                        # 根据关系类型创建关系
                        if rel["relation"] == "执导":
                            graph.query("""
                                MATCH (p:Person {name: $person_name})
                                MATCH (m:Movie {name: $movie_name})
                                MERGE (p)-[:DIRECTED]->(m)
                            """, {
                                "person_name": rel["source"],
                                "movie_name": movie["movie_name"]
                            })
                        elif rel["relation"] == "编写":
                            graph.query("""
                                MATCH (p:Person {name: $person_name})
                                MATCH (m:Movie {name: $movie_name})
                                MERGE (p)-[:WROTE]->(m)
                            """, {
                                "person_name": rel["source"],
                                "movie_name": movie["movie_name"]
                            })
                        elif rel["relation"] == "主演":
                            graph.query("""
                                MATCH (p:Person {name: $person_name})
                                MATCH (m:Movie {name: $movie_name})
                                MERGE (p)-[:ACTED_IN]->(m)
                            """, {
                                "person_name": rel["source"],
                                "movie_name": movie["movie_name"]
                            })
                    
                    success_count += 1
                    pbar.update(1)
                    pbar.set_description(f"成功: {success_count}, 失败: {error_count}")
                    
                except Exception as e:
                    error_count += 1
                    print(f"\n导入电影 {movie.get('movie_name', 'Unknown')} 失败: {str(e)}")
                    pbar.update(1)
                    pbar.set_description(f"成功: {success_count}, 失败: {error_count}")
                    continue
        
        # 打印统计信息
        print("\n导入完成!")
        print(f"成功导入: {success_count}")
        print(f"导入失败: {error_count}")
        
        # 打印详细统计
        stats_result = graph.query("""
            MATCH (m:Movie)
            WITH COUNT(m) as movies
            MATCH (p:Person)
            WITH movies, COUNT(p) as persons
            MATCH ()-[r]->()
            WITH movies, persons, COUNT(r) as relations
            RETURN movies, persons, relations
        """)
        
        if stats_result and len(stats_result) > 0:
            stats = stats_result[0]
            print("\n数据库统计:")
            print(f"电影节点数: {stats['movies']}")
            print(f"人物节点数: {stats['persons']}")
            print(f"关系数: {stats['relations']}")
            
            # 打印各类关系的统计
            relation_stats = graph.query("""
                MATCH ()-[r]->()
                WITH type(r) as relation_type, COUNT(*) as count
                RETURN relation_type, count
                ORDER BY count DESC
            """)
            
            print("\n关系类型统计:")
            for rel in relation_stats:
                print(f"{rel['relation_type']}: {rel['count']} 条")
        
    except Exception as e:
        print(f"导入过程中出错: {str(e)}")
        raise e

# 使用示例
try:
    # 连接到Neo4j数据库
    graph = Neo4jGraph(
        url="neo4j://localhost:7687",
        username="neo4j",
        password="12345678"  # 替换为您的实际密码
    )
    
    # 导入数据
    import_to_neo4j(graph, 'movie_relations.json')
    
except Exception as e:
    print(f"导入失败: {str(e)}")

8、设置QA链实现关于电影的问答功能

python 复制代码
# 导入必要的依赖
from langchain_core.prompts import PromptTemplate
from langchain.chains import GraphCypherQAChain

# 修改Cypher查询模板
CYPHER_GENERATION_TEMPLATE = """任务: 生成查询图数据库的Cypher语句。
说明:
数据库中的关系模式如下:
- (Person)-[:DIRECTED]->(Movie) 表示导演关系
- (Person)-[:ACTED_IN]->(Movie) 表示演员关系
- (Person)-[:WROTE]->(Movie) 表示编剧关系
所有节点和关系的属性:
- Person节点有name属性
- Movie节点有name属性

注意: 
- 不要使用未定义的关系类型或属性
- 返回完整的节点信息

问题是:
{question}"""

# 创建Cypher生成提示
CYPHER_GENERATION_PROMPT = PromptTemplate(
    input_variables=["schema", "question"], 
    template=CYPHER_GENERATION_TEMPLATE
)

# 创建新的QA链
chain = GraphCypherQAChain.from_llm(
    llm, 
    graph=graph, 
    verbose=True, 
    allow_dangerous_requests=True,  # 添加这个参数
    cypher_prompt=PromptTemplate(
        input_variables=["question"], 
        template=CYPHER_GENERATION_TEMPLATE
    )
)

这样设置的QA链可以:

将自然语言问题转换为Cypher查询在Neo4j数据库中执行查询将结果转换回自然语言回答您可以尝试各种问题,比如:某个导演导演了哪些电影、某个演员参演了哪些电影、某类型的电影推荐等。

python 复制代码
# 测试查询
response = chain.run("宫崎骏执导了哪些电影?")
print(response)
python 复制代码
# 测试查询
response = chain.run("评分最高的前5部电影是什么?")
print(response)
python 复制代码
# 测试查询
response = chain.run("我想看一部战争与爱情相关的评分较高的电影有什么推荐?")
print(response)

9、实现电影推荐

通过修改查询模板,添加相关的说明。

python 复制代码
# 修改Cypher查询模板,添加推荐相关的说明
CYPHER_GENERATION_TEMPLATE = """任务: 生成查询图数据库的Cypher语句。
说明:
数据库中的关系模式如下:
- (Person)-[:DIRECTED]->(Movie) 表示导演关系
- (Person)-[:ACTED_IN]->(Movie) 表示演员关系
- (Person)-[:WROTE]->(Movie) 表示编剧关系
Movie节点的属性包括:
- name: 电影名称
- type: 电影类型
- rating: 豆瓣评分
- plot: 剧情简介

请根据用户的偏好生成合适的推荐查询。

问题是:
{question}"""

# 创建新的QA链
chain = GraphCypherQAChain.from_llm(
    llm, 
    graph=graph, 
    verbose=True, 
    allow_dangerous_requests=True,
    cypher_prompt=PromptTemplate(
        input_variables=["question"], 
        template=CYPHER_GENERATION_TEMPLATE
    )
)

# 测试各种推荐场景
recommendation_questions = [
    # 基于类型推荐
    "请推荐一些好看的爱情片",
    "我想看评分高的动作片",
    
    # 基于导演推荐
    "我喜欢宫崎骏的作品风格,请推荐类似的动画电影",
    "我看过张艺谋的《英雄》很喜欢,有类似风格的电影推荐吗",
    
    # 基于演员推荐
    "我很喜欢周星驰的喜剧电影,请推荐类似的",
    "我是汤姆·汉克斯的粉丝,想看他的经典作品",
    
    # 基于评分推荐
    "请推荐一些豆瓣评分9分以上的经典电影",
    "最近有什么好评的新电影推荐吗",
    
    # 组合条件推荐
    "想看一部评分高的爱情片,最好是90年代的经典",
    "请推荐一些适合全家观看的动画电影"
]

# 测试推荐
for question in recommendation_questions[:3]:  # 测试前3个问题
    print(f"\n问题:{question}")
    try:
        response = chain.run(question)
        print(f"推荐:{response}")
    except Exception as e:
        print(f"查询出错:{str(e)}")
    print("-" * 50)

改进与反思:

解决方法:

python 复制代码
import re
def normalize_movie_name(name):
    """标准化电影名称,处理各种特殊情况"""
    # 常见的电影名称映射
    name_mapping = {
        "Coco": "寻梦环游记",
        "小森林 夏秋篇 リトル・フォレスト 夏・秋": "小森林 夏秋篇",
        "城王之李 City Lights": "城市之光",
        "Rain Man": "雨人",
        "The Notebook": "恋恋笔记本",
        "Lord of War": "战争之王",
        "Waterloo Bridge": "魂断蓝桥",
        "黄崋小口橡小窗杀": "黄金三镖客",
        "芦苇镇": "芦苇镇"
    }
    
    if name in name_mapping:
        return name_mapping[name]
    return name

def parse_movie_attributes(doc):
    """从文档中解析电影属性"""
    movie = {}
    
    # 使用正则表达式提取各个属性
    patterns = {
        'name': r'电影名称:\s*(.*?)(?=\n|$)',
        'director': r'导演:\s*(.*?)(?=\n|$)',
        'writer': r'编剧:\s*(.*?)(?=\n|$)',
        'actors': r'主演:\s*(.*?)(?=\n|$)',
        'genres': r'类型:\s*(.*?)(?=\n|$)',
        'country': r'制片国家/地区:\s*(.*?)(?=\n|$)',
        'language': r'语言:\s*(.*?)(?=\n|$)',
        'release_date': r'上映日期:\s*(.*?)(?=\n|$)',
        'duration': r'片长:\s*(\d+).*?(?=\n|$)',
        'rating': r'豆瓣评分:\s*([\d.]+)(?=\n|$)',
        'plot': r'剧情简介:\s*(.*?)(?=\n\n|$)'
    }
    
    for key, pattern in patterns.items():
        match = re.search(pattern, doc, re.DOTALL)
        if match:
            value = match.group(1).strip()
            if key in ['director', 'writer', 'actors', 'genres']:
                # 处理列表类型的属性
                value = [v.strip() for v in value.split('/')]
            elif key == 'duration':
                # 转换为整数
                value = int(value)
            elif key == 'rating':
                # 转换为浮点数
                value = float(value)
            elif key == 'release_date':
                # 提取年份
                year_match = re.search(r'(\d{4})', value)
                if year_match:
                    value = int(year_match.group(1))
                else:
                    value = None
            movie[key] = value
    
    return movie

def update_movie_attributes(documents, graph):
    """更新数据库中电影的属性"""
    success_count = 0
    error_count = 0
    skipped_count = 0
    
    # 获取数据库中现有的电影名称
    existing_movies = graph.query("""
        MATCH (m:Movie)
        RETURN m.name as name
    """)
    existing_names = {record['name'] for record in existing_movies}
    print(f"数据库中现有电影数量:{len(existing_names)}")
    
    with tqdm(total=len(documents), desc="更新电影属性") as pbar:
        for doc in documents:
            try:
                # 确保从doc对象中正确获取文本内容
                content = doc.page_content if hasattr(doc, 'page_content') else str(doc)
                
                # 解析文档中的电影信息
                movie = parse_movie_attributes(content)
                if not movie or 'name' not in movie:
                    skipped_count += 1
                    pbar.update(1)
                    continue
                
                # 标准化电影名称
                movie_name = movie['name'].split()[0]  # 只取中文名部分
                db_name = None
                
                # 尝试直接匹配
                if movie_name in existing_names:
                    db_name = movie_name
                else:
                    # 尝试标准化后匹配
                    normalized_name = normalize_movie_name(movie_name)
                    if normalized_name in existing_names:
                        db_name = normalized_name
                
                # 只处理找到匹配的电影
                if db_name:
                    # 处理类型列表
                    genres = movie.get('genres', [])
                    if isinstance(genres, str):
                        genres = [g.strip() for g in genres.split('/')]
                    
                    # 更新电影属性
                    update_query = """
                    MATCH (m:Movie {name: $db_name})
                    SET 
                        m.rating = toFloat($rating),
                        m.year = toInteger($year),
                        m.duration = toInteger($duration),
                        m.country = $country,
                        m.language = $language,
                        m.genres = $genres,
                        m.plot = $plot
                    """
                    
                    params = {
                        'db_name': db_name,
                        'rating': movie.get('rating'),
                        'year': movie.get('release_date'),
                        'duration': movie.get('duration'),
                        'country': movie.get('country'),
                        'language': movie.get('language'),
                        'genres': genres,
                        'plot': movie.get('plot', '')
                    }
                    
                    # 打印调试信息
                    print(f"\n正在更新电影: {db_name}")
                    print(f"评分: {params['rating']}")
                    print(f"类型: {params['genres']}")
                    
                    graph.query(update_query, params)
                    success_count += 1
                else:
                    skipped_count += 1
                
            except Exception as e:
                print(f"处理电影时出错: {str(e)}")
                error_count += 1
            
            pbar.update(1)
    
    print(f"\n更新完成!")
    print(f"成功更新: {success_count}")
    print(f"更新失败: {error_count}")
    print(f"跳过处理: {skipped_count}")

最终结果:

设置QA链,根据电影内容进行推荐。

python 复制代码
# 修改Cypher查询模板,添加更多维度的推荐说明
CYPHER_GENERATION_TEMPLATE = """任务: 根据用户需求生成Neo4j查询语句,实现电影推荐。

数据库模式说明:
1. 节点类型:
- Movie: 电影节点
- Person: 人物节点(导演/演员/编剧)

2. 关系类型:
- (Person)-[:DIRECTED]->(Movie): 导演关系
- (Person)-[:ACTED_IN]->(Movie): 演员关系  
- (Person)-[:WROTE]->(Movie): 编剧关系

3. Movie节点属性:
- name: 电影名称
- rating: 豆瓣评分(float)
- year: 上映年份(int) 
- duration: 片长(int,分钟)
- country: 制片国家
- language: 语言
- genres: 电影类型(list)
- plot: 剧情简介

推荐维度说明:
1. 基于类型推荐: 使用genres属性匹配
2. 基于评分推荐: 使用rating属性筛选
3. 基于年代推荐: 使用year属性筛选
4. 基于导演/演员推荐: 通过关系查询
5. 基于国家/语言推荐: 使用country/language属性
6. 多维度组合推荐: 结合多个属性

用户问题:
{question}

请生成合适的Cypher查询语句,注意:
1. 优先按评分降序排序
2. 建议返回电影名称、评分、年份、类型等关键信息
3. 适当限制返回结果数量(如LIMIT 5)
4. 确保ORDER BY在LIMIT之前
"""

# 创建新的QA链
chain = GraphCypherQAChain.from_llm(
    llm,
    graph=graph,
    verbose=True,
    allow_dangerous_requests=True,  # 添加这个参数来确认安全风险
    cypher_prompt=PromptTemplate(
        input_variables=["question"],
        template=CYPHER_GENERATION_TEMPLATE
    )
)

# # 测试各种推荐场景
# test_questions = [
#     # 单维度推荐
#     "请推荐一些评分超过9分的经典电影",
#     "有什么好看的动作片推荐?",
#     "90年代的经典电影有哪些?",
#     "宫崎骏导演的动画电影推荐",
    
#     # 多维度组合推荐
#     "想看一部评分高的爱情片,最好是90年代的",
#     "有什么适合全家一起看的、评分不错的动画电影?",
#     "请推荐几部张艺谋导演的、评分高于8分的电影",
#     "想看一部评分高的日本电影,最好是剧情片"
# ]

# # 测试推荐效果
# for question in test_questions:
#     print(f"\n问题: {question}")
#     print("-" * 50)
#     try:
#         response = chain.run(question)
#         print(f"推荐结果:\n{response}")
#     except Exception as e:
#         print(f"查询出错: {str(e)}")
#     print("=" * 50)
python 复制代码
chain.run("请推荐一些评分超过9分的经典电影")
python 复制代码
test_queries = [
    "请推荐一些评分超过9分的经典电影",
    "有什么好看的动作片推荐?",
    "90年代的经典电影有哪些?",
    "宫崎骏导演的动画电影推荐"
]

for query in test_queries:
    print(f"\n问题: {query}")
    try:
        response = chain.run(query)
        print(f"回答:\n{response}")
    except Exception as e:
        print(f"错误: {str(e)}")

还是有一些不足,但是回答的正确率已经达到60%以上。

程序文件链接:

通过网盘分享的文件:豆瓣电影知识图谱构建.rar

链接: https://pan.baidu.com/s/1aKIHXtZXCi-FjFLrfW8z6A 提取码: 6bkp

--来自百度网盘超级会员v5的分享

相关推荐
Joeysoda7 分钟前
数据库索引:秋招面试中的经典高频题目 [特殊字符](索引原理/操作/优缺点/B+树)
数据库·sql·学习·mysql·adb·oracle
weixin_307779133 小时前
Apache Iceberg数据湖技术在海量实时数据处理、实时特征工程和模型训练的应用技术方案和具体实施步骤及代码
大数据·人工智能·语言模型·音视频
山海青风5 小时前
OpenAI 实战进阶教程 - 第七节: 与数据库集成 - 生成 SQL 查询与优化
数据库·人工智能·python·sql
_李白_5 小时前
深入解析 Redis AOF 机制:持久化原理、重写优化与 COW 影响
数据库·redis·缓存
pedestrian_h5 小时前
mysql操作语句与事务
数据库·mysql
AquaPluto5 小时前
MySQL锁详解
数据库·mysql·
lizhixiong25 小时前
使用C#开发一款通用数据库管理工具
数据库
ZWZhangYu6 小时前
【实践案例】基于大语言模型的海龟汤游戏
人工智能·游戏·语言模型
NPE~6 小时前
[漏洞篇]SQL注入漏洞详解
数据库·安全·渗透测试·教程·漏洞·sql注入