Skip to content

Commit bcd7948

Browse files
authored
Merge pull request #89 from UiPath/fix/schema_generation
fix: schema generation for simple workflows
2 parents 52ba44e + c5a91be commit bcd7948

File tree

4 files changed

+77
-45
lines changed

4 files changed

+77
-45
lines changed

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.1.2"
3+
version = "0.1.3"
44
description = "UiPath LlamaIndex SDK"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_llamaindex/runtime/breakpoints.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
InputRequiredEvent,
1515
)
1616

17+
from uipath_llamaindex.runtime.schema import get_step_config
18+
1719

1820
class DebuggableWorkflow(Protocol):
1921
context: Context | None = None
@@ -39,17 +41,24 @@ def inject_breakpoints(workflow: Workflow) -> None:
3941
cls = workflow.__class__
4042

4143
for name, fn in list(vars(cls).items()):
42-
if callable(fn) and hasattr(fn, "_step_config"):
43-
wrapped = make_wrapper(name, fn)
44-
# ensure `self` is bound correctly
45-
bound = wrapped.__get__(workflow, cls) # type: ignore[attr-defined]
46-
setattr(workflow, name, bound)
44+
step_config = get_step_config(name, fn)
45+
if step_config is None or not callable(fn):
46+
continue
47+
48+
wrapped = make_wrapper(name, fn)
49+
# ensure `self` is bound correctly
50+
bound = wrapped.__get__(workflow, cls) # type: ignore[attr-defined]
51+
setattr(workflow, name, bound)
4752

48-
# also patch in _step_functions if present
49-
if name in cls._step_functions:
50-
cls._step_functions[name] = bound
53+
# also patch in _step_functions if present
54+
if name in cls._step_functions:
55+
cls._step_functions[name] = bound
5156

5257
for name, fn in list(cls._step_functions.items()):
58+
step_config = get_step_config(name, fn)
59+
if step_config is None:
60+
continue
61+
5362
wrapped = make_wrapper(name, fn)
5463

5564
# If it was originally a bound method, bind it again

src/uipath_llamaindex/runtime/schema.py

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@
2121
InputRequiredEvent,
2222
StopEvent,
2323
)
24-
from workflows.utils import (
25-
get_steps_from_class,
26-
get_steps_from_instance,
27-
)
2824

2925

3026
def get_entrypoints_schema(workflow: Workflow) -> dict[str, Any]:
@@ -128,30 +124,24 @@ def get_workflow_schema(workflow: Workflow) -> UiPathRuntimeGraph:
128124
nodes: list[UiPathRuntimeNode] = []
129125
edges: list[UiPathRuntimeEdge] = []
130126

131-
# Add __start__ node
132127
nodes.append(
133128
UiPathRuntimeNode(
134129
id="__start__",
135130
name="__start__",
136131
type="__start__",
137-
metadata={},
138132
subgraph=None,
139133
)
140134
)
141135

142-
# Get all steps from the workflow
143-
steps = get_steps_from_class(workflow)
144-
if not steps:
145-
# If no steps are defined in the class, try to get them from the instance
146-
steps = get_steps_from_instance(workflow)
136+
steps = workflow._get_steps()
147137

148138
# Track if we need external step for human interaction
149139
has_human_interaction = False
150140
current_stop_event: type | None = None
151141

152142
# First pass: find the StopEvent used in this workflow and check for human interaction
153-
for _, step_func in steps.items():
154-
step_config: StepConfig | None = getattr(step_func, "__step_config", None)
143+
for name, step_func in steps.items():
144+
step_config: StepConfig | None = get_step_config(name, step_func)
155145
if step_config is None:
156146
continue
157147

@@ -167,7 +157,7 @@ def get_workflow_schema(workflow: Workflow) -> UiPathRuntimeGraph:
167157

168158
# Create step nodes (all steps are type "node")
169159
for step_name, step_func in steps.items():
170-
step_config = getattr(step_func, "__step_config", None)
160+
step_config = get_step_config(step_name, step_func)
171161
if step_config is None:
172162
continue
173163

@@ -199,17 +189,25 @@ def get_workflow_schema(workflow: Workflow) -> UiPathRuntimeGraph:
199189
id="external_step",
200190
name="external_step",
201191
type="external",
202-
metadata={},
203192
subgraph=None,
204193
)
205194
)
206195

196+
nodes.append(
197+
UiPathRuntimeNode(
198+
id="__end__",
199+
name="__end__",
200+
type="__end__",
201+
subgraph=None,
202+
)
203+
)
204+
207205
# Create edges based on event flow
208206
start_event_class = workflow._start_event_class
209207
first_step_found = False
210208

211209
for step_name, step_func in steps.items():
212-
step_config = getattr(step_func, "__step_config", None)
210+
step_config = get_step_config(step_name, step_func)
213211
if step_config is None:
214212
continue
215213

@@ -230,33 +228,34 @@ def get_workflow_schema(workflow: Workflow) -> UiPathRuntimeGraph:
230228
if return_type is type(None):
231229
continue
232230

231+
# If this returns StopEvent, connect to __end__
232+
if issubclass(return_type, StopEvent):
233+
if current_stop_event and return_type == current_stop_event:
234+
edges.append(
235+
UiPathRuntimeEdge(
236+
source=step_name,
237+
target="__end__",
238+
label=return_type.__name__,
239+
)
240+
)
241+
continue # Don't look for steps that accept StopEvent
242+
233243
# Find steps that accept this return type
234244
for target_step_name, target_step_func in steps.items():
235-
target_config: StepConfig | None = getattr(
236-
target_step_func, "__step_config", None
245+
target_config: StepConfig | None = get_step_config(
246+
target_step_name, target_step_func
237247
)
238248
if target_config is None:
239249
continue
240250

241251
if return_type in target_config.accepted_events:
242-
# Special handling for StopEvent - only connect to the actual StopEvent being used
243-
if issubclass(return_type, StopEvent):
244-
if current_stop_event and return_type == current_stop_event:
245-
edges.append(
246-
UiPathRuntimeEdge(
247-
source=step_name,
248-
target=target_step_name,
249-
label=return_type.__name__,
250-
)
251-
)
252-
else:
253-
edges.append(
254-
UiPathRuntimeEdge(
255-
source=step_name,
256-
target=target_step_name,
257-
label=return_type.__name__,
258-
)
252+
edges.append(
253+
UiPathRuntimeEdge(
254+
source=step_name,
255+
target=target_step_name,
256+
label=return_type.__name__,
259257
)
258+
)
260259

261260
# If this returns InputRequiredEvent, add edge to external_step
262261
if issubclass(return_type, InputRequiredEvent):
@@ -282,6 +281,30 @@ def get_workflow_schema(workflow: Workflow) -> UiPathRuntimeGraph:
282281
return UiPathRuntimeGraph(nodes=nodes, edges=edges)
283282

284283

284+
def get_step_config(step_name: str, step_func: Any) -> StepConfig | None:
285+
"""
286+
Get the step configuration from a step function.
287+
288+
Returns None if:
289+
- The step name starts with underscore (internal method)
290+
- No step config is found
291+
292+
Args:
293+
step_name: Name of the step
294+
step_func: The step function
295+
296+
Returns:
297+
StepConfig if found and valid, None otherwise
298+
"""
299+
# Skip internal methods
300+
if step_name.startswith("_"):
301+
return None
302+
303+
return getattr(step_func, "_step_config", None) or getattr(
304+
step_func, "__step_config", None
305+
)
306+
307+
285308
def _resolve_refs(
286309
schema: dict[str, Any],
287310
root: dict[str, Any] | None = None,

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)