vellum-workflow-server 1.9.1.post2__tar.gz → 1.9.6.post2__tar.gz
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.
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/PKG-INFO +2 -2
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/pyproject.toml +2 -2
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +12 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/core/executor.py +10 -1
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/core/workflow_executor_context.py +1 -0
- vellum_workflow_server-1.9.6.post2/src/workflow_server/utils/exit_handler.py +56 -0
- vellum_workflow_server-1.9.1.post2/src/workflow_server/utils/exit_handler.py +0 -27
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/README.md +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/__init__.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/__init__.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/auth_middleware.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/healthz_view.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/status_view.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/tests/__init__.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/tests/test_input_display_mapping.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/tests/test_workflow_view.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/api/workflow_view.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/code_exec_runner.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/config.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/core/__init__.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/core/cancel_workflow.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/core/events.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/core/utils.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/logging_config.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/server.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/start.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/__init__.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/log_proxy.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/oom_killer.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/sentry.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/system_utils.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/tests/__init__.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/tests/test_sentry_integration.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/tests/test_system_utils.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/tests/test_utils.py +0 -0
- {vellum_workflow_server-1.9.1.post2 → vellum_workflow_server-1.9.6.post2}/src/workflow_server/utils/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vellum-workflow-server
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.6.post2
|
|
4
4
|
Summary:
|
|
5
5
|
License: AGPL
|
|
6
6
|
Requires-Python: >=3.9.0,<4
|
|
@@ -29,7 +29,7 @@ Requires-Dist: pyjwt (==2.10.0)
|
|
|
29
29
|
Requires-Dist: python-dotenv (==1.0.1)
|
|
30
30
|
Requires-Dist: retrying (==1.3.4)
|
|
31
31
|
Requires-Dist: sentry-sdk[flask] (==2.20.0)
|
|
32
|
-
Requires-Dist: vellum-ai (==1.9.
|
|
32
|
+
Requires-Dist: vellum-ai (==1.9.6)
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
|
|
35
35
|
# Vellum Workflow Runner Server
|
|
@@ -3,7 +3,7 @@ name = "vellum-workflow-server"
|
|
|
3
3
|
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "vellum-workflow-server"
|
|
6
|
-
version = "1.9.
|
|
6
|
+
version = "1.9.6.post2"
|
|
7
7
|
description = ""
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = []
|
|
@@ -45,7 +45,7 @@ flask = "2.3.3"
|
|
|
45
45
|
orderly-set = "5.2.2"
|
|
46
46
|
pebble = "5.0.7"
|
|
47
47
|
gunicorn = "23.0.0"
|
|
48
|
-
vellum-ai = "1.9.
|
|
48
|
+
vellum-ai = "1.9.6"
|
|
49
49
|
python-dotenv = "1.0.1"
|
|
50
50
|
retrying = "1.3.4"
|
|
51
51
|
sentry-sdk = {extras = ["flask"], version = "2.20.0"}
|
|
@@ -5,6 +5,7 @@ import io
|
|
|
5
5
|
import json
|
|
6
6
|
from queue import Empty
|
|
7
7
|
import re
|
|
8
|
+
import time
|
|
8
9
|
from unittest import mock
|
|
9
10
|
from uuid import uuid4
|
|
10
11
|
|
|
@@ -133,6 +134,8 @@ class Workflow(BaseWorkflow):
|
|
|
133
134
|
|
|
134
135
|
with mock.patch("builtins.open", mock.mock_open(read_data="104857600")):
|
|
135
136
|
# WHEN we call the stream route
|
|
137
|
+
ts_ns = time.time_ns()
|
|
138
|
+
request_body["vembda_service_initiated_timestamp"] = ts_ns
|
|
136
139
|
status_code, events = both_stream_types(request_body)
|
|
137
140
|
|
|
138
141
|
# THEN we get a 200 response
|
|
@@ -177,6 +180,15 @@ class Workflow(BaseWorkflow):
|
|
|
177
180
|
assert "is_new_server" in server_metadata
|
|
178
181
|
assert server_metadata["is_new_server"] is False
|
|
179
182
|
|
|
183
|
+
# AND the initiated event should have initiated_latency within a reasonable range
|
|
184
|
+
assert "initiated_latency" in server_metadata, "initiated_latency should be present in server_metadata"
|
|
185
|
+
initiated_latency = server_metadata["initiated_latency"]
|
|
186
|
+
assert isinstance(initiated_latency, int), "initiated_latency should be an integer (nanoseconds)"
|
|
187
|
+
# Latency should be positive and less than 60 seconds (60_000_000_000 nanoseconds) for CI
|
|
188
|
+
assert (
|
|
189
|
+
0 < initiated_latency < 60_000_000_000
|
|
190
|
+
), f"initiated_latency should be between 0 and 60 seconds, got {initiated_latency} ns"
|
|
191
|
+
|
|
180
192
|
assert events[2]["name"] == "workflow.execution.fulfilled", events[2]
|
|
181
193
|
assert events[2]["body"]["workflow_definition"]["module"] == ["test", "workflow"]
|
|
182
194
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from datetime import datetime
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
2
|
from io import StringIO
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
@@ -191,6 +191,7 @@ def stream_workflow(
|
|
|
191
191
|
previous_execution_id=executor_context.previous_execution_id,
|
|
192
192
|
timeout=executor_context.timeout,
|
|
193
193
|
trigger=trigger,
|
|
194
|
+
execution_id=executor_context.workflow_span_id,
|
|
194
195
|
)
|
|
195
196
|
except WorkflowInitializationException as e:
|
|
196
197
|
cancel_watcher_kill_switch.set()
|
|
@@ -449,6 +450,14 @@ def _enrich_event(event: WorkflowEvent, executor_context: Optional[BaseExecutorC
|
|
|
449
450
|
|
|
450
451
|
if executor_context is not None:
|
|
451
452
|
metadata["is_new_server"] = executor_context.is_new_server
|
|
453
|
+
|
|
454
|
+
if executor_context.vembda_service_initiated_timestamp is not None and event.timestamp is not None:
|
|
455
|
+
event_ts = event.timestamp
|
|
456
|
+
if event_ts.tzinfo is None:
|
|
457
|
+
event_ts = event_ts.replace(tzinfo=timezone.utc)
|
|
458
|
+
event_ts_ns = int(event_ts.timestamp() * 1_000_000_000)
|
|
459
|
+
initiated_latency = event_ts_ns - executor_context.vembda_service_initiated_timestamp
|
|
460
|
+
metadata["initiated_latency"] = initiated_latency
|
|
452
461
|
elif event.name == "workflow.execution.fulfilled" and is_deployment:
|
|
453
462
|
metadata = {}
|
|
454
463
|
memory_mb = get_memory_in_use_mb()
|
|
@@ -40,6 +40,7 @@ class BaseExecutorContext(UniversalBaseModel):
|
|
|
40
40
|
# The actual 'execution id' of the workflow that we pass into the workflow
|
|
41
41
|
# when running in async mode.
|
|
42
42
|
workflow_span_id: Optional[UUID] = None
|
|
43
|
+
vembda_service_initiated_timestamp: Optional[int] = None
|
|
43
44
|
|
|
44
45
|
@field_validator("inputs", mode="before")
|
|
45
46
|
@classmethod
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import multiprocessing
|
|
4
|
+
import signal
|
|
5
|
+
from time import sleep
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from workflow_server.config import IS_ASYNC_MODE, is_development
|
|
9
|
+
from workflow_server.utils.system_utils import get_active_process_count
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
process_killed_switch = multiprocessing.Event()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _wait_for_workers() -> None:
|
|
16
|
+
# Would be annoying to have this on for dev since would prevent reload restarts. Also disabling this
|
|
17
|
+
# for non async mode for now since it shouldn't be needed anyway cus we keep the requests open.
|
|
18
|
+
if is_development() and not IS_ASYNC_MODE:
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
start_time = datetime.now()
|
|
22
|
+
loops = 0
|
|
23
|
+
|
|
24
|
+
while get_active_process_count() > 0:
|
|
25
|
+
if loops % 30 == 0:
|
|
26
|
+
logger.info("Waiting for workflow processes to finish...")
|
|
27
|
+
|
|
28
|
+
# TODO needa pass in max workflow time here for VPC
|
|
29
|
+
if (datetime.now() - start_time).total_seconds() > 1800:
|
|
30
|
+
logger.warning("Max elapsed time waiting for workflow processes to complete exceeded, shutting down")
|
|
31
|
+
exit(1)
|
|
32
|
+
|
|
33
|
+
sleep(1)
|
|
34
|
+
loops += 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def gunicorn_exit_handler(_worker: Any) -> None:
|
|
38
|
+
logger.info("Received gunicorn kill signal")
|
|
39
|
+
process_killed_switch.set()
|
|
40
|
+
_wait_for_workers()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def exit_handler(_signal: int, _frame: Any) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Gunicorn overrides this signal handler but theres periods where the gunicorn server
|
|
46
|
+
hasn't initialized or for local dev where this will get called.
|
|
47
|
+
"""
|
|
48
|
+
process_killed_switch.set()
|
|
49
|
+
logger.warning("Received kill signal")
|
|
50
|
+
_wait_for_workers()
|
|
51
|
+
exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def init_signal_handlers() -> None:
|
|
55
|
+
signal.signal(signal.SIGTERM, exit_handler)
|
|
56
|
+
signal.signal(signal.SIGINT, exit_handler)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import multiprocessing
|
|
3
|
-
import signal
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__name__)
|
|
7
|
-
process_killed_switch = multiprocessing.Event()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def gunicorn_exit_handler(_worker: Any) -> None:
|
|
11
|
-
process_killed_switch.set()
|
|
12
|
-
logger.warning("Received gunicorn kill signal")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def exit_handler(_signal: int, _frame: Any) -> None:
|
|
16
|
-
"""
|
|
17
|
-
Gunicorn overrides this signal handler but theres periods where the gunicorn server
|
|
18
|
-
hasn't initialized or for local dev where this will get called.
|
|
19
|
-
"""
|
|
20
|
-
process_killed_switch.set()
|
|
21
|
-
logger.warning("Received kill signal")
|
|
22
|
-
exit(1)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def init_signal_handlers() -> None:
|
|
26
|
-
signal.signal(signal.SIGTERM, exit_handler)
|
|
27
|
-
signal.signal(signal.SIGINT, exit_handler)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|