vellum-workflow-server 1.4.1.post1__py3-none-any.whl → 1.4.1.post3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vellum-workflow-server might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-workflow-server
3
- Version: 1.4.1.post1
3
+ Version: 1.4.1.post3
4
4
  Summary:
5
5
  License: AGPL
6
6
  Requires-Python: >=3.9.0,<4
@@ -5,16 +5,16 @@ workflow_server/api/healthz_view.py,sha256=itiRvBDBXncrw8Kbbc73UZLwqMAhgHOR3uSre
5
5
  workflow_server/api/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  workflow_server/api/tests/test_input_display_mapping.py,sha256=drBZqMudFyB5wgiUOcMgRXz7E7ge-Qgxbstw4E4f0zE,2211
7
7
  workflow_server/api/tests/test_workflow_view.py,sha256=RlAw1tHeIlnOXGrFQN-w3EOLPZkhp6Dfy6d1r7kU5oc,22573
8
- workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=Qo8u6mPyRCmE2jamY1yIh8l44hgo4-Nwlq03z61ND5g,27031
9
- workflow_server/api/workflow_view.py,sha256=_WhjNgimTPoS10C-npRWDfJixzg4eHTJ5xIKACStZf4,21943
10
- workflow_server/code_exec_runner.py,sha256=lBnMIorPZL8zZBye6TjeCIs06WTJM7P2HR07B1fjJJI,2533
8
+ workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=FaEIgGsbq8M7ZF2opVjBdGCYeRPF_vqsUhFTruLInxA,31358
9
+ workflow_server/api/workflow_view.py,sha256=UZTxWImM9kmof819SCa3ljJKiYEsCrBFD8vp2_f-zAg,21719
10
+ workflow_server/code_exec_runner.py,sha256=E-HsjAL53L-znSMPg7lDiQNzyCjL6W076ZoWWbrSRrU,2217
11
11
  workflow_server/config.py,sha256=qmmTr6ty3ZN5LDOFs3TfUxYshYe6Mmn_LanplHHeE9Q,1796
12
12
  workflow_server/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  workflow_server/core/cancel_workflow.py,sha256=Ffkc3mzmrdMEUcD-sHfEhX4IwVrka-E--SxKA1dUfIU,2185
14
14
  workflow_server/core/events.py,sha256=24MA66DVQuaLJJcZrS8IL1Zq4Ohi9CoouKZ5VgoH3Cs,1402
15
- workflow_server/core/executor.py,sha256=bNryNvXBbg7IHW3BFzzFaGoCO1rGufeEE4FrY9TDA90,18294
16
- workflow_server/core/utils.py,sha256=aIpSINstLGslP2PIoDLM82_1GlJ1uC_0AIrP-V7Yobo,3230
17
- workflow_server/core/workflow_executor_context.py,sha256=w3OhV_AXpgh7AxpjEsc0vo-IJypgJcgr5DXJCqGptOU,1587
15
+ workflow_server/core/executor.py,sha256=5cji5KQSukLrABmihJp9cgKjXS145TocOz2cOcnof04,18962
16
+ workflow_server/core/utils.py,sha256=cmwHbKCfXqtUutBD3akGus0Ga7a1xG3zlOw-jEMx6mI,1795
17
+ workflow_server/core/workflow_executor_context.py,sha256=VafZg74t_GQ3_2DEWVroy38rSy_spcAw4c3NrOOWOKY,3198
18
18
  workflow_server/server.py,sha256=QBU12AaAfAgLqfCDBd24qIJl_mbheiq0-hfcWV7rZM4,1234
19
19
  workflow_server/start.py,sha256=pkwRcms6I4tkVHP06LdrZY6rG_DFHfBx4ioY5X91W5k,2264
20
20
  workflow_server/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,7 +28,7 @@ workflow_server/utils/tests/test_sentry_integration.py,sha256=LGmWiaLhFrx-jslrRj
28
28
  workflow_server/utils/tests/test_system_utils.py,sha256=_4GwXvVvU5BrATxUEWwQIPg0bzQXMWBtiBmjP8MTxJM,4314
29
29
  workflow_server/utils/tests/test_utils.py,sha256=0Nq6du8o-iBtTrip9_wgHES53JSiJbVdSXaBnPobw3s,6930
30
30
  workflow_server/utils/utils.py,sha256=ZPoM1Suhid22dpB8oEFLux8wx-9iyzmSfWuYxSCrgWk,4774
31
- vellum_workflow_server-1.4.1.post1.dist-info/METADATA,sha256=sXR0B1bNZZvRQswy2hK4IqACrHG67fw6XLErZfqRoRE,2273
32
- vellum_workflow_server-1.4.1.post1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
33
- vellum_workflow_server-1.4.1.post1.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
34
- vellum_workflow_server-1.4.1.post1.dist-info/RECORD,,
31
+ vellum_workflow_server-1.4.1.post3.dist-info/METADATA,sha256=tZiQ-25OwiPIcV3lWGZsF9gRuPuAZaypRCtAVbVUirs,2273
32
+ vellum_workflow_server-1.4.1.post3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
33
+ vellum_workflow_server-1.4.1.post3.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
34
+ vellum_workflow_server-1.4.1.post3.dist-info/RECORD,,
@@ -10,6 +10,8 @@ from uuid import uuid4
10
10
 
11
11
  import requests_mock
12
12
 
13
+ from vellum.workflows.emitters.base import WorkflowEvent
14
+ from vellum.workflows.emitters.vellum_emitter import VellumEmitter
13
15
  from workflow_server.code_exec_runner import run_code_exec_stream
14
16
  from workflow_server.server import create_app
15
17
  from workflow_server.utils.system_utils import get_active_process_count
@@ -371,7 +373,9 @@ from vellum.workflows.inputs import BaseInputs
371
373
  # THEN we get a 200 response
372
374
  assert status_code == 200, events
373
375
 
374
- # THEN we get the expected events
376
+ # THEN we get the expected events: vembda initiated, workflow initiated, workflow rejected, vembda fulfilled
377
+ assert len(events) == 4
378
+
375
379
  assert events[0] == {
376
380
  "id": mock.ANY,
377
381
  "trace_id": mock.ANY,
@@ -387,7 +391,15 @@ from vellum.workflows.inputs import BaseInputs
387
391
  },
388
392
  }
389
393
 
390
- assert events[1] == {
394
+ assert events[1]["name"] == "workflow.execution.initiated"
395
+
396
+ assert events[2]["name"] == "workflow.execution.rejected"
397
+ assert events[2]["span_id"] == events[1]["span_id"]
398
+ assert (
399
+ "Failed to initialize workflow: unexpected indent (inputs.py, line 3)" in events[2]["body"]["error"]["message"]
400
+ )
401
+
402
+ assert events[3] == {
391
403
  "id": mock.ANY,
392
404
  "trace_id": events[0]["trace_id"],
393
405
  "span_id": str(span_id),
@@ -398,15 +410,75 @@ from vellum.workflows.inputs import BaseInputs
398
410
  "name": "vembda.execution.fulfilled",
399
411
  "body": mock.ANY,
400
412
  }
401
- assert events[1]["body"] == {
402
- "exit_code": -1,
413
+ assert events[3]["body"] == {
414
+ "exit_code": 0,
403
415
  "log": "",
404
- "stderr": "Failed to initialize workflow: unexpected indent (inputs.py, line 3)",
416
+ "stderr": "",
405
417
  "timed_out": False,
406
418
  "container_overhead_latency": mock.ANY,
407
419
  }
408
420
 
409
- assert len(events) == 2
421
+
422
+ def test_stream_workflow_route__invalid_inputs_initialization_events(both_stream_types):
423
+ """
424
+ Tests that invalid inputs initialization gets us back a workflow initiated and workflow rejected event.
425
+ """
426
+ # GIVEN a valid request body with valid inputs file but omitting required input to cause
427
+ # WorkflowInitializationException
428
+ span_id = uuid4()
429
+ request_body = {
430
+ "timeout": 360,
431
+ "execution_id": str(span_id),
432
+ "inputs": [
433
+ # Omit the required input to trigger WorkflowInitializationException
434
+ ],
435
+ "environment_api_key": "test",
436
+ "module": "workflow",
437
+ "files": {
438
+ "__init__.py": "",
439
+ "workflow.py": """\
440
+ from vellum.workflows import BaseWorkflow
441
+ from vellum.workflows.state import BaseState
442
+ from .inputs import Inputs
443
+
444
+ class Workflow(BaseWorkflow[Inputs, BaseState]):
445
+ class Outputs(BaseWorkflow.Outputs):
446
+ foo = "hello"
447
+ """,
448
+ "inputs.py": """\
449
+ from vellum.workflows.inputs import BaseInputs
450
+
451
+ class Inputs(BaseInputs):
452
+ foo: str
453
+ """,
454
+ },
455
+ }
456
+
457
+ # WHEN we call the stream route
458
+ status_code, events = both_stream_types(request_body)
459
+
460
+ # THEN we get a 200 response
461
+ assert status_code == 200, events
462
+
463
+ # THEN we get the expected events: vembda initiated, workflow initiated, workflow rejected, vembda fulfilled
464
+ assert len(events) == 4
465
+
466
+ # AND the first event should be vembda execution initiated
467
+ assert events[0]["name"] == "vembda.execution.initiated"
468
+ assert events[0]["span_id"] == str(span_id)
469
+
470
+ # AND the second event should be workflow execution initiated
471
+ assert events[1]["name"] == "workflow.execution.initiated"
472
+
473
+ # AND the third event should be workflow execution rejected
474
+ assert events[2]["name"] == "workflow.execution.rejected"
475
+ assert events[1]["span_id"] == events[2]["span_id"]
476
+ assert "Required input variables foo should have defined value" in events[2]["body"]["error"]["message"]
477
+
478
+ # AND the fourth event should be vembda execution fulfilled
479
+ assert events[3]["name"] == "vembda.execution.fulfilled"
480
+ assert events[3]["span_id"] == str(span_id)
481
+ assert events[3]["body"]["exit_code"] == 0
410
482
 
411
483
 
412
484
  @pytest.mark.parametrize(
@@ -912,3 +984,50 @@ class Workflow(BaseWorkflow):
912
984
 
913
985
  # AND we get the expected timeout error message
914
986
  assert response_data == {"detail": "Request timed out trying to initiate the Workflow"}
987
+
988
+
989
+ @pytest.mark.parametrize("non_process_stream_types", [code_exec_stream, flask_stream_disable_process_wrapper])
990
+ def test_stream_workflow_route__vembda_emitting_calls_monitoring_api(non_process_stream_types):
991
+ """
992
+ Tests that the monitoring API is called when vembda emitting is enabled.
993
+ """
994
+
995
+ # GIVEN a valid request body with vembda emitting enabled
996
+ span_id = uuid4()
997
+ request_body = {
998
+ "execution_id": str(span_id),
999
+ "inputs": [],
1000
+ "environment_api_key": "test",
1001
+ "module": "workflow",
1002
+ "timeout": 360,
1003
+ "feature_flags": {"vembda-event-emitting-enabled": True},
1004
+ "files": {
1005
+ "__init__.py": "",
1006
+ "workflow.py": """\
1007
+ from vellum.workflows import BaseWorkflow
1008
+
1009
+ class Workflow(BaseWorkflow):
1010
+ class Outputs(BaseWorkflow.Outputs):
1011
+ foo = "hello"
1012
+ """,
1013
+ },
1014
+ }
1015
+ emitted_events = []
1016
+
1017
+ def send_events(self, events: list[WorkflowEvent]) -> None:
1018
+ for event in events:
1019
+ emitted_events.append(event)
1020
+
1021
+ VellumEmitter._send_events = send_events
1022
+
1023
+ # WHEN we call the stream route with mocked monitoring API
1024
+ status_code, events = non_process_stream_types(request_body)
1025
+
1026
+ # THEN we get a 200 response
1027
+ assert status_code == 200, events
1028
+
1029
+ # AND the expected workflow events were emitted
1030
+ event_names = [event.name for event in emitted_events]
1031
+ assert len(event_names) == 2, "Should include 2 events"
1032
+ assert "workflow.execution.initiated" in event_names, "Should include workflow.execution.initiated event"
1033
+ assert "workflow.execution.fulfilled" in event_names, "Should include workflow.execution.fulfilled event"
@@ -38,7 +38,6 @@ from workflow_server.core.events import (
38
38
  )
39
39
  from workflow_server.core.executor import stream_node_pebble_timeout, stream_workflow, stream_workflow_process_timeout
40
40
  from workflow_server.core.utils import (
41
- create_vellum_client,
42
41
  create_vembda_rejected_event,
43
42
  is_events_emitting_enabled,
44
43
  serialize_vembda_rejected_event,
@@ -47,6 +46,7 @@ from workflow_server.core.workflow_executor_context import (
47
46
  DEFAULT_TIMEOUT_SECONDS,
48
47
  NodeExecutorContext,
49
48
  WorkflowExecutorContext,
49
+ create_vellum_client,
50
50
  )
51
51
  from workflow_server.utils.oom_killer import get_is_oom_killed
52
52
  from workflow_server.utils.system_utils import (
@@ -169,11 +169,6 @@ def stream_workflow_route() -> Response:
169
169
  span_id_emitted = True
170
170
  for event in workflow_iterator:
171
171
  yield event
172
- except WorkflowInitializationException as e:
173
- if not span_id_emitted:
174
- yield f"{SPAN_ID_EVENT}:{uuid4()}"
175
-
176
- yield serialize_vembda_rejected_event(context, str(e))
177
172
  except Exception as e:
178
173
  if not span_id_emitted:
179
174
  yield f"{SPAN_ID_EVENT}:{uuid4()}"
@@ -6,7 +6,6 @@ from threading import Event as ThreadingEvent
6
6
  from uuid import uuid4
7
7
  from typing import Optional
8
8
 
9
- from vellum.workflows.exceptions import WorkflowInitializationException
10
9
  from workflow_server.api.workflow_view import get_workflow_request_context
11
10
  from workflow_server.core.events import VembdaExecutionInitiatedBody, VembdaExecutionInitiatedEvent
12
11
  from workflow_server.core.executor import stream_workflow
@@ -47,18 +46,14 @@ def run_code_exec_stream() -> None:
47
46
 
48
47
  print(f"{_EVENT_LINE}{initiated_event}") # noqa: T201
49
48
 
50
- try:
51
- stream_iterator, span_id = stream_workflow(
52
- context,
53
- disable_redirect=True,
54
- # Timeouts are handled at the code exec level right now so just passing in an unused threading event
55
- timeout_signal=ThreadingEvent(),
56
- )
57
- for line in stream_iterator:
58
- print(f"{_EVENT_LINE}{json.dumps(line)}") # noqa: T201
59
- except WorkflowInitializationException as e:
60
- fulfilled_event = serialize_vembda_rejected_event(context, str(e))
61
- print(f"{_EVENT_LINE}{fulfilled_event}") # noqa: T201
49
+ stream_iterator, span_id = stream_workflow(
50
+ context,
51
+ disable_redirect=True,
52
+ # Timeouts are handled at the code exec level right now so just passing in an unused threading event
53
+ timeout_signal=ThreadingEvent(),
54
+ )
55
+ for line in stream_iterator:
56
+ print(f"{_EVENT_LINE}{json.dumps(line)}") # noqa: T201
62
57
  except Exception as e:
63
58
  logger.exception(e)
64
59
 
@@ -20,10 +20,10 @@ from vellum_ee.workflows.display.utils.events import event_enricher
20
20
  from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
21
21
  from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
22
22
 
23
- from vellum import Vellum
24
23
  from vellum.workflows import BaseWorkflow
25
24
  from vellum.workflows.emitters.base import BaseWorkflowEmitter
26
25
  from vellum.workflows.emitters.vellum_emitter import VellumEmitter
26
+ from vellum.workflows.events.exception_handling import stream_initialization_exception
27
27
  from vellum.workflows.events.types import BaseEvent
28
28
  from vellum.workflows.events.workflow import WorkflowEventDisplayContext
29
29
  from vellum.workflows.exceptions import WorkflowInitializationException
@@ -44,7 +44,7 @@ from workflow_server.core.events import (
44
44
  VembdaExecutionFulfilledBody,
45
45
  VembdaExecutionFulfilledEvent,
46
46
  )
47
- from workflow_server.core.utils import create_vellum_client, is_events_emitting_enabled, serialize_vembda_rejected_event
47
+ from workflow_server.core.utils import is_events_emitting_enabled, serialize_vembda_rejected_event
48
48
  from workflow_server.core.workflow_executor_context import (
49
49
  DEFAULT_TIMEOUT_SECONDS,
50
50
  BaseExecutorContext,
@@ -110,11 +110,6 @@ def _stream_workflow_wrapper(
110
110
  for event in stream_iterator:
111
111
  queue.put(json.dumps(event))
112
112
 
113
- except WorkflowInitializationException as e:
114
- if not span_id_emitted:
115
- queue.put(f"{SPAN_ID_EVENT}:{uuid4()}")
116
-
117
- queue.put(serialize_vembda_rejected_event(executor_context, str(e)))
118
113
  except Exception as e:
119
114
  if not span_id_emitted:
120
115
  queue.put(f"{SPAN_ID_EVENT}:{uuid4()}")
@@ -122,11 +117,6 @@ def _stream_workflow_wrapper(
122
117
  logger.exception(e)
123
118
  queue.put(serialize_vembda_rejected_event(executor_context, "Internal Server Error"))
124
119
 
125
- emitter_thread = next(
126
- (t for t in threading.enumerate() if t.name.endswith(".background_thread") and t.is_alive()), None
127
- )
128
- if emitter_thread:
129
- emitter_thread.join()
130
120
  queue.put(STREAM_FINISHED_EVENT)
131
121
 
132
122
  exit(0)
@@ -173,27 +163,28 @@ def stream_workflow(
173
163
  disable_redirect: bool = True,
174
164
  cancel_signal: Optional[ThreadingEvent] = None,
175
165
  ) -> tuple[Iterator[dict], UUID]:
176
- workflow, namespace = _gather_workflow(executor_context)
177
- workflow_inputs = _get_workflow_inputs(executor_context, workflow.__class__)
178
- display_context = _gather_display_context(workflow, namespace)
179
- workflow_state = (
180
- workflow.deserialize_state(
181
- executor_context.state,
182
- workflow_inputs=workflow_inputs or BaseInputs(),
166
+ cancel_watcher_kill_switch = ThreadingEvent()
167
+ try:
168
+ workflow, namespace = _gather_workflow(executor_context)
169
+ workflow_inputs = _get_workflow_inputs(executor_context, workflow.__class__)
170
+
171
+ display_context = _gather_display_context(workflow, namespace)
172
+ workflow_state = (
173
+ workflow.deserialize_state(
174
+ executor_context.state,
175
+ workflow_inputs=workflow_inputs or BaseInputs(),
176
+ )
177
+ if executor_context.state
178
+ else None
179
+ )
180
+ run_from_node = _get_run_from_node(executor_context, workflow)
181
+ node_output_mocks = MockNodeExecution.validate_all(
182
+ executor_context.node_output_mocks,
183
+ workflow.__class__,
183
184
  )
184
- if executor_context.state
185
- else None
186
- )
187
- run_from_node = _get_run_from_node(executor_context, workflow)
188
- node_output_mocks = MockNodeExecution.validate_all(
189
- executor_context.node_output_mocks,
190
- workflow.__class__,
191
- )
192
185
 
193
- cancel_watcher_kill_switch = ThreadingEvent()
194
- cancel_signal = cancel_signal or ThreadingEvent()
186
+ cancel_signal = cancel_signal or ThreadingEvent()
195
187
 
196
- try:
197
188
  stream = workflow.stream(
198
189
  inputs=workflow_inputs,
199
190
  state=workflow_state,
@@ -203,6 +194,26 @@ def stream_workflow(
203
194
  entrypoint_nodes=[run_from_node] if run_from_node else None,
204
195
  previous_execution_id=executor_context.previous_execution_id,
205
196
  )
197
+ except WorkflowInitializationException as e:
198
+ cancel_watcher_kill_switch.set()
199
+ initialization_exception_stream = stream_initialization_exception(e)
200
+
201
+ def _stream_generator() -> Generator[dict[str, Any], Any, None]:
202
+ for event in initialization_exception_stream:
203
+ yield _dump_event(
204
+ event=event,
205
+ executor_context=executor_context,
206
+ )
207
+
208
+ return (
209
+ _call_stream(
210
+ executor_context=executor_context,
211
+ stream_generator=_stream_generator,
212
+ disable_redirect=disable_redirect,
213
+ timeout_signal=timeout_signal,
214
+ ),
215
+ initialization_exception_stream.span_id,
216
+ )
206
217
  except Exception:
207
218
  cancel_watcher_kill_switch.set()
208
219
  logger.exception("Failed to generate Workflow Stream")
@@ -235,7 +246,6 @@ def stream_workflow(
235
246
  yield _dump_event(
236
247
  event=event,
237
248
  executor_context=executor_context,
238
- client=workflow.context.vellum_client,
239
249
  )
240
250
  except Exception as e:
241
251
  logger.exception("Failed to generate event from Workflow Stream")
@@ -243,6 +253,16 @@ def stream_workflow(
243
253
  finally:
244
254
  cancel_watcher_kill_switch.set()
245
255
 
256
+ emitter_thread = next(
257
+ (t for t in threading.enumerate() if t.name.endswith(".background_thread") and t.is_alive()), None
258
+ )
259
+ if emitter_thread:
260
+ emitter_thread.join()
261
+
262
+ timer_thread = next((t for t in threading.enumerate() if t.name.startswith("Thread-")), None)
263
+ if timer_thread:
264
+ timer_thread.join()
265
+
246
266
  return (
247
267
  _call_stream(
248
268
  executor_context=executor_context,
@@ -372,18 +392,13 @@ def _create_workflow(executor_context: BaseExecutorContext, namespace: str) -> B
372
392
 
373
393
 
374
394
  def _create_workflow_context(executor_context: BaseExecutorContext) -> WorkflowContext:
375
- vellum_client = create_vellum_client(
376
- api_key=executor_context.environment_api_key,
377
- api_version=executor_context.api_version,
378
- )
379
-
380
395
  if executor_context.environment_variables:
381
396
  os.environ.update(executor_context.environment_variables)
382
397
 
383
398
  namespace = _get_file_namespace(executor_context)
384
399
 
385
400
  return WorkflowContext(
386
- vellum_client=vellum_client,
401
+ vellum_client=executor_context.vellum_client,
387
402
  execution_context=executor_context.execution_context,
388
403
  generated_files=executor_context.files,
389
404
  namespace=namespace,
@@ -403,9 +418,11 @@ def _get_file_namespace(executor_context: BaseExecutorContext) -> str:
403
418
  )
404
419
 
405
420
 
406
- def _dump_event(event: BaseEvent, executor_context: BaseExecutorContext, client: Vellum) -> dict:
421
+ def _dump_event(event: BaseEvent, executor_context: BaseExecutorContext) -> dict:
407
422
  module_base = executor_context.module.split(".")
408
- dump = event.model_dump(mode="json", context={"event_enricher": lambda event: event_enricher(event, client)})
423
+ dump = event.model_dump(
424
+ mode="json", context={"event_enricher": lambda event: event_enricher(event, executor_context.vellum_client)}
425
+ )
409
426
  if dump["name"] in {
410
427
  "workflow.execution.initiated",
411
428
  "workflow.execution.fulfilled",
@@ -1,10 +1,7 @@
1
1
  from datetime import datetime
2
- import os
3
2
  from uuid import uuid4
4
3
  from typing import Optional
5
4
 
6
- from vellum import ApiVersionEnum, Vellum, VellumEnvironment
7
- from workflow_server.config import IS_VPC, VELLUM_API_URL_HOST, VELLUM_API_URL_PORT
8
5
  from workflow_server.core.events import VembdaExecutionFulfilledBody, VembdaExecutionFulfilledEvent
9
6
  from workflow_server.core.workflow_executor_context import BaseExecutorContext
10
7
 
@@ -51,42 +48,3 @@ def is_events_emitting_enabled(executor_context: Optional[BaseExecutorContext])
51
48
  return False
52
49
 
53
50
  return executor_context.feature_flags.get("vembda-event-emitting-enabled") or False
54
-
55
-
56
- def create_vellum_client(
57
- api_key: str,
58
- api_version: Optional[ApiVersionEnum] = None,
59
- ) -> Vellum:
60
- """
61
- Create a VellumClient with proper environment configuration.
62
-
63
- Args:
64
- api_key: The API key for the Vellum client
65
- api_version: Optional API version to use
66
-
67
- Returns:
68
- Configured Vellum client instance
69
-
70
- Note: Ideally we replace this with `vellum.workflows.vellum_client.create_vellum_client`
71
- """
72
- if IS_VPC:
73
- environment = VellumEnvironment(
74
- default=os.getenv("VELLUM_DEFAULT_API_URL", VellumEnvironment.PRODUCTION.default),
75
- documents=os.getenv("VELLUM_DOCUMENTS_API_URL", VellumEnvironment.PRODUCTION.documents),
76
- predict=os.getenv("VELLUM_PREDICT_API_URL", VellumEnvironment.PRODUCTION.predict),
77
- )
78
- elif os.getenv("USE_LOCAL_VELLUM_API") == "true":
79
- VELLUM_API_URL = f"http://{VELLUM_API_URL_HOST}:{VELLUM_API_URL_PORT}"
80
- environment = VellumEnvironment(
81
- default=VELLUM_API_URL,
82
- documents=VELLUM_API_URL,
83
- predict=VELLUM_API_URL,
84
- )
85
- else:
86
- environment = VellumEnvironment.PRODUCTION
87
-
88
- return Vellum(
89
- api_key=api_key,
90
- environment=environment,
91
- api_version=api_version,
92
- )
@@ -1,14 +1,57 @@
1
1
  from dataclasses import field
2
+ import os
2
3
  from uuid import UUID
3
4
  from typing import Any, Optional
4
5
 
6
+ from _pytest.compat import cached_property
7
+
8
+ from vellum import ApiVersionEnum, Vellum, VellumEnvironment
5
9
  from vellum.client.core import UniversalBaseModel
6
- from vellum.client.types.api_version_enum import ApiVersionEnum
7
10
  from vellum.workflows.context import ExecutionContext
11
+ from workflow_server.config import IS_VPC, VELLUM_API_URL_HOST, VELLUM_API_URL_PORT
8
12
 
9
13
  DEFAULT_TIMEOUT_SECONDS = 60 * 30
10
14
 
11
15
 
16
+ def create_vellum_client(
17
+ api_key: str,
18
+ api_version: Optional[ApiVersionEnum] = None,
19
+ ) -> Vellum:
20
+ """
21
+ Create a VellumClient with proper environment configuration.
22
+
23
+ Args:
24
+ api_key: The API key for the Vellum client
25
+ api_version: Optional API version to use
26
+
27
+ Returns:
28
+ Configured Vellum client instance
29
+
30
+ Note: Ideally we replace this with `vellum.workflows.vellum_client.create_vellum_client`
31
+ """
32
+ if IS_VPC:
33
+ environment = VellumEnvironment(
34
+ default=os.getenv("VELLUM_DEFAULT_API_URL", VellumEnvironment.PRODUCTION.default),
35
+ documents=os.getenv("VELLUM_DOCUMENTS_API_URL", VellumEnvironment.PRODUCTION.documents),
36
+ predict=os.getenv("VELLUM_PREDICT_API_URL", VellumEnvironment.PRODUCTION.predict),
37
+ )
38
+ elif os.getenv("USE_LOCAL_VELLUM_API") == "true":
39
+ VELLUM_API_URL = f"http://{VELLUM_API_URL_HOST}:{VELLUM_API_URL_PORT}"
40
+ environment = VellumEnvironment(
41
+ default=VELLUM_API_URL,
42
+ documents=VELLUM_API_URL,
43
+ predict=VELLUM_API_URL,
44
+ )
45
+ else:
46
+ environment = VellumEnvironment.PRODUCTION
47
+
48
+ return Vellum(
49
+ api_key=api_key,
50
+ environment=environment,
51
+ api_version=api_version,
52
+ )
53
+
54
+
12
55
  class BaseExecutorContext(UniversalBaseModel):
13
56
  inputs: dict
14
57
  state: Optional[dict] = None
@@ -35,6 +78,13 @@ class BaseExecutorContext(UniversalBaseModel):
35
78
  def trace_id(self) -> UUID:
36
79
  return self.execution_context.trace_id
37
80
 
81
+ @cached_property
82
+ def vellum_client(self) -> Vellum:
83
+ return create_vellum_client(
84
+ api_key=self.environment_api_key,
85
+ api_version=self.api_version,
86
+ )
87
+
38
88
  def __hash__(self) -> int:
39
89
  # do we think we need anything else for a unique hash for caching?
40
90
  return hash(str(self.execution_id))