From 885f4e9185b5a9a93c970a2b17ada0d50d878d54 Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Mon, 9 Mar 2026 14:38:06 -0700 Subject: [PATCH 1/2] feat(tracing): derive LLM Ops URL source param from span Source field Previously the source query parameter in the LLM Ops API URL was hardcoded to "Robots". Now it reads the Source field from the span data (set via uipath.source attribute) and maps it to the correct SourceEnum string name, defaulting to "Robots" if missing. Co-Authored-By: Claude Opus 4.6 --- .../src/uipath/platform/common/_span_utils.py | 11 +++++ .../src/uipath/tracing/_otel_exporters.py | 7 ++- .../tests/tracing/test_otel_exporters.py | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/uipath-platform/src/uipath/platform/common/_span_utils.py b/packages/uipath-platform/src/uipath/platform/common/_span_utils.py index 448648067..41384615f 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_span_utils.py +++ b/packages/uipath-platform/src/uipath/platform/common/_span_utils.py @@ -18,6 +18,17 @@ # SourceEnum.Robots = 4 (default for Python SDK / coded agents) DEFAULT_SOURCE = 4 +SOURCE_NAMES: dict[int, str] = { + 0: "Testing", + 1: "Agents", + 2: "ProcessOrchestration", + 3: "ApiWorkflows", + 4: "Robots", + 5: "ConversationalAgentsService", + 6: "IntegrationServiceTrigger", + 7: "Playground", +} + class AttachmentProvider(IntEnum): ORCHESTRATOR = 0 diff --git a/packages/uipath/src/uipath/tracing/_otel_exporters.py b/packages/uipath/src/uipath/tracing/_otel_exporters.py index e3b4bbb5f..86791af5d 100644 --- a/packages/uipath/src/uipath/tracing/_otel_exporters.py +++ b/packages/uipath/src/uipath/tracing/_otel_exporters.py @@ -13,6 +13,7 @@ from uipath._utils._ssl_context import get_httpx_client_kwargs from uipath.platform.common import _SpanUtils +from uipath.platform.common._span_utils import DEFAULT_SOURCE, SOURCE_NAMES from uipath.platform.common.retry import NON_RETRYABLE_STATUS_CODES logger = logging.getLogger(__name__) @@ -389,7 +390,11 @@ def _process_span_attributes(self, span_data: Dict[str, Any]) -> None: def _build_url(self, span_list: list[Dict[str, Any]]) -> str: """Construct the URL for the API request.""" trace_id = str(span_list[0]["TraceId"]) - return f"{self.base_url}/api/Traces/spans?traceId={trace_id}&source=Robots" + source_int = span_list[0].get("Source", DEFAULT_SOURCE) + source_name = SOURCE_NAMES.get(source_int, "Robots") + return ( + f"{self.base_url}/api/Traces/spans?traceId={trace_id}&source={source_name}" + ) def _send_with_retries( self, url: str, payload: list[Dict[str, Any]], max_retries: int = 4 diff --git a/packages/uipath/tests/tracing/test_otel_exporters.py b/packages/uipath/tests/tracing/test_otel_exporters.py index a55fa5d60..0397a0c3c 100644 --- a/packages/uipath/tests/tracing/test_otel_exporters.py +++ b/packages/uipath/tests/tracing/test_otel_exporters.py @@ -216,6 +216,51 @@ def test_get_base_url(): assert exporter.base_url == "https://custom-trace.example.com/prefix" +def test_build_url_uses_span_source(): + """Test _build_url derives source query param from span's Source field.""" + with ( + patch("uipath.tracing._otel_exporters.httpx.Client"), + patch.dict(os.environ, {"UIPATH_ACCESS_TOKEN": "t"}, clear=True), + ): + exporter = LlmOpsHttpExporter() + exporter.base_url = "https://example.com/llmops" + + # Source=1 (Agents) + url = exporter._build_url([{"TraceId": "abc", "Source": 1}]) + assert ( + url + == "https://example.com/llmops/api/Traces/spans?traceId=abc&source=Agents" + ) + + # Source=4 (Robots) - default + url = exporter._build_url([{"TraceId": "abc", "Source": 4}]) + assert ( + url + == "https://example.com/llmops/api/Traces/spans?traceId=abc&source=Robots" + ) + + # Missing Source defaults to Robots + url = exporter._build_url([{"TraceId": "abc"}]) + assert ( + url + == "https://example.com/llmops/api/Traces/spans?traceId=abc&source=Robots" + ) + + # Source=7 (Playground) + url = exporter._build_url([{"TraceId": "abc", "Source": 7}]) + assert ( + url + == "https://example.com/llmops/api/Traces/spans?traceId=abc&source=Playground" + ) + + # Unknown source int falls back to Robots + url = exporter._build_url([{"TraceId": "abc", "Source": 99}]) + assert ( + url + == "https://example.com/llmops/api/Traces/spans?traceId=abc&source=Robots" + ) + + def test_internal_headers_set_when_trace_base_url_present(): """Test that internal tenant/account headers are set when UIPATH_TRACE_BASE_URL is configured.""" with patch.dict( From c98fa4ae881e83d2d0284a1f2b1b2d059c019b38 Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Mon, 9 Mar 2026 14:43:39 -0700 Subject: [PATCH 2/2] chore: bump uipath to 2.10.12 and update lock files Co-Authored-By: Claude Opus 4.6 --- packages/uipath/pyproject.toml | 2 +- packages/uipath/uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index a1e30f8e6..62d710fd2 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.11" +version = "2.10.12" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index f7e3594d4..0d7ade036 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2531,7 +2531,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.11" +version = "2.10.12" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, @@ -2652,7 +2652,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.0.17" +version = "0.0.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -2661,9 +2661,9 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/c9/e7568133f3a657af16b67444c4e090259941078acc62acb1e2c072903da4/uipath_platform-0.0.17.tar.gz", hash = "sha256:a2c228462d7e2642dcfc249547d9b8e94ba1c72b68f16ba673ee3e58204e9365", size = 264143, upload-time = "2026-03-06T20:34:22.23Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/eb/848292baafcb7273b189004161b754e46431bb28a711b7f359a2b7642b27/uipath_platform-0.0.18.tar.gz", hash = "sha256:0d0cf196ffc06de90c3ec12a7b52d88b81f38a34e361eb81690ff88cd2a9a0bd", size = 264141, upload-time = "2026-03-09T16:24:11.158Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/d1/a1c357dbea16a8b5d5b8ae5311ff2353cc03a8b5dd15ff83b0b693687930/uipath_platform-0.0.17-py3-none-any.whl", hash = "sha256:7b88f2b4eb189877fb2f99d704fc0cbc6e4244f01dac59cf812fa8a03db95e36", size = 159073, upload-time = "2026-03-06T20:34:20.993Z" }, + { url = "https://files.pythonhosted.org/packages/36/69/a96b8c66d4a0ed5d5ac1a3e44a5836d1b9e4151519a884b67d8db18fb3d8/uipath_platform-0.0.18-py3-none-any.whl", hash = "sha256:9f46b0b01254b95b18cc753bd91f5a5802d0a15e7284224d6f9a1309bb71b6bf", size = 159073, upload-time = "2026-03-09T16:24:09.717Z" }, ] [[package]]