很多人第一次接触知识图谱时,会把注意力全部放在:
- RDF
- 三元组
- Ontology
- OWL
- SPARQL
- GraphRAG
但真正把知识图谱放到生产环境里之后,你很快会遇到另外一个非常现实的问题:
知识图谱里的数据写错了怎么办?
例如我们有一个人员知识图谱:
张三 -> 姓名 -> "张三" 张三 -> 年龄 -> 28 张三 -> 邮箱 -> "zhangsan@example.com" 张三 -> 所属公司 -> 阿里巴巴看起来非常正常。
但如果某一天数据变成这样:
张三 -> 年龄 -> "二十八" 张三 -> 邮箱 -> "123456" 张三 -> 所属公司 -> "Alibaba"从 RDF 的角度来说,这些数据完全有可能依然是合法 RDF。
问题在于:
RDF 语法合法,不代表你的业务数据是正确的。
这时候,就轮到SHACL出场了。
而 Python 生态里,一个非常常用的 SHACL 校验工具就是:
pySHACL
项目地址:
https://github.com/RDFLib/pySHACLpySHACL 是一个基于 Python 的 SHACL Validator,可以使用 SHACL Shapes 对 RDF Graph 进行数据验证。项目底层使用 RDFLib 处理 RDF 数据,并按照 SHACL 规范执行约束验证。
今天我们不讲特别复杂的知识图谱理论。
就讲清楚一个问题:
如果知识图谱里的数据错了,怎么通过 pySHACL 自动找出来?
我们重点看 5 类非常实用的规则:
minCount datatype class pattern cardinality学完这几个,已经足够解决相当一部分实际的数据质量问题。
一、先搞清楚:SHACL 到底解决什么问题?
先来看传统关系型数据库。
假设我们有这样一张 MySQL 表:
CREATETABLEuser(idBIGINTPRIMARYKEY,nameVARCHAR(100)NOTNULL,ageINT,emailVARCHAR(255),company_idBIGINT);数据库天然可以帮我们限制一些东西。
比如:
name 不能为空 age 必须是 INT id 必须唯一甚至可以进一步添加:
ALTERTABLEuserADDCONSTRAINTchk_ageCHECK(age>=0);也就是说:
关系型数据库不仅存数据,还可以通过 Schema 和 Constraint 对数据进行约束。
但是 RDF 世界不太一样。
例如:
@prefix ex: <http://example.org/> . ex:zhangsan ex:name "张三" ; ex:age "二十八岁" ; ex:email "哈哈哈" .它是不是 RDF?
是。
语法上并没有什么大问题。
但从我们的业务角度:
age = "二十八岁"可能就是错误数据。
因为我们希望年龄必须是:
xsd:integer邮箱也不能随便写。
于是我们需要另外定义一套:
这个 RDF Graph 应该长什么样。
这就是 SHACL 的核心思想。
SHACL 全称:
Shapes Constraint Language你可以把它简单理解成:
给 RDF / 知识图谱定义数据校验规则。
W3C 的 SHACL Recommendation 定义了包括sh:minCount、sh:maxCount、sh:datatype、sh:class、sh:pattern等约束组件,用来描述 RDF 节点和属性应该满足什么条件。
二、理解 SHACL 最简单的方法:把它当成“知识图谱版 JSON Schema”
很多程序员第一次看到 SHACL:
ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; ] .第一反应可能是:
这什么东西?
实际上思想特别简单。
如果你用过 JSON Schema:
{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}SHACL 做的是类似的事情。
只不过:
JSON Schema验证:
JSON而:
SHACL验证:
RDF Graph你甚至可以粗略这样理解:
RDF ≈ 数据 SHACL ≈ 数据规则 pySHACL ≈ 校验器整个流程就是:
RDF Data ↓ SHACL Shapes ↓ pySHACL ↓ Validation Report三、准备一个故意写错的知识图谱
我们直接做一个人员知识图谱。
新建:
data.ttl内容:
@prefix ex: <http://example.org/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:company001 a ex:Company ; ex:name "JavaPub Technology" . ex:zhangsan a ex:Person ; ex:name "张三" ; ex:age 28 ; ex:email "zhangsan@example.com" ; ex:worksFor ex:company001 .这里的数据目前没有什么问题。
我们可以把它理解成:
张三 ├── 类型:Person ├── 姓名:张三 ├── 年龄:28 ├── 邮箱:zhangsan@example.com └── 公司:company001接下来我们专门制定一套规则来检查它。
四、第一个规则:minCount,检查“必填字段”
这是 SHACL 里最好理解,同时也是最常用的规则之一。
假设我们的业务要求:
每一个 Person 都必须有 name。
那么可以写:
@prefix ex: <http://example.org/> . @prefix sh: <http://www.w3.org/ns/shacl#> . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; ] .重点就是:
sh:minCount 1意思是:
ex:name至少出现一次。
W3C SHACL 规范中,sh:minCount表示某个属性对应的 value nodes 最少应该有多少个;当实际数量小于这个值时,就会产生 Validation Result。
把它翻译成程序员熟悉的写法,大概就是:
iflen(person.name)<1:raiseValidationError("name不能为空")或者 SQL:
nameVARCHAR(100)NOTNULL五、故意制造一条错误数据
现在把:
ex:zhangsan a ex:Person ; ex:name "张三" .改成:
ex:zhangsan a ex:Person ; ex:age 28 .也就是说:
name 没了按照我们的 SHACL Shape:
sh:minCount 1这条数据应该无法通过。
六、安装 pySHACL
Python 环境直接安装:
pipinstallpyshacl官方项目也提供 CLI,可以直接给它数据图和 Shapes Graph 进行校验。
例如:
pyshacl-sshapes.ttl data.ttl其中:
data.ttl是:
真正的数据而:
shapes.ttl是:
数据规则可以进一步输出人类更容易阅读的报告:
pyshacl\-sshapes.ttl\-fhuman\data.ttl当 Graph 不满足 Shapes 时,pySHACL 会返回 Non-Conformant 的校验报告。
七、用 Python 调用 pySHACL
很多时候我们不会单独跑命令,而是集成到自己的 Python 服务里。
例如:
frompyshaclimportvalidate conforms,results_graph,results_text=validate(data_graph="data.ttl",shacl_graph="shapes.ttl")print("校验是否通过:",conforms)print(results_text)pySHACL 的validate()会返回三个主要结果:
conforms results_graph results_text分别可以理解为:
conforms ↓ 是否通过 results_graph ↓ RDF 格式的验证结果 results_text ↓ 人类可读的验证报告这也是 pySHACL 官方 Python API 的基本使用方式。
因此我们可以这样写:
frompyshaclimportvalidate conforms,result_graph,result_text=validate(data_graph="data.ttl",shacl_graph="shapes.ttl",inference="none",abort_on_first=False)ifconforms:print("数据正常")else:print("发现错误")print(result_text)这已经可以变成真正的数据质量检查程序了。
八、datatype:年龄不能写成“二十八岁”
下面看第二个非常有价值的约束:
datatype假设我们规定:
Person.age必须是:
xsd:integerSHACL 可以这样写:
@prefix ex: <http://example.org/> . @prefix sh: <http://www.w3.org/ns/shacl#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; ] .正确数据:
ex:zhangsan ex:age 28 .或者显式写:
ex:zhangsan ex:age "28"^^xsd:integer .但下面这种:
ex:zhangsan ex:age "二十八岁" .就不符合我们的约束。
因为:
"二十八岁"本质是字符串。
而我们要求:
xsd:integer这就是:
sh:datatype xsd:integer真正有价值的地方。
九、datatype 非常适合抓 ETL 数据错误
想象你从 Excel 导入知识图谱。
原始表格:
姓名 年龄 张三 28 李四 35 王五 未知 赵六 31岁程序批量转换成 RDF 后可能得到:
ex:zhangsan ex:age 28 . ex:lisi ex:age 35 . ex:wangwu ex:age "未知" . ex:zhaoliu ex:age "31岁" .如果有几十万条数据,你显然不可能人工一个个检查。
这时候:
sh:datatype xsd:integer实际上就相当于一台:
自动知识图谱质检机。
所有不是整数的数据都可以在入库阶段被拦下来。
十、pattern:邮箱格式对不对?
但是仅仅判断:
email 是 string显然还不够。
因为下面这些都是字符串:
zhangsan@example.com 123456 abc 哈哈哈所以还需要:
pattern例如:
sh:property [ sh:path ex:email ; sh:datatype xsd:string ; sh:pattern "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" ; ] .这里:
sh:pattern就是正则表达式约束。
SHACL 对sh:pattern的定义是使用兼容 SPARQLREGEX的模式,对 value node 的字符串表示执行匹配。
例如:
zhangsan@example.com可以通过。
但是:
123456无法通过。
十一、pattern 不只是检查邮箱
很多人看到 pattern 第一反应就是正则。
但在知识图谱场景里,它非常有用。
例如检查手机号:
sh:pattern "^1[3-9][0-9]{9}$" ;检查员工号:
EMP-2026-00001可以写:
sh:pattern "^EMP-[0-9]{4}-[0-9]{5}$" ;检查商品编号:
SKU-A001 SKU-B102可以写:
sh:pattern "^SKU-[A-Z][0-9]{3}$" ;甚至可以约束 URL:
sh:pattern "^https://.*" ;所以:
pattern特别适合解决:
数据类型没问题,但是内容格式错了。
十二、class:属性指向的对象类型对不对?
下面这个非常重要。
假设:
Person有一个:
worksFor属性。
我们的要求是:
worksFor 必须指向一个 Company。
比如:
ex:zhangsan ex:worksFor ex:company001 . ex:company001 a ex:Company .这是合理的。
SHACL:
sh:property [ sh:path ex:worksFor ; sh:class ex:Company ; ] .什么意思?
就是:
zhangsan | | worksFor ↓ company001 | | rdf:type ↓ Company十三、如果 worksFor 指向一个 Person 呢?
假设有人把数据写成:
ex:zhangsan ex:worksFor ex:lisi . ex:lisi a ex:Person .语法有没有问题?
没有。
它依然是一组三元组:
张三 -> 就职于 -> 李四RDF 不会因为业务语义奇怪就自动拒绝。
但是:
sh:class ex:Company就可以把它检查出来。
这也是 SHACL 和普通 JSON Schema 一个非常大的不同。
因为知识图谱里的数据关系往往是:
Node → Node所以不仅要检查:
属性值是什么类型还要检查:
属性指向的节点是什么 Class。十四、class 在实际知识图谱里特别重要
比如医疗知识图谱:
Patient ↓ takesDrug Drug你可以约束:
sh:class ex:Drug公司知识图谱:
Employee ↓ worksFor Company可以约束:
sh:class ex:Company商品知识图谱:
Product ↓ belongsToCategory Category可以要求:
sh:class ex:Category科研知识图谱:
Paper ↓ author Researcher可以要求:
sh:class ex:Researcher这已经不仅仅是字段校验了。
而是在:
验证知识图谱中的关系语义是否正确。
十五、Cardinality:一个人到底能有几个出生日期?
接下来讲一个知识图谱里非常重要的概念:
Cardinality中文一般叫:
基数简单理解就是:
一个属性允许出现多少次。
SHACL 里非常常见的就是:
sh:minCount sh:maxCount例如:
sh:minCount 1 ; sh:maxCount 1 ;意思就是:
必须有,而且只能有一个。
SHACL 规范把sh:minCount和sh:maxCount都归到 Cardinality Constraint Components。minCount规定最少值数量,maxCount规定最多值数量。
十六、一个很典型的错误:两个出生日期
例如:
ex:zhangsan ex:birthDate "1995-01-01"^^xsd:date ; ex:birthDate "1996-01-01"^^xsd:date .语法完全合法。
但是一般业务规则可能要求:
出生日期只能有一个那么:
sh:property [ sh:path ex:birthDate ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:date ; ] .这样:
0 个 birthDate不行。
1 个 birthDate通过。
2 个 birthDate也不行。
是不是特别像数据库?
实际上就类似:
NOT NULL + 单值约束十七、minCount 和 maxCount 可以组合出很多规则
可选字段
sh:maxCount 1表示:
0 或 1 个例如:
middleName有的人有,有的人没有。
必填字段
sh:minCount 1表示:
至少一个必须且唯一
sh:minCount 1 ; sh:maxCount 1 ;表示:
有且只有一个例如:
身份证号 出生日期 员工编号至少两个
比如团队要求至少两位负责人:
sh:minCount 2最多三个标签
sh:maxCount 3所以 SHACL 其实可以表达非常多业务规则。
十八、把规则组合起来
真正生产环境里不会只写一个约束。
我们可以完整定义一个:
PersonShape例如:
@prefix ex: <http://example.org/> . @prefix sh: <http://www.w3.org/ns/shacl#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; ] ; sh:property [ sh:path ex:age ; sh:maxCount 1 ; sh:datatype xsd:integer ; ] ; sh:property [ sh:path ex:email ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; sh:pattern "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" ; ] ; sh:property [ sh:path ex:worksFor ; sh:minCount 1 ; sh:maxCount 1 ; sh:class ex:Company ; ] .翻译成人话:
Person: name: 必须存在 只能有一个 必须是字符串 age: 最多一个 必须是整数 email: 必须存在 只能有一个 必须是字符串 而且必须符合邮箱格式 worksFor: 必须存在 只能有一个 而且必须指向 Company到这里,知识图谱已经第一次拥有了真正意义上的:
数据契约。
十九、现在故意造一份“全是 Bug”的 RDF
例如:
@prefix ex: <http://example.org/> . ex:zhangsan a ex:Person ; ex:name "张三" ; ex:name "老张" ; ex:age "28岁" ; ex:email "123456" ; ex:worksFor ex:lisi . ex:lisi a ex:Person .这里至少有四个问题。
第一:
name 出现了两个违反:
sh:maxCount 1第二:
age = "28岁"违反:
sh:datatype xsd:integer第三:
email = "123456"违反:
sh:pattern第四:
worksFor → lisi但:
lisi = Person而我们要求:
worksFor → Company违反:
sh:class ex:Company这就是 SHACL 真正有意思的地方:
它不是检查 RDF 能不能解析,而是在检查这些 RDF 数据是否符合我们定义的业务语义。
二十、Python 里自动处理校验失败
我们可以进一步写一个实际一点的程序:
frompyshaclimportvalidatedefvalidate_graph(data_file,shapes_file):conforms,results_graph,results_text=validate(data_graph=data_file,shacl_graph=shapes_file,inference="none",abort_on_first=False,meta_shacl=False,advanced=False,debug=False)ifconforms:print("✅ RDF 数据校验通过")returnTrueprint("❌ RDF 数据存在问题")print("-"*80)print(results_text)returnFalseif__name__=="__main__":validate_graph("data.ttl","shapes.ttl")这里特别注意:
abort_on_first=False意味着:
不要发现第一个错误就停。
比如一份数据有:
27 个问题我们希望一次全部找出来,而不是:
改一个 运行一次 再改一个 再运行一次二十一、还可以直接使用 RDFLib Graph
如果你的知识图谱本来就是程序动态生成的,就没有必要先保存文件。
可以直接使用:
fromrdflibimportGraphfrompyshaclimportvalidate data_graph=Graph()data_graph.parse(data=""" @prefix ex: <http://example.org/> . ex:zhangsan a ex:Person ; ex:name "张三" ; ex:age "二十八" . """,format="turtle")shape_graph=Graph()shape_graph.parse(data=""" @prefix ex: <http://example.org/> . @prefix sh: <http://www.w3.org/ns/shacl#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; ] ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; ] . """,format="turtle")conforms,results_graph,results_text=validate(data_graph,shacl_graph=shape_graph)print(conforms)print(results_text)这样就能很容易接进:
Flask FastAPI Django ETL Pipeline Airflow 数据同步服务 知识图谱导入程序里面。
二十二、SHACL 最适合放在哪一层?
我比较推荐一个思路:
Excel CSV MySQL API 爬虫 LLM ↓ 数据转换 ↓ RDF ↓ SHACL Validation ↓ 合法 ↓ Graph Database也就是说:
SHACL 放在知识图谱入库之前。
例如:
rdf=transform_to_rdf(raw_data)result=validate_rdf(rdf)ifresult:save_to_graph_database(rdf)else:save_to_error_queue(rdf)这样 Graph Database 里面尽量只留下干净数据。
二十三、尤其是现在 LLM 生成知识图谱,更需要 SHACL
这几年非常流行一种做法:
文章 PDF 网页 合同 论文 ↓ LLM ↓ 实体抽取 关系抽取 ↓ RDF ↓ 知识图谱问题来了。
大模型很强。
但大模型不是数据库 Constraint。
例如你告诉模型:
请从文章中提取人物姓名、年龄、邮箱和所属公司。它最终可能生成:
{"name":"张三","age":"大约30岁","email":"暂无","company":"可能是某科技公司"}人看起来似乎还能理解。
但是知识图谱真正入库时:
age = "大约30岁"到底是不是整数?
company到底有没有对应 Company 节点?
email = "暂无"到底算邮箱还是缺失?
所以一个越来越实用的架构就是:
LLM ↓ 生成 RDF ↓ SHACL ↓ 发现错误 ↓ 修复 / 重试 ↓ Graph Database换句话说:
LLM 负责生成,SHACL 负责守门。
二十四、甚至可以让 LLM 根据 SHACL 自动修数据
再进一步。
假设 pySHACL 返回:
Person: zhangsan Property: age Constraint: datatype xsd:integer Current Value: "28岁"我们完全可以把这个结果重新交给模型:
下面是一条 RDF 数据: ex:zhangsan ex:age "28岁" . SHACL 校验要求: ex:age 的 datatype 必须是 xsd:integer。 请修复 RDF。模型返回:
ex:zhangsan ex:age 28 .然后:
再次 pySHACL 校验如果通过:
写入数据库于是一个自动循环就出现了:
LLM Generate ↓ pySHACL Validate ↓ 失败 ↓ LLM Repair ↓ pySHACL Validate ↓ 成功 ↓ Graph DB这个设计其实非常适合现在的:
GraphRAG 企业知识库 AI Agent Memory Ontology Pipeline二十五、SHACL 和 OWL 到底有什么区别?
这里也是新手非常容易混淆的地方。
有人会问:
有 OWL 了为什么还要 SHACL?
可以先用一个非常粗略、但容易理解的方法区分:
OWL 更关心: 这个世界的知识意味着什么? SHACL 更关心: 这份数据合不合格?例如:
Employee 是 Person 的子类这比较像:
OWL / Ontology而:
Employee 必须有 employeeId employeeId 只能有一个 employeeId 必须符合 EMP-XXXXX 格式这明显更像:
SHACL所以实际系统中,两者完全可以一起出现:
Ontology / OWL ↓ 定义知识语义 SHACL ↓ 定义数据质量约束而 pySHACL 本身也支持配合 RDFS / OWL-RL 推理进行验证,例如其validate()API 可以通过inference参数配置none、rdfs、owlrl或both。
二十六、最值得先掌握的 5 个 SHACL 规则
如果你刚学 SHACL,我并不建议一开始把所有 Constraint 都背下来。
先记住下面几个就够了。
sh:minCount解决:
这个字段是不是必须存在?sh:maxCount解决:
最多允许几个值?sh:datatype解决:
这个 Literal 是不是正确的数据类型?sh:class解决:
这个关系指向的是不是正确类型的节点?sh:pattern解决:
字符串内容是不是符合指定格式?如果你能熟练使用这五个,已经能覆盖很多常见的数据质量问题。
二十七、最后看一张完整关系图
整个知识图谱校验流程其实可以压缩成:
PersonShape │ ┌────────────┼────────────┐ │ │ │ name age email │ │ │ minCount=1 datatype pattern maxCount=1 integer EMAIL │ │ │ └────────────┼────────────┘ │ Person │ worksFor │ ▼ Company │ class check而 pySHACL 做的事情就是:
RDF Graph │ ▼ ┌────────────┐ │ pySHACL │ └────────────┘ ▲ │ SHACL Shapes │ ▼ Validation Report写在最后
很多人学习知识图谱时,只关注:
怎么把数据存进去?但真正进入生产环境后,更重要的问题往往变成:
怎么保证存进去的数据是对的?
因为错误的知识图谱,比没有知识图谱更加危险。
如果:
年龄错了可能只是一个字段错误。
但如果:
人物关系错了 公司关系错了 药物关系错了 供应链关系错了这些错误最终进入:
GraphRAG 搜索系统 推荐系统 AI Agent 智能问答那么模型得到的上下文本身就是错误的。
再强的大模型:
Garbage In ↓ Garbage Out所以在一个相对完整的知识图谱工程里,我更喜欢这样的流程:
原始数据 ↓ RDF ↓ Ontology ↓ SHACL ↓ pySHACL ↓ 验证报告 ↓ 清洗 / 修复 ↓ Graph Database ↓ GraphRAG / Agent如果把 Ontology 看成:
知识图谱的语义说明书。
那么 SHACL 更像:
知识图谱的质检标准。
而 pySHACL:
就是那个真正拿着质检标准,一条条帮你检查 RDF 数据的人。
对于准备做知识图谱、GraphRAG、企业知识库的人来说,这一层非常容易被忽略。
但它可能恰恰决定了:
你的知识图谱到底只是“有数据”,还是“数据真的可信”。