vellum-ai 0.10.9__py3-none-any.whl → 0.11.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/evaluations/resources.py +7 -12
  3. vellum/evaluations/utils/env.py +1 -3
  4. vellum/evaluations/utils/paginator.py +0 -1
  5. vellum/evaluations/utils/typing.py +1 -1
  6. vellum/evaluations/utils/uuid.py +1 -1
  7. vellum/plugins/vellum_mypy.py +3 -1
  8. vellum/workflows/events/node.py +7 -6
  9. vellum/workflows/events/tests/test_event.py +0 -1
  10. vellum/workflows/events/types.py +0 -1
  11. vellum/workflows/events/workflow.py +19 -1
  12. vellum/workflows/nodes/bases/base.py +17 -56
  13. vellum/workflows/nodes/bases/tests/test_base_node.py +0 -1
  14. vellum/workflows/nodes/core/templating_node/node.py +1 -0
  15. vellum/workflows/nodes/core/try_node/node.py +2 -2
  16. vellum/workflows/nodes/core/try_node/tests/test_node.py +1 -3
  17. vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
  18. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +0 -1
  19. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +0 -1
  20. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +2 -1
  21. vellum/workflows/nodes/displayable/bases/search_node.py +0 -1
  22. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +0 -1
  23. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -2
  24. vellum/workflows/nodes/displayable/conditional_node/node.py +1 -1
  25. vellum/workflows/nodes/displayable/guardrail_node/node.py +0 -1
  26. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +1 -0
  27. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +3 -1
  28. vellum/workflows/nodes/displayable/search_node/node.py +1 -0
  29. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +3 -2
  30. vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +10 -7
  31. vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py +0 -1
  32. vellum/workflows/outputs/base.py +2 -4
  33. vellum/workflows/ports/node_ports.py +1 -1
  34. vellum/workflows/runner/runner.py +152 -191
  35. vellum/workflows/state/base.py +0 -2
  36. vellum/workflows/types/core.py +1 -0
  37. vellum/workflows/types/tests/test_utils.py +1 -0
  38. vellum/workflows/types/utils.py +0 -1
  39. vellum/workflows/utils/functions.py +74 -0
  40. vellum/workflows/utils/tests/test_functions.py +171 -0
  41. vellum/workflows/utils/tests/test_vellum_variables.py +0 -1
  42. vellum/workflows/utils/vellum_variables.py +2 -2
  43. vellum/workflows/workflows/base.py +74 -34
  44. vellum/workflows/workflows/event_filters.py +4 -12
  45. {vellum_ai-0.10.9.dist-info → vellum_ai-0.11.0.dist-info}/METADATA +1 -1
  46. {vellum_ai-0.10.9.dist-info → vellum_ai-0.11.0.dist-info}/RECORD +96 -90
  47. vellum_cli/__init__.py +147 -13
  48. vellum_cli/config.py +0 -1
  49. vellum_cli/image_push.py +1 -1
  50. vellum_cli/pull.py +29 -19
  51. vellum_cli/push.py +9 -10
  52. vellum_cli/tests/__init__.py +0 -0
  53. vellum_cli/tests/conftest.py +40 -0
  54. vellum_cli/tests/test_main.py +11 -0
  55. vellum_cli/tests/test_pull.py +125 -71
  56. vellum_cli/tests/test_push.py +173 -0
  57. vellum_ee/workflows/display/nodes/base_node_display.py +3 -2
  58. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +2 -2
  59. vellum_ee/workflows/display/nodes/get_node_display_class.py +1 -1
  60. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +1 -1
  61. vellum_ee/workflows/display/nodes/vellum/__init__.py +1 -1
  62. vellum_ee/workflows/display/nodes/vellum/api_node.py +4 -7
  63. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +39 -22
  64. vellum_ee/workflows/display/nodes/vellum/error_node.py +3 -3
  65. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +0 -2
  66. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -1
  67. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +1 -1
  68. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +4 -2
  69. vellum_ee/workflows/display/nodes/vellum/map_node.py +11 -5
  70. vellum_ee/workflows/display/nodes/vellum/merge_node.py +2 -2
  71. vellum_ee/workflows/display/nodes/vellum/note_node.py +1 -3
  72. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +1 -1
  73. vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -1
  74. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -1
  75. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
  76. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +5 -5
  77. vellum_ee/workflows/display/nodes/vellum/utils.py +4 -4
  78. vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +45 -0
  79. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +13 -24
  80. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -39
  81. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +2 -2
  82. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +62 -58
  83. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +25 -4
  84. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +2 -1
  85. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +2 -2
  86. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +2 -2
  87. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +1 -1
  88. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -1
  89. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +2 -2
  90. vellum_ee/workflows/display/types.py +4 -4
  91. vellum_ee/workflows/display/utils/vellum.py +2 -6
  92. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +4 -1
  93. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +6 -2
  94. vellum/workflows/runner/types.py +0 -16
  95. {vellum_ai-0.10.9.dist-info → vellum_ai-0.11.0.dist-info}/LICENSE +0 -0
  96. {vellum_ai-0.10.9.dist-info → vellum_ai-0.11.0.dist-info}/WHEEL +0 -0
  97. {vellum_ai-0.10.9.dist-info → vellum_ai-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -37,6 +37,8 @@ from vellum.workflows.events.workflow import (
37
37
  WorkflowExecutionRejectedBody,
38
38
  WorkflowExecutionResumedBody,
39
39
  WorkflowExecutionResumedEvent,
40
+ WorkflowExecutionSnapshottedBody,
41
+ WorkflowExecutionSnapshottedEvent,
40
42
  WorkflowExecutionStreamingBody,
41
43
  )
42
44
  from vellum.workflows.exceptions import NodeException
@@ -45,7 +47,6 @@ from vellum.workflows.outputs import BaseOutputs
45
47
  from vellum.workflows.outputs.base import BaseOutput
46
48
  from vellum.workflows.ports.port import Port
47
49
  from vellum.workflows.references import ExternalInputReference, OutputReference
48
- from vellum.workflows.runner.types import WorkItemEvent
49
50
  from vellum.workflows.state.base import BaseState
50
51
  from vellum.workflows.types.generics import OutputsType, StateType, WorkflowInputsType
51
52
 
@@ -73,11 +74,10 @@ class WorkflowRunner(Generic[StateType]):
73
74
  parent_context: Optional[ParentContext] = None,
74
75
  ):
75
76
  if state and external_inputs:
76
- raise ValueError(
77
- "Can only run a Workflow providing one of state or external inputs, not both"
78
- )
77
+ raise ValueError("Can only run a Workflow providing one of state or external inputs, not both")
79
78
 
80
79
  self.workflow = workflow
80
+ self._is_resuming = False
81
81
  if entrypoint_nodes:
82
82
  if len(list(entrypoint_nodes)) > 1:
83
83
  raise ValueError("Cannot resume from multiple nodes")
@@ -100,10 +100,9 @@ class WorkflowRunner(Generic[StateType]):
100
100
  for ei in external_inputs
101
101
  if issubclass(ei.inputs_class.__parent_class__, BaseNode)
102
102
  ]
103
+ self._is_resuming = True
103
104
  else:
104
- normalized_inputs = (
105
- deepcopy(inputs) if inputs else self.workflow.get_default_inputs()
106
- )
105
+ normalized_inputs = deepcopy(inputs) if inputs else self.workflow.get_default_inputs()
107
106
  if state:
108
107
  self._initial_state = deepcopy(state)
109
108
  self._initial_state.meta.workflow_inputs = normalized_inputs
@@ -111,9 +110,16 @@ class WorkflowRunner(Generic[StateType]):
111
110
  self._initial_state = self.workflow.get_default_state(normalized_inputs)
112
111
  self._entrypoints = self.workflow.get_entrypoints()
113
112
 
114
- self._work_item_event_queue: Queue[WorkItemEvent[StateType]] = Queue()
115
- self._workflow_event_queue: Queue[WorkflowEvent] = Queue()
113
+ # This queue is responsible for sending events from WorkflowRunner to the outside world
114
+ self._workflow_event_outer_queue: Queue[WorkflowEvent] = Queue()
115
+
116
+ # This queue is responsible for sending events from the inner worker threads to WorkflowRunner
117
+ self._workflow_event_inner_queue: Queue[WorkflowEvent] = Queue()
118
+
119
+ # This queue is responsible for sending events from WorkflowRunner to the background thread
120
+ # for user defined emitters
116
121
  self._background_thread_queue: Queue[BackgroundThreadItem] = Queue()
122
+
117
123
  self._dependencies: Dict[Type[BaseNode], Set[Type[BaseNode]]] = defaultdict(set)
118
124
  self._state_forks: Set[StateType] = {self._initial_state}
119
125
 
@@ -126,9 +132,20 @@ class WorkflowRunner(Generic[StateType]):
126
132
  "__snapshot_callback__",
127
133
  lambda s: self._snapshot_state(s),
128
134
  )
129
- self.workflow.context._register_event_queue(self._workflow_event_queue)
135
+ self.workflow.context._register_event_queue(self._workflow_event_inner_queue)
130
136
 
131
137
  def _snapshot_state(self, state: StateType) -> StateType:
138
+ self._workflow_event_inner_queue.put(
139
+ WorkflowExecutionSnapshottedEvent(
140
+ trace_id=state.meta.trace_id,
141
+ span_id=state.meta.span_id,
142
+ body=WorkflowExecutionSnapshottedBody(
143
+ workflow_definition=self.workflow.__class__,
144
+ state=state,
145
+ ),
146
+ parent=self._parent_context,
147
+ )
148
+ )
132
149
  self.workflow._store.append_state_snapshot(state)
133
150
  self._background_thread_queue.put(state)
134
151
  return state
@@ -139,22 +156,19 @@ class WorkflowRunner(Generic[StateType]):
139
156
  return event
140
157
 
141
158
  def _run_work_item(self, node: BaseNode[StateType], span_id: UUID) -> None:
142
- self._work_item_event_queue.put(
143
- WorkItemEvent(
144
- node=node,
145
- event=NodeExecutionInitiatedEvent(
146
- trace_id=node.state.meta.trace_id,
159
+ self._workflow_event_inner_queue.put(
160
+ NodeExecutionInitiatedEvent(
161
+ trace_id=node.state.meta.trace_id,
162
+ span_id=span_id,
163
+ body=NodeExecutionInitiatedBody(
164
+ node_definition=node.__class__,
165
+ inputs=node._inputs,
166
+ ),
167
+ parent=WorkflowParentContext(
147
168
  span_id=span_id,
148
- body=NodeExecutionInitiatedBody(
149
- node_definition=node.__class__,
150
- inputs=node._inputs,
151
- ),
152
- parent=WorkflowParentContext(
153
- span_id=span_id,
154
- workflow_definition=self.workflow.__class__,
155
- parent=self._parent_context,
156
- type="WORKFLOW",
157
- ),
169
+ workflow_definition=self.workflow.__class__,
170
+ parent=self._parent_context,
171
+ type="WORKFLOW",
158
172
  ),
159
173
  )
160
174
  )
@@ -190,29 +204,24 @@ class WorkflowRunner(Generic[StateType]):
190
204
  instance=None,
191
205
  outputs_class=node.Outputs,
192
206
  )
193
- node.state.meta.node_outputs[output_descriptor] = (
194
- streaming_output_queues[output.name]
195
- )
207
+ node.state.meta.node_outputs[output_descriptor] = streaming_output_queues[output.name]
196
208
  initiated_output: BaseOutput = BaseOutput(name=output.name)
197
209
  initiated_ports = initiated_output > ports
198
- self._work_item_event_queue.put(
199
- WorkItemEvent(
200
- node=node,
201
- event=NodeExecutionStreamingEvent(
202
- trace_id=node.state.meta.trace_id,
210
+ self._workflow_event_inner_queue.put(
211
+ NodeExecutionStreamingEvent(
212
+ trace_id=node.state.meta.trace_id,
213
+ span_id=span_id,
214
+ body=NodeExecutionStreamingBody(
215
+ node_definition=node.__class__,
216
+ output=initiated_output,
217
+ invoked_ports=initiated_ports,
218
+ ),
219
+ parent=WorkflowParentContext(
203
220
  span_id=span_id,
204
- body=NodeExecutionStreamingBody(
205
- node_definition=node.__class__,
206
- output=initiated_output,
207
- invoked_ports=initiated_ports,
208
- ),
209
- parent=WorkflowParentContext(
210
- span_id=span_id,
211
- workflow_definition=self.workflow.__class__,
212
- parent=self._parent_context,
213
- ),
221
+ workflow_definition=self.workflow.__class__,
222
+ parent=self._parent_context,
214
223
  ),
215
- )
224
+ ),
216
225
  )
217
226
 
218
227
  for output in node_run_response:
@@ -224,50 +233,47 @@ class WorkflowRunner(Generic[StateType]):
224
233
  initiate_node_streaming_output(output)
225
234
 
226
235
  streaming_output_queues[output.name].put(output.delta)
227
- self._work_item_event_queue.put(
228
- WorkItemEvent(
229
- node=node,
230
- event=NodeExecutionStreamingEvent(
231
- trace_id=node.state.meta.trace_id,
236
+ self._workflow_event_inner_queue.put(
237
+ NodeExecutionStreamingEvent(
238
+ trace_id=node.state.meta.trace_id,
239
+ span_id=span_id,
240
+ body=NodeExecutionStreamingBody(
241
+ node_definition=node.__class__,
242
+ output=output,
243
+ invoked_ports=invoked_ports,
244
+ ),
245
+ parent=WorkflowParentContext(
232
246
  span_id=span_id,
233
- body=NodeExecutionStreamingBody(
234
- node_definition=node.__class__,
235
- output=output,
236
- invoked_ports=invoked_ports,
237
- ),
238
- parent=WorkflowParentContext(
239
- span_id=span_id,
240
- workflow_definition=self.workflow.__class__,
241
- parent=self._parent_context,
242
- ),
247
+ workflow_definition=self.workflow.__class__,
248
+ parent=self._parent_context,
243
249
  ),
244
- )
250
+ ),
245
251
  )
246
252
  elif output.is_fulfilled:
247
253
  if output.name in streaming_output_queues:
248
254
  streaming_output_queues[output.name].put(UNDEF)
249
255
 
250
256
  setattr(outputs, output.name, output.value)
251
- self._work_item_event_queue.put(
252
- WorkItemEvent(
253
- node=node,
254
- event=NodeExecutionStreamingEvent(
255
- trace_id=node.state.meta.trace_id,
257
+ self._workflow_event_inner_queue.put(
258
+ NodeExecutionStreamingEvent(
259
+ trace_id=node.state.meta.trace_id,
260
+ span_id=span_id,
261
+ body=NodeExecutionStreamingBody(
262
+ node_definition=node.__class__,
263
+ output=output,
264
+ invoked_ports=invoked_ports,
265
+ ),
266
+ parent=WorkflowParentContext(
256
267
  span_id=span_id,
257
- body=NodeExecutionStreamingBody(
258
- node_definition=node.__class__,
259
- output=output,
260
- invoked_ports=invoked_ports,
261
- ),
262
- parent=WorkflowParentContext(
263
- span_id=span_id,
264
- workflow_definition=self.workflow.__class__,
265
- parent=self._parent_context,
266
- ),
268
+ workflow_definition=self.workflow.__class__,
269
+ parent=self._parent_context,
267
270
  ),
268
271
  )
269
272
  )
270
273
 
274
+ invoked_ports = ports(outputs, node.state)
275
+ node.state.meta.node_execution_cache.fulfill_node_execution(node.__class__, span_id)
276
+
271
277
  for descriptor, output_value in outputs:
272
278
  if output_value is UNDEF:
273
279
  if descriptor in node.state.meta.node_outputs:
@@ -276,81 +282,63 @@ class WorkflowRunner(Generic[StateType]):
276
282
 
277
283
  node.state.meta.node_outputs[descriptor] = output_value
278
284
 
279
- invoked_ports = ports(outputs, node.state)
280
- node.state.meta.node_execution_cache.fulfill_node_execution(
281
- node.__class__, span_id
282
- )
283
-
284
- self._work_item_event_queue.put(
285
- WorkItemEvent(
286
- node=node,
287
- event=NodeExecutionFulfilledEvent(
288
- trace_id=node.state.meta.trace_id,
285
+ self._workflow_event_inner_queue.put(
286
+ NodeExecutionFulfilledEvent(
287
+ trace_id=node.state.meta.trace_id,
288
+ span_id=span_id,
289
+ body=NodeExecutionFulfilledBody(
290
+ node_definition=node.__class__,
291
+ outputs=outputs,
292
+ invoked_ports=invoked_ports,
293
+ ),
294
+ parent=WorkflowParentContext(
289
295
  span_id=span_id,
290
- body=NodeExecutionFulfilledBody(
291
- node_definition=node.__class__,
292
- outputs=outputs,
293
- invoked_ports=invoked_ports,
294
- ),
295
- parent=WorkflowParentContext(
296
- span_id=span_id,
297
- workflow_definition=self.workflow.__class__,
298
- parent=self._parent_context,
299
- ),
296
+ workflow_definition=self.workflow.__class__,
297
+ parent=self._parent_context,
300
298
  ),
301
299
  )
302
300
  )
303
301
  except NodeException as e:
304
- self._work_item_event_queue.put(
305
- WorkItemEvent(
306
- node=node,
307
- event=NodeExecutionRejectedEvent(
308
- trace_id=node.state.meta.trace_id,
302
+ self._workflow_event_inner_queue.put(
303
+ NodeExecutionRejectedEvent(
304
+ trace_id=node.state.meta.trace_id,
305
+ span_id=span_id,
306
+ body=NodeExecutionRejectedBody(
307
+ node_definition=node.__class__,
308
+ error=e.error,
309
+ ),
310
+ parent=WorkflowParentContext(
309
311
  span_id=span_id,
310
- body=NodeExecutionRejectedBody(
311
- node_definition=node.__class__,
312
- error=e.error,
313
- ),
314
- parent=WorkflowParentContext(
315
- span_id=span_id,
316
- workflow_definition=self.workflow.__class__,
317
- parent=self._parent_context,
318
- ),
312
+ workflow_definition=self.workflow.__class__,
313
+ parent=self._parent_context,
319
314
  ),
320
315
  )
321
316
  )
322
317
  except Exception as e:
323
- logger.exception(
324
- f"An unexpected error occurred while running node {node.__class__.__name__}"
325
- )
318
+ logger.exception(f"An unexpected error occurred while running node {node.__class__.__name__}")
326
319
 
327
- self._work_item_event_queue.put(
328
- WorkItemEvent(
329
- node=node,
330
- event=NodeExecutionRejectedEvent(
331
- trace_id=node.state.meta.trace_id,
332
- span_id=span_id,
333
- body=NodeExecutionRejectedBody(
334
- node_definition=node.__class__,
335
- error=VellumError(
336
- message=str(e),
337
- code=VellumErrorCode.INTERNAL_ERROR,
338
- ),
339
- ),
340
- parent=WorkflowParentContext(
341
- span_id=span_id,
342
- workflow_definition=self.workflow.__class__,
343
- parent=self._parent_context,
320
+ self._workflow_event_inner_queue.put(
321
+ NodeExecutionRejectedEvent(
322
+ trace_id=node.state.meta.trace_id,
323
+ span_id=span_id,
324
+ body=NodeExecutionRejectedBody(
325
+ node_definition=node.__class__,
326
+ error=VellumError(
327
+ message=str(e),
328
+ code=VellumErrorCode.INTERNAL_ERROR,
344
329
  ),
345
330
  ),
346
- )
331
+ parent=WorkflowParentContext(
332
+ span_id=span_id,
333
+ workflow_definition=self.workflow.__class__,
334
+ parent=self._parent_context,
335
+ ),
336
+ ),
347
337
  )
348
338
 
349
339
  logger.debug(f"Finished running node: {node.__class__.__name__}")
350
340
 
351
- def _handle_invoked_ports(
352
- self, state: StateType, ports: Optional[Iterable[Port]]
353
- ) -> None:
341
+ def _handle_invoked_ports(self, state: StateType, ports: Optional[Iterable[Port]]) -> None:
354
342
  if not ports:
355
343
  return
356
344
 
@@ -380,16 +368,12 @@ class WorkflowRunner(Generic[StateType]):
380
368
  return
381
369
 
382
370
  all_deps = self._dependencies[node_class]
383
- node_span_id = state.meta.node_execution_cache.queue_node_execution(
384
- node_class, all_deps, invoked_by
385
- )
371
+ node_span_id = state.meta.node_execution_cache.queue_node_execution(node_class, all_deps, invoked_by)
386
372
  if not node_class.Trigger.should_initiate(state, all_deps, node_span_id):
387
373
  return
388
374
 
389
375
  node = node_class(state=state, context=self.workflow.context)
390
- state.meta.node_execution_cache.initiate_node_execution(
391
- node_class, node_span_id
392
- )
376
+ state.meta.node_execution_cache.initiate_node_execution(node_class, node_span_id)
393
377
  self._active_nodes_by_execution_id[node_span_id] = node
394
378
 
395
379
  worker_thread = Thread(
@@ -398,13 +382,9 @@ class WorkflowRunner(Generic[StateType]):
398
382
  )
399
383
  worker_thread.start()
400
384
 
401
- def _handle_work_item_event(
402
- self, work_item_event: WorkItemEvent[StateType]
403
- ) -> Optional[VellumError]:
404
- node = work_item_event.node
405
- event = work_item_event.event
406
-
407
- if event.name == "node.execution.initiated":
385
+ def _handle_work_item_event(self, event: WorkflowEvent) -> Optional[VellumError]:
386
+ node = self._active_nodes_by_execution_id.get(event.span_id)
387
+ if not node:
408
388
  return None
409
389
 
410
390
  if event.name == "node.execution.rejected":
@@ -416,15 +396,12 @@ class WorkflowRunner(Generic[StateType]):
416
396
  node_output_descriptor = workflow_output_descriptor.instance
417
397
  if not isinstance(node_output_descriptor, OutputReference):
418
398
  continue
419
- if (
420
- node_output_descriptor.outputs_class
421
- != event.node_definition.Outputs
422
- ):
399
+ if node_output_descriptor.outputs_class != event.node_definition.Outputs:
423
400
  continue
424
401
  if node_output_descriptor.name != event.output.name:
425
402
  continue
426
403
 
427
- self._workflow_event_queue.put(
404
+ self._workflow_event_outer_queue.put(
428
405
  self._stream_workflow_event(
429
406
  BaseOutput(
430
407
  name=workflow_output_descriptor.name,
@@ -444,7 +421,7 @@ class WorkflowRunner(Generic[StateType]):
444
421
 
445
422
  return None
446
423
 
447
- raise ValueError(f"Invalid event name: {event.name}")
424
+ return None
448
425
 
449
426
  def _initiate_workflow_event(self) -> WorkflowExecutionInitiatedEvent:
450
427
  return WorkflowExecutionInitiatedEvent(
@@ -457,9 +434,7 @@ class WorkflowRunner(Generic[StateType]):
457
434
  parent=self._parent_context,
458
435
  )
459
436
 
460
- def _stream_workflow_event(
461
- self, output: BaseOutput
462
- ) -> WorkflowExecutionStreamingEvent:
437
+ def _stream_workflow_event(self, output: BaseOutput) -> WorkflowExecutionStreamingEvent:
463
438
  return WorkflowExecutionStreamingEvent(
464
439
  trace_id=self._initial_state.meta.trace_id,
465
440
  span_id=self._initial_state.meta.span_id,
@@ -470,9 +445,7 @@ class WorkflowRunner(Generic[StateType]):
470
445
  parent=self._parent_context,
471
446
  )
472
447
 
473
- def _fulfill_workflow_event(
474
- self, outputs: OutputsType
475
- ) -> WorkflowExecutionFulfilledEvent:
448
+ def _fulfill_workflow_event(self, outputs: OutputsType) -> WorkflowExecutionFulfilledEvent:
476
449
  return WorkflowExecutionFulfilledEvent(
477
450
  trace_id=self._initial_state.meta.trace_id,
478
451
  span_id=self._initial_state.meta.span_id,
@@ -483,9 +456,7 @@ class WorkflowRunner(Generic[StateType]):
483
456
  parent=self._parent_context,
484
457
  )
485
458
 
486
- def _reject_workflow_event(
487
- self, error: VellumError
488
- ) -> WorkflowExecutionRejectedEvent:
459
+ def _reject_workflow_event(self, error: VellumError) -> WorkflowExecutionRejectedEvent:
489
460
  return WorkflowExecutionRejectedEvent(
490
461
  trace_id=self._initial_state.meta.trace_id,
491
462
  span_id=self._initial_state.meta.span_id,
@@ -505,9 +476,7 @@ class WorkflowRunner(Generic[StateType]):
505
476
  ),
506
477
  )
507
478
 
508
- def _pause_workflow_event(
509
- self, external_inputs: Iterable[ExternalInputReference]
510
- ) -> WorkflowExecutionPausedEvent:
479
+ def _pause_workflow_event(self, external_inputs: Iterable[ExternalInputReference]) -> WorkflowExecutionPausedEvent:
511
480
  return WorkflowExecutionPausedEvent(
512
481
  trace_id=self._initial_state.meta.trace_id,
513
482
  span_id=self._initial_state.meta.span_id,
@@ -522,7 +491,7 @@ class WorkflowRunner(Generic[StateType]):
522
491
  # TODO: We should likely handle this during initialization
523
492
  # https://app.shortcut.com/vellum/story/4327
524
493
  if not self._entrypoints:
525
- self._workflow_event_queue.put(
494
+ self._workflow_event_outer_queue.put(
526
495
  self._reject_workflow_event(
527
496
  VellumError(
528
497
  message="No entrypoints defined",
@@ -539,16 +508,14 @@ class WorkflowRunner(Generic[StateType]):
539
508
  try:
540
509
  self._run_node_if_ready(self._initial_state, node_cls)
541
510
  except NodeException as e:
542
- self._workflow_event_queue.put(self._reject_workflow_event(e.error))
511
+ self._workflow_event_outer_queue.put(self._reject_workflow_event(e.error))
543
512
  return
544
513
  except Exception:
545
514
  err_message = f"An unexpected error occurred while initializing node {node_cls.__name__}"
546
515
  logger.exception(err_message)
547
- self._workflow_event_queue.put(
516
+ self._workflow_event_outer_queue.put(
548
517
  self._reject_workflow_event(
549
- VellumError(
550
- code=VellumErrorCode.INTERNAL_ERROR, message=err_message
551
- ),
518
+ VellumError(code=VellumErrorCode.INTERNAL_ERROR, message=err_message),
552
519
  )
553
520
  )
554
521
  return
@@ -559,21 +526,20 @@ class WorkflowRunner(Generic[StateType]):
559
526
  if not self._active_nodes_by_execution_id:
560
527
  break
561
528
 
562
- work_item_event = self._work_item_event_queue.get()
563
- event = work_item_event.event
529
+ event = self._workflow_event_inner_queue.get()
564
530
 
565
- self._workflow_event_queue.put(event)
531
+ self._workflow_event_outer_queue.put(event)
566
532
 
567
- rejection_error = self._handle_work_item_event(work_item_event)
533
+ rejection_error = self._handle_work_item_event(event)
568
534
  if rejection_error:
569
535
  break
570
536
 
571
537
  # Handle any remaining events
572
538
  try:
573
- while work_item_event := self._work_item_event_queue.get_nowait():
574
- self._workflow_event_queue.put(work_item_event.event)
539
+ while event := self._workflow_event_inner_queue.get_nowait():
540
+ self._workflow_event_outer_queue.put(event)
575
541
 
576
- rejection_error = self._handle_work_item_event(work_item_event)
542
+ rejection_error = self._handle_work_item_event(event)
577
543
  if rejection_error:
578
544
  break
579
545
  except Empty:
@@ -589,14 +555,13 @@ class WorkflowRunner(Generic[StateType]):
589
555
  if node_input_value is UNDEF
590
556
  }
591
557
  if unresolved_external_inputs:
592
- self._workflow_event_queue.put(
558
+ self._workflow_event_outer_queue.put(
593
559
  self._pause_workflow_event(unresolved_external_inputs),
594
560
  )
595
561
  return
596
562
 
597
- final_state.meta.is_terminated = True
598
563
  if rejection_error:
599
- self._workflow_event_queue.put(self._reject_workflow_event(rejection_error))
564
+ self._workflow_event_outer_queue.put(self._reject_workflow_event(rejection_error))
600
565
  return
601
566
 
602
567
  fulfilled_outputs = self.workflow.Outputs()
@@ -610,7 +575,7 @@ class WorkflowRunner(Generic[StateType]):
610
575
  descriptor.instance.resolve(final_state),
611
576
  )
612
577
 
613
- self._workflow_event_queue.put(self._fulfill_workflow_event(fulfilled_outputs))
578
+ self._workflow_event_outer_queue.put(self._fulfill_workflow_event(fulfilled_outputs))
614
579
 
615
580
  def _run_background_thread(self) -> None:
616
581
  state_class = self.workflow.get_state_class()
@@ -631,7 +596,7 @@ class WorkflowRunner(Generic[StateType]):
631
596
  return
632
597
 
633
598
  self._cancel_signal.wait()
634
- self._workflow_event_queue.put(
599
+ self._workflow_event_outer_queue.put(
635
600
  self._reject_workflow_event(
636
601
  VellumError(
637
602
  code=VellumErrorCode.WORKFLOW_CANCELLED,
@@ -664,16 +629,12 @@ class WorkflowRunner(Generic[StateType]):
664
629
  cancel_thread.start()
665
630
 
666
631
  event: WorkflowEvent
667
- if (
668
- self._initial_state.meta.is_terminated
669
- or self._initial_state.meta.is_terminated is None
670
- ):
671
- event = self._initiate_workflow_event()
672
- else:
632
+ if self._is_resuming:
673
633
  event = self._resume_workflow_event()
634
+ else:
635
+ event = self._initiate_workflow_event()
674
636
 
675
637
  yield self._emit_event(event)
676
- self._initial_state.meta.is_terminated = False
677
638
 
678
639
  # The extra level of indirection prevents the runner from waiting on the caller to consume the event stream
679
640
  stream_thread = Thread(
@@ -684,7 +645,7 @@ class WorkflowRunner(Generic[StateType]):
684
645
 
685
646
  while stream_thread.is_alive():
686
647
  try:
687
- event = self._workflow_event_queue.get(timeout=0.1)
648
+ event = self._workflow_event_outer_queue.get(timeout=0.1)
688
649
  except Empty:
689
650
  continue
690
651
 
@@ -694,7 +655,7 @@ class WorkflowRunner(Generic[StateType]):
694
655
  break
695
656
 
696
657
  try:
697
- while event := self._workflow_event_queue.get_nowait():
658
+ while event := self._workflow_event_outer_queue.get_nowait():
698
659
  yield self._emit_event(event)
699
660
  except Empty:
700
661
  pass
@@ -192,7 +192,6 @@ class StateMeta(UniversalBaseModel):
192
192
  node_outputs: Dict[OutputReference, Any] = field(default_factory=dict)
193
193
  node_execution_cache: NodeExecutionCache = field(default_factory=NodeExecutionCache)
194
194
  parent: Optional["BaseState"] = None
195
- is_terminated: Optional[bool] = None
196
195
  __snapshot_callback__: Optional[Callable[[], None]] = field(init=False, default=None)
197
196
 
198
197
  def model_post_init(self, context: Any) -> None:
@@ -286,7 +285,6 @@ class BaseState(metaclass=_BaseStateMeta):
286
285
  {values}
287
286
  meta:
288
287
  id={self.meta.id}
289
- is_terminated={self.meta.is_terminated}
290
288
  updated_ts={self.meta.updated_ts}
291
289
  node_outputs:{' Empty' if not node_outputs else ''}
292
290
  {node_outputs}
@@ -85,6 +85,7 @@ class MergeBehavior(Enum):
85
85
  AWAIT_ANY = "AWAIT_ANY"
86
86
  AWAIT_ATTRIBUTES = "AWAIT_ATTRIBUTES"
87
87
 
88
+
88
89
  class ConditionType(Enum):
89
90
  IF = "IF"
90
91
  ELIF = "ELIF"
@@ -33,6 +33,7 @@ class ExampleGenericClass(Generic[T]):
33
33
  class ExampleInheritedClass(ExampleClass):
34
34
  theta: int
35
35
 
36
+
36
37
  @TryNode.wrap()
37
38
  class ExampleNode(BaseNode):
38
39
  class Outputs(BaseNode.Outputs):
@@ -1,7 +1,6 @@
1
1
  from copy import deepcopy
2
2
  from datetime import datetime
3
3
  import importlib
4
- import sys
5
4
  from typing import (
6
5
  Any,
7
6
  ClassVar,