ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI陪练实战:用大模型抽取实体关系,构建可查询的知识图谱

AI陪练实战:用大模型抽取实体关系,构建可查询的知识图谱

今年年初给自己立了个flag:把知识图谱真正建起来。为什么说“真正”?因为在那之前的三个月里,我收藏了几十个教程、看了好几篇科普文章、下载了Neo4j Desktop,甚至连《知识图谱应用实践指南》都翻了一小半,但项目的状态始终停留在“我应该学知识图谱”这个阶段。收藏夹越来越满,动手的次数为零。后来是一个很偶然的机会,把AI拉进来当“学习陪练”,我花了一个周末,从文本里抽实体、抽关系、写进图数据库,做出了一张能查、能看、能自己提问答案的人物关系知识图谱。这篇文章就把这个过程完整记录下来,包括卡住我的原因、怎么用AI把活干起来、踩了哪些坑,以及我测试下来觉得好用的方法和环节。不管你是刚接触图数据库,还是正在纠结要不要学知识图谱,这篇实操记录应该都能帮上点忙。

1. 从“我应该学”到“真正在做”:卡住我的不是资料,是任务

1.1 囤了三个月教程,图谱还是建不起来

我的学习路径其实很典型:先刷到一篇关于知识图谱的文章,觉得这东西很厉害;接着去搜索,发现概念还挺多,实体、关系、属性、本体、RDF、LPG,每个词都认识,连起来就不知道从哪下手;然后收藏了一堆教程,下载了Neo4j Desktop,装到一半卡在环境变量上,心想算了反正也没想好要做什么图。

这个问题现在回头看特别清楚了:我一直在“学知识”,却没有一个具体的“任务”来承载这些知识。就像看了很多游泳教学视频,但从来没下水扑腾过。知识图谱这件事特别吃“动手”,因为它不像SQL,你学会一个SELECT就能马上用起来。一个图谱项目从前到后的链路很长,缺任何一个环节都跑不通。只靠看,永远不会发现真正的问题在哪里。

1.2 AI陪练带来的转机:打破“不会做”的借口

转机来得很简单。某个下午我又打开收藏夹找教程,突然想到:与其继续找下一个人告诉我“知识图谱是什么”,不如把问题交给AI,直接说“帮我设计一个项目方案,把一段文本变成知识图谱”。五分钟内我拿到了一份带步骤的清单,虽然有些步骤当时还看不懂,但至少我知道该做什么了。

紧接着我做了第二个决策:不追求一次到位,先用最小成本把流程跑通。我把这个决策交给AI去拆解,让它给出最少需要几步能完成。结果是:找数据、设计本体结构、让大模型抽实体关系、存入图数据库、查询验证。五步。我后来才知道,这其实就是一个极简版知识图谱构建的完整pipeline。关键不在于步骤多高明,而在于我终于有了可以执行的目标。

2. 先别急着写代码:知识图谱的底层思维

2.1 实体、关系、属性:用一家公司来理解

知识图谱听起来高深,底层逻辑其实特别朴素。我后来给朋友讲的时候用了这么个类比:

想象你在一家公司上班,脑子里有一张“人际关系网”:老张是部门经理,小王是老张的下属,小李和小王在同一个项目组,整个项目组挂在“智慧城市”这个项目下。这些信息天然有结构,而知识图谱就是把这些结构显式地表达出来。

术语对照表在这里:老张、小王、小李、项目组、智慧城市这些参与方叫“实体”(Entity);部门经理、下属、同一个项目组、负责这些连接关系叫“关系”(Relation);每个人还有名字、工号、入职时间,每个项目有预算、周期,这些描述细节叫“属性”(Property)。实体是图里的节点,关系是节点之间的边,属性挂在节点或边上。数据库表适合存“一本花名册”,而知识图谱适合回答“老王认识的所有人里,谁在和老李合作的项目组”这种要顺着关系链去找答案的问题。

2.2 一个图谱项目要经历的四个阶段

不管图谱多简单,构建过程都绕不开几个环节,这也是AI给我列的清单后来变成我理解整个领域的框架:

  • 数据准备:拿到一段结构化或非结构化的文本。你要先知道数据长什么样,有哪些信息值得提取。
  • 本体与Schema设计:决定要建哪些类型的实体、哪些类型的关系。这一步就像给数据库设计表结构,但更灵活。
  • 信息抽取:从文本里识别出实体和关系。传统做法靠NLP工具,现在可以交给大模型。后面我会详写这部分。
  • 图存储与查询:把三元组(头实体、关系、尾实体)写入图数据库,再用查询语言去“问”图。

前两个阶段偏设计,第三个阶段偏工程,第四个阶段偏使用。很多教程只讲第二和第四,但实际项目里最耗时、最容易出问题的其实是第一和第三。我第一次做的时候把大量时间花在数据清洗和实体对齐上,学到这个教训后,后面再设计项目流程心里就有数了。

3. AI陪练实操:把一段文本变成人物关系图谱

3.1 环境和工具选型:Neo4j + Python + 大模型API

我选择的技术栈是三件套:Neo4j做图存储,Python做胶水代码,大模型做信息抽取。这套组合也是目前个人做知识图谱项目最省力的方案,我先把安装和准备步骤列一下,如果你也刚开始,可以直接照着来。

Neo4j的安装我推荐用Docker,省去本地环境配置的很多麻烦。一条命令就能跑起来:

docker run \ --name neo4j \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTH=neo4j/your_password \ -d neo4j:5.26

注意两个端口:7474是浏览器可视化界面的入口,7687是Python驱动连接用的Bolt端口。浏览器打开 http://localhost:7474 就能看到Neo4j Browser,验证数据库是否启动成功。

Python端需要安装两个库:

pip install neo4j openai

neo4j库负责和数据库通信,openai库用来调用大模型接口。如果要换其他兼容OpenAI协议的服务,本质上也是把base_url换一下,代码结构不用大变。实测下来,模型能力对抽取质量的影响非常大。我用不同模型跑同一段文本做过对比,强模型抽出来的三元组几乎不用改,弱模型会漏关系、会把实体归类错。这个钱不建议省。

3.2 核心提示词设计:让大模型输出结构化数据

信息抽取是整个流程里最有意思的一步,也是AI价值最大的环节。以前做实体识别要训练专门的NLP模型,现在我用一段提示词就能完成。关键在于让大模型输出严格的JSON格式,而不是自然语言。我最终用的提示词模板长这样:

你是一个知识图谱构建助手。请阅读下面的文本,抽取所有实体和关系,严格按JSON格式输出。 实体类型(Entity Type): - Person:人物 - Project:项目 - Dept:部门 关系类型(Relation Type): - manages:manage关系,A管理B - participates_in:A参与项目B - works_in:A在部门B工作(把“属于”“就职于”归入此关系) 输出格式要求: { "entities": [ {"id": "老张", "type": "Person", "props": {"name": "老张", "title": "部门经理"}} ], "relations": [ {"source": "老张", "target": "小王", "type": "manages"} ] } 要求: 1. 实体id必须与文本中出现的人名或项目名一致,不要改写。 2. 不要输出推理得到的信息,只抽取文本中明确出现的内容。 3. 如果同一实体有多种叫法(比如“张总”和“老张”),选择出现次数最多的写法作为id,其他写法放入props的alias字段。

为什么要强调“实体id与文本一致”?因为后续要写库,两段文本里都出现“老张”才能合并成同一个节点。如果模型把“老张”改写成“张建国”,那第一次抽取和第二抽取就变成两个节点了,图谱会变得稀碎。把温度参数调到0也是同样的目的,减少抽取结果的随机性。我第一次用的温度是0.7,跑了两遍同一个文本,抽出来的关系数量居然不一样,后来改成0就没这个问题了。

3.3 从文本到三元组:完整抽取与入库代码

数据准备好之后,我把文本按段落切片交给大模型,每段返回一批实体和关系。为什么按段落切片?因为整篇文章一次性丢给模型,容易超出上下文窗口,而且抽取质量会下降。按段落切的好处是每段聚焦一小块内容,模型抽得准;坏处是同一个关系可能跨段落,需要后面做合并。我的处理方式是把每个段落的实体临时存起来,全部抽完之后做一次实体合并,再写图数据库。

核心代码分两大块。第一大块是调用大模型抽取信息:

import json from openai import OpenAI client = OpenAI() # 需要在环境变量里配好API Key def extract_triples(text_chunk: str) -> dict: prompt = f"""使用下面的提示词抽取实体和关系。 {PROMPT_TEMPLATE} 待处理文本: {text_chunk} """ resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0, response_format={"type": "json_object"}, ) content = resp.choices[0].message.content return json.loads(content)

注意我加了response_format={"type": "json_object"},这样可以强迫模型输出合法JSON,省去解析时处理一堆怪邪格式的麻烦。但如果用的API不支持这个参数,要把它去掉,否则会报错。

第二大块是把三元组写入Neo4j。先清空旧库,再建节点,再建关系:

from neo4j import GraphDatabase driver = GraphDatabase.driver( "bolt://localhost:7687", auth=("neo4j", "your_password") ) def clear_database(tx): tx.run("MATCH (n) DETACH DELETE n") def create_entity(tx, entity): label = entity["type"] eid = entity["id"] query = ( f"CREATE (n:{label} {{id: $id, name: $name}}) " f"RETURN n" ) tx.run(query, id=eid, name=eid) def create_relation(tx, relation): source = relation["source"] target = relation["target"] rel_type = relation["type"] query = f""" MATCH (a {{id: $source}}), (b {{id: $target}}) MERGE (a)-[:`{rel_type}`]->(b) """ tx.run(query, source=source, target=target) with driver.session() as session: session.execute_write(clear_database) session.execute_write(create_entity, ...) session.execute_write(create_relation, ...)

这里有几个细节值得解释一下。为什么建节点用CREATE而建关系用MERGE?因为我的实体合并逻辑在Python侧处理完了,写入时用CREATE能保证性能;而关系可能因为跨段落重复抽取,用MERGE能去重。如果后面你要做增量更新,那就全部改用MERGE,以实体的id字段作为唯一键。实体id是名称本身,所以如果文本里有两个人同名,就需要在props里加工号或编号来区分,这算是图设计里一个常见的取舍。

3.4 图谱建好了,怎么查才不虚

数据进了Neo4j,下一步就是用Cypher查询。Cypher是图数据库的查询语言,语法和SQL有点像,但思维模式完全不一样。SQL关注的是表连接和聚合,Cypher关注的是沿路径游走。

最简单的开局查询是,看整张图有几个节点、几种关系:

MATCH (n) RETURN labels(n) AS label, count(*) AS cnt;

想看看某个人直接影响了谁,可以用:

MATCH (p:Person {id: '老张'})-[:manages]->(sub) RETURN sub.name;

更进阶一点,查“老张”向下两层的所有下属,这在关系型数据库里要么递归CTE,要么写多次JOIN,而Cypher只用一行:

MATCH (p:Person {id: '老张'})-[:manages*1..2]->(sub:Person) RETURN DISTINCT sub.name;

*1..2表示沿着manages关系走1到2层。我第一次查到结果的时候,还挺有成就感的——不是这语法多难,而是我终于把“知识”变成了能跑通的东西。

在Neo4j Browser里可以直接看图,默认会做一个布局,节点太密时可以把节点大小和颜色按照类型映射,这样一眼能看出不同角色的分布。如果信息熵太高,说明你的抽取环节出了问题——关系太杂、类型太乱。我第一次自己跑出来的图五花八门,后来把关系收敛到几个明确的类型,图的可读性一下子提升了。

4. 踩坑实录:知识图谱项目的常见问题速查

4.1 Schema设计被反复推翻:原因是数据长什么样太晚才看清

做数据库设计的人都知道“先设计表结构,再填数据”的流程,但知识图谱项目里我建议反过来:先把一小撮数据放进去,用实际数据验证Schema能不能覆盖,再固化下来。

我第一次做Schema的时候,设计了 Person、Project、Dept 三种节点。看起来挺合理,但把数据放进去之后发现一个问题:文本里大量出现的是“项目组”和“任务”这样的中间概念。比如“老张负责智慧城市的对外协调”,“对外协调”其实是一种工作职能,既不是Project也不是Dept。两难之下,我把所有不好归类的全塞进了Project,结果图里的Project节点变成一个大杂烩,后续查询需要反复过滤掉无用节点,很痛苦。

后来我把Schema做了分层:核心节点(Person、Project、Dept)加上自由标签(Tag)。不好归类的信息统一进Tag,通过has_tag关系连到相关实体上。这样一来,核心图谱层次清晰,没有丢失的信息也不会被强行塞进错误类型。这个调整只花了一个小时,但对图的可读性提升非常大。

4.2 实体对齐与去重:同一个“王总”的三种写法

实体对齐(Entity Alignment)是知识图谱项目里最容易被低估的脏活。我有一段文本里,同一个人出现了三个名字:王总、王建国、王工。如果直接用原始名字当实体id,图谱里就会出现三个互不相连的节点,而这个人的关系网络被拆成了三块。

我试过几种方案。最简单的:让大模型在抽取时给出alias,把别名收进props.alias字段(提示词里那个要求就是干这个的)。抽取结束后,我把所有alias展开做一次统一映射。另一种方案是模糊匹配,用字符相似度做全量对比。中文文本里“王总”和“王建国”字符相似度基本为零,这种场景下,基于大模型的alias方案更可靠,毕竟它是在理解语义层面做判断,而不是在字面层面做匹配。

实体对齐这一步千万别省。我身边不少做知识图谱的朋友,第一版图建出来不好看,一半原因就是没对齐。两条本应连在一起的关系,因为叫法不同断成了两截,图的质量会打折。

4.3 大模型的抽取结果怎么验证:别全信,也别全不信

大模型做信息抽取不是100%精确。我把自己手工标注的50条三元组作为金标准,和模型抽取结果做了一次抽样对比,发现两个高频问题:

第一个是关系类型误判。“张三参与智慧城市项目”可能被抽成“张三负责智慧城市项目”,models的manages和participates_in就差一个词,语义完全不同。这个问题靠事后人工改提示词能缓解。我在提示词里加了每类关系的正例和反例,误判率下降了不少。第二个是幻觉。文本里根本没有提到“李四加入项目组”,但模型根据上下文推断出了这条关系,还写得很自然。这种幻觉最难防,只能靠抽样人工审核来发现。

我给自己的流程定了一个硬规矩:每跑完一轮抽取,从库里随机抽10条关系,一条一条看,对照原文,确认关系类型和方向都对。成本很低,但保证了整张图的下限。等以后数据量大了,再考虑上自动化校验工具。

4.4 常见问题速查表

问题现象可能原因建议处理方案
每次抽取结果不一样温度参数太高把 temperature 调到 0 或接近0
实体id不统一,同一人分裂成多个节点模型改写人名/项目名提示词里强制实体id与原文一致,增加alias字段
关系类型过杂,图谱可读性差Schema设计太松散收敛核心关系类型;杂类信息用Tag标签承接
抽取结果包含明显不存在的信息大模型幻觉设置抽样人工审核流程;关键场景用更强模型
写入Neo4j报语法错误关系类型名包含非法字符关系类型使用大写英文字母和下划线,不要用中文、空格
图谱查询速度慢节点/关系量级过大且未建索引对频繁查询的属性建索引,如CREATE INDEX FOR (p:Person) ON (p.id)
模型截断输出导致JSON不完整上下文过长或输出限制按段落切片抽取;启用response_format;增加输出参数max_tokens

5. 从AI陪练到AI Agent:下一阶段能往哪走

5.1 让AI当“陪练”而不是“代练”:关键在提问方式

用了几天AI辅助之后,我发现学习效果的分水岭在提问方式上。直接问“给我一个知识图谱的代码”属于找代练,代码跑通了,但你还是不会;问“这个Cypher为什么用MERGE而不是CREATE”属于找陪练,AI解释原理的时候,你在跟着思考。

我的习惯是把代码问题时,顺便要求AI解释一下关键设计的选择理由。比如“这里为什么用MERGE?如果改成CREATE会有什么问题?”一次两次觉得啰嗦,攒上几天,很多之前模糊的概念就被盘顺了。这种互动方式对大模型本身没有特殊要求,但对使用者的心态要求很高——你真的是来学习的,不是来抄答案的。

5.2 把流程串成Agent:从半自动到全自动的扩展

当数据量从一段短文变成几十个文档时,手工一条条抽取显然不现实。这时候可以考虑把前面的流程做成一个多步骤自动化的AI Agent。用到的概念就是最近很火的AI Agent:一个大模型作为“调度中枢”,把“读取文档—切片—抽取—实体对齐—写入图谱—校验”拆成一个任务列表,模型根据每个任务的输入输出决定下一步动作。

我的建议是:刚开始不要直接用LangGraph或者复杂Agent框架,先把之前的Python函数串成一条流水线。每步的输入输出都用JSON传递,发现问题时直接在那个环节加日志。等流程稳定了,再考虑把调度部分交给大模型,让Agent动态决定哪些文档要先处理、哪些关系要合并。

到这一步,你其实已经不是在“学知识图谱”了,而是在用知识图谱技术做产品原型了。这中间的距离,也就一个周末外加几次AI对话而已。

整个实操走下来,我个人最深的感受是:知识图谱不是靠“学”出来的,而是靠“做”出来的。AI陪练的意义不在于替你写代码,而在于把你从“我不知道下一步该干什么”的僵局里推出来,先跑通一个最小闭环,再一点点补充细节。如果你也在收藏夹里躺着一堆知识图谱资料,建议你挑一小段自己熟悉的文本,今天就把Neo4j启动起来,把AI提示词复制进去,看第一次抽出来的三元组长什么样。做出来的图可能很粗糙,但从此以后,你就是在“真正做”知识图谱了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进