Skip to content

Commit 96912a6

Browse files
feat: add optional OWL ontology compiler for Data Fabric writes
Builds the ontology layer from write RFC v2. The OWL ontology is the authoring/storage format; it is compiled into a CompiledOntology intermediate representation (NOT injected as raw OWL — research shows LLMs reason poorly over raw OWL Turtle). - compiled_ontology.py: CompiledOntology model (entity_access, measure_fields, state_fields, reference_fields, hitl_operations, entity_relationships) per RFC §5.2 - ontology_compiler.py: compile_ontology(owl_turtle) via rdflib. Supports both ontology dialects — the .ttl dialect (rdfs:subClassOf df:WritableEntity + action-derived ops + df:hasField) and the RFC dialect (a df:WritableEntity + df:allowsOperation). Resilient to partial annotations; raises OntologyCompileError only on malformed Turtle. - write_validation.py: validate_mutation_intent gains optional compiled_ontology — rejects operations not in entity_access. State transition validation deferred to v3 (documented TODO). - datafabric_tool.py: DataFabricWriteHandler best-effort fetches + compiles the ontology via get_ontology_file_async. The method is absent from the current platform package, so this degrades gracefully to the metadata-only path (compiled_ontology stays None) — the build does not break. - rdflib>=7.0.0 added to dependencies. 23 new tests (refund + order-management dialects, RFC dialect, graceful paths). 109 datafabric_tool tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 80886b2 commit 96912a6

8 files changed

Lines changed: 867 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies = [
2424
"pillow>=12.1.1",
2525
"a2a-sdk>=0.2.0,<1.0.0",
2626
"uipath-langchain-client[openai]>=1.14.0,<1.15.0",
27+
"rdflib>=7.0.0,<8.0.0",
2728
]
2829

2930
classifiers = [
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Compiled OWL ontology model for Data Fabric writes.
2+
3+
A ``CompiledOntology`` is the intermediate representation produced by
4+
``ontology_compiler.compile_ontology`` from an OWL 2 QL Turtle source
5+
(the ``df:`` write-extension vocabulary, see ``p1-owl-write-extension.ttl``).
6+
7+
The ontology is OPTIONAL. When present it enriches and constrains writes
8+
with semantics that entity metadata alone cannot express:
9+
10+
- which entities are writable and which operations they allow
11+
- field semantics (state / measure / reference)
12+
- HITL markers on destructive operations
13+
- entity-to-entity relationships
14+
15+
When the ontology is absent the metadata-only write path still works
16+
(graceful fallback). See RFC ``p1-write-rfc-v2-ontology.md`` §5.2.
17+
"""
18+
19+
from __future__ import annotations
20+
21+
from pydantic import BaseModel, Field
22+
23+
24+
class CompiledOntology(BaseModel):
25+
"""Result of compiling an OWL write-extension ontology.
26+
27+
All members are keyed by the ``df:entityKey`` / ``df:fieldKey`` strings,
28+
which are the exact values the LLM uses in ``DataFabricWriteInput``.
29+
Field-level members are keyed as ``"<entity_key>.<field_key>"``.
30+
31+
Every member defaults to empty, so a partial or empty ontology yields a
32+
valid (if sparse) ``CompiledOntology`` rather than raising.
33+
"""
34+
35+
entity_access: dict[str, set[str]] = Field(default_factory=dict)
36+
"""entity_key -> set of allowed operations, e.g. ``{"insert", "update"}``."""
37+
38+
measure_fields: dict[str, str] = Field(default_factory=dict)
39+
"""``"entity_key.field_key"`` -> ``"additive"`` | ``"replacement"``."""
40+
41+
state_fields: dict[str, str] = Field(default_factory=dict)
42+
"""``"entity_key.field_key"`` -> choiceset / state-machine key."""
43+
44+
reference_fields: dict[str, str] = Field(default_factory=dict)
45+
"""``"entity_key.field_key"`` -> referenced entity_key (FK target)."""
46+
47+
hitl_operations: dict[str, set[str]] = Field(default_factory=dict)
48+
"""entity_key -> set of operations that require human-in-the-loop."""
49+
50+
entity_relationships: dict[str, list[str]] = Field(default_factory=dict)
51+
"""entity_key -> list of referenced entity_keys (semantic relationships)."""
52+
53+
def is_empty(self) -> bool:
54+
"""True if no ontology facts were extracted (graceful-fallback signal)."""
55+
return not (
56+
self.entity_access
57+
or self.measure_fields
58+
or self.state_fields
59+
or self.reference_fields
60+
or self.hitl_operations
61+
or self.entity_relationships
62+
)

src/uipath_langchain/agent/tools/datafabric_tool/datafabric_tool.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from uipath.platform.entities import DataFabricEntityItem
2828

2929
from ..base_uipath_structured_tool import BaseUiPathStructuredTool
30+
from .compiled_ontology import CompiledOntology
3031
from .models import (
3132
DataFabricQueryInput,
3233
DataFabricWriteInput,
@@ -168,6 +169,7 @@ def __init__(
168169
self._entity_set = entity_set
169170
self._write_schemas: dict[str, EntityWriteSchema] | None = None
170171
self._write_tool_description: str | None = None
172+
self._compiled_ontology: CompiledOntology | None = None
171173
self._executor: Any | None = None
172174
self._init_lock = asyncio.Lock()
173175

@@ -203,12 +205,60 @@ async def _ensure_initialized(self) -> None:
203205
writable_fields=writable,
204206
)
205207

208+
# Optional ontology layer: fetch + compile the OWL ontology if the
209+
# platform exposes get_ontology_file_async. This method may only
210+
# exist on a feature branch — if it is absent we degrade gracefully
211+
# to the metadata-only write path (compiled_ontology stays None).
212+
self._compiled_ontology = await self._maybe_compile_ontology(
213+
resolution.entities_service
214+
)
215+
216+
entity_access = (
217+
self._compiled_ontology.entity_access
218+
if self._compiled_ontology
219+
else None
220+
)
206221
self._write_tool_description = build_write_tool_description(
207-
self._write_schemas
222+
self._write_schemas,
223+
entity_access=entity_access,
208224
)
209225

210226
self._executor = WriteExecutor(resolution.entities_service)
211227

228+
async def _maybe_compile_ontology(
229+
self, entities_service: Any
230+
) -> CompiledOntology | None:
231+
"""Best-effort fetch + compile of the optional OWL ontology.
232+
233+
Returns the compiled ontology, or ``None`` when no ontology is
234+
available or the platform package does not expose the fetch method.
235+
Never raises — any failure degrades to the metadata-only path.
236+
"""
237+
get_ontology = getattr(entities_service, "get_ontology_file_async", None)
238+
if not callable(get_ontology):
239+
logger.debug(
240+
"EntitiesService has no get_ontology_file_async; "
241+
"skipping ontology compilation (metadata-only writes)."
242+
)
243+
return None
244+
245+
from .ontology_compiler import compile_ontology
246+
247+
try:
248+
owl_turtle = await get_ontology("owl")
249+
if not owl_turtle:
250+
logger.debug("No OWL ontology returned; metadata-only writes.")
251+
return None
252+
compiled = compile_ontology(owl_turtle)
253+
logger.debug(
254+
"Compiled ontology with %d writable entities.",
255+
len(compiled.entity_access),
256+
)
257+
return compiled
258+
except Exception as exc: # graceful no-op on any fetch/parse failure
259+
logger.debug("Ontology fetch/compile skipped: %s", exc)
260+
return None
261+
212262
async def __call__(
213263
self,
214264
entity_key: str,
@@ -243,8 +293,10 @@ async def __call__(
243293
fields=fields,
244294
)
245295

246-
# Validate
247-
errors = validate_mutation_intent(intent, self._write_schemas)
296+
# Validate (ontology, when present, constrains allowed operations)
297+
errors = validate_mutation_intent(
298+
intent, self._write_schemas, self._compiled_ontology
299+
)
248300
if errors:
249301
return json.dumps(
250302
{

0 commit comments

Comments
 (0)