知识图谱里的数据为什么不能只靠 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
posted @ 2026-09-29 11:10  JavaPub  阅读(4)  评论(0)    收藏  举报