Skip to content

Commit 30d9b31

Browse files
committed
fix: use InternalContext for debug breakpoints in llamaindex runtime
the breakpoint wrapper was calling wait_for_event on the workflow-level ExternalContext instead of a per-step InternalContext. this caused ContextStateError on every uipath debug run (uipath run was unaffected because it skips breakpoint injection). fix: create an InternalContext via Context._create_internal(workflow) inside the wrapper, which runs within the step worker where the framework has already set up the required context variables. also adds a pexpect-based integration test (testcases/debug-breakpoints) that exercises single/multiple breakpoints, step mode, and quit against the debug-agent sample.
1 parent feccaf7 commit 30d9b31

23 files changed

Lines changed: 6115 additions & 28 deletions

File tree

packages/uipath-llamaindex/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-llamaindex"
3-
version = "0.5.8"
3+
version = "0.5.9"
44
description = "Python SDK that enables developers to build and deploy LlamaIndex agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-llamaindex/src/uipath_llamaindex/runtime/breakpoints.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from __future__ import annotations
66

77
import functools
8-
from typing import Any, Protocol, cast
8+
from typing import Any, cast
99

1010
from workflows import Context, Workflow
1111
from workflows.decorators import StepFunction
@@ -17,10 +17,6 @@
1717
from uipath_llamaindex.runtime.schema import get_step_config
1818

1919

20-
class DebuggableWorkflow(Protocol):
21-
context: Context | None = None
22-
23-
2420
class BreakpointEvent(InputRequiredEvent):
2521
"""Event emitted when a breakpoint is hit (before step execution)."""
2622

@@ -74,25 +70,28 @@ def make_wrapper(
7470
) -> StepFunction[..., Any]:
7571
"""
7672
Return a wrapped step function that pauses on breakpoints.
73+
74+
The wrapper creates an InternalContext via ``Context._create_internal``
75+
to call ``wait_for_event``. This works because the wrapper executes
76+
inside the step worker where the framework has already set the
77+
``StepWorkerStateContextVar``.
7778
"""
7879

7980
@functools.wraps(original)
8081
async def wrapper(self, *args: Any, **kwargs: Any) -> Any:
81-
# Grab ctx from the workflow, as wired by UiPathLlamaIndexRuntime
82-
ctx: Context | None = getattr(self, "context", None)
83-
84-
if isinstance(ctx, Context):
85-
bp_event = BreakpointEvent(
86-
breakpoint_node=step_name,
87-
prefix=f"Breakpoint at {step_name}",
88-
)
89-
# Suspend until debugger resumes
90-
await ctx.wait_for_event(
91-
BreakpointResumeEvent,
92-
waiter_event=bp_event,
93-
waiter_id=f"bp_{step_name}",
94-
timeout=None,
95-
)
82+
ctx = Context._create_internal(workflow=self)
83+
84+
bp_event = BreakpointEvent(
85+
breakpoint_node=step_name,
86+
prefix=f"Breakpoint at {step_name}",
87+
)
88+
# Suspend until debugger resumes
89+
await ctx.wait_for_event(
90+
BreakpointResumeEvent,
91+
waiter_event=bp_event,
92+
waiter_id=f"bp_{step_name}",
93+
timeout=None,
94+
)
9695

9796
# Continue original step logic
9897
return await original(self, *args, **kwargs)

packages/uipath-llamaindex/src/uipath_llamaindex/runtime/runtime.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
import json
55
import logging
6-
from typing import Any, AsyncGenerator, cast
6+
from typing import Any, AsyncGenerator
77
from uuid import uuid4
88

99
from llama_index.core.agent.workflow.workflow_events import (
@@ -40,7 +40,6 @@
4040
from uipath_llamaindex.runtime.breakpoints import (
4141
BreakpointEvent,
4242
BreakpointResumeEvent,
43-
DebuggableWorkflow,
4443
inject_breakpoints,
4544
)
4645
from uipath_llamaindex.runtime.chat import UiPathChatMessagesMapper
@@ -144,11 +143,6 @@ async def _run_workflow(
144143

145144
self._context = await self._load_context()
146145

147-
# Make the Context discoverable from inside steps
148-
if self.debug_mode and self._context is not None:
149-
debug_workflow = cast(DebuggableWorkflow, self.workflow)
150-
debug_workflow.context = self._context
151-
152146
if is_resuming:
153147
handler: WorkflowHandler = self.workflow.run(ctx=self._context)
154148
if workflow_input:

0 commit comments

Comments
 (0)