做 MySQL 的时候,我们对“数据校验”其实很熟悉。
比如用户表:
CREATETABLEuser(idBIGINTPRIMARYKEY,usernameVARCHAR(64)NOTNULL,ageINT);这里已经偷偷规定了很多规则:
id 必须有 username 不能为空 username 最长 64 age 必须是整数前端还可能继续限制:
年龄必须大于 0 手机号必须 11 位 邮箱必须符合格式所以数据库世界里,我们很自然地知道:
数据不是能存进去就行,还必须符合规则。
但当我们开始学习:
RDF OWL Ontology Knowledge Graph以后,很容易忽略一个问题:
知识图谱里的数据写错了怎么办?
比如我们定义:
Employee表示员工。
员工应该至少有:
姓名 工号 所属部门结果某一天进来一条数据:
王小明 类型:Employee 年龄:"二十八岁"甚至:
王小明 email:123456或者:
员工 A 同时属于 8 个主部门从 RDF 的角度来看,这些数据可能依然能够组成三元组。
但从我们的业务角度看:
明显有问题。
这时候就需要今天要讲的东西:
SHACL。
以及一个非常实用的 Python 开源项目:
RDFLib/pySHACLGitHub:
https://github.com/RDFLib/pySHACLpySHACL 做的事情可以用一句话概括:
拿一套 SHACL 规则,检查你的 RDF 数据到底合不合格。
一、先用人话理解 SHACL
假设现在有一个员工:
王小明我们希望所有员工满足这些条件:
必须有姓名 姓名只能有一个 必须有邮箱 年龄必须是整数 年龄不能小于 18 必须属于一个部门这其实就是一套:
数据规则在普通程序里,我们可能写:
ifuser.nameisNone:raiseValueError("姓名不能为空")ifuser.age<18:raiseValueError("年龄不能小于 18")在 Java 里可能写:
@NotNullprivateStringname;@Min(18)privateIntegerage;那知识图谱呢?
也需要类似的机制。
SHACL 就可以理解成:
RDF / Knowledge Graph 世界里的数据校验规则。
SHACL 全称:
Shapes Constraint Language可以简单理解成:
Shape = 一个数据应该长什么样比如:
EmployeeShape规定:
Employee 应该长什么样。二、Ontology 不是已经定义 Class 和 Property 了吗?
这是很多小白第一次接触 SHACL 时最疑惑的问题。
我们不是已经有:
OWL Ontology了吗?
例如:
Employee有:
name age email belongsToDepartment为什么还需要 SHACL?
因为:
“描述知识”与“校验数据”不是完全一回事。
Ontology 更关注:
这个世界有哪些概念? 它们之间是什么关系? 哪些类存在继承? 可以推理出什么新知识?而 SHACL 更关注:
这条数据合不合格?可以简单记:
OWL = 知识是什么意思SHACL = 数据写得对不对举个例子。
Ontology 告诉我们:
BackendEngineer 是 Employee 的一种这属于:
语义建模而 SHACL 告诉我们:
Employee 必须有一个 name age 必须是整数 email 最少有一个这是:
数据约束两者完全可以一起使用。
三、我们先准备一份 RDF 数据
假设公司知识图谱里有两个员工。
新建:
data.ttl内容:
@prefix ex: <http://example.com/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:zhangsan a ex:Employee ; ex:name "张三" ; ex:age 28 ; ex:email "zhangsan@example.com" . ex:lisi a ex:Employee ; ex:name "李四" ; ex:age "十八岁" .第一位员工:
张三数据比较正常:
name = 张三 age = 28 email = zhangsan@example.com第二位:
李四有两个问题。
第一个:
age = "十八岁"这是字符串。
我们希望它是:
整数第二个:
没有 email假设业务规定:
员工必须有邮箱那么这条数据就应该被检查出来。
四、没有 SHACL 时会发生什么?
从 RDF 角度来看:
ex:lisi ex:age "十八岁" .依然是一条正常的三元组。
结构依旧是:
Subject Predicate Object也就是:
李四 年龄 十八岁RDF 本身并不会像 MySQL:
INT字段那样直接阻止你保存。
这也是知识图谱和传统关系数据库一个非常明显的区别。
知识图谱非常灵活。
但是:
灵活的代价就是数据质量更难控制。
于是 SHACL 出场了。
五、给 Employee 定义一个 Shape
新建:
shapes.ttl我们先写一套非常简单的规则:
@prefix sh: <http://www.w3.org/ns/shacl#> . @prefix ex: <http://example.com/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:EmployeeShape a sh:NodeShape ; sh:targetClass ex:Employee ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; ] ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minCount 1 ; ] ; sh:property [ sh:path ex:email ; sh:minCount 1 ; ] .第一次看可能有点乱。
我们拆开。
六、sh:targetClass 是什么意思?
这里:
sh:targetClass ex:Employee ;意思就是:
这套规则检查所有 Employee。
也就是说,只要数据里出现:
ex:zhangsan a ex:Employee .那么:
zhangsan就需要接受:
EmployeeShape的检查。
可以理解成:
EmployeeShape ↓ 检查所有 ↓ Employee七、姓名必须存在怎么写?
看这一段:
sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; ] ;这里:
sh:path ex:name表示:
我要检查 name 这个属性。
然后:
sh:minCount 1表示:
至少出现一次。
再:
sh:maxCount 1表示:
最多出现一次。
合起来就是:
一个员工必须有且只能有一个姓名。
如果:
ex:user001 a ex:Employee .完全没有:
name那么:
校验失败如果:
ex:user001 ex:name "张三" ; ex:name "李四" .两个姓名。
同样:
校验失败八、这其实很像数据库约束
你可以这样对照。
数据库:
nameVARCHAR(64)NOTNULLSHACL:
sh:path ex:name ; sh:minCount 1 ;数据库:
必须唯一只有一个值SHACL:
sh:maxCount 1 ;虽然两者机制完全不同,但对于刚入门的人,这样理解非常直观。
九、怎么规定 age 必须是整数?
我们写:
sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minCount 1 ; ] ;这里最关键的是:
sh:datatype xsd:integer ;意思:
age 必须是整数类型所以:
ex:age 28 ;可以通过。
但:
ex:age "十八岁" ;就不符合规则。
是不是已经开始有点像:
类型系统了?
这也是为什么我很喜欢把 SHACL 给程序员解释成:
给 RDF 加上一层“类型 + 校验规则”。
十、甚至还能限制年龄范围
比如业务规定:
员工年龄不能低于 18 岁。可以继续增加:
sh:minInclusive 18 ;完整:
sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minInclusive 18 ; sh:minCount 1 ; ] ;现在:
ex:age 28 ;通过。
但是:
ex:age 16 ;失败。
是不是已经非常接近:
ifage<18:raiseValueError(...)了?
十一、SHACL 规则有了,谁来执行?
这就轮到:
pySHACL
出场了。
pySHACL 是一个纯 Python 的 SHACL 验证器,可以拿:
RDF Data Graph去对照:
SHACL Shapes Graph执行检查。
整个过程:
data.ttl ↓ 真实 RDF 数据 shapes.ttl ↓ SHACL 校验规则 ↓ pySHACL ↓ Validation Report最终告诉你:
通过 或者 哪里没通过十二、安装 pySHACL
安装很简单:
pipinstallpyshacl检查:
pyshacl--help如果能正常看到参数说明:
pyshacl基本安装完成。
十三、最简单的一条命令
现在目录:
demo/ ├── data.ttl └── shapes.ttl执行:
pyshacl\-sshapes.ttl\data.ttl这里:
-s表示:
SHACL Shapes Graph也就是我们的规则文件。
而:
data.ttl就是:
待检查数据完整流程:
data.ttl + shapes.ttl ↓ pyshacl ↓ 检查十四、如果数据全部正常
假设:
ex:zhangsan a ex:Employee ; ex:name "张三" ; ex:age 28 ; ex:email "zhangsan@example.com" .满足:
name 至少一个 name 最多一个 age 是整数 email 至少一个那么结果类似:
Conforms: True这就表示:
数据符合规则。
十五、如果数据不符合呢?
我们的:
李四存在:
age 类型错误 email 缺失那么验证结果会告诉你:
Conforms: False同时还会生成:
Validation Report指出:
哪个节点有问题 哪个属性有问题 违反了什么 Constraint这就比程序里简单打印:
数据错误有用太多了。
十六、让输出更适合人看
pySHACL 的命令行可以指定:
-fhuman例如:
pyshacl\-sshapes.ttl\-fhuman\data.ttl这样输出更偏:
人类可读特别适合本地调试。
如果你后面想把结果:
交给程序处理还可以选择其他 RDF / JSON-LD 等输出格式。
十七、也可以直接用 Python 调用
如果你本身就在做:
Python AI Knowledge Graph GraphRAG那其实没必要每次跑命令行。
可以直接:
frompyshaclimportvalidate然后:
frompyshaclimportvalidate conforms,results_graph,results_text=validate(data_graph="data.ttl",shacl_graph="shapes.ttl",)print("是否合法:",conforms)print(results_text)运行:
python validate_graph.py如果:
conforms = True说明:
验证通过如果:
False就可以查看:
results_text定位问题。
十八、validate 为什么返回三个东西?
pySHACL 的:
validate()通常返回:
conforms,results_graph,results_text分别可以理解为:
conforms = 最终通过没通过results_graph = 结构化的验证报告 RDF Graphresults_text = 方便人阅读的文本报告所以最简单:
ifconforms:print("数据合法")else:print("数据有问题")print(results_text)已经可以用于很多场景。
十九、实际项目里怎么用?
假设你正在做:
企业知识库数据来源很多:
MySQL Excel 接口 人工录入 大模型抽取 PDF 爬虫最后全部转成:
RDF问题来了。
不同来源的数据质量可能完全不同。
比如 AI 从文档抽取出:
张三 职位:Java但是你的知识模型规定:
职位应该是:
BackendEngineer FrontendEngineer ProductManager而:
Java应该属于:
Skill如果不做检查:
脏数据 ↓ 直接进入知识图谱 ↓ 越来越多 ↓ 查询结果开始奇怪 ↓ GraphRAG 最后也被污染这就是非常现实的问题。
二十、所以可以在入库前增加一道校验
架构可以变成:
PDF / Excel / MySQL / API ↓ 数据抽取 ↓ RDF 数据 ↓ pySHACL ↓ 是否符合 Shape? ↙ ↘ 否 是 ↓ ↓ 错误队列 写入图谱这样:
SHACL 就变成了知识图谱的数据质量防火墙。
这个说法对于程序员来说特别容易理解。
二十一、例如 AI 自动抽取知识
假设大模型从简历里提取:
{"name":"王小明","age":"28岁","skill":["Go","Redis"]}AI 看起来抽对了。
但:
age = "28岁"如果我们的 Ontology 要求:
age = integer那么它实际上是不符合规则的。
我们把数据转成 RDF:
ex:wangxiaoming a ex:Employee ; ex:name "王小明" ; ex:age "28岁" .pySHACL 马上就可以发现:
DatatypeConstraintComponent没有通过。
然后我们就可以:
拦下来 ↓ 修复 ↓ 重新校验 ↓ 再进入知识图谱这在:
LLM + Knowledge Graph
场景里其实特别有价值。
二十二、还能校验字符串长度
比如:
员工编号不能太短。
可以:
sh:property [ sh:path ex:employeeCode ; sh:minLength 5 ; ] ;如果:
E1太短。
就会验证失败。
二十三、还能使用正则表达式
比如邮箱:
sh:property [ sh:path ex:email ; sh:pattern "^[^@]+@[^@]+\\.[^@]+$" ; ] ;那么:
zhangsan@example.com可以通过。
而:
123456不能通过。
这就特别像后端经常写的:
regex校验。
二十四、还能限制值只能来自某几个选项
例如员工状态只能:
active disabled left可以使用:
sh:in ( "active" "disabled" "left" ) ;于是:
active合法。
但是:
abc不合法。
和 Java 里的:
Enum是不是很像?
二十五、还可以检查对象类型
假设:
Employee有一个:
belongsToDepartment我们希望它指向的必须是:
Department可以:
sh:property [ sh:path ex:belongsToDepartment ; sh:class ex:Department ; ] ;正确:
王小明 ↓ belongsToDepartment ↓ 研发部其中:
研发部 a Department但是如果:
王小明 ↓ belongsToDepartment ↓ Go那显然就奇怪了。
SHACL 可以把这类问题找出来。
二十六、一个比较完整的 EmployeeShape
最后可以写成:
@prefix sh: <http://www.w3.org/ns/shacl#> . @prefix ex: <http://example.com/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:EmployeeShape a sh:NodeShape ; sh:targetClass ex:Employee ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; ] ; sh:property [ sh:path ex:age ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:integer ; sh:minInclusive 18 ; ] ; sh:property [ sh:path ex:email ; sh:minCount 1 ; sh:pattern "^[^@]+@[^@]+\\.[^@]+$" ; ] ; sh:property [ sh:path ex:belongsToDepartment ; sh:minCount 1 ; sh:maxCount 1 ; sh:class ex:Department ; ] .现在我们的员工就有了比较明确的数据规则:
姓名 必须存在 只能有一个 必须是字符串 年龄 必须存在 必须是整数 必须 >= 18 邮箱 必须存在 必须符合格式 部门 必须存在 只能一个 必须指向 Department这已经非常接近真实项目了。
二十七、pySHACL 还可以配合推理
pySHACL 不仅仅能:
读取 RDF ↓ 直接验证还可以在验证前选择:
RDFS OWL RL等推理方式。
Python:
conforms,graph,text=validate("data.ttl",shacl_graph="shapes.ttl",inference="rdfs",)或者命令行:
pyshacl\-sshapes.ttl\-irdfs\data.ttl为什么有用?
例如 Ontology 定义:
BackendEngineer ↓ subClassOf ↓ Employee然后:
王小明 a BackendEngineer虽然数据没有直接写:
王小明 a Employee但是经过 RDFS 推理后,可以得到:
王小明 也是 Employee再进行:
EmployeeShape验证。
这样:
推理 + 数据校验就串起来了。
二十八、这和 Protégé 又是什么关系?
如果你看过前面 Protégé 那篇,可以这样理解。
Protégé:
设计 Ontology例如:
Employee Department Skill Employee belongsToDepartment Department然后:
pySHACL负责:
检查实际数据是不是符合规则可以形成:
Protégé ↓ 设计知识模型 SHACL ↓ 设计数据约束 pySHACL ↓ 执行数据校验 RDF Graph ↓ 合格的数据这就是比较完整的知识工程工作流。
二十九、SHACL 和 Java Bean Validation 其实很像
如果你是 Java 程序员,可以直接这样类比。
Java:
publicclassUser{@NotNullprivateStringusername;@Min(18)privateIntegerage;}SHACL:
sh:property [ sh:path ex:username ; sh:minCount 1 ; ] ; sh:property [ sh:path ex:age ; sh:minInclusive 18 ; ] ;本质思路差不多:
Java Object + Validation Annotation对应:
RDF Graph + SHACL Shape而:
Hibernate Validator有点类似:
pySHACL虽然实现机制不同,但作为入门理解非常好用。
三十、为什么我觉得 pySHACL 很适合知识图谱新手?
因为很多知识图谱教程一开始就在讲:
RDF RDFS OWL SPARQL Reasoner Description Logic非常抽象。
但 pySHACL 解决的是一个极其具体的问题:
我的数据到底对不对?
你只需要:
一份 RDF 数据 + 一份规则然后:
pyshacl-sshapes.ttl data.ttl马上就可以看到结果。
学习反馈非常直接。
三十一、它特别适合哪些项目?
第一种:
Knowledge Graph知识图谱入库前:
SHACL Validation保证数据质量。
第二种:
GraphRAG大模型抽取实体关系后:
LLM ↓ Triples ↓ SHACL ↓ Knowledge Graph减少错误实体和关系进入图谱。
第三种:
企业数据治理不同部门:
Excel MySQL API CSV统一映射成 RDF 后:
SHACL作为统一质量规则。
第四种:
Ontology 项目Protégé 建模以后:
真正的数据需要一套独立验证机制。
三十二、如果数据量非常大怎么办?
简单项目可以直接:
pySHACL + RDFLib验证。
但 pySHACL 现在也支持:
SPARQL Remote Graph Mode也就是数据不一定非得:
全部下载成一个本地 ttl还可以针对远程 SPARQL Graph 做验证。
另外项目也提供:
HTTP REST Service模式。
也就是说,后面完全可以把:
pySHACL包装成一个:
知识图谱质量校验服务架构变成:
数据系统 ↓ POST /validate ↓ pySHACL Service ↓ Validation Report对于企业系统反而更加实用。
三十三、CI/CD 里甚至也可以用
这是我觉得非常有意思的一个玩法。
假设你的 GitHub 仓库里:
ontology/ shapes/ data/每次有人提交 RDF 数据:
git pushGitHub Actions 自动执行:
pyshacl\-sshapes.ttl\data.ttlpySHACL 的 CLI 会通过不同退出码告诉我们:
0 = 数据符合规则 1 = 数据不符合规则于是 CI:
RDF 合法 ↓ ✅ Pass或者:
RDF 不合法 ↓ ❌ Build Failed是不是很像:
代码提交之前跑单元测试?
所以你甚至可以把 SHACL 理解成:
Knowledge Graph 的自动化测试。
三十四、这可能才是 SHACL 最好理解的方式
我们平时写代码有:
Unit Test验证代码。
数据库有:
Constraint验证数据。
API 有:
JSON Schema验证请求。
Java 有:
Bean Validation验证对象。
而 RDF / Knowledge Graph:
SHACL验证图数据。
所以:
JSON → JSON Schema Java Object → Bean Validation RDF Graph → SHACL这样记,基本就不会忘。
三十五、最后总结
如果你正在学习:
Ontology RDF OWL Knowledge Graph GraphRAG我非常建议顺手把:
SHACL也学一下。
因为真实项目里,一个知识图谱的问题通常不是:
能不能存数据?
而是:
存进去的数据到底靠不靠谱?
完整的数据链路应该更像:
原始数据 ↓ 解析 / LLM 抽取 ↓ RDF ↓ SHACL Rules ↓ pySHACL ↓ Validation ↓ 修复错误 ↓ 进入 Knowledge Graph ↓ SPARQL / GraphRAG / Agent其中 pySHACL 做的事情看起来很简单:
检查数据但它解决的是知识工程里一个非常重要的问题:
数据质量。
如果前面的知识图谱里已经塞满了:
错误类型 错误关系 缺失属性 非法值那么后面不管:
SPARQL还是:
GraphRAG甚至:
AI Agent最终得到的结果都会受到影响。
所以 Ontology 告诉系统:
这个世界应该是什么样。
SHACL 告诉系统:
这批数据有没有按照这个世界的规则来。
而 pySHACL,就是负责真正执行这场“知识图谱体检”的工具。
项目地址:
https://github.com/RDFLib/pySHACL