知识图谱里的数据为什么不能只靠 if 校验?从 pySHACL 讲透 minCount、maxCount、datatype、pattern 与 class
前面我们用 Owlready2 讲过:
Class
ObjectProperty
InverseProperty
TransitiveProperty
Reasoner
比如定义:
Employee
+
hasSkill Go
=
GoEngineer
然后 Reasoner 可以自动推出:
张三
→ rdf:type
→ GoEngineer
看到这里,很多人自然会产生一个问题:
既然 Ontology 已经有 Class、Property、Domain、Range、Reasoner 这些规则了,为什么还需要数据校验?
比如我们定义:
Employee
那是不是可以顺便要求:
员工必须有姓名
必须有工号
年龄必须是整数
邮箱必须符合格式
至少掌握一个 Skill
最多属于一个主部门
?
答案是:
不能完全靠 OWL Reasoner。
因为:
Reasoning
和:
Validation
其实是两件不同的事情。
这也是 SHACL 出现的原因。
今天就用一个非常适合 Python 开发者的开源项目:
pySHACL
来把这个问题讲透。
项目地址:
https://github.com/RDFLib/pySHACL
pySHACL 本质上是:
一个用 Python 实现的 SHACL Validator。
它可以拿一份:
RDF Data Graph
再拿一份:
SHACL Shapes Graph
然后告诉你:
哪些数据符合规则
哪些数据不符合
哪里错了
违反了哪条约束
整个流程:
data.ttl
+
shapes.ttl
↓
pySHACL
↓
Validation Report
这篇我们直接通过一个:
员工知识图谱数据校验系统
来讲清楚。
一、先准备一份“有问题”的员工数据
假设我们有:
data.ttl
内容:
@prefix ex: <http://example.com/company#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:zhangsan
a ex:Employee ;
ex:name "张三" ;
ex:age 28 ;
ex:employeeCode "E10001" ;
ex:email "zhangsan@example.com" .
ex:lisi
a ex:Employee ;
ex:name "李四" ;
ex:age "二十六" ;
ex:employeeCode "10002" .
ex:wangwu
a ex:Employee ;
ex:age 31 ;
ex:employeeCode "E10003" ;
ex:email "wrong-email-format" .
看起来数据不少。
但其实已经有很多问题。
二、先人工找问题
张三:
name
age
employeeCode
email
都有。
看起来正常。
李四:
age = "二十六"
不是:
xsd:integer
而是字符串。
工号:
10002
我们希望格式必须:
E + 5 位数字
所以也不合格。
而且:
email
缺失。
王五:
缺少:
name
邮箱:
wrong-email-format
也明显不合法。
如果靠业务代码检查:
if not name:
...
当然可以。
但如果整个知识图谱里有:
Employee
Department
Project
Customer
Product
Server
Database
API
几十种实体。
每种实体:
10~30 条校验规则
你会很快得到一大堆:
if
elif
if
if
if
这就开始难维护了。
SHACL 就是用来:
把数据约束本身也变成一种可声明的规则。
三、什么是 Shape?
SHACL 最核心的概念叫:
Shape
可以简单理解:
某种数据应该“长什么样”。
例如:
EmployeeShape
规定:
Employee
必须有 name
name 必须是 string
必须有 employeeCode
employeeCode 必须符合 E12345
age 必须是 integer
email 如果存在必须符合邮箱格式
这就是 Shape。
四、创建第一个 EmployeeShape
新建:
shapes.ttl
先写 Prefix:
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.com/company#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
然后:
ex:EmployeeShape
a sh:NodeShape ;
sh:targetClass
ex:Employee .
这句:
sh:targetClass ex:Employee
意思是:
所有 Employee 都要接受这个 Shape 检查。
五、第一条规则:员工必须有姓名
写:
ex:EmployeeShape
a sh:NodeShape ;
sh:targetClass
ex:Employee ;
sh:property [
sh:path ex:name ;
sh:minCount 1 ;
] .
这里最重要:
sh:path ex:name
表示:
我要检查
ex:name这个 Property。
然后:
sh:minCount 1
表示:
至少出现 1 次。
换成人话:
Employee 必须有 name。
六、minCount 是什么?
比如:
sh:minCount 1
意思:
最少 1 个值
如果数据:
张三
name 张三
符合。
如果:
王五
没有 name
就违反规则。
七、minCount 还能写 2
例如:
sh:minCount 2
表示:
至少两个值。
比如:
Project
→ hasMember
→ Employee
我们希望一个 Project:
至少 2 个成员
可以:
sh:property [
sh:path ex:hasMember ;
sh:minCount 2 ;
] .
这比在 Python 中写:
if len(members) < 2:
更加语义化。
八、再加 maxCount
如果:
员工只能有一个 employeeCode
写:
sh:property [
sh:path ex:employeeCode ;
sh:minCount 1 ;
sh:maxCount 1 ;
] .
意思:
至少 1 个
最多 1 个
最终:
必须且只能有一个。
这和数据库里的:
NOT NULL
+
单值字段
有一点像。
九、主部门只能有一个
比如:
Employee
→ belongsToDepartment
→ Department
如果业务要求:
一个员工只能属于一个主部门。
可以:
sh:property [
sh:path
ex:belongsToDepartment ;
sh:maxCount 1 ;
] .
如果出现:
张三
→ 研发部
张三
→ 产品部
就会违反规则。
十、datatype:数据类型校验
现在开始检查:
age
规则:
sh:property [
sh:path ex:age ;
sh:datatype
xsd:integer ;
] .
也就是说:
Employee.age
必须:
xsd:integer
十一、李四为什么会失败?
数据:
ex:lisi
ex:age "二十六" .
这个值其实是:
string
而不是:
integer
所以 pySHACL 会报告类似:
DatatypeConstraintComponent
违反。
换成人话:
ex:age的数据类型不符合要求。
十二、正确写法
可以:
ex:lisi
ex:age 26 .
或者显式:
ex:lisi
ex:age
"26"^^xsd:integer .
这样才符合:
sh:datatype xsd:integer
十三、datatype 不只检查数字
比如邮箱:
sh:datatype
xsd:string ;
布尔:
sh:datatype
xsd:boolean ;
日期:
sh:datatype
xsd:date ;
时间:
sh:datatype
xsd:dateTime ;
所以可以做非常多:
类型检查。
十四、年龄还应该有范围
只检查 integer 还不够。
比如:
age = -100
虽然是整数,但明显有问题。
可以:
sh:minInclusive 18 ;
例如:
sh:property [
sh:path ex:age ;
sh:datatype
xsd:integer ;
sh:minInclusive 18 ;
sh:maxInclusive 70 ;
] .
这样:
18 <= age <= 70
十五、常见数值范围约束
包括:
sh:minInclusive
大于等于。
sh:maxInclusive
小于等于。
sh:minExclusive
严格大于。
sh:maxExclusive
严格小于。
这和我们平时:
18 <= age <= 70
表达的是一类东西。
十六、pattern:字符串格式校验
现在处理员工工号。
要求:
E10001
E10002
E88990
格式:
E + 5 位数字
正则:
^E[0-9]{5}$
SHACL:
sh:property [
sh:path
ex:employeeCode ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:datatype
xsd:string ;
sh:pattern
"^E[0-9]{5}$" ;
] .
十七、李四就会被抓出来
数据:
ex:lisi
ex:employeeCode
"10002" .
因为:
10002
没有:
E
所以不匹配:
^E[0-9]{5}$
pySHACL 会报:
PatternConstraintComponent
十八、邮箱同样可以使用 pattern
例如一个简单版本:
^[^@\s]+@[^@\s]+\.[^@\s]+$
SHACL:
sh:property [
sh:path ex:email ;
sh:datatype
xsd:string ;
sh:pattern
"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" ;
] .
注意:
我们没有:
sh:minCount 1
所以它表达:
email 可以没有。
但是:
如果有,格式必须正确。
这特别实用。
十九、“可选但必须正确”怎么设计?
很多字段就是这种业务规则。
比如:
phone
允许不填写。
但是填写了以后必须:
符合手机号格式。
所以:
sh:property [
sh:path ex:phone ;
sh:pattern
"^1[3-9][0-9]{9}$" ;
] .
不要写:
sh:minCount 1
即可。
这就是声明式校验很方便的地方。
二十、class:Object 类型检查
现在假设:
Employee
→ belongsToDepartment
→ Department
我们要求:
belongsToDepartment
指向的对象必须:
rdf:type Department
可以写:
sh:property [
sh:path
ex:belongsToDepartment ;
sh:class
ex:Department ;
] .
这个就很有知识图谱味了。
二十一、如果关系指错类型会怎样?
比如错误数据:
ex:zhangsan
ex:belongsToDepartment
ex:redis .
而:
ex:redis
a ex:Skill .
不是:
Department
那就会违反:
sh:class ex:Department
这类问题普通数据库里可能依赖:
外键
但在 RDF 世界里:
IRI 可以指向任何 Resource。
所以 SHACL 很重要。
二十二、Skill 关系也可以检查 Class
例如:
Employee
→ hasSkill
→ Skill
Shape:
sh:property [
sh:path ex:hasSkill ;
sh:class ex:Skill ;
] .
如果:
张三
hasSkill
研发部
就会失败。
因为:
研发部
不是 Skill。
二十三、员工至少必须有一个 Skill
继续:
sh:property [
sh:path ex:hasSkill ;
sh:minCount 1 ;
sh:class ex:Skill ;
] .
这样同时保证:
至少有 1 个技能
并且:
每个目标都必须属于 Skill
这才是完整规则。
二十四、完整 EmployeeShape
现在组合:
@prefix sh:
<http://www.w3.org/ns/shacl#> .
@prefix ex:
<http://example.com/company#> .
@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:employeeCode ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:datatype
xsd:string ;
sh:pattern
"^E[0-9]{5}$" ;
] ;
sh:property [
sh:path
ex:age ;
sh:datatype
xsd:integer ;
sh:minInclusive
18 ;
sh:maxInclusive
70 ;
] ;
sh:property [
sh:path
ex:email ;
sh:datatype
xsd:string ;
sh:pattern
"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" ;
] ;
sh:property [
sh:path
ex:belongsToDepartment ;
sh:maxCount 1 ;
sh:class
ex:Department ;
] ;
sh:property [
sh:path
ex:hasSkill ;
sh:minCount 1 ;
sh:class
ex:Skill ;
] .
这一份 Shape 已经很像:
Knowledge Graph 的 Schema Validation。
二十五、开始使用 pySHACL
安装:
pip install pyshacl
最简单执行:
pyshacl \
-s shapes.ttl \
data.ttl
如果希望输出更友好:
pyshacl \
-s shapes.ttl \
-f human \
data.ttl
二十六、验证通过会怎样?
如果全部正确:
Conforms: True
也就是:
数据符合 SHACL Shapes。
如果失败:
Conforms: False
并给出:
哪个 Node
哪个 Property
违反哪个 Constraint
具体值是什么
这就是 Validation Report。
二十七、比如王五缺少 name
Shape:
sh:minCount 1
数据:
ex:wangwu
a ex:Employee ;
ex:age 31 .
Validation Report 会指出类似:
Focus Node:
ex:wangwu
Result Path:
ex:name
Constraint:
MinCountConstraintComponent
翻译:
王五这个 Employee
缺少 name。
二十八、什么是 Focus Node?
这个词非常重要。
假设:
EmployeeShape
目标:
所有 Employee
那么:
zhangsan
lisi
wangwu
每一个被检查的节点:
都叫 Focus Node。
当错误报告写:
Focus Node: ex:lisi
其实就是:
李四这条数据有问题。
二十九、Result Path 又是什么?
如果:
ex:lisi
违反:
age
规则。
那么:
Result Path:
ex:age
告诉你:
错误发生在哪个 Property。
所以 Validation Report 的核心信息:
Focus Node
+
Result Path
+
Constraint
+
Value
+
Message
基本已经够定位问题。
三十、Python 代码怎么验证?
pySHACL 提供:
validate()
例如:
from pyshacl import validate
conforms, results_graph, results_text = validate(
data_graph="data.ttl",
shacl_graph="shapes.ttl",
)
然后:
print(
conforms
)
以及:
print(
results_text
)
三十一、完整 Python 示例
from pyshacl import validate
def validate_graph(
data_file: str,
shape_file: str,
):
conforms, graph, report = validate(
data_graph=data_file,
shacl_graph=shape_file,
inference="none",
abort_on_first=False,
allow_infos=False,
allow_warnings=False,
debug=False,
)
print(
"Conforms:",
conforms
)
print(
report
)
return conforms
if __name__ == "__main__":
validate_graph(
"data.ttl",
"shapes.ttl"
)
运行:
python validate_graph.py
三十二、为什么返回三个值?
conforms
表示:
整体是否通过。
results_graph
本身也是:
RDF Graph。
也就是说:
Validation Report
不是简单字符串。
它也可以被程序继续处理。
results_text
则是:
适合人看的文本结果。
三十三、这就可以接进 CI/CD
比如项目目录:
knowledge/
├── data.ttl
├── ontology.ttl
└── shapes.ttl
GitHub Actions / CI:
提交 RDF
↓
运行 pySHACL
↓
Conforms?
如果:
False
直接:
Pipeline Failed
就像:
单元测试失败
一样。
这就是:
Knowledge Graph Test。
三十四、一个很简单的 CI 脚本
#!/bin/bash
pyshacl \
-s shapes.ttl \
-f human \
data.ttl
if [ $? -ne 0 ]; then
echo "SHACL validation failed"
exit 1
fi
echo "SHACL validation passed"
pySHACL CLI 的退出码非常适合自动化:
0
=
Conformant
1
=
Non-Conformant
所以非常容易接到:
CI/CD
ETL
Data Pipeline
中。
三十五、为什么不用 Python if?
比如:
if employee.name is None:
raise ValueError()
if employee.age < 18:
raise ValueError()
if not re.match(...):
raise ValueError()
小项目当然没问题。
但问题是:
规则被写死在程序里。
如果规则修改:
年龄上限 70
改成:
75
需要改:
代码
重新:
部署。
SHACL:
规则就是数据。
修改:
sh:maxInclusive 75
即可。
三十六、Schema 和程序代码解耦
传统:
Java/Python Code
↓
包含业务校验规则
SHACL:
Application
↓
RDF Data
+
SHACL Shape
也就是:
数据
和:
数据规范
分离。
对于跨:
Java
Python
Node.js
ETL
Agent
的系统特别有价值。
因为所有系统:
可以共享同一份 Shape。
三十七、这和 JSON Schema 有点像吗?
非常像。
例如 JSON Schema:
{
"type": "object",
"required": [
"name"
]
}
SHACL:
sh:path ex:name ;
sh:minCount 1 ;
都在做:
数据约束。
区别是 SHACL 面向:
RDF Graph。
所以它可以天然检查:
节点
关系
Class
Property Path
而不仅是:
JSON 字段。
三十八、比如关系数量检查就是图特有场景
例如:
Project
至少:
1 个负责人
最多:
1 个负责人
Shape:
sh:property [
sh:path
ex:hasOwner ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:class
ex:Employee ;
] .
这同时验证:
关系数量
和:
关系目标类型。
这就非常适合知识图谱。
三十九、sh:in:限制可选值
比如员工状态只能:
ACTIVE
DISABLED
LEAVE
可以:
sh:property [
sh:path
ex:status ;
sh:in (
"ACTIVE"
"DISABLED"
"LEAVE"
)
] .
如果:
status = "ABC"
直接失败。
四十、这就很像 Java Enum
Java:
enum EmployeeStatus {
ACTIVE,
DISABLED,
LEAVE
}
SHACL:
sh:in (
"ACTIVE"
"DISABLED"
"LEAVE"
)
思路一致。
只是:
一个约束对象数据,一个约束 RDF Graph。
四十一、sh:nodeKind:值必须是 IRI 还是 Literal
比如:
belongsToDepartment
我们希望:
Object
必须是:
IRI
而不是:
"研发部"
字符串。
可以:
sh:nodeKind
sh:IRI ;
完整:
sh:property [
sh:path
ex:belongsToDepartment ;
sh:nodeKind
sh:IRI ;
] .
这样:
ex:zhangsan
ex:belongsToDepartment
"研发部" .
会失败。
正确应该是:
ex:zhangsan
ex:belongsToDepartment
ex:rdDepartment .
四十二、Literal 和 Entity 一定要区分
这是知识图谱里一个特别重要的设计。
错误:
张三
→ belongsTo
→ "研发部"
这里:
研发部
只是字符串。
正确:
张三
→ belongsTo
→ Department-001
然后:
Department-001
→ name
→ "研发部"
这样:
研发部
才是一个真正可连接的 Entity。
SHACL 可以帮你保证这种结构。
四十三、组合约束
还可以做:
AND
OR
NOT
比如一个 Contact:
必须有 email
或者 phone
可以使用:
sh:or (
[
sh:path ex:email ;
sh:minCount 1 ;
]
[
sh:path ex:phone ;
sh:minCount 1 ;
]
) .
也就是说:
两个条件至少满足一个。
四十四、这就开始像业务规则引擎了
例如:
员工
必须:
name
+
employeeCode
+
email 或 phone
+
至少 1 个 Skill
全部都可以:
声明式描述。
而不是把规则散落:
Controller
Service
ETL
Agent
各种代码里。
四十五、Severity:错误也可以分级别
不是所有问题都必须:
阻止数据进入。
例如:
没有 name
可能是:
Violation
严重。
但:
没有 avatar
可能只是:
Warning
甚至:
Info
SHACL 支持:
sh:Violation
sh:Warning
sh:Info
这对真实数据治理很实用。
四十六、比如手机号只是 Warning
sh:property [
sh:path ex:phone ;
sh:pattern
"^1[3-9][0-9]{9}$" ;
sh:severity
sh:Warning ;
] .
那么:
格式不正确
可以提示:
Warning
但不一定让整个 Pipeline 停掉。
四十七、可以自定义 Message
比如:
sh:message
"员工工号必须符合 E + 5 位数字格式" .
完整:
sh:property [
sh:path
ex:employeeCode ;
sh:pattern
"^E[0-9]{5}$" ;
sh:message
"员工工号必须符合 E + 5 位数字格式" ;
] .
Validation Report 就更友好。
这对于:
运营人员
数据管理员
非常重要。
四十八、Reasoner 和 SHACL 到底有什么区别?
这是整篇最重要的地方。
假设知识图谱只有:
张三
rdf:type
Employee
但没有:
name
OWL Reasoner 通常不会说:
错了。
为什么?
因为:
Open World Assumption。
没有:
name
只代表:
我目前不知道 name。
而不是:
name 一定不存在。
四十九、SHACL 不一样
SHACL 可以明确说:
sh:minCount 1
也就是:
在当前这份数据里,Employee 必须至少存在一个 name。
如果没有:
Validation Failed。
所以:
OWL
关注:
事实意味着什么。
而:
SHACL
关注:
当前数据是否满足要求。
五十、最直观的对比
OWL:
Employee
subClassOf
Person
张三:
rdf:type Employee
Reasoner 可以推出:
张三
rdf:type
Person
这叫:
推理。
SHACL:
规定:
Employee
必须有 employeeCode。
张三没有。
返回:
Validation Error。
这叫:
校验。
五十一、所以 Reasoner ≠ Validator
可以记:
Reasoner
已知事实
+
Ontology
↓
推出更多事实
而:
Validator
当前数据
+
Shapes
↓
通过 / 不通过
两条链路完全不同。
五十二、但 pySHACL 还能在校验前做推理
这里又有意思了。
pySHACL 支持:
inference
例如:
pyshacl \
-s shapes.ttl \
-i rdfs \
data.ttl
或者 Python:
validate(
data_graph="data.ttl",
shacl_graph="shapes.ttl",
inference="rdfs",
)
还可以:
owlrl
或者:
both
五十三、为什么先推理再校验有用?
假设:
BackendEngineer
subClassOf
Employee
张三:
rdf:type
BackendEngineer
没有直接:
rdf:type
Employee
如果先进行 RDFS inference:
张三
可以得到:
Employee
然后:
EmployeeShape
继续对张三进行校验。
这就是:
Reasoning + Validation
结合。
五十四、完整流程可以变成
Data Graph
↓
RDFS / OWL RL Inference
↓
Expanded Graph
↓
SHACL Validation
↓
Validation Report
这就把:
Ontology
和:
SHACL
真正连起来了。
五十五、远程 SPARQL 数据也能校验
这是 pySHACL 很实用的一点。
如果数据不在本地:
data.ttl
而是在:
Fuseki
GraphDB
RDF Store
里面。
pySHACL 支持:
SPARQL Remote Graph Mode
也就是说:
远程 RDF Store
↓
SPARQL
↓
pySHACL
↓
Validation
这样就能做:
线上知识图谱数据检查。
五十六、这和 Apache Jena Fuseki 正好连起来
我们之前写过:
TDB2
↓
Fuseki
↓
SPARQL Endpoint
现在可以变成:
TDB2
↓
Fuseki
↓
SPARQL
↓
pySHACL
↓
Data Quality Report
也就是说:
Java
负责知识图谱服务。
Python
负责数据校验。
完全可以组合。
五十七、这就是一套 Knowledge Graph Quality Pipeline
例如:
MySQL
↓
Ontop
↓
RDF
PDF
↓
LLM Extraction
↓
RDF
API
↓
ETL
↓
RDF
所有数据:
↓
pySHACL
↓
Validation
↓
Pass
↓
Knowledge Graph
Fail
↓
Error Queue
这就是非常典型的:
Data Quality Gate。
五十八、LLM 抽 Triple 后尤其需要 SHACL
这是我认为现在最实际的场景之一。
比如让 LLM 从文档中抽:
员工
部门
项目
技术
LLM 输出:
ex:zhangsan
a ex:Employee ;
ex:belongsToDepartment
ex:rd ;
ex:hasSkill
ex:Go .
看起来很好。
但 LLM 可能也输出:
ex:lisi
a ex:Employee ;
ex:age
"二十六" ;
ex:belongsToDepartment
ex:Redis .
这里:
age 类型错
以及:
Department 指向 Skill
都错了。
不能因为:
LLM 输出了合法 Turtle
就认为:
知识是合法的。
五十九、正确流程应该是
Document
↓
LLM Extraction
↓
RDF Triple
↓
SHACL Validation
↓
Valid?
如果:
Yes
进入知识图谱。
如果:
No
可以:
退回重试
人工审核
LLM 修复
记录 Error
这就给 LLM 增加:
结构化防线。
六十、甚至可以让 LLM 根据 Validation Report 修复
例如 pySHACL 返回:
Focus Node:
ex:lisi
Path:
ex:age
Constraint:
DatatypeConstraintComponent
Expected:
xsd:integer
你可以把这个错误交给 LLM:
请根据 SHACL Validation Report
修复以下 RDF 数据。
LLM 原数据:
ex:lisi
ex:age
"二十六" .
修复成:
ex:lisi
ex:age
26 .
形成:
LLM Extraction
↓
SHACL
↓
Fail
↓
LLM Repair
↓
SHACL
↓
Pass
这就很像:
AI 数据自修复 Pipeline。
六十一、Python 可以直接做这个循环
from pyshacl import validate
def check(
rdf_file,
shape_file
):
conforms, graph, report = validate(
data_graph=rdf_file,
shacl_graph=shape_file,
inference="rdfs",
)
return (
conforms,
report
)
然后:
conforms, report = check(
"generated.ttl",
"shapes.ttl"
)
if not conforms:
print(
"需要修复:"
)
print(
report
)
下一步就可以:
调用 LLM 修复。
六十二、批量数据同样能做
比如每天:
10 万条知识
进入 Pipeline。
可以:
按 Batch
验证。
如果错误:
进入 Dead Letter Queue
例如:
Kafka
↓
Knowledge Extraction
↓
SHACL Validation
├── Pass → Graph Store
└── Fail → Error Topic
这已经是非常工程化的知识图谱架构了。
六十三、再封装一个 Validator 类
from pyshacl import validate
class KnowledgeValidator:
def __init__(
self,
shapes_file
):
self.shapes_file = (
shapes_file
)
def validate(
self,
data_file
):
conforms, graph, report = validate(
data_graph=data_file,
shacl_graph=self.shapes_file,
inference="rdfs",
abort_on_first=False,
)
return {
"valid":
conforms,
"report":
report,
"graph":
graph
}
使用:
validator = KnowledgeValidator(
"employee-shapes.ttl"
)
然后:
result = validator.validate(
"employee-data.ttl"
)
if result["valid"]:
print(
"数据可以入库"
)
else:
print(
"数据校验失败"
)
print(
result["report"]
)
六十四、再和 API 接起来
比如 FastAPI:
from fastapi import FastAPI
app = FastAPI()
接口:
@app.post(
"/knowledge/validate"
)
def validate_knowledge(
data_file: str
):
result = (
validator.validate(
data_file
)
)
return {
"valid":
result["valid"],
"report":
result["report"]
}
这样 SHACL 就变成一个:
Data Validation Service。
六十五、pySHACL 甚至自带 HTTP Server 模式
安装对应 HTTP 支持后,可以直接:
pyshacl --server
也就是说:
pySHACL
本身就可以作为:
Validation REST Service
使用。
默认还可以提供:
OpenAPI
Swagger
接口文档。
所以不一定非要自己再包 FastAPI。
六十六、知识图谱最终应该分成三层
我认为一个比较清晰的设计是:
第一层:
Ontology
描述:
什么是 Employee
什么是 Skill
Employee 和 Department 什么关系
第二层:
SHACL
描述:
Employee 数据必须长什么样
name 必须存在
employeeCode 格式
age 类型
hasSkill 至少一个
第三层:
Data
实际数据:
张三
李四
研发部
Go
Redis
最终:
Ontology
+
Shapes
+
Data
三层分开。
六十七、这三个东西不要混在一起
Ontology:
描述世界。
SHACL:
规定数据质量。
Data:
描述真实事实。
可以记:
Ontology
=
“是什么”
SHACL
=
“应该长什么样”
RDF Data
=
“现在实际是什么”
这三个概念搞清楚,知识图谱就突然清晰很多。
六十八、这也和 Java Bean Validation 很像
如果你写过 Java:
@NotNull
private String name;
@Min(18)
@Max(70)
private Integer age;
@Pattern(
regexp = "^E[0-9]{5}$"
)
private String employeeCode;
非常熟悉。
SHACL 本质上就是:
RDF 世界里的 Bean Validation。
对应关系:
@NotNull
≈
sh:minCount 1
@Size(max = 1)
≈
sh:maxCount 1
@Pattern
≈
sh:pattern
@Min
≈
sh:minInclusive
类型检查
≈
sh:datatype
所以 Java 程序员理解 SHACL,其实非常快。
六十九、但 SHACL 比 Bean Validation 更图结构化
因为它不仅能检查:
字段。
还可以检查:
关系目标的 Class
关系数量
多跳 Property Path
Node Shape
嵌套 Shape
Graph Pattern
也就是说:
Bean Validation
更像:
Object Validation
而:
SHACL
是:
Graph Validation。
七十、我们前面的项目终于完全串起来了
Protégé:
设计 Ontology
↓
Owlready2:
Python 操作 Ontology
+
Reasoner
↓
Apache Jena:
Java 操作 RDF
+
SPARQL
↓
TDB2 / Fuseki:
存储与服务化
↓
Ontop:
MySQL
→ Virtual RDF
↓
pySHACL:
检查 RDF 数据质量
↓
Graphiti:
时序知识
+
长期记忆
↓
TrustGraph:
GraphRAG
+
Provenance
+
Agent
这已经形成一套很完整的:
Semantic AI 技术栈。
总结
很多程序员第一次接触 Ontology,会觉得:
有 Class
有 Property
有 Domain / Range
还有 Reasoner
是不是:
数据质量问题都解决了?
其实没有。
因为:
Reasoner 的目标不是“挑错”。
它主要负责:
已知事实
+
语义规则
↓
推出更多知识
而数据治理真正需要的是:
这条数据完整吗?
这个字段必须存在吗?
类型对吗?
数量对吗?
格式对吗?
关系是不是指向正确 Class?
这些问题,正是:
SHACL
负责解决的。
所以:
OWL
和:
SHACL
并不是竞争关系。
而是:
Ontology
负责语义
Reasoner
负责推理
SHACL
负责验证
三者一起用。
对于现在越来越多的:
LLM → Triple → Knowledge Graph
系统尤其重要。
因为 LLM 可以帮我们:
理解文档
抽取 Entity
抽取 Relation
生成 RDF
但它不应该拥有:
“生成出来就一定正确”
这种特权。
更加可靠的 Pipeline 应该是:
LLM
↓
RDF
↓
pySHACL
↓
Validation
↓
Knowledge Graph
让:
AI 负责理解
而:
规则负责兜底。
如果只记住一句话:
OWL 告诉系统“知识意味着什么”,SHACL 告诉系统“这份数据合不合格”。
项目地址:
https://github.com/RDFLib/pySHACL

浙公网安备 33010602011771号