ARTICLE DETAIL

资讯详情

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

从PPT到知识图谱:大模型+Neo4j可追溯问答系统

从PPT到知识图谱:大模型+Neo4j可追溯问答系统 简介这份PPT围绕企业级知识图谱与大模型融合实践展开面向人工智能算法、知识工程、数据治理及企业架构从业者也可供研究者与学生梳理技术脉络。内容从知识图谱与大模型的定义、发展历程与核心特征切入比较两者在结构化语义推理与自然语言模式识别上的优势与局限并讨论落地瓶颈、技术演化、技术互补和知识库建设等融合路径同时给出融合系统评测体系及11个领域实践案例帮助读者判断融合可行性、收益与技术难点。整包为1个pptx文件约9.79MB章节化页面便于直接用于内部汇报、技术选型与培训讲解。当前已有184人学习适合需要快速建立知识图谱与大模型融合整体认知、了解企业级应用场景与工程化挑战的读者参考。1. 一份 120 页的 PPT为什么值得拆成知识图谱再交给大模型把 120 页的 PPT 整份丢给大模型前 20 页答得挺好翻到第 80 页就开始编——这是做内部资料问答时最常见的翻车现场。按页切块的向量检索能解决「找得到」解决不了「连得起来」一个指标口径在第 12 页定义、第 47 页被引用、第 88 页做了修订三处散在三个 chunk 里相似度排序很难把它们拼成一条完整链路。知识图谱补的正是这段关系大模型负责把关系翻译成人话并且指回具体页码。这套做法适合手上有一批结构化程度不高的 .pptx、想搭可追溯又可增量更新的问答系统、又不愿意一上来就上重型平台的工程师。下面按解析、抽取、neo4j 构建知识图谱、融合问答的顺序把能跑通的最小路径走一遍。2. PPT 解析与知识图谱本体设计从 pptx 文件到节点和边这一章决定后面所有环节的天花板。解析层丢掉的字段抽取层再怎么调 prompt 也补不回来本体设计错了图库里堆的只是一堆没有查询价值的孤点。2.1 用 python-pptx 抽取幻灯片正文、表格与演讲者备注pptx 本质是个 zip 包里面的 slideN.xml 已经带了形状层级不需要先转 PDF 再识别。python-pptx 是最省事的入口重点是别只抽正文标题占位符决定这页在讲什么表格里的数字往往才是用户真正要问的演讲者备注常写着正文没写的口径解释召回价值比正文还高。from pptx import Presentation def extract_slide(slide, idx): chunks [] for shape in slide.shapes: # 标题单独打标它是后面 chunk 加权的依据 if shape.has_text_frame and shape.text_frame.text.strip(): kind title if shape slide.shapes.title else body chunks.append({kind: kind, text: shape.text_frame.text.strip()}) # 表格逐行拼成文本保留行列语义别整块 json 化 if shape.has_table: for row in shape.table.rows: cells [c.text.strip() for c in row.cells] chunks.append({kind: table, text: | .join(cells)}) # 备注常常是正文没写的解释独立成 chunk if slide.has_notes_slide: note slide.notes_slide.notes_text_frame.text.strip() if note: chunks.append({kind: note, text: note}) return {slide_no: idx 1, chunks: chunks} prs Presentation(year-report.pptx) pages [extract_slide(s, i) for i, s in enumerate(prs.slides)]kind字段区分 title/body/table/note是后面给 chunk 加权排序的依据标题命中权重通常给到 1.5 至 2 倍。slide_no从 1 开始而不是 0是为了回答里直接说「第 47 页」时不用换算。备注单独成 chunk 而不拼进正文因为备注多是讲稿口吻混进去会拉低向量检索的语义集中度。跑完顺手看一眼slide.shapes.title is None的页这类多为纯图或纯表页要走 2.4 的分流逻辑。2.2 知识图谱本体设计幻灯片、章节、概念、指标四类节点本体不用一次设计到位但标签和关系类型的边界要提前定死否则同一批数据会出现 Concept 和 Term 两套节点查询时得写两遍 Cypher。做 PPT 场景四类节点基本够用。节点标签关键属性来源主要用途Slideslide_no、title、hash解析层直接产出定位与引用回跳Sectionname、order标题层级加人工校对章节级聚合问答Conceptname、alias、definition大模型抽取概念解释、关系推理Metricname、value、unit、period大模型加正则数值核对、跨页修订追踪关系类型同样要收敛枚举值越少text2cypher 的生成准确率越高。关系类型方向说明NEXTSlide→Slide保证顺序回跳原文用BELONGS_TOSlide→Section章节归属MENTIONSSlide→Concept弱关系作召回入口DEFINESSlide→Concept定义关系权重高REVISESSlide→Metric修订关系冲突消解用DEPENDS_ONConcept→Concept概念依赖支撑两跳推理提示关系类型宁可少不要多。枚举值超过 10 个之后大模型生成 Cypher 时挑错类型的概率会明显上升。2.3 切块粒度选型按页、按段落还是按语义单元粒度选择直接决定图谱节点和向量 chunk 能不能对齐。对齐了向量召回的 chunk 可以直接顺着 MENTIONS 关系扩出子图对不齐两套体系就得各查各的最后靠大模型硬拼。粒度chunk 数120 页估算召回准确率上下文完整性适用阶段按页约 120中差两小时内验证链路按段落约 600高中主流选择按语义单元约 300高好与图谱节点对齐常见做法是按段落切同时把每个 chunk 挂上slide_no和section两个属性。这样向量检索命中后能用(c:Chunk)-[:FROM]-(s:Slide)一步跳到图上的位置再按关系扩一跳。语义单元的合并规则可以简单点同一页内相邻段落如果标题层级相同且中间没有表格就合成一个 chunk。2.4 图片型 PPT 的兜底多模态大模型与 OCR 分流真实汇报材料里总有几页是截图、架构图、流程示意。这类页在解析层抽出来几乎是空的硬塞给抽取模型只会产出幻觉。分流规则按文本密度判断一页抽出的正文少于 30 字但有图片就送去多模态大模型做图像描述把描述文本当正文用同时在节点上标sourcemultimodal检索时可选择降权。扫描件页面则先做 OCR 再做抽取顺序不要颠倒OCR 的错误字符会直接污染概念名后面去重很难救回来。3. 大模型知识抽取schema 约束下的实体与关系产出抽取这一步的目标不是让大模型「读懂 PPT」而是让它按固定格式吐出结构。放开格式后面每一环都要写兼容代码。3.1 抽取 prompt 与 JSON schema 的设计schema 的关键是给关系类型加 enum。一旦枚举收敛模型输出就越界不了下游不用写类型映射表。抽取单位建议按单页调用不按整份调用单页上下文短准确率高失败重试的成本也低。{ type: object, properties: { concepts: { type: array, items: { type: object, properties: { name: {type: string}, definition: {type: string}, confidence: {type: number} }, required: [name, definition, confidence] } }, metrics: { type: array, items: { type: object, properties: { name: {type: string}, value: {type: string}, unit: {type: string}, confidence: {type: number} }, required: [name, value, confidence] } }, relations: { type: array, items: { type: object, properties: { head: {type: string}, relation: { type: string, enum: [DEFINES, REVISES, DEPENDS_ON, MENTIONS] }, tail: {type: string} }, required: [head, relation, tail] } } }, required: [concepts, metrics, relations] }system prompt 里要写清三件事概念名统一用原文表述、不做同义改写数值必须连带单位一起抽找不到关系的概念不要硬造。第三句最容易被忽略但它是幻觉关系的主要来源。3.2 用 vLLM 部署大模型并批量跑抽取任务批量抽取对吞吐敏感本地部署比调云端接口更划算。vLLM 的 OpenAI 兼容接口可以直接被 openai 客户端调用切换成本几乎为零。# 单卡 24G 显存跑 7B/8B 级别抽取模型的一组常见起点 vllm serve /models/Qwen2.5-7B-Instruct \ --served-model-name extractor \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 32 \ --port 8000--max-model-len给到 8192 足够吞下单页内容加 schema开太大只会白白吃掉 KV cache。--gpu-memory-utilization 0.90是常见的本地部署配置起点留 10% 给显存碎片。--max-num-seqs就是并发请求上限调到 32 通常能打满吞吐再往上要盯显存而不是盯 CPU。import json from openai import OpenAI client OpenAI(base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY) SCHEMA json.load(open(extract_schema.json, encodingutf-8)) def extract(chunk_text, slide_no): resp client.chat.completions.create( modelextractor, temperature0, # 抽取任务要确定性不要发散 max_tokens2048, response_format{ # schema 约束直接拿结构化结果 type: json_schema, json_schema: {name: slide_kg, schema: SCHEMA, strict: True}, }, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: f幻灯片页码{slide_no}\n内容\n{chunk_text}}, ], ) return json.loads(resp.choices[0].message.content)temperature0是为了让同一页重复抽取结果稳定方便做 diff。strict: True会强制模型输出严格贴合 schema代价是首次请求有一次语法编译开销可以接受。返回结果里confidence低于 0.6 的条目不要直接入库丢进待审队列。3.3 结果校验、去重与人工抽检的参数抽取出来的概念名有一大半是重复的只是表述不同。别指望模型自己归一写规则更可靠全角转半角、去首尾空白、英文统一小写、去掉「的」「相关」这类虚词后再比。参数建议值说明temperature0抽取任务不需要发散max_tokens2048单页抽取足够置信度阈值0.6低于此值进人工队列抽检比例5%每批随机抽检查关系方向去重距离阈值0.92编辑距离加别名表双保险抽检重点看关系方向尤其是 DEFINES 和 REVISES模型很容易把「第 88 页修订第 12 页口径」的箭头指反这类错误在图上表现出来就是查询结果反向。3.4 抽取准确率不够时的微调路线当专有名词密集、行业缩写多prompt 调到极限也只有七成准确率时就该考虑大模型微调。路线是先用现有抽取结果加人工修正攒出两三千条「页面文本→结构化 JSON」的样本再用 LLaMA-Factory 这类一站式微调平台跑 LoRA把训练语料和验证集按 9:1 划开。数据量不到一千条时不要动手LoRA 也救不了样本不足先扩数据更划算。微调后的模型替换掉 vLLM 上原来的权重接口和调用代码不用改。4. 用 Neo4j 构建知识图谱约束、批量导入与混合索引图谱建得好不好一半看导入脚本。约束没建就导数据重复跑两次就得到双份节点后面清理比重建更费时间。4.1 先建约束和唯一键避免重复节点// 幻灯片按页码唯一概念按归一化后的名字唯一 CREATE CONSTRAINT slide_no IF NOT EXISTS FOR (s:Slide) REQUIRE s.slide_no IS UNIQUE; CREATE CONSTRAINT concept_name IF NOT EXISTS FOR (c:Concept) REQUIRE c.name IS UNIQUE; // 建完约束再建索引导入速度差异很明显 CREATE INDEX chunk_slide IF NOT EXISTS FOR (c:Chunk) ON (c.slide_no);约束会在写入时强制判重配合 MERGE 使用重复导入同一份 CSV 不会产生脏节点。概念名唯一这一条要在 3.3 的归一化之后生效否则「召回率」和「召回率 」会变成两个节点。4.2 LOAD CSV 批量导入节点与关系// 节点MERGE 命中就更新不命中就新建 LOAD CSV WITH HEADERS FROM file:///concepts.csv AS row MERGE (c:Concept {name: row.name}) SET c.definition row.definition, c.updated_at datetime(); // 关系把概念挂到具体页码保留来源和置信度 LOAD CSV WITH HEADERS FROM file:///slide_concept.csv AS row MATCH (s:Slide {slide_no: toInteger(row.slide_no)}) MATCH (c:Concept {name: row.name}) MERGE (s)-[r:MENTIONS]-(c) SET r.confidence toFloat(row.confidence);MERGE而不是CREATE是关键它保证了幂等。关系的confidence属性一定要留检索时可以按阈值过滤掉低质量边比事后删节点灵活。导入前把 CSV 里的空行和被引号包住的换行检查一遍LOAD CSV 对这类问题很敏感报错信息也不够直观。4.3 向量索引与全文索引共存的建法概念问答靠图原文细节问答还得靠向量。两个索引同时挂在 Chunk 节点上一次检索可以拿到两路结果再做融合排序。// 维度要和 embedding 模型输出一致换模型必须重建索引 CREATE VECTOR INDEX chunk_embedding IF NOT EXISTS FOR (n:Chunk) ON (n.embedding) OPTIONS {indexConfig: { vector.dimensions: 1024, vector.similarity_function: cosine }}; CREATE FULLTEXT INDEX chunk_text IF NOT EXISTS FOR (n:Chunk) ON EACH [n.text];vector.dimensions写错是最常见的坑不同 embedding 模型输出维度不一致索引建完不会报错但查询结果永远是空的。全文索引则用来兜住专有名词和缩写的精确匹配向量对这种词常常召回不稳。4.4 检索语句模板两跳子图怎么查// 以概念为锚点取两跳子图限制条数避免上下文爆炸 MATCH (c:Concept {name: $name}) OPTIONAL MATCH (c)-[r:DEPENDS_ON|REVISES*1..2]-(n) OPTIONAL MATCH (s:Slide)-[:MENTIONS|DEFINES]-(c) RETURN c.name AS anchor, type(r) AS rel, labels(n)[0] AS kind, coalesce(n.name, n.title) AS target, s.slide_no AS source_slide LIMIT 30;*1..2的两跳范围是实践下来的甜点区一跳信息太少三跳开始出现大量无关节点还会让返回结果撑爆 prompt。LIMIT 30是硬保险子图节点数超过 30 之后大模型回答质量反而下降噪声盖过了有效信息。5. 大模型与知识图谱融合问答子图召回与 text2cypher 的落地链路融合的核心不是把两路结果都塞进 prompt而是先判断这个问题该走哪条路再决定给大模型看什么。5.1 路由哪些问题查图谱哪些问题走向量判断依据可以写得很朴素问题里出现概念名或指标名走图谱出现「第几页讲了什么」「原文怎么写的」走向量两者都有先图后向量。更稳一点的做法是先用全文索引在概念名上做一次匹配命中就走图谱分支没命中再走向量。这一步用一个轻量规则比再调一次大模型便宜得多延迟也能压住。5.2 text2cypher 生成与只读白名单校验让大模型直接生成 Cypher 必须加护栏只读校验是最低要求。import re from neo4j import GraphDatabase FORBIDDEN re.compile(r\b(CREATE|MERGE|DELETE|DETACH|SET|DROP|LOAD\sCSV)\b, re.I) def run_readonly(driver, cypher, paramsNone): if FORBIDDEN.search(cypher): raise ValueError(生成语句包含写操作拒绝执行) if LIMIT not in cypher.upper(): cypher cypher.rstrip(;\n ) LIMIT 50 # 兜底限流 with driver.session() as session: return [r.data() for r in session.run(cypher, params or {})]正则拦的是关键词不是语法所以还要给数据库账号配只读权限两层一起上。自动补LIMIT这一手看着糙但能挡住大部分「取全部节点」的失控查询。生成失败时降级到 4.4 的固定模板用问题里的名词当锚点比直接报错友好。5.3 上下文拼装把子图和三处页码一起塞进 prompt拼装顺序建议固定先给子图的三元组列表再给命中的原文 chunk最后给页码清单。三元组用「A -关系- B第 N 页」这种紧凑格式比 JSON 省 token 也好读。prompt 里明确要求回答时标注页码没有依据就说不知道。第 47 页引用、第 88 页修订这类跨页问题正是靠三元组里带的页码让模型串起来的纯向量检索做不到这一点。5.4 并发请求下的缓存与降级问答系统上线后真正的压力在大模型的并发请求上。缓存分两层子图查询结果按「锚点概念 关系集合」做短时缓存几分钟就够因为 PPT 内容基本不变回答结果按「归一化问题 子图哈希」缓存同一批人问同一个指标口径的概率比想象中高。降级顺序也要提前定大模型超时就返回子图原始三元组加页码让人自己看单页抽取服务挂了就只走向量分支。留意大模型投毒测试那类场景如果知识库来源包含外部上传的 PPT抽取结果入库前要过一遍白名单词表避免有人往备注里塞恶意指令。6. 效果验证与增量更新让知识图谱跟着 PPT 版本走上线之后真正费时间的是评测和版本迭代这两件事不做图谱三个月就废了。6.1 评测集构造与三类可量化指标评测集不用大五十到一百条就够但要从真实提问里捞。构造方法是先用系统跑一遍日志里的高频问题把回答错的挑出来人工标注正确答案和依据页码。指标计算方式目标区间召回命中率正确页码出现在召回结果中85% 以上答案准确率人工判定回答与原文一致75% 以上引用正确率标注页码确实包含该结论90% 以上幻觉率无依据却给出结论的比例5% 以下引用正确率最容易被忽略但它决定了用户敢不敢信这套系统。评测时把 generate 参数固定住否则同一批问题两次跑出来的分数没法比。6.2 增量更新只重抽哈希变化的幻灯片PPT 改版时全量重建图谱浪费太大用哈希做差集即可。解析层给每页算一个内容哈希写进 Slide 节点的hash属性下次导入前先算新哈希和新旧值比对只把变化页送去抽取。# 导出当前库里的页码与哈希和最新解析结果 diff python diff_slides.py --pptx year-report.pptx --out changed.json # 只对 changed.json 里的页码重跑抽取和导入 python run_extract.py --slides changed.json --write关系节点要跟着一起处理变化页原来的 MENTIONS 和 DEFINES 边先删再建其余页的边不动。这样一次改版通常只有十几页需要重抽抽取成本能压到全量重建的百分之几。哈希记得只对正文、表格、备注三类文本计算别把幻灯片编号或修改时间算进去否则每次保存都会全量触发。本文还有配套的精品资源点击获取
返回列表