简介:这份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 场景,四类节点基本够用。
| 节点标签 | 关键属性 | 来源 | 主要用途 |
|---|---|---|---|
| Slide | slide_no、title、hash | 解析层直接产出 | 定位与引用回跳 |
| Section | name、order | 标题层级加人工校对 | 章节级聚合问答 |
| Concept | name、alias、definition | 大模型抽取 | 概念解释、关系推理 |
| Metric | name、value、unit、period | 大模型加正则 | 数值核对、跨页修订追踪 |
关系类型同样要收敛,枚举值越少,text2cypher 的生成准确率越高。
| 关系类型 | 方向 | 说明 |
|---|---|---|
| NEXT | Slide→Slide | 保证顺序,回跳原文用 |
| BELONGS_TO | Slide→Section | 章节归属 |
| MENTIONS | Slide→Concept | 弱关系,作召回入口 |
| DEFINES | Slide→Concept | 定义关系,权重高 |
| REVISES | Slide→Metric | 修订关系,冲突消解用 |
| DEPENDS_ON | Concept→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 字但有图片,就送去多模态大模型做图像描述,把描述文本当正文用,同时在节点上标source=multimodal,检索时可选择降权。扫描件页面则先做 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_url="http://127.0.0.1:8000/v1", api_key="EMPTY") SCHEMA = json.load(open("extract_schema.json", encoding="utf-8")) def extract(chunk_text, slide_no): resp = client.chat.completions.create( model="extractor", temperature=0, # 抽取任务要确定性,不要发散 max_tokens=2048, 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)temperature=0是为了让同一页重复抽取结果稳定,方便做 diff。strict: True会强制模型输出严格贴合 schema,代价是首次请求有一次语法编译开销,可以接受。返回结果里confidence低于 0.6 的条目不要直接入库,丢进待审队列。
3.3 结果校验、去重与人工抽检的参数
抽取出来的概念名有一大半是重复的,只是表述不同。别指望模型自己归一,写规则更可靠:全角转半角、去首尾空白、英文统一小写、去掉「的」「相关」这类虚词后再比。
| 参数 | 建议值 | 说明 |
|---|---|---|
| temperature | 0 | 抽取任务不需要发散 |
| max_tokens | 2048 | 单页抽取足够 |
| 置信度阈值 | 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\s+CSV)\b", re.I) def run_readonly(driver, cypher, params=None): 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 边先删再建,其余页的边不动。这样一次改版通常只有十几页需要重抽,抽取成本能压到全量重建的百分之几。哈希记得只对正文、表格、备注三类文本计算,别把幻灯片编号或修改时间算进去,否则每次保存都会全量触发。
本文还有配套的精品资源,点击获取