Skip to content

feat: add ReferenceContext for span reference hierarchy propagation#1708

Open
jepadil23 wants to merge 3 commits into
mainfrom
feat/reference-hierarchy-context
Open

feat: add ReferenceContext for span reference hierarchy propagation#1708
jepadil23 wants to merge 3 commits into
mainfrom
feat/reference-hierarchy-context

Conversation

@jepadil23

Copy link
Copy Markdown

Summary

  • Adds ReferenceContext — an immutable, copy-on-write ordered list of ReferenceEntry objects (serviceType, referenceId, version) that flows via contextvars.ContextVar across await boundaries
  • _span_utils.py reads from ReferenceContextAccessor to populate UiPathSpan.context.referenceHierarchy — wire-format compatible with service-common x-uipath-tracebaggage header
  • Fixes BatchSpanProcessor thread-boundary issue by stamping the hierarchy as a span attribute at span start
  • Normalizes empty-string env vars (UIPATH_ORGANIZATION_ID, UIPATH_TENANT_ID, UIPATH_FOLDER_KEY) to None for Guid fields in UiPathSpan

Test plan

  • tests/services/test_reference_context.py — unit tests for ReferenceContext, ReferenceEntry, and ReferenceContextAccessor (push/pop/reset, async propagation, wire serialization)
  • Verify UiPathSpan.context.referenceHierarchy is populated when ReferenceContextAccessor has entries set upstream
  • Verify empty-string env vars normalize to None in span fields

🤖 Generated with Claude Code

jepadil23 and others added 3 commits June 8, 2026 21:12
Introduces an immutable, copy-on-write ReferenceContext (modelled after
service-common BaggageContext) that propagates the reference hierarchy
through all spans emitted to LLMOps/Traceview.

- `_reference_context.py`: ReferenceEntry, ReferenceContext (add /
  to_wire_list / from_baggage_header / to_baggage_header_value),
  ReferenceContextAccessor (ContextVar-backed, async-safe)
- `_span_utils.py`: UiPathSpan gains `context` field; to_dict() emits
  `Context.referenceHierarchy` when set; otel_span_to_uipath_span()
  reads ReferenceContextAccessor and builds the wire payload
- `uipath.tracing`: re-exports ReferenceEntry, ReferenceContext,
  ReferenceContextAccessor for downstream consumers
- Tests: 30 new tests covering immutability, wire format, baggage
  header round-trip, accessor lifecycle, and span wiring

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…hSpan

UIPATH_FOLDER_KEY, UIPATH_ORGANIZATION_ID, and UIPATH_TENANT_ID default
to "" when unset. SpanReq.FolderKey is Guid? on the server — sending ""
causes a JSON deserialization exception ("could not convert to
System.Nullable[Guid]") which cascades into "The spans field is required"
from ASP.NET's model binding. Normalize all three to None so unset vars
serialize as null instead of an unparseable empty string.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…Processor thread boundary

ContextVar values are not propagated to background threads. The
BatchSpanProcessor exports spans in a worker thread where
ReferenceContextAccessor.get() always returns None, so Context.referenceHierarchy
was never populated in the wire payload.

Fix: register a span-start hook (_inject_reference_hierarchy) that runs
synchronously in on_start — same thread/context as the span creator — reads
the ambient ReferenceContext and stamps it as uipath.reference_hierarchy on the
span. The attribute travels with the span to the export thread.
otel_span_to_uipath_span now reads from the attribute instead of the ContextVar,
and pops it from attributes_dict so it does not appear in the wire Attributes field.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 10, 2026 19:10
@github-actions github-actions Bot added test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-runtime test:uipath-integrations labels Jun 10, 2026
@sonarqubecloud

Copy link
Copy Markdown

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

Adds a new reference-hierarchy propagation mechanism for spans by introducing an immutable ReferenceContext carried via contextvars, stamping the hierarchy onto spans at start-time to survive BatchSpanProcessor thread boundaries, and wiring that stamped hierarchy into the UiPath span wire payload (Context.referenceHierarchy).

Changes:

  • Introduces ReferenceContext / ReferenceEntry plus a ReferenceContextAccessor backed by ContextVar.
  • Adds a span-start hook mechanism in core tracing processors and uses it in _span_utils.py to stamp uipath.reference_hierarchy at span creation time.
  • Extends UiPathSpan serialization to emit a top-level Context field and normalizes empty-string org/tenant/folder env vars to None.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/uipath/src/uipath/tracing/init.py Re-exports ReferenceContext* types from platform common tracing surface.
packages/uipath-platform/src/uipath/platform/common/_reference_context.py Adds the new immutable reference hierarchy model + ContextVar accessor + header (de)serialization.
packages/uipath-platform/src/uipath/platform/common/_span_utils.py Registers a span-start hook to stamp hierarchy and wires stamped hierarchy into UiPathSpan.Context.
packages/uipath-platform/src/uipath/platform/common/init.py Exposes ReferenceContext* from uipath.platform.common.
packages/uipath-core/src/uipath/core/tracing/processors.py Adds global span-start hook registration and invokes hooks in on_start.
packages/uipath-platform/tests/services/test_reference_context.py Adds unit tests for the new reference context model + span wiring behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +50 to +51
for hook in _span_start_hooks:
hook(span)
ReferenceContextAccessor.reset(token)
"""

__slots__ = ("_entries",)
Comment on lines +111 to +116
if isinstance(reference_id, uuid.UUID):
id_str = str(reference_id)
elif isinstance(reference_id, str):
id_str = reference_id
else:
raise TypeError("reference_id must be a UUID or string.")
Comment on lines +124 to +140
def to_wire_list(self) -> List[dict]:
"""Serialize to the ``referenceHierarchy`` wire format.

Returns:
A list of dicts suitable for JSON serialization as
``Context.referenceHierarchy`` in the span payload.
"""
result = []
for e in self._entries:
item: dict = {
"serviceType": e.service_type,
"referenceId": e.reference_id,
}
if e.version:
item["version"] = e.version
result.append(item)
return result
return cls._current.get()

@classmethod
def set(cls, value: Optional[ReferenceContext]) -> contextvars.Token:
return cls._current.set(value)

@classmethod
def reset(cls, token: contextvars.Token) -> None:
Comment on lines +3 to +17
import json
import os
from datetime import datetime
from unittest.mock import Mock

import pytest
from opentelemetry.sdk.trace import Span as OTelSpan
from opentelemetry.trace import SpanContext, StatusCode

from uipath.platform.common import _SpanUtils
from uipath.platform.common._reference_context import (
ReferenceContext,
ReferenceContextAccessor,
ReferenceEntry,
)
# Helpers
# ---------------------------------------------------------------------------

def _make_mock_span(attributes: dict | None = None) -> Mock:
Comment on lines +219 to +223
class ReferenceContextAccessor:
"""Ambient accessor for the current :class:`ReferenceContext`.

Backed by :mod:`contextvars` so the value propagates across ``await``
boundaries without being threaded through every call signature.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:uipath-integrations test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants