Skip to content

feat(datafabric-tool): ground relationship-field joins in text-to-SQL prompt [DS-8791]#962

Open
milind-jain-uipath wants to merge 13 commits into
mainfrom
feat/datafabric-relationship-join-grounding
Open

feat(datafabric-tool): ground relationship-field joins in text-to-SQL prompt [DS-8791]#962
milind-jain-uipath wants to merge 13 commits into
mainfrom
feat/datafabric-relationship-join-grounding

Conversation

@milind-jain-uipath

@milind-jain-uipath milind-jain-uipath commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What

Grounds the Data Fabric SQL sub-graph to join related (foreign-key) entities.

A relationship field stores the related record's Id, not its attributes. The prompt now surfaces each entity's relationships and instructs the model to join on related.Id = parent.<relField>, projecting explicit related columns.

Changes

  • models.py: FieldSchema carries ref_entity_table, ref_join_key (Id), and ref_field_name; foreign-key fields are tagged fk; adds is_relationship.
  • datafabric_prompt_builder.py: populates the reference fields from SDK field metadata (reference_entity, reference_field, field_display_type); renders a per-entity "Relationships" subsection with the join expression, gated to related entities present in the set.
  • prompts/v1.py: adds a RELATIONSHIP FIELDS section — join on related.Id = parent.<relField>, project explicit related columns, and choose the join type by intent: LEFT JOIN for optional relationships (keep parent rows), INNER JOIN when the relationship field is required, the related record must exist, or you filter on the related entity's columns.
  • datafabric_prompts.py: SQL_CONSTRAINTS permits LEFT JOIN only for relationship/foreign-key joins on Id; general joins remain INNER-only.

Dependency

Depends on the related-entity auto-registration in UiPath/Agents#5725: the related entity must be present in the agent's entity set (and thus routable) for the join to resolve at query time. This PR grounds the SQL; that PR makes the related entity queryable.

Testing

  • pytest tests/agent/tools passes.
  • ruff check, ruff format --check, and mypy clean on src/uipath_langchain/agent.

Rendered prompt (example)

Captured from a run over an entity set of SalesOrderSpace (with an optional relationship field accountAccountSpace) and AccountSpace.

Relationship guidance (static, from prompts/v1.py)
RELATIONSHIP FIELDS (foreign keys):
- A relationship field (marked `fk` in the schema) stores the RELATED record's Id (a GUID) — not its name, label, or any other attribute. Comparing such a column to a human-readable value (e.g. `WHERE Account = 'Acme'`) will never match.
- To filter on, or return, the related entity's attributes, JOIN the related entity on its Id and project the specific column(s) you need:
    SELECT parent.<cols>, related.<field> FROM parent LEFT JOIN related ON related.Id = parent.<relField>
  then put your filter/selection on `related`'s columns (e.g. `WHERE related.Name = 'Acme'`). The exact join and the related entity's representative field are listed under "Relationships for <table>" in the entity schemas above.
- Choose the join type by intent (the schema tags a relationship field `required` or not):
  - LEFT JOIN when the relationship is optional (not `required`) and you want every parent row, including those where it is unset (the related columns come back NULL). Use this when the question is about the parent entity and only enriches it with related data.
  - INNER JOIN when the relationship is marked `required` (the related record always exists, so no parent rows are dropped), when the related record must otherwise exist, or when you filter on the related entity's columns (e.g. "orders whose account region is APAC").
- If you only need the related record's identifier itself, select the relationship field directly — no JOIN.
- Only equi-joins on the related entity's Id are supported (`JOIN related ON related.Id = parent.<relField>`); the related entity's schema is one of the entities listed above.
Per-entity schema + Relationships subsection (rendered from SDK metadata)
### Entity: SalesOrderSpace (SQL table: `SalesOrderSpace`)
_Child entity (space-folder) with optional relationship to AccountSpace_

| Field | Type | Description |
|-------|------|-------------|
| OrderNumber | NVARCHAR, required |  |
| Amount | DECIMAL |  |
| account | UNIQUEIDENTIFIER, fk |  |
| UpdateTime | DATETIMEOFFSET, system | System built-in field |
| Id | UNIQUEIDENTIFIER, required, system | System built-in field |
| RecordOwner | UNIQUEIDENTIFIER, fk, system | System built-in field |
| CreateTime | DATETIMEOFFSET, required, system | System built-in field |
| CreatedBy | UNIQUEIDENTIFIER, required, fk, system | System built-in field |
| UpdatedBy | UNIQUEIDENTIFIER, fk, system | System built-in field |

**Relationships for SalesOrderSpace:**
_Join on the related entity's Id. Use LEFT JOIN to keep all SalesOrderSpace rows (relationship may be unset); INNER JOIN when the related record must exist or you filter on it. Project the specific related column you need — not `*`._

- `SalesOrderSpace.account` → `AccountSpace` (`LEFT JOIN AccountSpace ON AccountSpace.Id = SalesOrderSpace.account`, representative field `AccountSpace.Id`)

🤖 Generated with Claude Code

… prompt

Surface entity relationship (foreign-key) fields to the Data Fabric SQL
sub-graph so it can join related entities. A relationship field stores the
related record's Id; the prompt now instructs the model to join on
related.Id = parent.<relField> and project explicit related columns.

- FieldSchema carries the related entity's SQL table, join key (Id), and
  reference field; foreign-key fields are tagged "fk"; add is_relationship.
- build_entity_context populates these from the SDK field metadata
  (reference_entity, reference_field, field_display_type).
- The rendered schema adds a per-entity "Relationships" subsection with the
  join expression, gated to related entities present in the set.
- The v1 prompt adds a RELATIONSHIP FIELDS section: LEFT JOIN for optional
  relationships, INNER JOIN when the related record must exist or is filtered.
- SQL_CONSTRAINTS permits LEFT JOIN only for relationship/foreign-key joins on
  Id; general joins remain INNER-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@milind-jain-uipath milind-jain-uipath changed the title feat(datafabric-tool): ground relationship-field joins in text-to-SQL prompt feat(datafabric-tool): ground relationship-field joins in text-to-SQL prompt [DS-8791] Jul 1, 2026
@milind-jain-uipath milind-jain-uipath marked this pull request as ready for review July 2, 2026 21:32
Copilot AI review requested due to automatic review settings July 2, 2026 21:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the Data Fabric text-to-SQL prompting by explicitly grounding relationship/foreign-key fields as joins (instead of treating them like human-readable attributes), and relaxes join constraints to allow LEFT JOIN specifically for those relationship joins.

Changes:

  • Enriches FieldSchema with reference/relationship metadata (related table + representative field) and tags FK fields as fk.
  • Updates the prompt builder to render a per-entity Relationships subsection with a concrete join expression, only when the related entity is present in the entity set.
  • Expands the v1 prompt + SQL constraints to document relationship-field semantics and when LEFT JOIN vs INNER JOIN is appropriate.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/agent/tools/test_datafabric_prompt_builder.py Adds tests covering relationship join rendering and prompt documentation updates.
src/uipath_langchain/agent/tools/datafabric_tool/prompts/v1.py Documents relationship-field semantics and join intent guidance.
src/uipath_langchain/agent/tools/datafabric_tool/models.py Extends schema model with relationship metadata and fk display tagging.
src/uipath_langchain/agent/tools/datafabric_tool/datafabric_prompts.py Updates SQL constraints to allow LEFT JOIN only for FK/relationship joins on Id.
src/uipath_langchain/agent/tools/datafabric_tool/datafabric_prompt_builder.py Populates reference metadata from SDK fields and renders relationship join hints in the schema context.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8487c17f6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +161 to +163
- LEFT JOIN is allowed ONLY for relationship (foreign-key) joins on the related
entity's Id (see "Relationship fields" guidance) — use it for optional
relationships to keep parent rows

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep v0 from forbidding relationship LEFT JOINs

When callers render this builder with prompt_version="v0", the legacy SQL_EXPERT_SYSTEM_PROMPT from this same module is emitted before these relaxed constraints and still lists LEFT JOIN under unsupported join constructs. That leaves the v0 prompt simultaneously forbidding LEFT JOINs and allowing/recommending them for optional relationship fields, so optional relationship questions in that compatibility mode can still be steered away from the only join type the new relationship section is trying to ground. Update the legacy prompt or gate the new relationship/LEFT JOIN guidance by prompt version.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v0 is not going to be used

milind-jain-uipath and others added 3 commits July 3, 2026 03:14
- Drop the redundant "never SELECT parent.*" aside (SQL_CONSTRAINTS already
  forbids SELECT *).
- Tie join-type choice to the relationship field's required flag: a required
  field -> INNER JOIN is safe (related record always exists); optional -> LEFT
  JOIN to keep parent rows where it is unset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Detect a relationship as (is_foreign_key OR fieldDisplayType == "Relationship")
and use that single condition both to set is_foreign_key on the FieldSchema and
to extract the reference target, so a Relationship-typed field without the
is_foreign_key flag is still tagged fk and rendered in the Relationships section.
Read field_display_type via getattr for safety.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ionship-join-grounding

# Conflicts:
#	src/uipath_langchain/agent/tools/datafabric_tool/datafabric_prompt_builder.py
#	src/uipath_langchain/agent/tools/datafabric_tool/models.py
#	tests/agent/tools/test_datafabric_prompt_builder.py
milind-jain-uipath and others added 2 commits July 13, 2026 02:52
Pass source=LOW_CODE_AGENT from the DF tool's execute_sql call so FQS types
relationship (FK) fields as their scalar id, letting the agent's
rel = Other.Id joins validate and execute.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ionship-join-grounding

# Conflicts:
#	tests/agent/tools/test_datafabric_prompt_builder.py
try:
records = await self._entities.query_entity_records_async(
sql_query=sql_query,
source=LOW_CODE_AGENT,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would model it upstream at uipath-agents-python for the reason that this package can be used for coded agents also. Check with @cristian-groza on the right place to add this source.

milind-jain-uipath and others added 5 commits July 13, 2026 18:24
…te source header

The optional `source` parameter on query_entity_records[_async] (sent as the
`x-uipath-source` header) lands in uipath-platform 0.2.8, so raise the floor
and relock so the DF tool's source=LOW_CODE_AGENT call resolves against a
version that supports it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on instead of hardcoding

The DF tool is shared by the low-code runtime (uipath-agents-python) and coded
agents, so it no longer self-identifies as LOW_CODE_AGENT. create_tools_from_resources
accepts an optional `source` that flows through create_context_tool →
create_datafabric_query_tool → DataFabricTextQueryHandler → DataFabricGraph →
QueryExecutor → query_entity_records_async(source=…). The caller supplies the
value (the low-code runtime passes LOW_CODE_AGENT); defaults to None (no header).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0.14.5 is already released without the `source` parameter, so the source
threading in this PR must publish as a new version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ionship-join-grounding

# Conflicts:
#	uv.lock
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants