From ecbcf35c0f09be231dfb6ae9a96d87f207aa931f Mon Sep 17 00:00:00 2001 From: Jesus Date: Mon, 8 Jun 2026 21:13:29 -0700 Subject: [PATCH 01/22] feat(traceview): propagate reference hierarchy context from LangGraph runtime UiPathLangGraphRuntime._push_reference_context() reads any parent ReferenceContext set by upstream (e.g. agents-python start_agent_run), appends a "langgraph" entry from UIPATH_AGENT_ID / UIPATH_PROCESS_VERSION, and activates it via ReferenceContextAccessor. Both execute() and stream() call this at entry and reset in finally, so all child spans emitted during the run carry the full hierarchy in their Context.referenceHierarchy wire payload. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- src/uipath_langchain/runtime/runtime.py | 26 +++ .../runtime/test_reference_context_wiring.py | 205 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 tests/runtime/test_reference_context_wiring.py diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index a51de93d6..8538e02ed 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -1,3 +1,4 @@ +import contextvars import logging import os from collections.abc import Iterator @@ -18,6 +19,7 @@ UiPathRuntimeStorageProtocol, UiPathStreamOptions, ) +from uipath.tracing import ReferenceContext, ReferenceContextAccessor from uipath.runtime.errors import ( UiPathBaseRuntimeError, UiPathErrorCategory, @@ -75,12 +77,31 @@ def __init__( self.chat.client_side_tools = self._get_client_side_tools() self._middleware_node_names: set[str] = self._detect_middleware_nodes() + def _push_reference_context(self) -> contextvars.Token: + """Append this runtime's own entry to the ambient ReferenceContext. + + Reads any parent context already in the accessor (e.g. set by an + upstream middleware or the agents-python runtime), then appends a + ``langgraph`` entry for this runtime. Returns the ContextVar token + so the caller can reset in a ``finally`` block. + """ + agent_id = os.environ.get("UIPATH_AGENT_ID") + agent_version = os.environ.get("UIPATH_PROCESS_VERSION") or None + parent_ctx = ReferenceContextAccessor.get() or ReferenceContext.Empty + ref_ctx = ( + parent_ctx.add("langgraph", agent_id, agent_version) + if agent_id + else parent_ctx + ) + return ReferenceContextAccessor.set(ref_ctx) + async def execute( self, input: dict[str, Any] | None = None, options: UiPathExecuteOptions | None = None, ) -> UiPathRuntimeResult: """Execute the graph with the provided input and configuration.""" + ref_ctx_token = self._push_reference_context() try: graph_input = await self._get_graph_input(input, options) graph_config = self._get_graph_config() @@ -99,6 +120,8 @@ async def execute( except Exception as e: raise self.create_runtime_error(e) from e + finally: + ReferenceContextAccessor.reset(ref_ctx_token) async def stream( self, @@ -133,6 +156,7 @@ async def stream( Raises: LangGraphRuntimeError: If execution fails """ + ref_ctx_token = self._push_reference_context() try: graph_input = await self._get_graph_input(input, options) graph_config = self._get_graph_config() @@ -230,6 +254,8 @@ async def stream( except Exception as e: raise self.create_runtime_error(e) from e + finally: + ReferenceContextAccessor.reset(ref_ctx_token) async def get_schema(self) -> UiPathRuntimeSchema: """Get schema for this LangGraph runtime.""" diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py new file mode 100644 index 000000000..e944ca625 --- /dev/null +++ b/tests/runtime/test_reference_context_wiring.py @@ -0,0 +1,205 @@ +"""Tests for ReferenceContext wiring in UiPathLangGraphRuntime.""" + +import os +import tempfile +from typing import Any, TypedDict + +import pytest +from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver +from langgraph.graph import END, START, StateGraph + +from uipath.platform.common._reference_context import ( + ReferenceContext, + ReferenceContextAccessor, +) +from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime + + +# --------------------------------------------------------------------------- +# Minimal graph fixture +# --------------------------------------------------------------------------- + +class _State(TypedDict): + value: str + + +def _build_graph() -> Any: + graph = StateGraph(_State) + graph.add_node("step", lambda s: {"value": s.get("value", "") + "_done"}) + graph.add_edge(START, "step") + graph.add_edge("step", END) + return graph + + +def _clear_accessor() -> None: + token = ReferenceContextAccessor.set(None) + ReferenceContextAccessor.reset(token) + + +# --------------------------------------------------------------------------- +# _push_reference_context — unit tests (no graph needed) +# --------------------------------------------------------------------------- + +class TestPushReferenceContext: + def setup_method(self) -> None: + _clear_accessor() + + def teardown_method(self) -> None: + _clear_accessor() + + def test_sets_langgraph_entry_when_agent_id_present( + self, monkeypatch: pytest.MonkeyPatch, tmp_path + ) -> None: + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) + + from langgraph.graph import StateGraph + graph = _build_graph().compile() + runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="t") + + token = runtime._push_reference_context() + try: + ctx = ReferenceContextAccessor.get() + assert ctx is not None + assert len(ctx) == 1 + assert ctx.entries[0].service_type == "langgraph" + assert ctx.entries[0].reference_id == "550e8400-e29b-41d4-a716-446655440020" + assert ctx.entries[0].version is None + finally: + ReferenceContextAccessor.reset(token) + + def test_includes_version_when_env_set( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + monkeypatch.setenv("UIPATH_PROCESS_VERSION", "3.1.0") + + graph = _build_graph().compile() + runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="t") + + token = runtime._push_reference_context() + try: + ctx = ReferenceContextAccessor.get() + assert ctx is not None + assert ctx.entries[0].version == "3.1.0" + finally: + ReferenceContextAccessor.reset(token) + + def test_no_entry_when_agent_id_absent( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delenv("UIPATH_AGENT_ID", raising=False) + monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) + + graph = _build_graph().compile() + runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="t") + + token = runtime._push_reference_context() + try: + ctx = ReferenceContextAccessor.get() + assert ctx is not None + assert len(ctx) == 0 + finally: + ReferenceContextAccessor.reset(token) + + def test_stacks_on_top_of_parent_context( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) + + parent = ReferenceContext.Empty.add( + "agent", "550e8400-e29b-41d4-a716-446655440001", "1.0" + ) + parent_token = ReferenceContextAccessor.set(parent) + + graph = _build_graph().compile() + runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="t") + + token = runtime._push_reference_context() + try: + ctx = ReferenceContextAccessor.get() + assert ctx is not None + assert len(ctx) == 2 + assert ctx.entries[0].service_type == "agent" + assert ctx.entries[1].service_type == "langgraph" + finally: + ReferenceContextAccessor.reset(token) + ReferenceContextAccessor.reset(parent_token) + + +# --------------------------------------------------------------------------- +# execute() — context cleared after run +# --------------------------------------------------------------------------- + +async def test_context_cleared_after_execute( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + _clear_accessor() + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) + + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + db = f.name + + async with AsyncSqliteSaver.from_conn_string(db) as memory: + await memory.setup() + graph = _build_graph().compile(checkpointer=memory) + runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="exec-run") + await runtime.execute(input={"value": "hello"}) + + assert ReferenceContextAccessor.get() is None + + +async def test_context_cleared_after_execute_on_error( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + _clear_accessor() + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + + class _S(TypedDict): + v: str + + def _boom(s: _S) -> _S: + raise ValueError("explode") + + g = StateGraph(_S) + g.add_node("boom", _boom) + g.add_edge(START, "boom") + g.add_edge("boom", END) + + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + db = f.name + + async with AsyncSqliteSaver.from_conn_string(db) as memory: + await memory.setup() + compiled = g.compile(checkpointer=memory) + runtime = UiPathLangGraphRuntime(graph=compiled, runtime_id="err-run") + with pytest.raises(Exception): + await runtime.execute(input={"v": "x"}) + + assert ReferenceContextAccessor.get() is None + + +# --------------------------------------------------------------------------- +# stream() — context cleared after run +# --------------------------------------------------------------------------- + +async def test_context_cleared_after_stream( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + _clear_accessor() + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) + + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + db = f.name + + async with AsyncSqliteSaver.from_conn_string(db) as memory: + await memory.setup() + graph = _build_graph().compile(checkpointer=memory) + runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="stream-run") + async for _ in runtime.stream(input={"value": "hi"}): + pass + + assert ReferenceContextAccessor.get() is None From c6cdc6ac26263ee777198022a400701184fbc530 Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 21:46:36 -0700 Subject: [PATCH 02/22] feat(traceview): make changes for copilot comments --- src/uipath_langchain/runtime/runtime.py | 2 +- .../runtime/test_reference_context_wiring.py | 26 +- tests/runtime/test_runtime_unit.py | 273 ++++++++++++++++++ 3 files changed, 279 insertions(+), 22 deletions(-) create mode 100644 tests/runtime/test_runtime_unit.py diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index 8538e02ed..521af3fff 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -77,7 +77,7 @@ def __init__( self.chat.client_side_tools = self._get_client_side_tools() self._middleware_node_names: set[str] = self._detect_middleware_nodes() - def _push_reference_context(self) -> contextvars.Token: + def _push_reference_context(self) -> contextvars.Token[ReferenceContext | None]: """Append this runtime's own entry to the ambient ReferenceContext. Reads any parent context already in the accessor (e.g. set by an diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index e944ca625..03fffb0e9 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -1,17 +1,12 @@ """Tests for ReferenceContext wiring in UiPathLangGraphRuntime.""" -import os -import tempfile from typing import Any, TypedDict import pytest from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langgraph.graph import END, START, StateGraph -from uipath.platform.common._reference_context import ( - ReferenceContext, - ReferenceContextAccessor, -) +from uipath.tracing import ReferenceContext, ReferenceContextAccessor from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime @@ -32,8 +27,7 @@ def _build_graph() -> Any: def _clear_accessor() -> None: - token = ReferenceContextAccessor.set(None) - ReferenceContextAccessor.reset(token) + ReferenceContextAccessor.set(None) # --------------------------------------------------------------------------- @@ -53,7 +47,6 @@ def test_sets_langgraph_entry_when_agent_id_present( monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) - from langgraph.graph import StateGraph graph = _build_graph().compile() runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="t") @@ -139,10 +132,7 @@ async def test_context_cleared_after_execute( monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) - with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: - db = f.name - - async with AsyncSqliteSaver.from_conn_string(db) as memory: + async with AsyncSqliteSaver.from_conn_string(str(tmp_path / "mem.db")) as memory: await memory.setup() graph = _build_graph().compile(checkpointer=memory) runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="exec-run") @@ -168,10 +158,7 @@ def _boom(s: _S) -> _S: g.add_edge(START, "boom") g.add_edge("boom", END) - with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: - db = f.name - - async with AsyncSqliteSaver.from_conn_string(db) as memory: + async with AsyncSqliteSaver.from_conn_string(str(tmp_path / "mem.db")) as memory: await memory.setup() compiled = g.compile(checkpointer=memory) runtime = UiPathLangGraphRuntime(graph=compiled, runtime_id="err-run") @@ -192,10 +179,7 @@ async def test_context_cleared_after_stream( monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") monkeypatch.delenv("UIPATH_PROCESS_VERSION", raising=False) - with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: - db = f.name - - async with AsyncSqliteSaver.from_conn_string(db) as memory: + async with AsyncSqliteSaver.from_conn_string(str(tmp_path / "mem.db")) as memory: await memory.setup() graph = _build_graph().compile(checkpointer=memory) runtime = UiPathLangGraphRuntime(graph=graph, runtime_id="stream-run") diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py new file mode 100644 index 000000000..c9b336aa8 --- /dev/null +++ b/tests/runtime/test_runtime_unit.py @@ -0,0 +1,273 @@ +"""Unit tests for UiPathLangGraphRuntime helper methods (no graph execution).""" + +from typing import Any +from unittest.mock import MagicMock + +import pytest +from langgraph.errors import EmptyInputError, GraphRecursionError, InvalidUpdateError +from langgraph.graph.state import CompiledStateGraph +from langgraph.types import StateSnapshot +from uipath.runtime import UiPathRuntimeStatus +from uipath.runtime.errors import UiPathErrorCategory + +from uipath_langchain.runtime.errors import LangGraphErrorCode, LangGraphRuntimeError +from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime + + +def _make_runtime(**kwargs: Any) -> UiPathLangGraphRuntime: + graph = MagicMock(spec=CompiledStateGraph) + graph.nodes = {} + graph.output_channels = [] + return UiPathLangGraphRuntime(graph=graph, **kwargs) + + +def _snapshot( + next_nodes: tuple = (), + interrupts: tuple = (), + tasks: list | None = None, + values: dict | None = None, +) -> MagicMock: + snap = MagicMock(spec=StateSnapshot) + snap.next = next_nodes + snap.interrupts = interrupts + snap.tasks = tasks or [] + snap.values = values or {} + return snap + + +# --------------------------------------------------------------------------- +# _build_node_name +# --------------------------------------------------------------------------- + + +class TestBuildNodeName: + def setup_method(self) -> None: + self.rt = _make_runtime() + + def test_root_graph_returns_node_name(self) -> None: + assert self.rt._build_node_name((), "agent") == "agent" + + def test_single_subgraph_prepends_subgraph_name(self) -> None: + assert self.rt._build_node_name(("coder:abc123",), "generate") == "coder:generate" + + def test_nested_subgraphs_build_full_path(self) -> None: + assert self.rt._build_node_name(("coder:a", "debugger:b"), "analyze") == "coder:debugger:analyze" + + def test_namespace_without_colon_uses_full_segment(self) -> None: + assert self.rt._build_node_name(("subgraph",), "node") == "subgraph:node" + + def test_empty_string_segments_are_skipped(self) -> None: + assert self.rt._build_node_name(("",), "node") == "node" + + def test_non_tuple_namespace_falls_back_to_node_name(self) -> None: + assert self.rt._build_node_name("unexpected", "node") == "node" + + def test_none_namespace_falls_back_to_node_name(self) -> None: + assert self.rt._build_node_name(None, "node") == "node" + + +# --------------------------------------------------------------------------- +# _extract_graph_result +# --------------------------------------------------------------------------- + + +class TestExtractGraphResult: + def setup_method(self) -> None: + self.rt = _make_runtime() + + def test_non_dict_returned_unchanged(self) -> None: + self.rt.graph.output_channels = ["out"] + assert self.rt._extract_graph_result("string") == "string" + + def test_single_string_channel_extracts_matching_key(self) -> None: + self.rt.graph.output_channels = "result" + assert self.rt._extract_graph_result({"result": 42, "other": 1}) == 42 + + def test_single_string_channel_missing_returns_full_chunk(self) -> None: + self.rt.graph.output_channels = "result" + chunk = {"other": 1} + assert self.rt._extract_graph_result(chunk) == chunk + + def test_multi_channel_returns_only_present_keys(self) -> None: + self.rt.graph.output_channels = ["a", "b", "c"] + assert self.rt._extract_graph_result({"a": 1, "b": 2}) == {"a": 1, "b": 2} + + def test_multi_channel_unwraps_single_key_wrapping_dict(self) -> None: + self.rt.graph.output_channels = ["x", "y"] + assert self.rt._extract_graph_result({"node": {"x": 10, "y": 20}}) == {"x": 10, "y": 20} + + def test_tuple_format_unwraps_second_element(self) -> None: + self.rt.graph.output_channels = "val" + assert self.rt._extract_graph_result(("ignored", {"val": 99})) == 99 + + def test_empty_sequence_channels_returns_chunk_unchanged(self) -> None: + self.rt.graph.output_channels = [] + chunk = {"a": 1} + assert self.rt._extract_graph_result(chunk) == chunk + + +# --------------------------------------------------------------------------- +# create_runtime_error +# --------------------------------------------------------------------------- + + +class TestCreateRuntimeError: + def setup_method(self) -> None: + self.rt = _make_runtime() + + def test_langgraph_runtime_error_returned_unchanged(self) -> None: + err = LangGraphRuntimeError(LangGraphErrorCode.GRAPH_LOAD_ERROR, "t", "d", UiPathErrorCategory.USER) + assert self.rt.create_runtime_error(err) is err + + def test_graph_recursion_error_maps_to_graph_load_error(self) -> None: + result = self.rt.create_runtime_error(GraphRecursionError("too deep")) + assert result.error_info.code == "LANGGRAPH.GRAPH_LOAD_ERROR" + + def test_invalid_update_error_maps_to_graph_invalid_update(self) -> None: + result = self.rt.create_runtime_error(InvalidUpdateError("bad update")) + assert result.error_info.code == "LANGGRAPH.GRAPH_INVALID_UPDATE" + + def test_empty_input_error_maps_to_graph_empty_input(self) -> None: + result = self.rt.create_runtime_error(EmptyInputError()) + assert result.error_info.code == "LANGGRAPH.GRAPH_EMPTY_INPUT" + + def test_generic_exception_maps_to_execution_error(self) -> None: + result = self.rt.create_runtime_error(RuntimeError("boom")) + assert result.error_info.code == "LANGGRAPH.EXECUTION_ERROR" + assert "boom" in result.error_info.detail + + +# --------------------------------------------------------------------------- +# _get_graph_config +# --------------------------------------------------------------------------- + + +class TestGetGraphConfig: + def test_thread_id_set_from_runtime_id(self) -> None: + rt = _make_runtime(runtime_id="my-run") + assert rt._get_graph_config()["configurable"]["thread_id"] == "my-run" + + def test_no_recursion_limit_by_default(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("LANGCHAIN_RECURSION_LIMIT", raising=False) + assert "recursion_limit" not in _make_runtime()._get_graph_config() + + def test_recursion_limit_read_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("LANGCHAIN_RECURSION_LIMIT", "50") + assert _make_runtime()._get_graph_config()["recursion_limit"] == 50 + + def test_no_max_concurrency_by_default(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("LANGCHAIN_MAX_CONCURRENCY", raising=False) + assert "max_concurrency" not in _make_runtime()._get_graph_config() + + def test_max_concurrency_read_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("LANGCHAIN_MAX_CONCURRENCY", "4") + assert _make_runtime()._get_graph_config()["max_concurrency"] == 4 + + +# --------------------------------------------------------------------------- +# _detect_middleware_nodes / _is_middleware_node +# --------------------------------------------------------------------------- + + +class TestMiddlewareDetection: + def _make_with_nodes(self, node_names: list[str]) -> UiPathLangGraphRuntime: + graph = MagicMock(spec=CompiledStateGraph) + graph.nodes = {n: MagicMock() for n in node_names} + graph.output_channels = [] + return UiPathLangGraphRuntime(graph=graph) + + def test_node_with_dot_and_middleware_keyword_detected(self) -> None: + rt = self._make_with_nodes(["GuardrailsMiddleware.pre", "agent"]) + assert rt._is_middleware_node("GuardrailsMiddleware.pre") + + def test_regular_node_not_detected(self) -> None: + rt = self._make_with_nodes(["GuardrailsMiddleware.pre", "agent"]) + assert not rt._is_middleware_node("agent") + + def test_node_with_dot_but_no_middleware_keyword_not_detected(self) -> None: + rt = self._make_with_nodes(["some.node"]) + assert not rt._is_middleware_node("some.node") + + def test_middleware_keyword_without_dot_not_detected(self) -> None: + rt = self._make_with_nodes(["MiddlewareNode"]) + assert not rt._is_middleware_node("MiddlewareNode") + + def test_multiple_middleware_hooks_all_detected(self) -> None: + rt = self._make_with_nodes(["FooMiddleware.pre", "FooMiddleware.post", "tools"]) + assert rt._is_middleware_node("FooMiddleware.pre") + assert rt._is_middleware_node("FooMiddleware.post") + assert not rt._is_middleware_node("tools") + + +# --------------------------------------------------------------------------- +# _is_interrupted +# --------------------------------------------------------------------------- + + +class TestIsInterrupted: + def setup_method(self) -> None: + self.rt = _make_runtime() + + def test_true_when_next_nodes_present(self) -> None: + assert self.rt._is_interrupted(_snapshot(next_nodes=("step",))) + + def test_true_when_dynamic_interrupts_present(self) -> None: + assert self.rt._is_interrupted(_snapshot(interrupts=(MagicMock(),))) + + def test_false_when_both_empty(self) -> None: + assert not self.rt._is_interrupted(_snapshot()) + + +# --------------------------------------------------------------------------- +# _create_success_result +# --------------------------------------------------------------------------- + + +class TestCreateSuccessResult: + def setup_method(self) -> None: + self.rt = _make_runtime() + + def test_status_is_successful(self) -> None: + assert self.rt._create_success_result({"k": "v"}).status == UiPathRuntimeStatus.SUCCESSFUL + + def test_output_passed_through(self) -> None: + assert self.rt._create_success_result({"key": "val"}).output == {"key": "val"} + + def test_none_output_becomes_empty_dict(self) -> None: + assert self.rt._create_success_result(None).output == {} + + +# --------------------------------------------------------------------------- +# _create_breakpoint_result +# --------------------------------------------------------------------------- + + +class TestCreateBreakpointResult: + def setup_method(self) -> None: + self.rt = _make_runtime() + + def test_before_breakpoint_uses_next_node_name(self) -> None: + snap = _snapshot(next_nodes=("step_b",), values={"k": "v"}) + result = self.rt._create_breakpoint_result(snap) + assert result.breakpoint_type == "before" + assert "step_b" in result.breakpoint_node + assert result.next_nodes == ["step_b"] + + def test_after_breakpoint_uses_last_task_name(self) -> None: + task = MagicMock() + task.name = "last_step" + snap = _snapshot(next_nodes=(), tasks=[task], values={}) + result = self.rt._create_breakpoint_result(snap) + assert result.breakpoint_type == "after" + assert result.breakpoint_node == "last_step" + + def test_after_breakpoint_with_no_tasks_is_unknown(self) -> None: + snap = _snapshot(next_nodes=(), tasks=[], values={}) + result = self.rt._create_breakpoint_result(snap) + assert result.breakpoint_type == "after" + assert result.breakpoint_node == "unknown" + + def test_current_state_reflects_snapshot_values(self) -> None: + snap = _snapshot(next_nodes=("n",), values={"out": "done"}) + result = self.rt._create_breakpoint_result(snap) + assert result.current_state == {"out": "done"} From 65fdb0ce109e2a118bf0cd4241f62a59f0ed6760 Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 21:59:49 -0700 Subject: [PATCH 03/22] feat(traceview): handling reference context not added yet --- src/uipath_langchain/runtime/runtime.py | 35 ++++++++++++- tests/runtime/test_runtime_unit.py | 65 +++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index 521af3fff..87ef36a48 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -19,7 +19,33 @@ UiPathRuntimeStorageProtocol, UiPathStreamOptions, ) -from uipath.tracing import ReferenceContext, ReferenceContextAccessor +# Guarded import: ReferenceContext was added to uipath.tracing in a later release. +# Older installed packages lack it; the noop shim keeps the runtime loadable while +# silently disabling span propagation until the package is updated. +_noop_ref_ctx: contextvars.ContextVar[None] = contextvars.ContextVar( + "_uipath_langchain_noop_ref_ctx", default=None +) + + +class _NoopReferenceContextAccessor: + @staticmethod + def get() -> None: + return None + + @staticmethod + def set(value: Any) -> "contextvars.Token[None]": + return _noop_ref_ctx.set(None) + + @staticmethod + def reset(token: Any) -> None: + _noop_ref_ctx.reset(token) + + +try: + from uipath.tracing import ReferenceContext, ReferenceContextAccessor +except ImportError: + ReferenceContext = None # type: ignore[assignment,misc] + ReferenceContextAccessor = _NoopReferenceContextAccessor # type: ignore[assignment] from uipath.runtime.errors import ( UiPathBaseRuntimeError, UiPathErrorCategory, @@ -77,14 +103,19 @@ def __init__( self.chat.client_side_tools = self._get_client_side_tools() self._middleware_node_names: set[str] = self._detect_middleware_nodes() - def _push_reference_context(self) -> contextvars.Token[ReferenceContext | None]: + def _push_reference_context(self) -> "contextvars.Token[Any]": """Append this runtime's own entry to the ambient ReferenceContext. Reads any parent context already in the accessor (e.g. set by an upstream middleware or the agents-python runtime), then appends a ``langgraph`` entry for this runtime. Returns the ContextVar token so the caller can reset in a ``finally`` block. + + Returns a no-op token when the installed uipath package predates + reference-context support. """ + if ReferenceContext is None: + return ReferenceContextAccessor.set(None) agent_id = os.environ.get("UIPATH_AGENT_ID") agent_version = os.environ.get("UIPATH_PROCESS_VERSION") or None parent_ctx = ReferenceContextAccessor.get() or ReferenceContext.Empty diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index c9b336aa8..b232c4029 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -271,3 +271,68 @@ def test_current_state_reflects_snapshot_values(self) -> None: snap = _snapshot(next_nodes=("n",), values={"out": "done"}) result = self.rt._create_breakpoint_result(snap) assert result.current_state == {"out": "done"} + + +# --------------------------------------------------------------------------- +# ImportError fallback — _NoopReferenceContextAccessor +# --------------------------------------------------------------------------- + + +class TestImportErrorFallback: + """Cover the _NoopReferenceContextAccessor shim and the ReferenceContext=None + guard inside _push_reference_context.""" + + def test_noop_accessor_get_returns_none(self) -> None: + from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor + + assert _NoopReferenceContextAccessor.get() is None + + def test_noop_accessor_set_returns_token(self) -> None: + import contextvars + + from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor + + token = _NoopReferenceContextAccessor.set("anything") + assert isinstance(token, contextvars.Token) + + def test_noop_accessor_reset_does_not_raise(self) -> None: + from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor + + token = _NoopReferenceContextAccessor.set(None) + _NoopReferenceContextAccessor.reset(token) # must not raise + + def test_push_returns_token_when_reference_context_unavailable( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + import contextvars + + import uipath_langchain.runtime.runtime as rt_mod + from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor + + monkeypatch.setattr(rt_mod, "ReferenceContext", None) + monkeypatch.setattr(rt_mod, "ReferenceContextAccessor", _NoopReferenceContextAccessor) + + rt = _make_runtime() + token = rt._push_reference_context() + assert isinstance(token, contextvars.Token) + # cleanup must not raise + _NoopReferenceContextAccessor.reset(token) + + def test_push_does_not_set_real_accessor_when_unavailable( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + from uipath.tracing import ReferenceContextAccessor + + import uipath_langchain.runtime.runtime as rt_mod + from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor + + monkeypatch.setattr(rt_mod, "ReferenceContext", None) + monkeypatch.setattr(rt_mod, "ReferenceContextAccessor", _NoopReferenceContextAccessor) + + rt = _make_runtime() + before = ReferenceContextAccessor.get() + token = rt._push_reference_context() + after = ReferenceContextAccessor.get() + _NoopReferenceContextAccessor.reset(token) + + assert before == after From 26f05c06a949dd7c719b311b8792693595e012cc Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 22:14:38 -0700 Subject: [PATCH 04/22] fix(traceview): Fix from statements --- tests/runtime/test_reference_context_wiring.py | 14 +++++++++++++- tests/runtime/test_runtime_unit.py | 5 ++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index 03fffb0e9..21e3706aa 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -6,9 +6,21 @@ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langgraph.graph import END, START, StateGraph -from uipath.tracing import ReferenceContext, ReferenceContextAccessor +try: + from uipath.tracing import ReferenceContext, ReferenceContextAccessor + _reference_context_available = True +except ImportError: + _reference_context_available = False + ReferenceContext = None # type: ignore[assignment,misc] + ReferenceContextAccessor = None # type: ignore[assignment] + from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime +pytestmark = pytest.mark.skipif( + not _reference_context_available, + reason="installed uipath does not export ReferenceContext", +) + # --------------------------------------------------------------------------- # Minimal graph fixture diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index b232c4029..501e749d5 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -321,7 +321,10 @@ def test_push_returns_token_when_reference_context_unavailable( def test_push_does_not_set_real_accessor_when_unavailable( self, monkeypatch: pytest.MonkeyPatch ) -> None: - from uipath.tracing import ReferenceContextAccessor + try: + from uipath.tracing import ReferenceContextAccessor + except ImportError: + pytest.skip("installed uipath does not export ReferenceContext") import uipath_langchain.runtime.runtime as rt_mod from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor From c977399672d6af4a28ce68d47ee303121b976667 Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 22:18:26 -0700 Subject: [PATCH 05/22] fix(traceview): handle comments from error --- src/uipath_langchain/runtime/runtime.py | 6 +++--- tests/runtime/test_reference_context_wiring.py | 8 ++++---- tests/runtime/test_runtime_unit.py | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index 87ef36a48..8a60e45fc 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -42,10 +42,10 @@ def reset(token: Any) -> None: try: - from uipath.tracing import ReferenceContext, ReferenceContextAccessor + from uipath.tracing import ReferenceContext, ReferenceContextAccessor # type: ignore[attr-defined] except ImportError: - ReferenceContext = None # type: ignore[assignment,misc] - ReferenceContextAccessor = _NoopReferenceContextAccessor # type: ignore[assignment] + ReferenceContext = None + ReferenceContextAccessor = _NoopReferenceContextAccessor from uipath.runtime.errors import ( UiPathBaseRuntimeError, UiPathErrorCategory, diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index 21e3706aa..4b8368718 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -7,12 +7,12 @@ from langgraph.graph import END, START, StateGraph try: - from uipath.tracing import ReferenceContext, ReferenceContextAccessor + from uipath.tracing import ReferenceContext, ReferenceContextAccessor # type: ignore[attr-defined] _reference_context_available = True except ImportError: _reference_context_available = False - ReferenceContext = None # type: ignore[assignment,misc] - ReferenceContextAccessor = None # type: ignore[assignment] + ReferenceContext = None + ReferenceContextAccessor = None from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime @@ -166,7 +166,7 @@ def _boom(s: _S) -> _S: raise ValueError("explode") g = StateGraph(_S) - g.add_node("boom", _boom) + g.add_node("boom", _boom) # type: ignore[arg-type] g.add_edge(START, "boom") g.add_edge("boom", END) diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index 501e749d5..172c52b41 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -22,10 +22,10 @@ def _make_runtime(**kwargs: Any) -> UiPathLangGraphRuntime: def _snapshot( - next_nodes: tuple = (), - interrupts: tuple = (), - tasks: list | None = None, - values: dict | None = None, + next_nodes: tuple[Any, ...] = (), + interrupts: tuple[Any, ...] = (), + tasks: list[Any] | None = None, + values: dict[str, Any] | None = None, ) -> MagicMock: snap = MagicMock(spec=StateSnapshot) snap.next = next_nodes @@ -322,7 +322,7 @@ def test_push_does_not_set_real_accessor_when_unavailable( self, monkeypatch: pytest.MonkeyPatch ) -> None: try: - from uipath.tracing import ReferenceContextAccessor + from uipath.tracing import ReferenceContextAccessor # type: ignore[attr-defined] except ImportError: pytest.skip("installed uipath does not export ReferenceContext") From 8fd5dba2c1923801fff21d7913da7ce6d2d0b314 Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 22:27:01 -0700 Subject: [PATCH 06/22] fix(traceview): fix module order --- src/uipath_langchain/runtime/runtime.py | 52 +++++++++++++------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index 8a60e45fc..9fcd08a6f 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -19,6 +19,30 @@ UiPathRuntimeStorageProtocol, UiPathStreamOptions, ) +from uipath.runtime.errors import ( + UiPathBaseRuntimeError, + UiPathErrorCategory, + UiPathErrorCode, +) +from uipath.runtime.events import ( + UiPathRuntimeEvent, + UiPathRuntimeMessageEvent, + UiPathRuntimeStateEvent, + UiPathRuntimeStatePhase, +) +from uipath.runtime.schema import UiPathRuntimeSchema + +from uipath_langchain.agent.tools.client_side_tool import ClientSideToolInfo +from uipath_langchain.chat.hitl import ( + IS_CONVERSATIONAL_CLIENT_SIDE_TOOL, + get_confirmation_schema, +) +from uipath_langchain.runtime.errors import LangGraphErrorCode, LangGraphRuntimeError +from uipath_langchain.runtime.messages import UiPathChatMessagesMapper +from uipath_langchain.runtime.schema import get_entrypoints_schema, get_graph_schema + +from ._serialize import serialize_output + # Guarded import: ReferenceContext was added to uipath.tracing in a later release. # Older installed packages lack it; the noop shim keeps the runtime loadable while # silently disabling span propagation until the package is updated. @@ -42,33 +66,13 @@ def reset(token: Any) -> None: try: - from uipath.tracing import ReferenceContext, ReferenceContextAccessor # type: ignore[attr-defined] + from uipath.tracing import ( # type: ignore[attr-defined] + ReferenceContext, + ReferenceContextAccessor, + ) except ImportError: ReferenceContext = None ReferenceContextAccessor = _NoopReferenceContextAccessor -from uipath.runtime.errors import ( - UiPathBaseRuntimeError, - UiPathErrorCategory, - UiPathErrorCode, -) -from uipath.runtime.events import ( - UiPathRuntimeEvent, - UiPathRuntimeMessageEvent, - UiPathRuntimeStateEvent, - UiPathRuntimeStatePhase, -) -from uipath.runtime.schema import UiPathRuntimeSchema - -from uipath_langchain.agent.tools.client_side_tool import ClientSideToolInfo -from uipath_langchain.chat.hitl import ( - IS_CONVERSATIONAL_CLIENT_SIDE_TOOL, - get_confirmation_schema, -) -from uipath_langchain.runtime.errors import LangGraphErrorCode, LangGraphRuntimeError -from uipath_langchain.runtime.messages import UiPathChatMessagesMapper -from uipath_langchain.runtime.schema import get_entrypoints_schema, get_graph_schema - -from ._serialize import serialize_output logger = logging.getLogger(__name__) From a7c71b0146d19e8c7550a299625fbdb57d6a4b56 Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 22:31:26 -0700 Subject: [PATCH 07/22] fix(traceview): fix modules --- tests/runtime/test_reference_context_wiring.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index 4b8368718..a6341156c 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -7,13 +7,17 @@ from langgraph.graph import END, START, StateGraph try: - from uipath.tracing import ReferenceContext, ReferenceContextAccessor # type: ignore[attr-defined] + from uipath.tracing import ( # type: ignore[attr-defined] + ReferenceContext, + ReferenceContextAccessor, + ) _reference_context_available = True except ImportError: _reference_context_available = False ReferenceContext = None ReferenceContextAccessor = None +from uipath_langchain.runtime.errors import LangGraphRuntimeError from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime pytestmark = pytest.mark.skipif( @@ -174,7 +178,7 @@ def _boom(s: _S) -> _S: await memory.setup() compiled = g.compile(checkpointer=memory) runtime = UiPathLangGraphRuntime(graph=compiled, runtime_id="err-run") - with pytest.raises(Exception): + with pytest.raises(LangGraphRuntimeError): await runtime.execute(input={"v": "x"}) assert ReferenceContextAccessor.get() is None From b5993809c421f759fcb12284546b7981f0a7bd04 Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 22:37:00 -0700 Subject: [PATCH 08/22] fix(traceview): fix module order --- tests/runtime/test_runtime_unit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index 172c52b41..9c9fc18de 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -322,7 +322,9 @@ def test_push_does_not_set_real_accessor_when_unavailable( self, monkeypatch: pytest.MonkeyPatch ) -> None: try: - from uipath.tracing import ReferenceContextAccessor # type: ignore[attr-defined] + from uipath.tracing import ( + ReferenceContextAccessor, # type: ignore[attr-defined] + ) except ImportError: pytest.skip("installed uipath does not export ReferenceContext") From 7e1f7d8b119f25385ab17c35deebb70c9e512abc Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 23:11:46 -0700 Subject: [PATCH 09/22] fix(traceview): fix unit tests --- .../runtime/test_reference_context_wiring.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index a6341156c..09ed3f88c 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -203,3 +203,31 @@ async def test_context_cleared_after_stream( pass assert ReferenceContextAccessor.get() is None + + +async def test_context_cleared_after_stream_on_error( + monkeypatch: pytest.MonkeyPatch, tmp_path +) -> None: + _clear_accessor() + monkeypatch.setenv("UIPATH_AGENT_ID", "550e8400-e29b-41d4-a716-446655440020") + + class _S(TypedDict): + v: str + + def _boom(s: _S) -> _S: + raise ValueError("explode") + + g = StateGraph(_S) + g.add_node("boom", _boom) # type: ignore[arg-type] + g.add_edge(START, "boom") + g.add_edge("boom", END) + + async with AsyncSqliteSaver.from_conn_string(str(tmp_path / "stream-err.db")) as memory: + await memory.setup() + compiled = g.compile(checkpointer=memory) + runtime = UiPathLangGraphRuntime(graph=compiled, runtime_id="stream-err-run") + with pytest.raises(LangGraphRuntimeError): + async for _ in runtime.stream(input={"v": "x"}): + pass + + assert ReferenceContextAccessor.get() is None From b5763134d58d6cb984d6a138f8eb0a6de90b62fd Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 23:18:04 -0700 Subject: [PATCH 10/22] fix(traceview): fix dump module thing --- tests/runtime/test_runtime_unit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index 9c9fc18de..9949dec95 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -322,8 +322,8 @@ def test_push_does_not_set_real_accessor_when_unavailable( self, monkeypatch: pytest.MonkeyPatch ) -> None: try: - from uipath.tracing import ( - ReferenceContextAccessor, # type: ignore[attr-defined] + from uipath.tracing import ( # type: ignore[attr-defined] + ReferenceContextAccessor, ) except ImportError: pytest.skip("installed uipath does not export ReferenceContext") From 1bb9a63f1d8d591ff8b33b3a37c1f0d232672b5a Mon Sep 17 00:00:00 2001 From: Jesus Date: Sun, 5 Jul 2026 23:21:18 -0700 Subject: [PATCH 11/22] fix(traceview): format --- .../runtime/test_reference_context_wiring.py | 9 +++- tests/runtime/test_runtime_unit.py | 47 ++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index 09ed3f88c..6bf7e34c7 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -11,6 +11,7 @@ ReferenceContext, ReferenceContextAccessor, ) + _reference_context_available = True except ImportError: _reference_context_available = False @@ -30,6 +31,7 @@ # Minimal graph fixture # --------------------------------------------------------------------------- + class _State(TypedDict): value: str @@ -50,6 +52,7 @@ def _clear_accessor() -> None: # _push_reference_context — unit tests (no graph needed) # --------------------------------------------------------------------------- + class TestPushReferenceContext: def setup_method(self) -> None: _clear_accessor() @@ -141,6 +144,7 @@ def test_stacks_on_top_of_parent_context( # execute() — context cleared after run # --------------------------------------------------------------------------- + async def test_context_cleared_after_execute( monkeypatch: pytest.MonkeyPatch, tmp_path ) -> None: @@ -188,6 +192,7 @@ def _boom(s: _S) -> _S: # stream() — context cleared after run # --------------------------------------------------------------------------- + async def test_context_cleared_after_stream( monkeypatch: pytest.MonkeyPatch, tmp_path ) -> None: @@ -222,7 +227,9 @@ def _boom(s: _S) -> _S: g.add_edge(START, "boom") g.add_edge("boom", END) - async with AsyncSqliteSaver.from_conn_string(str(tmp_path / "stream-err.db")) as memory: + async with AsyncSqliteSaver.from_conn_string( + str(tmp_path / "stream-err.db") + ) as memory: await memory.setup() compiled = g.compile(checkpointer=memory) runtime = UiPathLangGraphRuntime(graph=compiled, runtime_id="stream-err-run") diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index 9949dec95..e63aa88cc 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -48,10 +48,15 @@ def test_root_graph_returns_node_name(self) -> None: assert self.rt._build_node_name((), "agent") == "agent" def test_single_subgraph_prepends_subgraph_name(self) -> None: - assert self.rt._build_node_name(("coder:abc123",), "generate") == "coder:generate" + assert ( + self.rt._build_node_name(("coder:abc123",), "generate") == "coder:generate" + ) def test_nested_subgraphs_build_full_path(self) -> None: - assert self.rt._build_node_name(("coder:a", "debugger:b"), "analyze") == "coder:debugger:analyze" + assert ( + self.rt._build_node_name(("coder:a", "debugger:b"), "analyze") + == "coder:debugger:analyze" + ) def test_namespace_without_colon_uses_full_segment(self) -> None: assert self.rt._build_node_name(("subgraph",), "node") == "subgraph:node" @@ -94,7 +99,10 @@ def test_multi_channel_returns_only_present_keys(self) -> None: def test_multi_channel_unwraps_single_key_wrapping_dict(self) -> None: self.rt.graph.output_channels = ["x", "y"] - assert self.rt._extract_graph_result({"node": {"x": 10, "y": 20}}) == {"x": 10, "y": 20} + assert self.rt._extract_graph_result({"node": {"x": 10, "y": 20}}) == { + "x": 10, + "y": 20, + } def test_tuple_format_unwraps_second_element(self) -> None: self.rt.graph.output_channels = "val" @@ -116,7 +124,9 @@ def setup_method(self) -> None: self.rt = _make_runtime() def test_langgraph_runtime_error_returned_unchanged(self) -> None: - err = LangGraphRuntimeError(LangGraphErrorCode.GRAPH_LOAD_ERROR, "t", "d", UiPathErrorCategory.USER) + err = LangGraphRuntimeError( + LangGraphErrorCode.GRAPH_LOAD_ERROR, "t", "d", UiPathErrorCategory.USER + ) assert self.rt.create_runtime_error(err) is err def test_graph_recursion_error_maps_to_graph_load_error(self) -> None: @@ -147,19 +157,27 @@ def test_thread_id_set_from_runtime_id(self) -> None: rt = _make_runtime(runtime_id="my-run") assert rt._get_graph_config()["configurable"]["thread_id"] == "my-run" - def test_no_recursion_limit_by_default(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_no_recursion_limit_by_default( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.delenv("LANGCHAIN_RECURSION_LIMIT", raising=False) assert "recursion_limit" not in _make_runtime()._get_graph_config() - def test_recursion_limit_read_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_recursion_limit_read_from_env( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("LANGCHAIN_RECURSION_LIMIT", "50") assert _make_runtime()._get_graph_config()["recursion_limit"] == 50 - def test_no_max_concurrency_by_default(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_no_max_concurrency_by_default( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.delenv("LANGCHAIN_MAX_CONCURRENCY", raising=False) assert "max_concurrency" not in _make_runtime()._get_graph_config() - def test_max_concurrency_read_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_max_concurrency_read_from_env( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setenv("LANGCHAIN_MAX_CONCURRENCY", "4") assert _make_runtime()._get_graph_config()["max_concurrency"] == 4 @@ -228,7 +246,10 @@ def setup_method(self) -> None: self.rt = _make_runtime() def test_status_is_successful(self) -> None: - assert self.rt._create_success_result({"k": "v"}).status == UiPathRuntimeStatus.SUCCESSFUL + assert ( + self.rt._create_success_result({"k": "v"}).status + == UiPathRuntimeStatus.SUCCESSFUL + ) def test_output_passed_through(self) -> None: assert self.rt._create_success_result({"key": "val"}).output == {"key": "val"} @@ -310,7 +331,9 @@ def test_push_returns_token_when_reference_context_unavailable( from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor monkeypatch.setattr(rt_mod, "ReferenceContext", None) - monkeypatch.setattr(rt_mod, "ReferenceContextAccessor", _NoopReferenceContextAccessor) + monkeypatch.setattr( + rt_mod, "ReferenceContextAccessor", _NoopReferenceContextAccessor + ) rt = _make_runtime() token = rt._push_reference_context() @@ -332,7 +355,9 @@ def test_push_does_not_set_real_accessor_when_unavailable( from uipath_langchain.runtime.runtime import _NoopReferenceContextAccessor monkeypatch.setattr(rt_mod, "ReferenceContext", None) - monkeypatch.setattr(rt_mod, "ReferenceContextAccessor", _NoopReferenceContextAccessor) + monkeypatch.setattr( + rt_mod, "ReferenceContextAccessor", _NoopReferenceContextAccessor + ) rt = _make_runtime() before = ReferenceContextAccessor.get() From 3db217f602d6e0e287830eb1e1fd002d41aa050e Mon Sep 17 00:00:00 2001 From: Jesus Date: Mon, 6 Jul 2026 06:27:04 -0700 Subject: [PATCH 12/22] feat(traceview): update versions --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4c35c825..7e443f8d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath-langchain" -version = "0.13.22" +version = "0.13.23" description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ - "uipath>=2.12.7, <2.13.0", + "uipath>=2.12.8, <2.13.0", "uipath-core>=0.5.20, <0.6.0", - "uipath-platform>=0.1.91, <0.2.0", + "uipath-platform>=0.1.92, <0.2.0", "uipath-runtime>=0.11.4, <0.12.0", "langgraph>=1.1.8, <2.0.0", "langchain-core>=1.2.27, <2.0.0", From 730bb4b3ecbb311963ae51b87252c5448ec88af1 Mon Sep 17 00:00:00 2001 From: Jesus Date: Mon, 6 Jul 2026 06:30:25 -0700 Subject: [PATCH 13/22] fix(traceview): revert package --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7e443f8d5..c9b065e6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.11" dependencies = [ "uipath>=2.12.8, <2.13.0", "uipath-core>=0.5.20, <0.6.0", - "uipath-platform>=0.1.92, <0.2.0", + "uipath-platform>=0.1.91, <0.2.0", "uipath-runtime>=0.11.4, <0.12.0", "langgraph>=1.1.8, <2.0.0", "langchain-core>=1.2.27, <2.0.0", From f46cc406b13aa38992d0616369c5089bead95ff2 Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 13:34:52 -0700 Subject: [PATCH 14/22] feat(traceview): update uv lock --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 22c3ab02f..4ae4d2b6e 100644 --- a/uv.lock +++ b/uv.lock @@ -4432,7 +4432,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.13.3" +version = "2.13.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "applicationinsights" }, @@ -4477,7 +4477,7 @@ wheels = [ [[package]] name = "uipath-langchain" -version = "0.14.1" +version = "0.14.2" source = { editable = "." } dependencies = [ { name = "a2a-sdk" }, @@ -4649,7 +4649,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.2" +version = "0.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, From 299957606515c1bedc6c1f4d20244d65fa371d75 Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 14:10:32 -0700 Subject: [PATCH 15/22] feat(traceview): update lock --- uv.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/uv.lock b/uv.lock index 4ae4d2b6e..a67862ae3 100644 --- a/uv.lock +++ b/uv.lock @@ -4432,7 +4432,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.13.5" +version = "2.13.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "applicationinsights" }, @@ -4456,23 +4456,23 @@ dependencies = [ { name = "uipath-platform" }, { name = "uipath-runtime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/2b/42848522df8a0d9e674aef9a0267fe15c3c4df69d490e080a3166f2400d2/uipath-2.13.3.tar.gz", hash = "sha256:5f35669d9f96f4e797a22278006f551be2ee24e067d0ad32b61d983933d18418", size = 4495433, upload-time = "2026-07-07T10:29:23.936Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/86/a67e1387d78a7923c16904b1825f22176fce6b88cf3dbedca5be4c0a0676/uipath-2.13.6.tar.gz", hash = "sha256:fbea0de3e973fb4b6dc83bd1d95b5cff53f22c52ef4c0ac634bf62e86cd06eb5", size = 4498617, upload-time = "2026-07-07T17:40:03.15Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/ee/f3991b7b5f321b5a5ae195d4bb0315ea99dc184d2670126269f294ff1378/uipath-2.13.3-py3-none-any.whl", hash = "sha256:95159a1d1b237152125327d52b33941b0aae808c77825240c2953629fe502649", size = 417701, upload-time = "2026-07-07T10:29:21.363Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3c/78821bc5baba234019df9ebe8b6e66f8bfa41cbce96f8b4abcc1f6725505/uipath-2.13.6-py3-none-any.whl", hash = "sha256:476b7b35dd808f4b78b0ce0799d68c7e282f0d8279a42515d3be0303103d267a", size = 418963, upload-time = "2026-07-07T17:40:00.997Z" }, ] [[package]] name = "uipath-core" -version = "0.5.29" +version = "0.5.30" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/bf/83e261295dd5d5137d2a17d455ee43d090decdb683654042f0f97bae74a5/uipath_core-0.5.29.tar.gz", hash = "sha256:f931d6ad866c3b70dc93e25e3d1cc9e484deb7061d61efe41f6ae094e5acafb2", size = 130862, upload-time = "2026-07-07T10:27:13.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/cc/a5acd1d4bbf3c8252a2c8ee3a57b584a790b8bfd86661cf2c236084875ac/uipath_core-0.5.30.tar.gz", hash = "sha256:746494d97a07fc557baff6dca341d53f28ffdd09109b666c19892092816c3335", size = 130833, upload-time = "2026-07-07T14:38:57.377Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/34/0b58685eacae7cfff0a2281348f33ea1f2559d91f3702f16c2b15ac4f504/uipath_core-0.5.29-py3-none-any.whl", hash = "sha256:fac1521f3963ff5787b63dc02d2e26417f298eef4bbc40a2851f857f24aa37cd", size = 55049, upload-time = "2026-07-07T10:27:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/a90a150141449a540be6b607dd01b6aa08823b3332531c54813ad627cfb6/uipath_core-0.5.30-py3-none-any.whl", hash = "sha256:b8c4f48a06a1c49504351bbfa42282e225af1a30467e93d4e2e2ea0e3a2f06f9", size = 55038, upload-time = "2026-07-07T14:38:55.979Z" }, ] [[package]] @@ -4555,7 +4555,7 @@ requires-dist = [ { name = "pillow", specifier = ">=12.1.1" }, { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "uipath", specifier = ">=2.13.3,<2.14.0" }, + { name = "uipath", specifier = ">=2.13.5,<2.14.0" }, { name = "uipath-core", specifier = ">=0.5.29,<0.6.0" }, { name = "uipath-langchain-client", extras = ["all"], marker = "extra == 'all'", specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-langchain-client", extras = ["anthropic"], marker = "extra == 'anthropic'", specifier = ">=1.16.1,<1.17.0" }, @@ -4565,7 +4565,7 @@ requires-dist = [ { name = "uipath-langchain-client", extras = ["openai"], specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-langchain-client", extras = ["vertexai"], marker = "extra == 'vertex'", specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-llm-client", specifier = ">=1.16.2,<1.17.0" }, - { name = "uipath-platform", specifier = ">=0.2.2,<0.3.0" }, + { name = "uipath-platform", specifier = ">=0.2.4,<0.3.0" }, { name = "uipath-runtime", specifier = ">=0.12.1,<0.13.0" }, ] provides-extras = ["anthropic", "vertex", "bedrock", "fireworks", "all"] @@ -4659,23 +4659,23 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/b6/a776ef1ba59189e69f6d04f3728dde1fbd02219f78f03bd80d9b888a834f/uipath_platform-0.2.2.tar.gz", hash = "sha256:c131b7c943efdde83f08e4161c8d1b4170b8fffd9d961dbea2486d6f8b85daf4", size = 414645, upload-time = "2026-07-07T10:28:10.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/feaeeaa028ee94fedcd6ebd40d2068097c3bfcacf683c798e40b35293ee5/uipath_platform-0.2.4.tar.gz", hash = "sha256:d79c7c99d74c484006a4684efc62dbd2572ba7053447e9882c624f3b272e2367", size = 421692, upload-time = "2026-07-07T17:13:56.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/c6/cab60e437565e9ca600d793e1ab60f6dc5aeac785de1ee5dde498fee642c/uipath_platform-0.2.2-py3-none-any.whl", hash = "sha256:9882f6d25473b6b4df3d26f537e46339d6f52081c1113f8b7e5543f8bafa9494", size = 274146, upload-time = "2026-07-07T10:28:08.971Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/fd8a88e50f99579483b8ead68912884d50505ecac8046491c8c1c1318e09/uipath_platform-0.2.4-py3-none-any.whl", hash = "sha256:a0736a60723e7c48a6efdbc42a64a933197205ca7c555a4f89988a50eb88a5c9", size = 277697, upload-time = "2026-07-07T17:13:55.444Z" }, ] [[package]] name = "uipath-runtime" -version = "0.12.1" +version = "0.12.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "chardet" }, { name = "uipath-core" }, { name = "vadersentiment" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/0b/6d6bfd912c2ad6978f58646becf3647430ccb087008a8c78dbc33b0b101e/uipath_runtime-0.12.1.tar.gz", hash = "sha256:a63ce739ea53da8479bb0314477f89d42724045ecbd7db3f2a3f0a09ab17a755", size = 235372, upload-time = "2026-07-06T11:14:51.947Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/eb/1638b8307c9305527a449664c95a03a2af1bfaf1bd150819fb882ef71415/uipath_runtime-0.12.2.tar.gz", hash = "sha256:0d1f56f41add0d932bbe0c975d473ec27a86c58e14e9aa745d6501356c51284a", size = 235789, upload-time = "2026-07-07T11:02:12.599Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/4c/433de72c4c5869ee26a63f68361b5c62d04bb03e9c7db9a69c0d3db7b458/uipath_runtime-0.12.1-py3-none-any.whl", hash = "sha256:88b014e0c5a50a5b41b2e532bd628eebc91d70ad42b6d3389fdc61b7ae78e0a6", size = 92004, upload-time = "2026-07-06T11:14:50.363Z" }, + { url = "https://files.pythonhosted.org/packages/09/7a/9042e41a71726386d6f7673f843decd5405daff0787bfe127a8ac15abaa4/uipath_runtime-0.12.2-py3-none-any.whl", hash = "sha256:8faa88ac0af208b48f08abfff11530ae0279f1e88a12e9d2935f7f74111ebde1", size = 92087, upload-time = "2026-07-07T11:02:11.221Z" }, ] [[package]] From 46ca34b083c8b3199f1a1e0d6924613a265d3cbc Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 14:32:05 -0700 Subject: [PATCH 16/22] Revert "feat(traceview): update lock" This reverts commit 299957606515c1bedc6c1f4d20244d65fa371d75. --- uv.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/uv.lock b/uv.lock index a67862ae3..4ae4d2b6e 100644 --- a/uv.lock +++ b/uv.lock @@ -4432,7 +4432,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.13.6" +version = "2.13.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "applicationinsights" }, @@ -4456,23 +4456,23 @@ dependencies = [ { name = "uipath-platform" }, { name = "uipath-runtime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/86/a67e1387d78a7923c16904b1825f22176fce6b88cf3dbedca5be4c0a0676/uipath-2.13.6.tar.gz", hash = "sha256:fbea0de3e973fb4b6dc83bd1d95b5cff53f22c52ef4c0ac634bf62e86cd06eb5", size = 4498617, upload-time = "2026-07-07T17:40:03.15Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/2b/42848522df8a0d9e674aef9a0267fe15c3c4df69d490e080a3166f2400d2/uipath-2.13.3.tar.gz", hash = "sha256:5f35669d9f96f4e797a22278006f551be2ee24e067d0ad32b61d983933d18418", size = 4495433, upload-time = "2026-07-07T10:29:23.936Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/3c/78821bc5baba234019df9ebe8b6e66f8bfa41cbce96f8b4abcc1f6725505/uipath-2.13.6-py3-none-any.whl", hash = "sha256:476b7b35dd808f4b78b0ce0799d68c7e282f0d8279a42515d3be0303103d267a", size = 418963, upload-time = "2026-07-07T17:40:00.997Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ee/f3991b7b5f321b5a5ae195d4bb0315ea99dc184d2670126269f294ff1378/uipath-2.13.3-py3-none-any.whl", hash = "sha256:95159a1d1b237152125327d52b33941b0aae808c77825240c2953629fe502649", size = 417701, upload-time = "2026-07-07T10:29:21.363Z" }, ] [[package]] name = "uipath-core" -version = "0.5.30" +version = "0.5.29" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/cc/a5acd1d4bbf3c8252a2c8ee3a57b584a790b8bfd86661cf2c236084875ac/uipath_core-0.5.30.tar.gz", hash = "sha256:746494d97a07fc557baff6dca341d53f28ffdd09109b666c19892092816c3335", size = 130833, upload-time = "2026-07-07T14:38:57.377Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/bf/83e261295dd5d5137d2a17d455ee43d090decdb683654042f0f97bae74a5/uipath_core-0.5.29.tar.gz", hash = "sha256:f931d6ad866c3b70dc93e25e3d1cc9e484deb7061d61efe41f6ae094e5acafb2", size = 130862, upload-time = "2026-07-07T10:27:13.171Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/da/a90a150141449a540be6b607dd01b6aa08823b3332531c54813ad627cfb6/uipath_core-0.5.30-py3-none-any.whl", hash = "sha256:b8c4f48a06a1c49504351bbfa42282e225af1a30467e93d4e2e2ea0e3a2f06f9", size = 55038, upload-time = "2026-07-07T14:38:55.979Z" }, + { url = "https://files.pythonhosted.org/packages/1f/34/0b58685eacae7cfff0a2281348f33ea1f2559d91f3702f16c2b15ac4f504/uipath_core-0.5.29-py3-none-any.whl", hash = "sha256:fac1521f3963ff5787b63dc02d2e26417f298eef4bbc40a2851f857f24aa37cd", size = 55049, upload-time = "2026-07-07T10:27:11.921Z" }, ] [[package]] @@ -4555,7 +4555,7 @@ requires-dist = [ { name = "pillow", specifier = ">=12.1.1" }, { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "uipath", specifier = ">=2.13.5,<2.14.0" }, + { name = "uipath", specifier = ">=2.13.3,<2.14.0" }, { name = "uipath-core", specifier = ">=0.5.29,<0.6.0" }, { name = "uipath-langchain-client", extras = ["all"], marker = "extra == 'all'", specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-langchain-client", extras = ["anthropic"], marker = "extra == 'anthropic'", specifier = ">=1.16.1,<1.17.0" }, @@ -4565,7 +4565,7 @@ requires-dist = [ { name = "uipath-langchain-client", extras = ["openai"], specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-langchain-client", extras = ["vertexai"], marker = "extra == 'vertex'", specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-llm-client", specifier = ">=1.16.2,<1.17.0" }, - { name = "uipath-platform", specifier = ">=0.2.4,<0.3.0" }, + { name = "uipath-platform", specifier = ">=0.2.2,<0.3.0" }, { name = "uipath-runtime", specifier = ">=0.12.1,<0.13.0" }, ] provides-extras = ["anthropic", "vertex", "bedrock", "fireworks", "all"] @@ -4659,23 +4659,23 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/6b/feaeeaa028ee94fedcd6ebd40d2068097c3bfcacf683c798e40b35293ee5/uipath_platform-0.2.4.tar.gz", hash = "sha256:d79c7c99d74c484006a4684efc62dbd2572ba7053447e9882c624f3b272e2367", size = 421692, upload-time = "2026-07-07T17:13:56.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/b6/a776ef1ba59189e69f6d04f3728dde1fbd02219f78f03bd80d9b888a834f/uipath_platform-0.2.2.tar.gz", hash = "sha256:c131b7c943efdde83f08e4161c8d1b4170b8fffd9d961dbea2486d6f8b85daf4", size = 414645, upload-time = "2026-07-07T10:28:10.412Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/fc/fd8a88e50f99579483b8ead68912884d50505ecac8046491c8c1c1318e09/uipath_platform-0.2.4-py3-none-any.whl", hash = "sha256:a0736a60723e7c48a6efdbc42a64a933197205ca7c555a4f89988a50eb88a5c9", size = 277697, upload-time = "2026-07-07T17:13:55.444Z" }, + { url = "https://files.pythonhosted.org/packages/47/c6/cab60e437565e9ca600d793e1ab60f6dc5aeac785de1ee5dde498fee642c/uipath_platform-0.2.2-py3-none-any.whl", hash = "sha256:9882f6d25473b6b4df3d26f537e46339d6f52081c1113f8b7e5543f8bafa9494", size = 274146, upload-time = "2026-07-07T10:28:08.971Z" }, ] [[package]] name = "uipath-runtime" -version = "0.12.2" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "chardet" }, { name = "uipath-core" }, { name = "vadersentiment" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/eb/1638b8307c9305527a449664c95a03a2af1bfaf1bd150819fb882ef71415/uipath_runtime-0.12.2.tar.gz", hash = "sha256:0d1f56f41add0d932bbe0c975d473ec27a86c58e14e9aa745d6501356c51284a", size = 235789, upload-time = "2026-07-07T11:02:12.599Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/0b/6d6bfd912c2ad6978f58646becf3647430ccb087008a8c78dbc33b0b101e/uipath_runtime-0.12.1.tar.gz", hash = "sha256:a63ce739ea53da8479bb0314477f89d42724045ecbd7db3f2a3f0a09ab17a755", size = 235372, upload-time = "2026-07-06T11:14:51.947Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/7a/9042e41a71726386d6f7673f843decd5405daff0787bfe127a8ac15abaa4/uipath_runtime-0.12.2-py3-none-any.whl", hash = "sha256:8faa88ac0af208b48f08abfff11530ae0279f1e88a12e9d2935f7f74111ebde1", size = 92087, upload-time = "2026-07-07T11:02:11.221Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4c/433de72c4c5869ee26a63f68361b5c62d04bb03e9c7db9a69c0d3db7b458/uipath_runtime-0.12.1-py3-none-any.whl", hash = "sha256:88b014e0c5a50a5b41b2e532bd628eebc91d70ad42b6d3389fdc61b7ae78e0a6", size = 92004, upload-time = "2026-07-06T11:14:50.363Z" }, ] [[package]] From 51a2eb6493c8aa6d3bbfe8175ee2daf432bee07a Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 14:43:38 -0700 Subject: [PATCH 17/22] fix(traceview): update url --- uv.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index 4ae4d2b6e..ecdcee7e6 100644 --- a/uv.lock +++ b/uv.lock @@ -4456,9 +4456,9 @@ dependencies = [ { name = "uipath-platform" }, { name = "uipath-runtime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/2b/42848522df8a0d9e674aef9a0267fe15c3c4df69d490e080a3166f2400d2/uipath-2.13.3.tar.gz", hash = "sha256:5f35669d9f96f4e797a22278006f551be2ee24e067d0ad32b61d983933d18418", size = 4495433, upload-time = "2026-07-07T10:29:23.936Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/e4/3fb853b93cdb79ede574566de4222630542e8da82aaefc51a2932a584ef0/uipath-2.13.5.tar.gz", hash = "sha256:fa7b5c37b4beb10b162778b59b449ef7158bd05420f75a2a5bfb084832d29035", size = 4497874, upload-time = "2026-07-07T17:14:45.421295Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/ee/f3991b7b5f321b5a5ae195d4bb0315ea99dc184d2670126269f294ff1378/uipath-2.13.3-py3-none-any.whl", hash = "sha256:95159a1d1b237152125327d52b33941b0aae808c77825240c2953629fe502649", size = 417701, upload-time = "2026-07-07T10:29:21.363Z" }, + { url = "https://files.pythonhosted.org/packages/3e/7c/51400a789db89ee040b07c5a877297e1b9faca6add00988ce31f81ce1266/uipath-2.13.5-py3-none-any.whl", hash = "sha256:6705b6f9fcd50e0c3d8228312d47969ef382f7895b927ca2f3c6fd16c57267a1", size = 418209, upload-time = "2026-07-07T17:14:43.292295Z" }, ] [[package]] From 35ed7072bf042c18d4460f8a968c0941ceca24c1 Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 14:48:12 -0700 Subject: [PATCH 18/22] fix(traceview): fix module --- uv.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index ecdcee7e6..fb388f806 100644 --- a/uv.lock +++ b/uv.lock @@ -4659,9 +4659,9 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/b6/a776ef1ba59189e69f6d04f3728dde1fbd02219f78f03bd80d9b888a834f/uipath_platform-0.2.2.tar.gz", hash = "sha256:c131b7c943efdde83f08e4161c8d1b4170b8fffd9d961dbea2486d6f8b85daf4", size = 414645, upload-time = "2026-07-07T10:28:10.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/feaeeaa028ee94fedcd6ebd40d2068097c3bfcacf683c798e40b35293ee5/uipath_platform-0.2.4.tar.gz", hash = "sha256:d79c7c99d74c484006a4684efc62dbd2572ba7053447e9882c624f3b272e2367", size = 421692, upload-time = "2026-07-07T17:13:56.832163Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/c6/cab60e437565e9ca600d793e1ab60f6dc5aeac785de1ee5dde498fee642c/uipath_platform-0.2.2-py3-none-any.whl", hash = "sha256:9882f6d25473b6b4df3d26f537e46339d6f52081c1113f8b7e5543f8bafa9494", size = 274146, upload-time = "2026-07-07T10:28:08.971Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/fd8a88e50f99579483b8ead68912884d50505ecac8046491c8c1c1318e09/uipath_platform-0.2.4-py3-none-any.whl", hash = "sha256:a0736a60723e7c48a6efdbc42a64a933197205ca7c555a4f89988a50eb88a5c9", size = 277697, upload-time = "2026-07-07T17:13:55.444420Z" }, ] [[package]] From dcb6939ec0c0809a3ef860631975fd67d7b76aaa Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 14:52:40 -0700 Subject: [PATCH 19/22] fix(traceview): modules --- uv.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index fb388f806..6c477a85b 100644 --- a/uv.lock +++ b/uv.lock @@ -4555,7 +4555,7 @@ requires-dist = [ { name = "pillow", specifier = ">=12.1.1" }, { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "uipath", specifier = ">=2.13.3,<2.14.0" }, + { name = "uipath", specifier = ">=2.13.5,<2.14.0" }, { name = "uipath-core", specifier = ">=0.5.29,<0.6.0" }, { name = "uipath-langchain-client", extras = ["all"], marker = "extra == 'all'", specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-langchain-client", extras = ["anthropic"], marker = "extra == 'anthropic'", specifier = ">=1.16.1,<1.17.0" }, @@ -4565,7 +4565,7 @@ requires-dist = [ { name = "uipath-langchain-client", extras = ["openai"], specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-langchain-client", extras = ["vertexai"], marker = "extra == 'vertex'", specifier = ">=1.16.1,<1.17.0" }, { name = "uipath-llm-client", specifier = ">=1.16.2,<1.17.0" }, - { name = "uipath-platform", specifier = ">=0.2.2,<0.3.0" }, + { name = "uipath-platform", specifier = ">=0.2.4,<0.3.0" }, { name = "uipath-runtime", specifier = ">=0.12.1,<0.13.0" }, ] provides-extras = ["anthropic", "vertex", "bedrock", "fireworks", "all"] From 9e2745ba7cd47a21c045850d1d6ef6fdf194a9fe Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 14:57:39 -0700 Subject: [PATCH 20/22] fix(traceview): fix assignments --- src/uipath_langchain/runtime/runtime.py | 6 +++--- tests/runtime/test_reference_context_wiring.py | 6 +++--- tests/runtime/test_runtime_unit.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index 9fcd08a6f..9b952711b 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -66,13 +66,13 @@ def reset(token: Any) -> None: try: - from uipath.tracing import ( # type: ignore[attr-defined] + from uipath.tracing import ( ReferenceContext, ReferenceContextAccessor, ) except ImportError: - ReferenceContext = None - ReferenceContextAccessor = _NoopReferenceContextAccessor + ReferenceContext = None # type: ignore[assignment] + ReferenceContextAccessor = _NoopReferenceContextAccessor # type: ignore[assignment] logger = logging.getLogger(__name__) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index 6bf7e34c7..479442a72 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -7,7 +7,7 @@ from langgraph.graph import END, START, StateGraph try: - from uipath.tracing import ( # type: ignore[attr-defined] + from uipath.tracing import ( ReferenceContext, ReferenceContextAccessor, ) @@ -15,8 +15,8 @@ _reference_context_available = True except ImportError: _reference_context_available = False - ReferenceContext = None - ReferenceContextAccessor = None + ReferenceContext = None # type: ignore[assignment] + ReferenceContextAccessor = None # type: ignore[assignment] from uipath_langchain.runtime.errors import LangGraphRuntimeError from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime diff --git a/tests/runtime/test_runtime_unit.py b/tests/runtime/test_runtime_unit.py index e63aa88cc..e6c14a130 100644 --- a/tests/runtime/test_runtime_unit.py +++ b/tests/runtime/test_runtime_unit.py @@ -345,7 +345,7 @@ def test_push_does_not_set_real_accessor_when_unavailable( self, monkeypatch: pytest.MonkeyPatch ) -> None: try: - from uipath.tracing import ( # type: ignore[attr-defined] + from uipath.tracing import ( ReferenceContextAccessor, ) except ImportError: From 9011694b584a2e075a23114a9adc90e059747260 Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 15:02:14 -0700 Subject: [PATCH 21/22] fix(traceview): fix mypy --- src/uipath_langchain/runtime/runtime.py | 6 ++++-- tests/runtime/test_reference_context_wiring.py | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/uipath_langchain/runtime/runtime.py b/src/uipath_langchain/runtime/runtime.py index 9b952711b..04657b1e7 100644 --- a/src/uipath_langchain/runtime/runtime.py +++ b/src/uipath_langchain/runtime/runtime.py @@ -65,14 +65,16 @@ def reset(token: Any) -> None: _noop_ref_ctx.reset(token) +ReferenceContext: Any = None +ReferenceContextAccessor: Any = _NoopReferenceContextAccessor + try: from uipath.tracing import ( ReferenceContext, ReferenceContextAccessor, ) except ImportError: - ReferenceContext = None # type: ignore[assignment] - ReferenceContextAccessor = _NoopReferenceContextAccessor # type: ignore[assignment] + pass logger = logging.getLogger(__name__) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index 479442a72..d2c9656a6 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -6,6 +6,10 @@ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langgraph.graph import END, START, StateGraph +ReferenceContext: Any = None +ReferenceContextAccessor: Any = None +_reference_context_available = False + try: from uipath.tracing import ( ReferenceContext, @@ -14,9 +18,7 @@ _reference_context_available = True except ImportError: - _reference_context_available = False - ReferenceContext = None # type: ignore[assignment] - ReferenceContextAccessor = None # type: ignore[assignment] + pass from uipath_langchain.runtime.errors import LangGraphRuntimeError from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime From 65f8924aa0848347e51ba4d5dd4faf9ac1bc1e19 Mon Sep 17 00:00:00 2001 From: Jesus Date: Tue, 7 Jul 2026 15:04:23 -0700 Subject: [PATCH 22/22] fix(traceview): modules --- tests/runtime/test_reference_context_wiring.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/runtime/test_reference_context_wiring.py b/tests/runtime/test_reference_context_wiring.py index d2c9656a6..37e82aa29 100644 --- a/tests/runtime/test_reference_context_wiring.py +++ b/tests/runtime/test_reference_context_wiring.py @@ -6,6 +6,9 @@ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langgraph.graph import END, START, StateGraph +from uipath_langchain.runtime.errors import LangGraphRuntimeError +from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime + ReferenceContext: Any = None ReferenceContextAccessor: Any = None _reference_context_available = False @@ -20,9 +23,6 @@ except ImportError: pass -from uipath_langchain.runtime.errors import LangGraphRuntimeError -from uipath_langchain.runtime.runtime import UiPathLangGraphRuntime - pytestmark = pytest.mark.skipif( not _reference_context_available, reason="installed uipath does not export ReferenceContext",