feat(datafabric): standalone ontology tool grounded on OWL + R2RML#911
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an optional fetch_ontology inner tool to the Data Fabric SQL sub-agent so the inner LLM can retrieve a configured ontology’s OWL schema from the QueryEngine REST API and use it to generate semantically-correct SQL.
Changes:
- Introduces an ontology REST client (
fetch_ontology_owl) with name validation and size limiting. - Adds a
fetch_ontologyleaf tool with an instance-level cache and wires it into the inner Data Fabric subgraph alongsideexecute_sql. - Threads
ontology_name/folder_keyinto the Data Fabric tool construction path (with an env-var fallback).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/uipath_langchain/agent/tools/datafabric_tool/ontology_fetch_tool.py |
New leaf tool (fetch_ontology) and cached fetcher wrapper for inner SQL agent use. |
src/uipath_langchain/agent/tools/datafabric_tool/ontology_client.py |
New client helper to fetch OWL content via EntitiesService.request_async, including name validation and payload cap. |
src/uipath_langchain/agent/tools/datafabric_tool/models.py |
Adds an intentionally-empty args schema (OntologyFetchInput) for the new tool. |
src/uipath_langchain/agent/tools/datafabric_tool/datafabric_tool.py |
Plumbs ontology_name / folder_key into the query handler creation (currently with env-var fallback). |
src/uipath_langchain/agent/tools/datafabric_tool/datafabric_subgraph.py |
Adds optional fetch_ontology tool binding and dispatch-by-tool-name inside the inner subgraph. |
…logy_file (drop local client)
…age.status to match host node
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ogy-fetch-tool # Conflicts: # pyproject.toml
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44905ac to
69d884c
Compare
…-builder sections Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flib R2RML parser - revert datafabric_subgraph.py / datafabric_tool.py to main (no pollution) - move ontology files under datafabric_tool/ontology/ with duplicated prompt-agnostic subgraph - replace regex R2RML parser with rdflib; add rdflib dep Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…P/CPD false positives - resolve_ontology_entities: gather folder-key + schema fetches concurrently - suppress S5332 on RDF namespace IRIs (identifiers, not endpoints) - exclude intentionally-duplicated ontology subpackage from Sonar CPD - clarify entity-name->folder routing model Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ogy-fetch-tool # Conflicts: # uv.lock
…ogy-fetch-tool # Conflicts: # pyproject.toml # uv.lock
…tity_set_async Resolve name->id then delegate to the SDK's public entity-set resolver to build the folder-scoped service, removing reliance on SDK-private config/execution_context.
…docstring path Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UIPath-Harshit
left a comment
There was a problem hiding this comment.
PR Review: feat(datafabric): standalone ontology tool grounded on OWL + R2RML
Overall: Well-structured, thoughtful PR. The isolation strategy (separate subpackage, entity tool untouched) is the right call. A few concerns below.
Positives
- Clean isolation — all ontology code in
datafabric_tool/ontology/, entity tool byte-identical to main. Sonar CPD exclusion is the right trade-off for keeping them decoupled. - Closed allow-list (Option A) — entities bounded by R2RML, LLM can't widen scope. Good security posture.
- rdflib over regex — real Turtle parser handles authoring variations correctly. Tests validate compact/multi-line/reordered syntax.
- Defense in depth — flag checked at both tool creation (
context_tool.py) and lazy init (_ensure_graph). Tool is fully inert when off. - OWL degrades gracefully, R2RML is fatal — correct priority since R2RML is the allow-list.
- Test coverage — comprehensive unit tests for parser, resolver, subgraph, prompt builder, fetcher. Edge cases covered well.
Concerns
1. rdflib as a new dependency (medium)
rdflibpulls inpyparsing. This adds ~5MB to the package for a feature-flagged path. Is lazy importing therdflibmodule an option? Currentlyontology_r2rml.pyimports it at module level — ifcontext_tool.pydoes the lazy import of the ontology package only when the flag is on, this is probably fine, but worth confirmingrdflibdoesn't get loaded for non-ontology agents.
2. Duplicated sub-graph (ontology_subgraph.py — 233 lines)
- I understand the rationale (entity tool stays untouched), but this is a full copy of
DataFabricGraph+QueryExecutor+ state model. The only difference is the constructor takessystem_prompt: strinstead of building it internally. Could the entity sub-graph be refactored to also accept a pre-built prompt, making duplication unnecessary? The PR description says "deliberately isolated" — if this is a temporary measure until the entity tool is also refactored, document the intended convergence timeline.
3. Folder resolution is sequential-then-concurrent but not batched optimally (ontology_tool.py:725-753)
- Step 1a resolves distinct folder paths concurrently (good). Step 1b resolves entities by name concurrently (good). But steps 1a and 1b are sequential — 1b waits for all folder keys before starting any entity lookups. Since entity lookups need the folder key, this is correct, but worth noting that for N entities across M folders, latency =
folder_resolve_time + entity_resolve_time(not parallelized across steps). Acceptable for small ontologies, but flag if california-schools (3 entities, 1 folder) takes noticeable time.
4. _MAX_ONTOLOGY_BYTES = 2_000_000 (ontology_fetcher.py:114)
- 2MB of raw text injected into a prompt is a lot of tokens (~500K+). The size cap protects against blowup, but in practice even a 200KB OWL could degrade LLM quality. Consider a tighter warning threshold or token-budget-aware truncation, especially if customers publish large ontologies.
5. asyncio.Lock for lazy init (ontology_tool.py:778)
- This is a per-handler lock, so it's scoped correctly. But if the handler is shared across event loops (unlikely but possible in some deployment patterns),
asyncio.Lockwould fail. Just a note — probably fine for the current runtime.
6. Missing error context in resolve_ontology_entities (ontology_tool.py:742-746)
- When
retrieve_by_name_asyncfails for one entity in theasyncio.gather, the whole batch fails with the first exception. Considerreturn_exceptions=True+ a summary error that names which entities failed, to aid debugging multi-entity ontologies.
7. Test: test_factory_builds_handler_from_ontology_set accesses private _ontologies (test_datafabric_ontology_tool.py:1321)
- Minor, but fragile. Consider testing via the tool description or metadata instead.
Nits
ontology_r2rml.py:344-345: The# NOSONARcomments are clear, but consider adding aruff: noqaequivalent if ruff also flagshttp://in strings.ontology_prompt_builder.py:199:(field.description or "").replace("|", r"\|").replace("\n", " ")— good defensive escaping. The entity tool's prompt builder should probably do the same if it doesn't already.ontology_tool.py:809:sdk = UiPath()inside_ensure_graph— this instantiates a new SDK client on every first-call. Is there a reason not to accept the SDK as a constructor parameter for testability?
Verdict
Approve with minor suggestions. The architecture is sound, security model is correct, and the feature is properly gated. The duplicated sub-graph is the main thing worth discussing — it's defensible now but should have a convergence plan. The rdflib dependency is justified by the robustness gains over regex parsing.
…t oversized files Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ogy-fetch-tool # Conflicts: # uv.lock
a429fe5 to
3b12c2d
Compare
|



What
Adds a standalone Data Fabric ontology tool for low-code agents. The agent selects ontologies (not entities); the tool derives the entities it may query from the ontology's R2RML, resolves their schemas, and runs the existing inner SQL sub-graph grounded on both the OWL (semantic schema) and the R2RML (ontology→table/column mapping).
An ontology context (
contextType: "datafabricontology") now becomes a tool on its own — noentitySetrequired inagent.json.How it works (first invocation, cached)
EntitiesService.get_ontology_file_async.rdflib→ the closed(entity_name, folder_path)allow-list, reading onerr:tableName(viarr:logicalTable) + oneuipath:folderPathperrr:TriplesMap.asyncio.gather): distinctfolderPath → folder_key(folders.retrieve_key_async), thenname → id(entities.retrieve_by_name_async), and delegate to the SDK's publicentities.resolve_entity_set_asyncto fetch the schemas and build the folder-scopedEntitiesService(routing each entity's queries to its folder). Using the public resolver means the tool builds nothing from SDK internals.DataFabricGraph— the inner agent still has a single tool,execute_sql.Everything from the sub-graph down (
execute_sql→query_entity_records_async) mirrors the entity tool.Layout — the entity tool is left untouched
All ontology code lives in its own
datafabric_tool/ontology/subpackage. The entity tool's files are byte-identical tomain:datafabric_tool.py,datafabric_subgraph.py,datafabric_prompt_builder.py, and the package__init__.pyare unchanged by this PR.context_tool.pyis the only shared file touched — it builds the standalone ontology tool for the ontology context (flag-gated) and imports the tool directly fromdatafabric_tool.ontology.New —
datafabric_tool/ontology/ontology_r2rml.py—rdflib-based parser:parse_r2rml_entities()→ the(entity_name, folder_path)allow-list (parses Turtle as an RDF graph;R2RMLParseErroron invalid Turtle or contract violations).ontology_tool.py—resolve_ontology_entities()(the concurrent resolver),DataFabricOntologyQueryHandler,create_datafabric_ontology_tool(); owns theDATAFABRIC_ONTOLOGY_FFflag constant.ontology_subgraph.py— a self-contained, prompt-agnostic duplicate ofDataFabricGraph(takes a pre-builtsystem_prompt), so the entity sub-graph stays identical tomain.ontology_prompt_builder.py— the ontology tool's inner prompt (OWL + R2RML + entity schemas), self-contained.ontology_fetcher.py—fetch_ontology_file(raw content + media type; raises) +fence_ontology_block.Design decisions
execute_sql's blast radius = the resolved set.uipath:folderPath(a folder path, not a GUID): R2RML has no folder concept and is deployment-agnostic;rr:tableNamestays a valid SQL table name. Folder identity is resolved through the trusted folder service, never from mapping content. (Authoring contract documented for the ontology-authoring skill.)rdflibparser: the R2RML is parsed as an RDF graph rather than scanned with regexes, so authoring variations (predicate order, whitespace, compact one-line maps, prefix/@basedifferences) are handled by a real Turtle parser. Validated against the livecalifornia-schoolsmapping fetched from Data Fabric.ontology_subgraph.pyis an intentional copy ofdatafabric_subgraph.pyso the entity path is not modified. It is excluded from Sonar copy-paste detection (sonar.cpd.exclusions) rather than sharing code, to preserve that isolation.uipath-langchain-pythonover existing publicuipath-platformmethods.Feature flag
DataFabricOntologyEnabled(default off), a single shared constant owned by the ontology subpackage.context_tool— off ⇒ no tool created, feature fully inert) and the handler's lazy init (re-checks before any OWL/R2RML fetch/parse/resolve). Off ⇒ the agent runs exactly as before; the entity tool is flag-independent.Security
execute_sql's single-statementsqlparseguard is unchanged.Testing
california-schoolsshape), resolver (concurrent folder-key + by-name id fetch, delegation toresolve_entity_set_async), the ontology prompt builder, the ontology sub-graph nodes, the factory, and the flag guard. New ontology modules are covered ≥99%.datafabric/ab, gpt-5.4,california-schoolsontology): flag prefetched on → R2RML + OWL fetched →folderPathresolved → 3 entities resolved by name → OWL+R2RML-grounded SQL (join on the R2RML FK) executed folder-scoped → correct answer. Confirmedretrieve_by_name(/metadata) returns populated field schemas.Notes / dependencies
ontologySeton the context,AgentContextType.DATA_FABRIC_ONTOLOGY,get_ontology_file_async, andresolve_entity_set_async. The feature needsuipath>=2.13.2/uipath-platform>=0.2.1;pyproject.tomlcurrently pins the higheruipath>=2.13.5/uipath-platform>=0.2.4adopted frommain. No env-based configuration fallback is used — ontologies come only from the context'sontologySet.rdflib>=7.0.0, <8.0.0for the R2RML parser.DataFabricOntologyEnabledinuipath-agents-python's_ALL_FLAGSprefetch + the gitops flag deployed for the target tenants (both already in place from the prior work).uipath:folderPathperrr:TriplesMap(authoring-skill guidelines provided) — otherwise resolution fails loudly by design.