vellum-workflow-server 1.11.0.post1__tar.gz → 1.12.0.post1__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.11.0.post1 → vellum_workflow_server-1.12.0.post1}/PKG-INFO +5 -3
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/README.md +3 -1
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/pyproject.toml +2 -2
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/tests/test_workflow_view.py +9 -1
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +77 -1
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/workflow_view.py +94 -8
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/core/cancel_workflow.py +11 -7
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/core/executor.py +3 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/core/workflow_executor_context.py +1 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/utils.py +9 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/__init__.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/__init__.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/auth_middleware.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/healthz_view.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/status_view.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/tests/__init__.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/tests/test_input_display_mapping.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/api/tests/test_workflow_view_async_exec.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/code_exec_runner.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/config.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/core/__init__.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/core/events.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/core/utils.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/logging_config.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/server.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/start.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/__init__.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/exit_handler.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/log_proxy.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/oom_killer.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/sentry.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/system_utils.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/tests/__init__.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/tests/test_sentry_integration.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/tests/test_system_utils.py +0 -0
- {vellum_workflow_server-1.11.0.post1 → vellum_workflow_server-1.12.0.post1}/src/workflow_server/utils/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vellum-workflow-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0.post1
|
|
4
4
|
Summary:
|
|
5
5
|
License: AGPL
|
|
6
6
|
Requires-Python: >=3.9.0,<4
|
|
@@ -30,13 +30,15 @@ Requires-Dist: pyjwt (==2.10.0)
|
|
|
30
30
|
Requires-Dist: python-dotenv (==1.2.1)
|
|
31
31
|
Requires-Dist: retrying (==1.3.4)
|
|
32
32
|
Requires-Dist: sentry-sdk[flask] (==2.20.0)
|
|
33
|
-
Requires-Dist: vellum-ai (==1.
|
|
33
|
+
Requires-Dist: vellum-ai (==1.12.0)
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
36
36
|
# Vellum Workflow Runner Server
|
|
37
|
+
|
|
37
38
|
This package is meant for installing on container images in order to use custom docker images when using Vellum Workflows.
|
|
38
39
|
|
|
39
40
|
## Example Dockerfile Usage:
|
|
41
|
+
|
|
40
42
|
```
|
|
41
43
|
FROM python:3.11.6-slim-bookworm
|
|
42
44
|
|
|
@@ -49,7 +51,6 @@ RUN pip install --upgrade pip
|
|
|
49
51
|
RUN pip --no-cache-dir install vellum-workflow-server==0.13.2
|
|
50
52
|
|
|
51
53
|
ENV PYTHONUNBUFFERED 1
|
|
52
|
-
ENV PYTHONDONTWRITEBYTECODE 1
|
|
53
54
|
COPY ./base-image/code_exec_entrypoint.sh .
|
|
54
55
|
RUN chmod +x /code_exec_entrypoint.sh
|
|
55
56
|
|
|
@@ -57,5 +58,6 @@ CMD ["vellum_start_server"]
|
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
## Skipping Publishes
|
|
61
|
+
|
|
60
62
|
If you wish to automatically skip publishing a new version when merging to main you can add a [skip-publish] to your commit message. This is useful if your changes are not time sensitive and can just go out with the next release. This avoids causing new services being created causing extra cold starts for our customers and also keeps our public versioning more tidy.
|
|
61
63
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Vellum Workflow Runner Server
|
|
2
|
+
|
|
2
3
|
This package is meant for installing on container images in order to use custom docker images when using Vellum Workflows.
|
|
3
4
|
|
|
4
5
|
## Example Dockerfile Usage:
|
|
6
|
+
|
|
5
7
|
```
|
|
6
8
|
FROM python:3.11.6-slim-bookworm
|
|
7
9
|
|
|
@@ -14,7 +16,6 @@ RUN pip install --upgrade pip
|
|
|
14
16
|
RUN pip --no-cache-dir install vellum-workflow-server==0.13.2
|
|
15
17
|
|
|
16
18
|
ENV PYTHONUNBUFFERED 1
|
|
17
|
-
ENV PYTHONDONTWRITEBYTECODE 1
|
|
18
19
|
COPY ./base-image/code_exec_entrypoint.sh .
|
|
19
20
|
RUN chmod +x /code_exec_entrypoint.sh
|
|
20
21
|
|
|
@@ -22,4 +23,5 @@ CMD ["vellum_start_server"]
|
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
## Skipping Publishes
|
|
26
|
+
|
|
25
27
|
If you wish to automatically skip publishing a new version when merging to main you can add a [skip-publish] to your commit message. This is useful if your changes are not time sensitive and can just go out with the next release. This avoids causing new services being created causing extra cold starts for our customers and also keeps our public versioning more tidy.
|
|
@@ -3,7 +3,7 @@ name = "vellum-workflow-server"
|
|
|
3
3
|
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "vellum-workflow-server"
|
|
6
|
-
version = "1.
|
|
6
|
+
version = "1.12.0.post1"
|
|
7
7
|
description = ""
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = []
|
|
@@ -46,7 +46,7 @@ orderly-set = "5.2.2"
|
|
|
46
46
|
pebble = "5.0.7"
|
|
47
47
|
gunicorn = "23.0.0"
|
|
48
48
|
orjson = "3.11.4"
|
|
49
|
-
vellum-ai = "1.
|
|
49
|
+
vellum-ai = "1.12.0"
|
|
50
50
|
python-dotenv = "1.2.1"
|
|
51
51
|
retrying = "1.3.4"
|
|
52
52
|
sentry-sdk = {extras = ["flask"], version = "2.20.0"}
|
|
@@ -389,7 +389,15 @@ class MyAdditionNode(BaseNode):
|
|
|
389
389
|
},
|
|
390
390
|
"id": "2464b610-fb6d-495b-b17c-933ee147f19f",
|
|
391
391
|
"label": "My Addition Node",
|
|
392
|
-
"outputs": [
|
|
392
|
+
"outputs": [
|
|
393
|
+
{
|
|
394
|
+
"id": "f39d85c9-e7bf-45e1-bb67-f16225db0118",
|
|
395
|
+
"name": "result",
|
|
396
|
+
"type": "NUMBER",
|
|
397
|
+
"value": None,
|
|
398
|
+
"schema": {"type": "integer"},
|
|
399
|
+
}
|
|
400
|
+
],
|
|
393
401
|
"ports": [{"id": "bc489295-cd8a-4aa2-88bb-34446374100d", "name": "default", "type": "DEFAULT"}],
|
|
394
402
|
"trigger": {"id": "ff580cad-73d6-44fe-8f2c-4b8dc990ee70", "merge_behavior": "AWAIT_ATTRIBUTES"},
|
|
395
403
|
"type": "GENERIC",
|
|
@@ -549,7 +549,10 @@ class Inputs(BaseInputs):
|
|
|
549
549
|
# AND the third event should be workflow execution rejected
|
|
550
550
|
assert events[2]["name"] == "workflow.execution.rejected"
|
|
551
551
|
assert events[1]["span_id"] == events[2]["span_id"]
|
|
552
|
-
|
|
552
|
+
actual_error_message = events[2]["body"]["error"]["message"]
|
|
553
|
+
assert "Required input variables" in actual_error_message
|
|
554
|
+
assert "foo" in actual_error_message
|
|
555
|
+
assert "should have defined value" in actual_error_message
|
|
553
556
|
|
|
554
557
|
# AND the fourth event should be vembda execution fulfilled
|
|
555
558
|
assert events[3]["name"] == "vembda.execution.fulfilled"
|
|
@@ -1310,3 +1313,76 @@ class OOMWorkflow(BaseWorkflow):
|
|
|
1310
1313
|
assert (
|
|
1311
1314
|
vembda_fulfilled_event["body"].get("timed_out") is not True
|
|
1312
1315
|
), "timed_out flag should not be set when OOM occurs"
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
@mock.patch("workflow_server.api.workflow_view.ENABLE_PROCESS_WRAPPER", False)
|
|
1319
|
+
def test_stream_workflow_route__client_disconnect_emits_rejected_event():
|
|
1320
|
+
"""
|
|
1321
|
+
Tests that when a client disconnects mid-stream (GeneratorExit), we emit a workflow execution
|
|
1322
|
+
rejected event to the events.create API.
|
|
1323
|
+
"""
|
|
1324
|
+
# GIVEN a valid request body for a workflow that yields multiple events
|
|
1325
|
+
span_id = uuid4()
|
|
1326
|
+
trace_id = uuid4()
|
|
1327
|
+
request_body = {
|
|
1328
|
+
"timeout": 360,
|
|
1329
|
+
"execution_id": str(span_id),
|
|
1330
|
+
"execution_context": {
|
|
1331
|
+
"trace_id": str(trace_id),
|
|
1332
|
+
},
|
|
1333
|
+
"inputs": [],
|
|
1334
|
+
"environment_api_key": "test",
|
|
1335
|
+
"module": "workflow",
|
|
1336
|
+
"files": {
|
|
1337
|
+
"__init__.py": "",
|
|
1338
|
+
"workflow.py": """\
|
|
1339
|
+
from vellum.workflows import BaseWorkflow
|
|
1340
|
+
|
|
1341
|
+
class Workflow(BaseWorkflow):
|
|
1342
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
1343
|
+
foo = "hello"
|
|
1344
|
+
""",
|
|
1345
|
+
},
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
# AND a mock to capture events.create calls
|
|
1349
|
+
events_create_calls = []
|
|
1350
|
+
|
|
1351
|
+
def mock_events_create(request):
|
|
1352
|
+
events_create_calls.append(request)
|
|
1353
|
+
|
|
1354
|
+
# WHEN we call the stream route and simulate a client disconnect
|
|
1355
|
+
flask_app = create_app()
|
|
1356
|
+
with flask_app.test_client() as test_client:
|
|
1357
|
+
with mock.patch("workflow_server.core.workflow_executor_context.create_vellum_client") as mock_create_client:
|
|
1358
|
+
mock_client = mock.MagicMock()
|
|
1359
|
+
mock_client.events.create = mock_events_create
|
|
1360
|
+
mock_create_client.return_value = mock_client
|
|
1361
|
+
|
|
1362
|
+
response = test_client.post("/workflow/stream", json=request_body)
|
|
1363
|
+
|
|
1364
|
+
# Get the response iterator and consume a few chunks to start the stream
|
|
1365
|
+
response_iter = response.response
|
|
1366
|
+
next(response_iter)
|
|
1367
|
+
|
|
1368
|
+
# Close the response to trigger GeneratorExit
|
|
1369
|
+
response_iter.close()
|
|
1370
|
+
|
|
1371
|
+
# THEN the events.create API should have been called with rejected event
|
|
1372
|
+
assert len(events_create_calls) > 0, "events.create should have been called on client disconnect"
|
|
1373
|
+
|
|
1374
|
+
# AND the call should include a workflow.execution.rejected event (sent as SDK event model)
|
|
1375
|
+
last_call = events_create_calls[-1]
|
|
1376
|
+
assert isinstance(last_call, list), "events.create should be called with a list"
|
|
1377
|
+
assert len(last_call) == 1, "Should have exactly one rejected event"
|
|
1378
|
+
|
|
1379
|
+
rejected_event = last_call[0]
|
|
1380
|
+
assert rejected_event.name == "workflow.execution.rejected", "Should be a rejected event"
|
|
1381
|
+
|
|
1382
|
+
# AND the rejected event should have the correct error message
|
|
1383
|
+
assert "client disconnected" in rejected_event.body.error.message.lower()
|
|
1384
|
+
|
|
1385
|
+
# AND the rejected event should have a workflow_definition
|
|
1386
|
+
# TODO: In the future, we should capture the real workflow_definition from the initiated event.
|
|
1387
|
+
# For now, we use BaseWorkflow as a placeholder.
|
|
1388
|
+
assert rejected_event.body.workflow_definition is not None, "Should have a workflow_definition"
|
|
@@ -22,6 +22,14 @@ from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
|
|
22
22
|
from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
|
|
23
23
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
|
24
24
|
|
|
25
|
+
from vellum.workflows import BaseWorkflow
|
|
26
|
+
from vellum.workflows.errors import WorkflowError, WorkflowErrorCode
|
|
27
|
+
from vellum.workflows.events.workflow import (
|
|
28
|
+
WorkflowExecutionInitiatedBody,
|
|
29
|
+
WorkflowExecutionInitiatedEvent,
|
|
30
|
+
WorkflowExecutionRejectedBody,
|
|
31
|
+
WorkflowExecutionRejectedEvent,
|
|
32
|
+
)
|
|
25
33
|
from vellum.workflows.exceptions import WorkflowInitializationException
|
|
26
34
|
from vellum.workflows.nodes import BaseNode
|
|
27
35
|
from vellum.workflows.vellum_client import create_vellum_client
|
|
@@ -135,7 +143,7 @@ def stream_workflow_route() -> Response:
|
|
|
135
143
|
# These can happen either from Vembda disconnects (possibily from predict disconnects) or
|
|
136
144
|
# from knative activator gateway timeouts which are caused by idleTimeout or responseStartSeconds
|
|
137
145
|
# being exceeded.
|
|
138
|
-
app.logger.
|
|
146
|
+
app.logger.warning(
|
|
139
147
|
"Client disconnected in the middle of the Workflow Stream",
|
|
140
148
|
extra={
|
|
141
149
|
"sentry_tags": {
|
|
@@ -144,6 +152,11 @@ def stream_workflow_route() -> Response:
|
|
|
144
152
|
}
|
|
145
153
|
},
|
|
146
154
|
)
|
|
155
|
+
_emit_client_disconnect_events(
|
|
156
|
+
context,
|
|
157
|
+
span_id,
|
|
158
|
+
"Client disconnected in the middle of the Workflow Stream",
|
|
159
|
+
)
|
|
147
160
|
return
|
|
148
161
|
except Exception as e:
|
|
149
162
|
logger.exception("Error during workflow response stream generator", extra={"error": e})
|
|
@@ -174,6 +187,75 @@ def stream_workflow_route() -> Response:
|
|
|
174
187
|
return resp
|
|
175
188
|
|
|
176
189
|
|
|
190
|
+
def _emit_async_error_events(
|
|
191
|
+
context: WorkflowExecutorContext, error_message: str, stacktrace: Optional[str] = None
|
|
192
|
+
) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Emit workflow execution error events when async execution fails before or during workflow startup.
|
|
195
|
+
|
|
196
|
+
This ensures that errors in async mode are properly reported to Vellum's events API,
|
|
197
|
+
making them visible in the executions UI.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
workflow_span_id = context.workflow_span_id or str(uuid4())
|
|
201
|
+
|
|
202
|
+
initiated_event = WorkflowExecutionInitiatedEvent[Any, Any](
|
|
203
|
+
trace_id=context.trace_id,
|
|
204
|
+
span_id=workflow_span_id,
|
|
205
|
+
body=WorkflowExecutionInitiatedBody(inputs=context.inputs),
|
|
206
|
+
parent=context.execution_context.parent_context if context.execution_context else None,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
rejected_event = WorkflowExecutionRejectedEvent(
|
|
210
|
+
trace_id=context.trace_id,
|
|
211
|
+
span_id=workflow_span_id,
|
|
212
|
+
body=WorkflowExecutionRejectedBody(
|
|
213
|
+
error=WorkflowError(
|
|
214
|
+
message=error_message,
|
|
215
|
+
code=WorkflowErrorCode.INTERNAL_ERROR,
|
|
216
|
+
),
|
|
217
|
+
stacktrace=stacktrace,
|
|
218
|
+
),
|
|
219
|
+
parent=context.execution_context.parent_context if context.execution_context else None,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
context.vellum_client.events.create(request=[initiated_event, rejected_event]) # type: ignore[list-item]
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.exception(f"Failed to emit async error events: {e}")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _emit_client_disconnect_events(
|
|
228
|
+
context: WorkflowExecutorContext,
|
|
229
|
+
workflow_span_id: str,
|
|
230
|
+
error_message: str,
|
|
231
|
+
) -> None:
|
|
232
|
+
"""
|
|
233
|
+
Emit workflow execution rejected event when a client disconnects mid-stream.
|
|
234
|
+
|
|
235
|
+
Since the workflow has already started streaming (the initiated event was already emitted),
|
|
236
|
+
we only need to emit the rejected event to properly close out the execution.
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
# TODO: In the future, we should capture the real workflow_definition from the initiated event
|
|
240
|
+
# For now, we use BaseWorkflow as a placeholder
|
|
241
|
+
rejected_event = WorkflowExecutionRejectedEvent(
|
|
242
|
+
trace_id=context.trace_id,
|
|
243
|
+
span_id=workflow_span_id,
|
|
244
|
+
body=WorkflowExecutionRejectedBody(
|
|
245
|
+
workflow_definition=BaseWorkflow,
|
|
246
|
+
error=WorkflowError(
|
|
247
|
+
message=error_message,
|
|
248
|
+
code=WorkflowErrorCode.WORKFLOW_CANCELLED,
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
parent=context.execution_context.parent_context if context.execution_context else None,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
context.vellum_client.events.create(request=[rejected_event]) # type: ignore[list-item]
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.exception(f"Failed to emit client disconnect events: {e}")
|
|
257
|
+
|
|
258
|
+
|
|
177
259
|
@bp.route("/async-exec", methods=["POST"])
|
|
178
260
|
def async_exec_workflow() -> Response:
|
|
179
261
|
data = request.get_json()
|
|
@@ -208,8 +290,8 @@ def async_exec_workflow() -> Response:
|
|
|
208
290
|
try:
|
|
209
291
|
start_workflow_result = _start_workflow(context)
|
|
210
292
|
if isinstance(start_workflow_result, Response):
|
|
211
|
-
|
|
212
|
-
|
|
293
|
+
error_detail = start_workflow_result.get_json().get("detail", "Unknown error during workflow startup")
|
|
294
|
+
_emit_async_error_events(context, error_detail)
|
|
213
295
|
return
|
|
214
296
|
|
|
215
297
|
workflow_events, vembda_initiated_event, process, span_id, headers = start_workflow_result
|
|
@@ -223,6 +305,7 @@ def async_exec_workflow() -> Response:
|
|
|
223
305
|
)
|
|
224
306
|
except Exception as e:
|
|
225
307
|
logger.exception("Error during workflow async background worker", e)
|
|
308
|
+
_emit_async_error_events(context, str(e), traceback.format_exc())
|
|
226
309
|
finally:
|
|
227
310
|
if ENABLE_PROCESS_WRAPPER:
|
|
228
311
|
try:
|
|
@@ -531,11 +614,18 @@ def serialize_route() -> Response:
|
|
|
531
614
|
is_new_server = data.get("is_new_server", False)
|
|
532
615
|
module = data.get("module")
|
|
533
616
|
|
|
617
|
+
headers = {
|
|
618
|
+
"X-Vellum-Is-New-Server": str(is_new_server).lower(),
|
|
619
|
+
}
|
|
620
|
+
|
|
534
621
|
if not files:
|
|
622
|
+
error_message = "No files received"
|
|
623
|
+
logger.warning(error_message)
|
|
535
624
|
return Response(
|
|
536
|
-
json.dumps({"detail":
|
|
625
|
+
json.dumps({"detail": error_message}),
|
|
537
626
|
status=400,
|
|
538
627
|
content_type="application/json",
|
|
628
|
+
headers=headers,
|
|
539
629
|
)
|
|
540
630
|
|
|
541
631
|
client = create_vellum_client(api_key=workspace_api_key)
|
|
@@ -544,10 +634,6 @@ def serialize_route() -> Response:
|
|
|
544
634
|
namespace = get_random_namespace()
|
|
545
635
|
virtual_finder = VirtualFileFinder(files, namespace, source_module=module)
|
|
546
636
|
|
|
547
|
-
headers = {
|
|
548
|
-
"X-Vellum-Is-New-Server": str(is_new_server).lower(),
|
|
549
|
-
}
|
|
550
|
-
|
|
551
637
|
try:
|
|
552
638
|
sys.meta_path.append(virtual_finder)
|
|
553
639
|
result = BaseWorkflowDisplay.serialize_module(namespace, client=client, dry_run=True)
|
|
@@ -14,14 +14,18 @@ logger = logging.getLogger(__name__)
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def get_is_workflow_cancelled(execution_id: UUID, vembda_public_url: Optional[str]) -> bool:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
try:
|
|
18
|
+
response = requests.get(
|
|
19
|
+
f"{vembda_public_url}/vembda-public/cancel-workflow-execution-status/{execution_id}",
|
|
20
|
+
headers={"Accept": "application/json"},
|
|
21
|
+
timeout=5,
|
|
22
|
+
)
|
|
23
|
+
response.raise_for_status()
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
return response.json().get("cancelled", False)
|
|
26
|
+
except Exception:
|
|
27
|
+
logger.exception("Error checking workflow cancellation status")
|
|
28
|
+
return False
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
class CancelWorkflowWatcherThread(Thread):
|
|
@@ -14,6 +14,7 @@ from typing import Any, Callable, Generator, Iterator, Optional, Tuple
|
|
|
14
14
|
|
|
15
15
|
import orjson
|
|
16
16
|
from vellum_ee.workflows.display.utils.events import event_enricher
|
|
17
|
+
from vellum_ee.workflows.display.utils.expressions import base_descriptor_validator
|
|
17
18
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
|
18
19
|
|
|
19
20
|
from vellum.workflows import BaseWorkflow
|
|
@@ -177,6 +178,7 @@ def stream_workflow(
|
|
|
177
178
|
node_output_mocks = MockNodeExecution.validate_all(
|
|
178
179
|
executor_context.node_output_mocks,
|
|
179
180
|
workflow.__class__,
|
|
181
|
+
descriptor_validator=base_descriptor_validator,
|
|
180
182
|
)
|
|
181
183
|
|
|
182
184
|
cancel_signal = cancel_signal or ThreadingEvent()
|
|
@@ -192,6 +194,7 @@ def stream_workflow(
|
|
|
192
194
|
timeout=executor_context.timeout,
|
|
193
195
|
trigger=trigger,
|
|
194
196
|
execution_id=executor_context.workflow_span_id,
|
|
197
|
+
event_max_size=executor_context.event_max_size,
|
|
195
198
|
)
|
|
196
199
|
except WorkflowInitializationException as e:
|
|
197
200
|
cancel_watcher_kill_switch.set()
|
|
@@ -41,6 +41,7 @@ class BaseExecutorContext(UniversalBaseModel):
|
|
|
41
41
|
# when running in async mode.
|
|
42
42
|
workflow_span_id: Optional[UUID] = None
|
|
43
43
|
vembda_service_initiated_timestamp: Optional[int] = None
|
|
44
|
+
event_max_size: Optional[int] = None
|
|
44
45
|
|
|
45
46
|
@field_validator("inputs", mode="before")
|
|
46
47
|
@classmethod
|
|
@@ -59,10 +59,19 @@ def convert_json_inputs_to_vellum(inputs: List[dict]) -> dict:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def get_version() -> dict:
|
|
62
|
+
# Return hotswappable lock file so we can save it and reuse it
|
|
63
|
+
lock_file = None
|
|
64
|
+
try:
|
|
65
|
+
with open("/app/uv.lock", "r") as f:
|
|
66
|
+
lock_file = f.read()
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
62
70
|
return {
|
|
63
71
|
"sdk_version": version("vellum-ai"),
|
|
64
72
|
"server_version": "local" if is_development() else version("vellum-workflow-server"),
|
|
65
73
|
"container_image": CONTAINER_IMAGE,
|
|
74
|
+
"lock_file": lock_file,
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
|
|
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
|