vellum-workflow-server 0.14.68__tar.gz → 0.14.69__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.
Potentially problematic release.
This version of vellum-workflow-server might be problematic. Click here for more details.
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/PKG-INFO +2 -2
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/pyproject.toml +2 -2
- vellum_workflow_server-0.14.69/src/workflow_server/api/tests/test_workflow_view.py +228 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/api/workflow_view.py +42 -0
- vellum_workflow_server-0.14.68/src/workflow_server/api/tests/test_workflow_view.py +0 -13
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/README.md +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/__init__.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/api/__init__.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/api/auth_middleware.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/api/healthz_view.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/api/tests/__init__.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/code_exec_runner.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/config.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/core/__init__.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/core/cancel_workflow.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/core/events.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/core/executor.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/core/workflow_executor_context.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/server.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/start.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/__init__.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/exit_handler.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/log_proxy.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/oom_killer.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/sentry.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/tests/__init__.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/tests/test_utils.py +0 -0
- {vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/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: 0.14.
|
|
3
|
+
Version: 0.14.69
|
|
4
4
|
Summary:
|
|
5
5
|
License: AGPL
|
|
6
6
|
Requires-Python: >=3.9.0,<4
|
|
@@ -28,7 +28,7 @@ Requires-Dist: pebble (==5.0.7)
|
|
|
28
28
|
Requires-Dist: pyjwt (==2.10.0)
|
|
29
29
|
Requires-Dist: python-dotenv (==1.0.1)
|
|
30
30
|
Requires-Dist: sentry-sdk[flask] (==2.20.0)
|
|
31
|
-
Requires-Dist: vellum-ai (==0.14.
|
|
31
|
+
Requires-Dist: vellum-ai (==0.14.69)
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
34
34
|
# Vellum Workflow Runner Server
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "vellum-workflow-server"
|
|
3
|
-
version = "0.14.
|
|
3
|
+
version = "0.14.69"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = []
|
|
@@ -41,7 +41,7 @@ flask = "2.3.3"
|
|
|
41
41
|
orderly-set = "5.2.2"
|
|
42
42
|
pebble = "5.0.7"
|
|
43
43
|
gunicorn = "23.0.0"
|
|
44
|
-
vellum-ai = "0.14.
|
|
44
|
+
vellum-ai = "0.14.69"
|
|
45
45
|
python-dotenv = "1.0.1"
|
|
46
46
|
sentry-sdk = {extras = ["flask"], version = "2.20.0"}
|
|
47
47
|
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from workflow_server.server import create_app
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_version_route():
|
|
10
|
+
flask_app = create_app()
|
|
11
|
+
|
|
12
|
+
with flask_app.test_client() as test_client:
|
|
13
|
+
response = test_client.get("/workflow/version")
|
|
14
|
+
assert response.status_code == 200
|
|
15
|
+
assert re.match(r"[0-9]*\.[0-9]*\.[0-9]*", response.json["sdk_version"])
|
|
16
|
+
assert response.json["server_version"] == "local"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_version_route__with_single_node_file(tmp_path):
|
|
20
|
+
# GIVEN a temporary custom_nodes directory with a test node
|
|
21
|
+
custom_nodes_dir = tmp_path / "vellum_custom_nodes"
|
|
22
|
+
custom_nodes_dir.mkdir()
|
|
23
|
+
|
|
24
|
+
node_file = custom_nodes_dir / "test_node.py"
|
|
25
|
+
node_file.write_text(
|
|
26
|
+
"""
|
|
27
|
+
from vellum.workflows.nodes import BaseNode
|
|
28
|
+
|
|
29
|
+
class TestNode(BaseNode):
|
|
30
|
+
\"""A test node for processing data.
|
|
31
|
+
|
|
32
|
+
This is a detailed description of what the node does.
|
|
33
|
+
\"""
|
|
34
|
+
label = "Test Node"
|
|
35
|
+
"""
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
flask_app = create_app()
|
|
39
|
+
|
|
40
|
+
# WHEN we make a request to the version route
|
|
41
|
+
with patch("os.getcwd", return_value=str(tmp_path)), flask_app.test_client() as test_client:
|
|
42
|
+
response = test_client.get("/workflow/version")
|
|
43
|
+
|
|
44
|
+
# THEN we should get a successful response
|
|
45
|
+
assert response.status_code == 200
|
|
46
|
+
|
|
47
|
+
# AND we should find exactly one node
|
|
48
|
+
nodes = response.json["nodes"]
|
|
49
|
+
assert len(nodes) == 1
|
|
50
|
+
|
|
51
|
+
# AND the node should have the correct metadata
|
|
52
|
+
node = nodes[0]
|
|
53
|
+
assert UUID(node["id"])
|
|
54
|
+
assert node["module"] == "vellum_custom_nodes"
|
|
55
|
+
assert node["name"] == "TestNode"
|
|
56
|
+
assert node["label"] == "Test Node"
|
|
57
|
+
assert "A test node for processing data." in node["description"]
|
|
58
|
+
assert "This is a detailed description" in node["description"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_version_route__with_nodes_in_multiple_files(tmp_path):
|
|
62
|
+
# GIVEN a temporary custom_nodes directory
|
|
63
|
+
custom_nodes_dir = tmp_path / "vellum_custom_nodes"
|
|
64
|
+
custom_nodes_dir.mkdir()
|
|
65
|
+
|
|
66
|
+
# AND a first node file
|
|
67
|
+
first_node_file = custom_nodes_dir / "first_node.py"
|
|
68
|
+
first_node_file.write_text(
|
|
69
|
+
"""
|
|
70
|
+
from vellum.workflows.nodes import BaseNode
|
|
71
|
+
|
|
72
|
+
class SomeNode(BaseNode):
|
|
73
|
+
\"""This is Some Node.\"""
|
|
74
|
+
label = "Some node"
|
|
75
|
+
"""
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# AND a second node file
|
|
79
|
+
second_node_file = custom_nodes_dir / "second_node.py"
|
|
80
|
+
second_node_file.write_text(
|
|
81
|
+
"""
|
|
82
|
+
from vellum.workflows.nodes import BaseNode
|
|
83
|
+
|
|
84
|
+
class SomeOtherNode(BaseNode):
|
|
85
|
+
\"""This is Some Other Node.\"""
|
|
86
|
+
label = "Some other node"
|
|
87
|
+
"""
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
flask_app = create_app()
|
|
91
|
+
|
|
92
|
+
# WHEN we make a request to the version route
|
|
93
|
+
with patch("os.getcwd", return_value=str(tmp_path)), flask_app.test_client() as test_client:
|
|
94
|
+
response = test_client.get("/workflow/version")
|
|
95
|
+
|
|
96
|
+
# THEN we should get a successful response
|
|
97
|
+
assert response.status_code == 200
|
|
98
|
+
|
|
99
|
+
# AND we should find both nodes
|
|
100
|
+
nodes = response.json["nodes"]
|
|
101
|
+
assert len(nodes) == 2
|
|
102
|
+
|
|
103
|
+
# AND the first node should have correct metadata
|
|
104
|
+
some_node = nodes[0]
|
|
105
|
+
assert some_node["label"] == "Some node"
|
|
106
|
+
assert some_node["description"] == "This is Some Node."
|
|
107
|
+
assert UUID(some_node["id"])
|
|
108
|
+
assert some_node["module"] == "vellum_custom_nodes"
|
|
109
|
+
|
|
110
|
+
# AND the second node should have correct metadata
|
|
111
|
+
some_other_node = nodes[1]
|
|
112
|
+
assert some_other_node["label"] == "Some other node"
|
|
113
|
+
assert some_other_node["description"] == "This is Some Other Node."
|
|
114
|
+
assert UUID(some_other_node["id"])
|
|
115
|
+
assert some_other_node["module"] == "vellum_custom_nodes"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_version_route__no_custom_nodes_dir(tmp_path):
|
|
119
|
+
# GIVEN a Flask application and an empty temp directory
|
|
120
|
+
flask_app = create_app()
|
|
121
|
+
|
|
122
|
+
# WHEN we make a request to the version route
|
|
123
|
+
with patch("os.getcwd", return_value=str(tmp_path)), flask_app.test_client() as test_client:
|
|
124
|
+
response = test_client.get("/workflow/version")
|
|
125
|
+
|
|
126
|
+
# THEN we should get a successful response
|
|
127
|
+
assert response.status_code == 200
|
|
128
|
+
|
|
129
|
+
# AND the nodes list should be empty
|
|
130
|
+
assert response.json["nodes"] == []
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_version_route__with_multiple_nodes_in_file(tmp_path):
|
|
134
|
+
# Create a temporary custom_nodes directory with multiple nodes in one file
|
|
135
|
+
custom_nodes_dir = tmp_path / "vellum_custom_nodes"
|
|
136
|
+
custom_nodes_dir.mkdir()
|
|
137
|
+
|
|
138
|
+
# Create a test node file with multiple nodes
|
|
139
|
+
node_file = custom_nodes_dir / "multiple_nodes.py"
|
|
140
|
+
node_file.write_text(
|
|
141
|
+
"""
|
|
142
|
+
from vellum.workflows.nodes import BaseNode
|
|
143
|
+
|
|
144
|
+
class ProcessingNode(BaseNode):
|
|
145
|
+
\"""Processes input data.\"""
|
|
146
|
+
label = "Processing Node"
|
|
147
|
+
|
|
148
|
+
class TransformationNode(BaseNode):
|
|
149
|
+
\"""Transforms data format.\"""
|
|
150
|
+
label = "Transformation Node"
|
|
151
|
+
|
|
152
|
+
# This class should not be discovered
|
|
153
|
+
class HelperClass:
|
|
154
|
+
pass
|
|
155
|
+
"""
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
flask_app = create_app()
|
|
159
|
+
|
|
160
|
+
# Mock the current working directory to point to our temp directory
|
|
161
|
+
with patch("os.getcwd", return_value=str(tmp_path)), flask_app.test_client() as test_client:
|
|
162
|
+
response = test_client.get("/workflow/version")
|
|
163
|
+
|
|
164
|
+
assert response.status_code == 200
|
|
165
|
+
nodes = response.json["nodes"]
|
|
166
|
+
assert len(nodes) == 2
|
|
167
|
+
|
|
168
|
+
# Nodes should be discovered regardless of their order in the file
|
|
169
|
+
node_names = {node["name"] for node in nodes}
|
|
170
|
+
assert node_names == {"ProcessingNode", "TransformationNode"}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_version_route__with_invalid_node_file(tmp_path, caplog):
|
|
174
|
+
caplog.set_level(logging.WARNING)
|
|
175
|
+
|
|
176
|
+
# GIVEN a temporary custom_nodes directory
|
|
177
|
+
custom_nodes_dir = tmp_path / "vellum_custom_nodes"
|
|
178
|
+
custom_nodes_dir.mkdir()
|
|
179
|
+
|
|
180
|
+
# AND a valid node file
|
|
181
|
+
valid_node_file = custom_nodes_dir / "valid_node.py"
|
|
182
|
+
valid_node_file.write_text(
|
|
183
|
+
"""
|
|
184
|
+
from vellum.workflows.nodes import BaseNode
|
|
185
|
+
|
|
186
|
+
class SomeNode(BaseNode):
|
|
187
|
+
\"\"\"This is Some Node.\"\"\"
|
|
188
|
+
label = "Some node"
|
|
189
|
+
"""
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# AND an invalid node file with syntax error of missing colon in the class
|
|
193
|
+
invalid_node_file = custom_nodes_dir / "invalid_node.py"
|
|
194
|
+
invalid_node_file.write_text(
|
|
195
|
+
"""
|
|
196
|
+
from vellum.workflows.nodes import BaseNode
|
|
197
|
+
|
|
198
|
+
class BrokenNode(BaseNode)
|
|
199
|
+
\"\"\"This node has a syntax error.\"\"\"
|
|
200
|
+
label = "Broken Node"
|
|
201
|
+
"""
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
flask_app = create_app()
|
|
205
|
+
|
|
206
|
+
# WHEN we make a request to the version route
|
|
207
|
+
with patch("os.getcwd", return_value=str(tmp_path)), flask_app.test_client() as test_client:
|
|
208
|
+
response = test_client.get("/workflow/version")
|
|
209
|
+
|
|
210
|
+
# THEN we should get a successful response
|
|
211
|
+
assert response.status_code == 200
|
|
212
|
+
|
|
213
|
+
# AND we should find only the valid node
|
|
214
|
+
nodes = response.json["nodes"]
|
|
215
|
+
assert len(nodes) == 1
|
|
216
|
+
|
|
217
|
+
# AND the valid node should have correct metadata
|
|
218
|
+
valid_node = nodes[0]
|
|
219
|
+
assert valid_node["label"] == "Some node"
|
|
220
|
+
assert valid_node["description"] == "This is Some Node."
|
|
221
|
+
assert UUID(valid_node["id"])
|
|
222
|
+
assert valid_node["module"] == "vellum_custom_nodes"
|
|
223
|
+
|
|
224
|
+
# AND the error should be logged with full traceback
|
|
225
|
+
assert len(caplog.records) > 0
|
|
226
|
+
error_message = caplog.records[0].message
|
|
227
|
+
assert "Failed to load node from module invalid_node" in error_message
|
|
228
|
+
assert "invalid_node.py, line 4" in error_message
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
import importlib
|
|
3
|
+
import inspect
|
|
2
4
|
import json
|
|
3
5
|
import logging
|
|
4
6
|
from multiprocessing import Queue, set_start_method
|
|
7
|
+
import os
|
|
8
|
+
import pkgutil
|
|
5
9
|
from queue import Empty
|
|
10
|
+
import sys
|
|
6
11
|
import time
|
|
7
12
|
import traceback
|
|
8
13
|
from uuid import uuid4
|
|
@@ -11,6 +16,7 @@ from typing import Generator, Iterator, Union
|
|
|
11
16
|
from flask import Blueprint, Response, current_app as app, request, stream_with_context
|
|
12
17
|
from pydantic import ValidationError
|
|
13
18
|
|
|
19
|
+
from vellum.workflows.nodes import BaseNode
|
|
14
20
|
from workflow_server.config import MEMORY_LIMIT_MB
|
|
15
21
|
from workflow_server.core.events import (
|
|
16
22
|
STREAM_FINISHED_EVENT,
|
|
@@ -35,6 +41,8 @@ set_start_method("fork", force=True)
|
|
|
35
41
|
|
|
36
42
|
logger = logging.getLogger(__name__)
|
|
37
43
|
|
|
44
|
+
CUSTOM_NODES_DIRECTORY = "vellum_custom_nodes"
|
|
45
|
+
|
|
38
46
|
|
|
39
47
|
@bp.route("/stream", methods=["POST"])
|
|
40
48
|
def stream_workflow_route() -> Response:
|
|
@@ -322,6 +330,40 @@ def stream_node_route() -> Response:
|
|
|
322
330
|
def get_version_route() -> tuple[dict, int]:
|
|
323
331
|
resp = get_version()
|
|
324
332
|
|
|
333
|
+
try:
|
|
334
|
+
# Discover nodes in the container
|
|
335
|
+
nodes = []
|
|
336
|
+
|
|
337
|
+
# Look for custom_nodes directory in the container
|
|
338
|
+
custom_nodes_path = os.path.join(os.getcwd(), CUSTOM_NODES_DIRECTORY)
|
|
339
|
+
if os.path.exists(custom_nodes_path):
|
|
340
|
+
# Add the custom_nodes directory to Python path so we can import from it
|
|
341
|
+
sys.path.append(os.path.dirname(custom_nodes_path))
|
|
342
|
+
|
|
343
|
+
# Import all Python files in the custom_nodes directory
|
|
344
|
+
for _, name, _ in pkgutil.iter_modules([custom_nodes_path]):
|
|
345
|
+
try:
|
|
346
|
+
module = importlib.import_module(f"{CUSTOM_NODES_DIRECTORY}.{name}")
|
|
347
|
+
for _, obj in inspect.getmembers(module):
|
|
348
|
+
# Look for classes that inherit from BaseNode
|
|
349
|
+
if inspect.isclass(obj) and obj != BaseNode and issubclass(obj, BaseNode):
|
|
350
|
+
nodes.append(
|
|
351
|
+
{
|
|
352
|
+
"id": str(uuid4()),
|
|
353
|
+
"module": CUSTOM_NODES_DIRECTORY,
|
|
354
|
+
"name": obj.__name__,
|
|
355
|
+
"label": obj().label if hasattr(obj, "label") else obj.__name__, # type: ignore
|
|
356
|
+
"description": inspect.getdoc(obj) or "",
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.warning(f"Failed to load node from module {name}: {str(e)}", exc_info=True)
|
|
361
|
+
|
|
362
|
+
resp["nodes"] = nodes
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.exception(f"Failed to discover nodes: {str(e)}")
|
|
365
|
+
resp["nodes"] = []
|
|
366
|
+
|
|
325
367
|
return resp, 200
|
|
326
368
|
|
|
327
369
|
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from workflow_server.server import create_app
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_version_route():
|
|
7
|
-
flask_app = create_app()
|
|
8
|
-
|
|
9
|
-
with flask_app.test_client() as test_client:
|
|
10
|
-
response = test_client.get("/workflow/version")
|
|
11
|
-
assert response.status_code == 200
|
|
12
|
-
assert re.match(r"[0-9]*\.[0-9]*\.[0-9]*", response.json["sdk_version"])
|
|
13
|
-
assert response.json["server_version"] == "local"
|
|
File without changes
|
{vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/core/events.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/server.py
RENAMED
|
File without changes
|
{vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/start.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vellum_workflow_server-0.14.68 → vellum_workflow_server-0.14.69}/src/workflow_server/utils/utils.py
RENAMED
|
File without changes
|