A large part of my current job as Data Engineer at Logius (ministry of the internal affairs) consists out of developing linked data, RDF and Yarrml.
Testing the Linked Data was assigned to me. A list of requirements exists, and for each requirement a shacl script and some validation tests should be writen.

A perfect task for Agentic AI!

Setup

All requirements are stored in —/requirements, and the shacl scripts and tests are stored in —/shacl and —/tests respectively.

Requirements

All requirements are numbered and flagged with boolean to track progress

# Progress
SHACL code: FALSE
valid test set: FALSE
invalid test set: FALSE

# requirement 01
each resource should contain a rdfs:label. rdfs:label shopuld be a string, not a langString

Validation script

A python environment that can be controlled using MCP was created, the following script can be used to run the SHACL:

from pyshacl import validate
from rdflib import Graph

data_graph = Graph().parse("../input.ttl", format="turtle")
shapes_graph = Graph().parse("shapes.ttl", format="turtle")

results = validate(
    data_graph,
    shacl_graph=shapes_graph,
    inference='rdfs',
    abort_on_first=False,
    meta_shacl=False,
    advanced=True,
    debug=True
)

conforms, results_graph, results_text = results
print("Conforms?", conforms)
print(results_text)
results_graph.serialize("results.ttl", format="turtle")

Prompt

The following prompt was used to generate the SHACL code and test sets:

You are an expert in SHACL, RDF, and Turtle syntax. You will read requirements, generate SHACL and RDF test sets, validate them using PySHACL, and update progress—skipping items already marked complete.
PROJECT STRUCTURE

Base RDF reference (optional context): /rdf/voorbeeld-excel.ttl
Requirement files (Markdown): ./requirements/*.md
SHACL output: ./shacl/
Test RDF output: ./testsets/

A large part of my work as a Data Engineer at Logius (Ministry of the Interior) involves producing and validating linked data: RDF and YARRML mappings.

I was given the validation task for a set of requirements: for each requirement we must provide a SHACL shapes file plus minimal "valid" and "invalid" RDF test graphs. This made the work a good candidate for an agent-driven workflow.

## Local layout

- Requirements: `./requirements/` (one Markdown file per requirement)
- Generated SHACL shapes: `./shacl/`
- Generated test graphs: `./testsets/`

Each requirement contains a small Progress block that tracks three boolean flags:

```text
# Progress
SHACL code: FALSE
valid test set: FALSE
invalid test set: FALSE
```

When an artifact is generated and validated it is flipped to `TRUE` in the Progress block.

## Example requirement

R01 (example): "Each resource should contain an `rdfs:label` as a plain string (no language-tagged literal)."

## Validation script

I used a small Python validation helper (PySHACL + rdflib). Run this from the directory that contains your `shapes.ttl` and `input.ttl` files:

```python
from pyshacl import validate
from rdflib import Graph

data_graph = Graph().parse("../input.ttl", format="turtle")
shapes_graph = Graph().parse("shapes.ttl", format="turtle")

conforms, results_graph, results_text = validate(
    data_graph,
    shacl_graph=shapes_graph,
    inference='rdfs',
    abort_on_first=False,
    meta_shacl=False,
    advanced=True,
    debug=False
)

print("Conforms?", conforms)
print(results_text)
results_graph.serialize("results.ttl", format="turtle")
```

Or use the `pyshacl` CLI for quick checks:

```sh
pyshacl -s ./shacl/__shapes.ttl -d ./testsets/__valid.ttl --inference rdfs --abort false --advanced
pyshacl -s ./shacl/__shapes.ttl -d ./testsets/__invalid.ttl --inference rdfs --abort false --advanced
```

## Agent prompt (summary)

The agent was given a concise instruction: read each requirement, skip items already marked `TRUE`, generate the missing SHACL and minimal test graphs, validate them with PySHACL, and update the Progress block only after successful validation.

Key constraints for generated SHACL:

- Prefer explicit constraints (`sh:datatype`, `sh:minCount`, `sh:pattern`, `sh:nodeKind`, etc.)
- Use `sh:targetClass` where appropriate
- Add `sh:message` and set severity to `sh:Violation` for MUST requirements

## Generated artifacts (example: R01)

### SHACL (R01)

```ttl
@prefix sh:   <http://www.w3.org/ns/shacl#> .
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
@prefix ex:   <http://example.com/ns#> .

# Shape for R01: require an rdfs:label that is a plain string (no language-tagged literal)
ex:R01-ResourceLabelShape
    a sh:NodeShape ;
    sh:description "Each instance of ex:SomeClass MUST have an rdfs:label as xsd:string (no language tag)." ;
    sh:targetClass ex:SomeClass ;
    sh:property [
        sh:path rdfs:label ;
        sh:minCount 1 ;
        sh:datatype xsd:string ;
        sh:message "R01: Instances must have an rdfs:label as a plain string (no language tag)." ;
        sh:severity sh:Violation ;
    ] .
```

### Valid test (R01)

```ttl
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
@prefix ex:   <http://example.com/ns#> .

ex:res1 a ex:SomeClass ;
    rdfs:label "Plain label" .

ex:res2 a ex:SomeClass ;
    rdfs:label "Typed label"^^xsd:string .
```

### Invalid test (R01)

```ttl
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
@prefix ex:   <http://example.com/ns#> .

# Invalid: rdfs:label has the wrong datatype (integer)
ex:res1 a ex:SomeClass ;
    rdfs:label "123"^^xsd:integer .
```

## Validation summary

- Valid test: conforms = true (expected)
- Invalid test: conforms = false (expected)

After successful validation the Progress block for R01 was updated to mark the three flags as `TRUE`.