第四章 基于本地部署的大语言模型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的分享

相关推荐
J_Xiong011721 分钟前
【LLMs篇】14:扩散语言模型的理论优势与局限性
人工智能·语言模型·自然语言处理
cookqq25 分钟前
mongodb源码分析session异步接受asyncSourceMessage()客户端流变Message对象
数据库·sql·mongodb·nosql
呼拉拉呼拉37 分钟前
Redis故障转移
数据库·redis·缓存·高可用架构
什么都想学的阿超40 分钟前
【Redis系列 04】Redis高可用架构实战:主从复制与哨兵模式从零到生产
数据库·redis·架构
pp-周子晗(努力赶上课程进度版)1 小时前
【MySQL】视图、用户管理、MySQL使用C\C++连接
数据库·mysql
斯特凡今天也很帅1 小时前
clickhouse常用语句汇总——持续更新中
数据库·sql·clickhouse
超级小忍2 小时前
如何配置 MySQL 允许远程连接
数据库·mysql·adb
吹牛不交税3 小时前
sqlsugar WhereIF条件的大于等于和等于查出来的坑
数据库·mysql
盛寒3 小时前
N元语言模型 —— 一文讲懂!!!
人工智能·语言模型·自然语言处理
hshpy3 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端