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.
- {vellum_workflow_server-1.4.1.post1.dist-info → vellum_workflow_server-1.4.1.post3.dist-info}/METADATA +1 -1
- {vellum_workflow_server-1.4.1.post1.dist-info → vellum_workflow_server-1.4.1.post3.dist-info}/RECORD +10 -10
- workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +125 -6
- workflow_server/api/workflow_view.py +1 -6
- workflow_server/code_exec_runner.py +8 -13
- workflow_server/core/executor.py +56 -39
- workflow_server/core/utils.py +0 -42
- workflow_server/core/workflow_executor_context.py +51 -1
- {vellum_workflow_server-1.4.1.post1.dist-info → vellum_workflow_server-1.4.1.post3.dist-info}/WHEEL +0 -0
- {vellum_workflow_server-1.4.1.post1.dist-info → vellum_workflow_server-1.4.1.post3.dist-info}/entry_points.txt +0 -0
{vellum_workflow_server-1.4.1.post1.dist-info → vellum_workflow_server-1.4.1.post3.dist-info}/RECORD
RENAMED
|
@@ -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=
|
|
9
|
-
workflow_server/api/workflow_view.py,sha256=
|
|
10
|
-
workflow_server/code_exec_runner.py,sha256=
|
|
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=
|
|
16
|
-
workflow_server/core/utils.py,sha256=
|
|
17
|
-
workflow_server/core/workflow_executor_context.py,sha256=
|
|
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.
|
|
32
|
-
vellum_workflow_server-1.4.1.
|
|
33
|
-
vellum_workflow_server-1.4.1.
|
|
34
|
-
vellum_workflow_server-1.4.1.
|
|
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[
|
|
402
|
-
"exit_code":
|
|
413
|
+
assert events[3]["body"] == {
|
|
414
|
+
"exit_code": 0,
|
|
403
415
|
"log": "",
|
|
404
|
-
"stderr": "
|
|
416
|
+
"stderr": "",
|
|
405
417
|
"timed_out": False,
|
|
406
418
|
"container_overhead_latency": mock.ANY,
|
|
407
419
|
}
|
|
408
420
|
|
|
409
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
workflow_server/core/executor.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
|
421
|
+
def _dump_event(event: BaseEvent, executor_context: BaseExecutorContext) -> dict:
|
|
407
422
|
module_base = executor_context.module.split(".")
|
|
408
|
-
dump = event.model_dump(
|
|
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",
|
workflow_server/core/utils.py
CHANGED
|
@@ -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))
|
{vellum_workflow_server-1.4.1.post1.dist-info → vellum_workflow_server-1.4.1.post3.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|