ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OneKE抽取三元组构建Neo4j知识图谱,并接入RAG问答系统实践

OneKE抽取三元组构建Neo4j知识图谱,并接入RAG问答系统实践 简介一份基于OneKE模型构建知识图谱并搭建问答系统的Python源码与文档资料面向计算机、人工智能、自动化等相关专业的毕设、课程设计或从业者进阶学习。项目完整覆盖了实体与关系抽取、图谱模式定义、数据转换、Neo4j导入及问答检索模块全部代码均经过调试与测试可稳定运行整体方案清晰答辩评分达98分适合作为高分毕设参考。压缩包共27个文件大小约2.87MB以JSON图谱schema与结果、Python脚本数据处理与转换、CSV三元组结果、Cypher图谱导入语句为主另含PNG流程图与README说明便于理解处理和导入流程。三元组抽取、知识融合等环节均给出可运行代码和示例数据并提供处理前后的JSON对比输出方便逐模块调试已有450人学习下载文档对关键步骤有解释既能满足新人入门也可供进阶者在此基础上二次扩展。1. 用 OneKE 抽三元组建图谱再搭问答系统这条路能不能跑通把 Python 和知识图谱放在一起最常被问的一句话是不手动标注到底能不能把文本变成图谱OneKE 这类统一抽取模型给出的答案是能——它把实体识别和关系抽取当成一个生成式任务喂一段中文原文直接回结构化 SPO 三元组Subject-Predicate-Object主语-谓语-宾语不再需要先训一个 NER 再训一个关系分类器。这份基于 OneKE 构建知识图谱并搭建问答系统的 Python 源码加文档走的就是这条路先抽取 SPO再对齐导入 Neo4j最后把图谱接进 RAG 问答链路。我拆完以后觉得它适合两类人一类是毕设和课程设计要快速产出可演示系统的学生答辩评审拿到 98 分说明整套流程是能闭环的另一类是手里有垂直行业文本想先建图谱再接问答、验证这条路值不值得趟的从业者。下面按我实际复现的顺序把脚本怎么调、参数怎么改、哪些环节最容易翻车说清楚。2. OneKE 做 SPO 抽取SPO_trans.py 把文本变三元组的完整流程这个项目里抽取是整个链路的起点也是决定图谱质量的关键。我这里花最多时间的地方就在SPO_trans.py和它的 schema 配置上先把这部分吃透后面 Cypher 导入和问答才不会白干。2.1 生成式抽取和传统序列标注的差别决定了你要不要换方案以前做中文知识图谱通用做法是拿 BERT 加 CRF 做序列标注输出 BIO 标签把实体边界标出来然后再单独训练一个关系分类器判断实体对之间是什么关系。这一套在垂直领域效果不差但成本摆在那里标注语料要人工整理模型要重新训练换一个领域又要重新标注。对毕设项目或者中小型业务来说时间成本往往比算力成本更敏感。OneKE 走的不是这个路线。它属于生成式信息抽取框架输入是一段文本输出直接是结构化的三元组实体边界和关系类别在一个解码阶段里同时决定。好处有两个第一不需要为每个领域重新训练模型通过修改 schema 提示就能把抽取范围约束到当前业务第二对中文的短语边界、嵌套实体处理得比老式 BIO 模型更稳因为它在解码时能看到全文上下文而不是只盯着一个窗口。在这个资源里SPO_trans.py封装的正是这套调用逻辑。你不用关心权重怎么部署只需要把文本准备好、把 schema 写对然后跑脚本拿结果。项目里带有sample.json、sample.txt和几个输出 json就是给你对照用的。2.2 SPO_trans.py 怎么调用输出长什么样我实际复现时先看的是README.md然后直接跑抽取脚本。核心命令是python SPO_trans.py \ --input sample.json \ --schema SPO_schema.json \ --output SPO_output.json这里三个参数各干一件事--input指向待抽取的样本数据sample.json的结构一般是 JSON 数组每个元素至少带id和text两个字段--schema指向SPO_schema.json它告诉 OneKE 当前任务要抽哪些实体类型和关系类型--output是抽取结果落盘路径也就是SPO_output.json。如果你的机器有多块 GPU我一般会在命令前面加环境变量指定设备否则容易把显存占满CUDA_VISIBLE_DEVICES0 python SPO_trans.py \ --input sample.json \ --schema SPO_schema.json \ --output SPO_output.json跑完之后SPO_output.json里的数据大概是这个结构[ { id: doc-001, triples: [ { subject: 北京智源人工智能研究院, relation: 发布, object: 悟道2.0, confidence: 0.97 }, { subject: 悟道2.0, relation: 参数规模, object: 1.75万亿, confidence: 0.89 } ] } ]注意看输出里每个三元组都带confidence。这个置信度不是拿来做摆设的我建议在写业务代码时设一个阈值比如 0.85 以下的直接过滤能明显减少低质量三元组对图谱的污染。id字段对应的是输入文本的编号方便后续回溯是哪句话抽出来的排查问题的时候特别有用。2.3 sample_trans.py 和 schema 先对齐再开工原始文本往往不是干净的 JSON项目里的sample_trans.py就是干这个的。它的作用是把纯文本转换成SPO_trans.py能识别的结构python sample_trans.py \ --input sample.txt \ --output sample.json \ --split-by-sentence--split-by-sentence这个参数值得多说一句。OneKE 这类生成式模型对输入长度有限制把整篇几千字的文章一次性喂进去后面的三元组大概率被截断。按句子切分后每句单独成一条记录既不超过模型长度限制又保留了语义完整性。切分粒度可以选句子也可以选段落我一般处理知乎类长文时选段落处理新闻短句时选句子。接下来把SPO_schema.json打开它是抽取效果的边界。项目里默认的配置大致长这样{ entity_types: [人物, 公司, 产品, 地点], relation_types: [就职于, 发布, 研发, 投资], language: zh }entity_types限定要抽哪些类别relation_types限定关系类型。如果你做的是医疗领域把实体类型改成“药物、疾病、症状”关系类型改成“适应症、副作用、禁忌”即可。这里有个小经验relation_types 尽量用短字符串不要带空格和标点因为后面它会被直接映射成 Neo4j 里的关系类型名带特殊字符会给你自己埋坑。提示改 schema 时不要把实体类型设得过大。之前我试过一次性抽 20 类实体结果模型把很多名词都当作实体候选抽出来的 SPO 散成一盘沙。控制在 5 类以内准确率要稳得多。3. 三元组到 Neo4j 图谱KG_trans.py 和 Cypher 导入全流程SPO 拿到手只是半成品下一步要把这些三元组变成真正的图结构。这个环节牵涉到实体对齐、去重、关系类型映射还有最关键的两条 Cypher 导入路径。项目里的KG_trans.py和KG_import.cypher是这条链路的纽带。3.1 KG_trans.py 到底做了什么如果直接把SPO_output.json里的三元组一条条导入 Neo4j你会得到一张全是重复节点的图——同一家公司在十篇文档里出现十次就会生成十个节点。KG_trans.py首要任务就是实体对齐和归并。它对subject和object做归一化处理比如去掉繁体转简体的差异、去除空格和全角字符然后给每个实体分配唯一的内部 ID。运行命令如下python KG_trans.py \ --spo SPO_output.json \ --schema KG_schema.json \ --output KG_output.json参数含义--spo指定上一步的抽取结果--schema指定图谱结构定义文件--output输出对齐后的图谱数据。这里注意KG_schema.json和SPO_schema.json是两个不同的文件许多小白会搞混我拆项目时做过一个对比表文件作用什么时候改SPO_schema.json定义 OneKE 抽取的实体类型和关系类型换领域、调整抽取范围时KG_schema.json定义图谱中节点的属性、关系的方向和类型设计图谱结构、决定怎么存时SPO_schema管的是“抽什么”KG_schema管的是“怎么存”。如果SPO_output.json里有关系类型研发但KG_schema.json里定义的关系类型叫研发了两者对不上导入后查询就会落空。KG_trans.py还会同时输出KG_output_handled.json和KG_output.json。我理解handled后缀的文件是经过人工修正或规则修正后的结果当你发现某个实体在同音异形字上出现问题时把修正逻辑加进去再重新生成 handled 版本后续导入以它为基准。3.2 两个 Cypher 导入脚本实际用哪个项目里给了两个导入文件SPO_import.cypher和KG_import.cypher。它们的关系不是二选一而是顺序执行先按 SPO 原始结构建基础节点和关系再根据 KG schema 做属性归并和类型补全。我复现时直接执行第二个文件就够了但如果你改了 schema两个都要同步。看一下SPO_import.cypher里的核心语句结构MERGE (s:Entity {name: 北京智源人工智能研究院}) SET s.category organization MERGE (o:Entity {name: 悟道2.0}) SET o.category product MERGE (s)-[r:发布]-(o) RETURN count(r) AS created_relations这段 Cypher 的关键是MERGE而不是CREATE。CREATE无条件插入跑十遍就会产生十份重复数据MERGE会先匹配存在就跳过不存在才创建保证幂等。SET s.category是在给实体打标签这个category字段的值最好在KG_schema.json里提前定义好。执行导入的方式有两种。如果你用的是 Neo4j Desktop直接把 cypher 文件拖进浏览器执行框就能跑如果是社区版服务端我一般用命令行cat SPO_import.cypher | cypher-shell -u neo4j -p your_password如果遇到认证问题看下 Neo4j 的conf/neo4j.conf里dbms.security.auth_enabled是否被改过。这类坑我放在下一章细说。3.3 导入完成怎么核对别等问答阶段才发现图是空的导入完成后我做的第一件事不是写问答脚本而是跑几条计数查询确认图谱规模MATCH (n:Entity) RETURN n.category AS category, count(*) AS cnt ORDER BY cnt DESC;MATCH ()-[r]-() RETURN type(r) AS relType, count(*) AS cnt ORDER BY cnt DESC;第一条告诉你每个类别下有多少实体第二条告诉你每种关系有多少条边。拿项目里自带的KG_result.csv和SPO_result.csv对照看两边的数字是否在合理区间。如果某个关系类型的边数是零大概率是KG_trans.py的关系映射没覆盖到那种类型。每章的关键行数太少我会让这段代码后面对应一份 check。还有一点所有节点名都用:Entity实际开发中如果你有预案区分“人物”和“公司”可以在 Cypher 里用:Entity统一承载把具体类别放属性里这样做问答检索时写查询更简单。4. 避坑OneKE 建图加问答最容易踩的五个坑这个项目整体能跑通但中间有几个环节的失败率明显高于其他部分。下面五条是我复现和改代码时最容易翻车的地方按照我踩坑的顺序列出来每条都按现象、原因、解决的路子说清楚。4.1 模型跑得慢但没报错以为是死机现象SPO_trans.py跑在 CPU 机器上等了十分钟没有输出控制台也没报错看起来像卡死。原因OneKE 是生成式模型生成三元组时要逐 token 解码CPU 推理速度比 GPU 慢一个数量级。而且默认生成长度可能被设得很大模型在一段长文本上反复计算时间就拖起来了。解决确认代码里有没有传入max_new_tokens参数我一般设置成 512 以内因为一个 SPO 三元组的 token 数通常不超过 100。同时对长文本先按句切分再逐句抽取避免单条输入过长。项目里someshell.sh是一个可参考的封装脚本它把切分、抽取、转换成图谱数据串成一条流水线我建议你打开它看一眼执行顺序再动代码。4.2 同一个实体在 JSON 里两种写法图谱出现双节点现象KG_output.json里“百度公司”和“百度”同时存在导入 Neo4j 后图谱里有大量相近节点查询路径被截断。原因实体对齐逻辑用的是精确字符串匹配没有做别名归一化。“百度公司”和“百度”、“腾讯科技”和“腾讯”在文本里都可能出现OneKE 不会帮你做实体融合它只负责抽取。解决在KG_trans.py里维护一张别名到规范名的映射表比如百度公司、百度在线都映射到百度。我一般会直接用 Python 字典或 CSV 维护这个映射跑完 SPO 之后先过一遍映射再做对齐。4.3 中文实体导入 Neo4j 后变成乱码现象在 Neo4j Browser 里看到实体名显示成??或一堆\u转义序列。原因导入 Cypher 文件时终端和文件的编码不一致。Windows 上默认可能是 GBK而 Neo4j 的 cypher-shell 按 UTF-8 读取字节流解析错位就乱码。解决所有脚本的输出文件统一用 UTF-8 编码写入比如json.dump(..., ensure_asciiFalse)是最容易被忽略的——如果没写ensure_asciiFalseJSON 里的中文会变成\uXXXXCypher 导入时 Neo4j 会当成普通字符串处理。在 Linux 上执行file -i SPO_import.cypher检查编码看到charsetutf-8再继续。4.4 SPO 里的关系类型和 KG_schema 定义不一致现象图谱导入时没有报错但问答阶段查某个关系类型总是空结果。原因SPO_trans.py按SPO_schema.json抽取得到的关系类型是中文“发布”而KG_schema.json里用的是英文PUBLISH。KG_trans.py做关系映射时没匹配上导致漏建了一部分边。解决从项目一开始就固定一套关系类型字符串让SPO_schema.json和KG_schema.json保持一致。如果你需要存中文关系名两个文件都用中文如果存英文两个都用英文。混合使用是最折磨人的。改完KG_schema.json后把KG_output.json里所有 relation 字段扫一遍写个小脚本统计类型分布确认没有漏网之鱼。4.5 问答阶段让大模型直接生成 Cypher多跳问题频繁出错现象想省事让 LLM 把用户问题直接转换成 Cypher结果生成出来的查询语句要么语法报错、要么把节点名写错尤其在实体名称含括号和空格时十条里错四条。原因生成式模型对 Cypher 语法的精确性要求很高一对括号、一个引号错误就会导致整条查询失败。多跳问题上模型还要自己推断出正确的路径模式难度更大。解决不要拿 NL-to-Cypher 作为主线方案。改成“先实体链接再子图检索”的路线把检索到的三元组拼成文本喂回大模型由大模型做自然语言生成。核心是让模型理解图数据而不是让模型生成图查询。5. 从图谱到 RAG 问答用子图检索搭建可以落地的问答链路图谱建好之后问答系统的设计决定了你这个项目是“能演示”还是“能实战”。我拆到的这套方案没有直接走“先向量化文档、再相似度检索”的老路而是把知识图谱本身作为检索源这在这类场景里通常更稳。5.1 图谱问答和普通 RAG 的差别在哪普通 RAG 的做法是把文档切块、向量化、存进向量库用户提问时召回相似度最高的几个块拼进 Prompt 让大模型回答。这个方案适合开放域问答但在“多跳问题”上表现很差——“谁投资了开发某个芯片的公司”这种问题答案分散在两三条边上单独的文本块里根本没有完整路径。知识图谱问答的优势在于关系路径是显式存好的。从实体 A 出发沿着关系走到实体 B再走到实体 C每一步都是结构化的。所以链路设计核心变成了先定位问题里的实体再环绕实体检索子图最后把子图序列化成文本交给生成模型。对比一下方案适用场景弱项向量检索开放域、答案分散在段落中多跳推理、关系判断基本靠蒙图谱子图检索结构化、关系明确、多跳问题实体识别失败时直接断链实际项目中这两者可以组合使用但主链路应该以图谱子图检索为主向量检索只是兜底。5.2 第一步实体链接先定位问题里的起点实体链接的作用是把自然语言问题中的名词映射到图谱里的实体节点。在这个资源里常见做法是用 jieba 分词后做词典匹配import jieba ENTITY_INDEX { 智源: 北京智源人工智能研究院, 北京智源: 北京智源人工智能研究院, 悟道: 悟道2.0 } def link_entities(question: str): results [] for token in jieba.cut(question.strip()): if token in ENTITY_INDEX: canonical ENTITY_INDEX[token] if canonical not in results: results.append(canonical) return results这段代码里ENTITY_INDEX是别名到规范实体名的映射函数把所有命中的实体名返回。注意做了一次去重因为一个别名可能对应多个 token 片段。这一步看似简单但它是整个问答链路里最容易漏答案的地方——分词工具对多字词识别不准会在“北京智源人工智能研究院”这种长实体上拆碎。我通常会在这里再加一层先从题目中抽取候选名词再和 Neo4j 里的实体名做最长匹配优先命中最长的那个。5.3 第二步子图检索控制展开深度和边数实体定位之后从该实体出发往外展开。这里的核心参数是depth和limit直接决定查询耗时和结果质量from py2neo import Graph graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) def fetch_subgraph(entity: str, depth: int 2, limit: int 30): cypher f MATCH (n:Entity {{name: $name}})-[rels*1..{depth}]-(m:Entity) RETURN n, rels, m LIMIT {limit} records graph.run(cypher, nameentity).data() return recordsdepth2表示从节点出发最多走两步能覆盖一对多和多跳关系limit30是结果上限防止中间节点太稠密时返回几千条记录把 Prompt 撑爆。如果图谱比较大我一般还会加上按关系类型过滤只保留与问题关键词相关的关系比如问题里出现“投资”就只展开[:投资]这条边。这一步有一层转换技巧Neo4j 返回的数据结构比较原始需要把每条记录转换成易读的三元组文本再拼进 Prompt。5.4 第三步把子图序列化成文本交给大模型生成回答大模型不能直接读图数据需要把图结构序列化成自然语言。我推荐的格式是每行一条三元组def triples_to_text(records) - str: lines [] for rec in records: n rec.get(n) m rec.get(m) rels rec.get(rels) if not rels: continue rel rels[-1] # 取路径上最后一条边 rel_type type(rel).__name__ s_name n.get(name, ) if isinstance(n, dict) else n.get(name) o_name m.get(name, ) if isinstance(m, dict) else m.get(name) lines.append(f{s_name} {rel_type} {o_name}) return \n.join(lines)然后把它拼进 Promptprompt f你是知识图谱问答助手。 背景知识知识图谱三元组 {triples_text} 用户问题{question} 请仅根据背景知识回答不要编造事实。如果背景知识不足以回答请指出信息不足。这个 Prompt 结构有三个要点一是明确告诉模型“仅根据背景知识回答”减少幻觉二是要求“信息不足就直说”避免模型强行作答三是三元组按行排列让模型更容易捕捉关系结构。实测下来这个方案比让大模型自己生成 Cypher 的稳定性高很多“谁-关系-谁”的格式对模型非常友好。如果问题中命中了多个实体把每个实体对应的子图记录合并后统一去重再一起拼进 Prompt。这一点上代码很简单就不单独贴了核心是在fetch_subgraph外面套一层循环。6. 验证与进阶让 OneKE 建图和问答项目换成自己的数据整个项目复现完后最重要的事情是验证链路然后把它搬到自己的数据上。不要一开始就拿全量业务数据跑先拿项目自带的样例数据打通再逐步替换能省掉大量排错时间。6.1 用项目自带数据做验证压缩包里有sample.txt、sample.json、SPO_test.json、KG_test.json等测试文件验证顺序我建议严格按链路走先跑sample_trans.py把sample.txt转成sample.json此时可以打开sample.json检查文本字段是否完整、是否有空行再跑SPO_trans.py得到SPO_output.json和SPO_test.json对比看三元组数量和实体名是否有明显缺漏接着跑KG_trans.py生成KG_output_handled.json对照KG_result.csv检查实体数量和关系数量最后执行KG_import.cypher导入 Neo4j在浏览器里跑MATCH (n:Entity) RETURN count(n)确认节点数再和图graph.png、example_1.png对比看结构是否一致。SPO_output_handled.json和KG_output_handled.json这两个文件值得好好利用。它们是已经修正过的版本如果你在跑完原始脚本后得到的结果与 handled 版本差异很大说明你的代码版本或依赖版本与原作者不一致需要回头排查环境问题。我复现时曾因为 Transformers 库版本太新导致抽取结果格式变化后来 pin 了旧版本才对齐。6.2 进阶改造的几个方向验证完之后如果你想把这个项目改造成自己的东西我一般会在三个方向上面改自定义KG_schema.json增加时间属性。原项目里的实体只有name和category如果图谱想表达关系随时间的演化就在节点上加start_time、end_time这类属性并在KG_trans.py里从原始文本中抽取时间信息补进去。升级实体对齐逻辑。从精确匹配升级为别名映射加拼音匹配处理同音字、简繁体差异。这个改造对中文语料尤其重要因为中文里同一个机构的全称、简称、旧称差异很大。把问答链路从“只检索图谱”升级为“图谱加向量混合检索”。图谱子图检索对实体明确的问题效果好但开放域问题容易断链。加一层向量检索兜底当子图为空时退回向量检索能明显提升回答的覆盖范围。有一件事值得每个人养成习惯每改一版 schema 或对齐逻辑都要从头重跑一遍sample_trans.py → SPO_trans.py → KG_trans.py → Cypher 导入这条链路。从那以后我每次拿到新语料建图谱都强制先把 sample 数据完完整整跑一遍再上业务数据哪怕改动再小也不跳过。这个习惯帮我挡掉了很多因为 schema 改了但导入脚本没同步造成的低级事故也让问答环节的排查范围小了很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表