vellum-workflow-server 1.7.4.post1__tar.gz → 1.9.1.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.
Files changed (36) hide show
  1. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/PKG-INFO +2 -2
  2. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/pyproject.toml +2 -2
  3. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/auth_middleware.py +2 -2
  4. vellum_workflow_server-1.9.1.post2/src/workflow_server/api/status_view.py +19 -0
  5. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/tests/test_workflow_view.py +53 -2
  6. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +53 -9
  7. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/workflow_view.py +214 -86
  8. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/config.py +8 -0
  9. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/core/cancel_workflow.py +1 -1
  10. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/core/executor.py +79 -92
  11. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/core/utils.py +1 -5
  12. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/core/workflow_executor_context.py +7 -41
  13. vellum_workflow_server-1.9.1.post2/src/workflow_server/logging_config.py +39 -0
  14. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/server.py +22 -5
  15. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/start.py +13 -1
  16. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/sentry.py +21 -0
  17. vellum_workflow_server-1.9.1.post2/src/workflow_server/utils/tests/test_sentry_integration.py +143 -0
  18. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/utils.py +2 -1
  19. vellum_workflow_server-1.7.4.post1/src/workflow_server/utils/tests/test_sentry_integration.py +0 -69
  20. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/README.md +0 -0
  21. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/__init__.py +0 -0
  22. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/__init__.py +0 -0
  23. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/healthz_view.py +0 -0
  24. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/tests/__init__.py +0 -0
  25. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/api/tests/test_input_display_mapping.py +0 -0
  26. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/code_exec_runner.py +0 -0
  27. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/core/__init__.py +0 -0
  28. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/core/events.py +0 -0
  29. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/__init__.py +0 -0
  30. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/exit_handler.py +0 -0
  31. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/log_proxy.py +0 -0
  32. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/oom_killer.py +0 -0
  33. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/system_utils.py +0 -0
  34. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/tests/__init__.py +0 -0
  35. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/src/workflow_server/utils/tests/test_system_utils.py +0 -0
  36. {vellum_workflow_server-1.7.4.post1 → vellum_workflow_server-1.9.1.post2}/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.7.4.post1
3
+ Version: 1.9.1.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.7.4)
32
+ Requires-Dist: vellum-ai (==1.9.1)
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.7.4.post1"
6
+ version = "1.9.1.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.7.4"
48
+ vellum-ai = "1.9.1"
49
49
  python-dotenv = "1.0.1"
50
50
  retrying = "1.3.4"
51
51
  sentry-sdk = {extras = ["flask"], version = "2.20.0"}
@@ -5,7 +5,7 @@ from flask import Flask, Request, Response
5
5
  import jwt
6
6
  from jwt import ExpiredSignatureError
7
7
 
8
- from workflow_server.config import IS_VPC, NAMESPACE, VEMBDA_PUBLIC_KEY, is_development
8
+ from workflow_server.config import IS_ASYNC_MODE, IS_VPC, NAMESPACE, VEMBDA_PUBLIC_KEY, is_development
9
9
 
10
10
 
11
11
  class AuthMiddleware:
@@ -15,7 +15,7 @@ class AuthMiddleware:
15
15
  def __call__(self, environ: Dict[str, Any], start_response: Any) -> Any:
16
16
  try:
17
17
  request = Request(environ)
18
- if not request.path.startswith("/healthz") and not is_development() and not IS_VPC:
18
+ if not request.path.startswith("/healthz") and not is_development() and not IS_VPC and not IS_ASYNC_MODE:
19
19
  token = request.headers.get("X-Vembda-Signature")
20
20
  if token:
21
21
  decoded = jwt.decode(token, VEMBDA_PUBLIC_KEY, algorithms=["RS256"])
@@ -0,0 +1,19 @@
1
+ from typing import Tuple
2
+
3
+ from flask import Blueprint, Response, jsonify
4
+
5
+ from workflow_server.config import CONCURRENCY
6
+ from workflow_server.utils.system_utils import get_active_process_count
7
+
8
+ bp = Blueprint("status", __name__)
9
+
10
+
11
+ @bp.route("/is_available", methods=["GET"])
12
+ def is_available() -> Tuple[Response, int]:
13
+ resp = jsonify(
14
+ available=get_active_process_count() < CONCURRENCY,
15
+ process_count=get_active_process_count(),
16
+ max_concurrency=CONCURRENCY,
17
+ )
18
+
19
+ return resp, 200
@@ -373,12 +373,12 @@ class MyAdditionNode(BaseNode):
373
373
  {
374
374
  "id": "aed3bcbb-d243-4a77-bb5e-409e9a28e868",
375
375
  "name": "arg1",
376
- "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
376
+ "value": None,
377
377
  },
378
378
  {
379
379
  "id": "9225d225-a41b-4642-8964-f28f58dcf4bf",
380
380
  "name": "arg2",
381
- "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
381
+ "value": None,
382
382
  },
383
383
  ],
384
384
  "base": {"module": ["vellum", "workflows", "nodes", "bases", "base"], "name": "BaseNode"},
@@ -537,6 +537,57 @@ def test_serialize_route__with_invalid_workspace_api_key():
537
537
  assert "exec_config" in response.json
538
538
 
539
539
 
540
+ def test_serialize_route__with_is_new_server_header():
541
+ """
542
+ Tests that the serialize route returns the is_new_server header.
543
+ """
544
+ # GIVEN a Flask application
545
+ flask_app = create_app()
546
+
547
+ workflow_files = {
548
+ "__init__.py": "",
549
+ "workflow.py": (
550
+ "from vellum.workflows import BaseWorkflow\n\n"
551
+ "class Workflow(BaseWorkflow):\n"
552
+ " class Outputs(BaseWorkflow.Outputs):\n"
553
+ " foo = 'hello'\n"
554
+ ),
555
+ }
556
+
557
+ # WHEN we make a request with is_new_server=True
558
+ with flask_app.test_client() as test_client:
559
+ response = test_client.post("/workflow/serialize", json={"files": workflow_files, "is_new_server": True})
560
+
561
+ # THEN we should get a successful response
562
+ assert response.status_code == 200
563
+
564
+ # AND the response should contain the is_new_server header set to true
565
+ assert "X-Vellum-Is-New-Server" in response.headers
566
+ assert response.headers["X-Vellum-Is-New-Server"] == "true"
567
+
568
+ # WHEN we make a request with is_new_server=False
569
+ with flask_app.test_client() as test_client:
570
+ response = test_client.post("/workflow/serialize", json={"files": workflow_files, "is_new_server": False})
571
+
572
+ # THEN we should get a successful response
573
+ assert response.status_code == 200
574
+
575
+ # AND the response should contain the is_new_server header set to false
576
+ assert "X-Vellum-Is-New-Server" in response.headers
577
+ assert response.headers["X-Vellum-Is-New-Server"] == "false"
578
+
579
+ # WHEN we make a request without is_new_server
580
+ with flask_app.test_client() as test_client:
581
+ response = test_client.post("/workflow/serialize", json={"files": workflow_files})
582
+
583
+ # THEN we should get a successful response
584
+ assert response.status_code == 200
585
+
586
+ # AND the response should contain the is_new_server header set to false (default)
587
+ assert "X-Vellum-Is-New-Server" in response.headers
588
+ assert response.headers["X-Vellum-Is-New-Server"] == "false"
589
+
590
+
540
591
  def test_stream_node_route__with_node_id():
541
592
  """
542
593
  Tests that the stream-node endpoint works with node_id.
@@ -117,7 +117,7 @@ def test_stream_workflow_route__happy_path(both_stream_types):
117
117
  "execution_id": str(span_id),
118
118
  "inputs": [],
119
119
  "environment_api_key": "test",
120
- "module": "workflow",
120
+ "module": "test",
121
121
  "timeout": 360,
122
122
  "files": {
123
123
  "__init__.py": "",
@@ -131,8 +131,9 @@ class Workflow(BaseWorkflow):
131
131
  },
132
132
  }
133
133
 
134
- # WHEN we call the stream route
135
- status_code, events = both_stream_types(request_body)
134
+ with mock.patch("builtins.open", mock.mock_open(read_data="104857600")):
135
+ # WHEN we call the stream route
136
+ status_code, events = both_stream_types(request_body)
136
137
 
137
138
  # THEN we get a 200 response
138
139
  assert status_code == 200, events
@@ -154,6 +155,7 @@ class Workflow(BaseWorkflow):
154
155
  }
155
156
 
156
157
  assert events[1]["name"] == "workflow.execution.initiated", events[1]
158
+ assert events[1]["body"]["workflow_definition"]["module"] == ["test", "workflow"]
157
159
  assert "display_context" in events[1]["body"], events[1]["body"]
158
160
  display_context = events[1]["body"]["display_context"]
159
161
  assert "node_displays" in display_context
@@ -163,7 +165,27 @@ class Workflow(BaseWorkflow):
163
165
  assert isinstance(display_context["workflow_inputs"], dict)
164
166
  assert isinstance(display_context["workflow_outputs"], dict)
165
167
  assert "foo" in display_context["workflow_outputs"]
168
+
169
+ # AND the initiated event should have server_metadata with version info and memory usage
170
+ assert "server_metadata" in events[1]["body"], events[1]["body"]
171
+ server_metadata = events[1]["body"]["server_metadata"]
172
+ assert server_metadata is not None, "server_metadata should not be None"
173
+ assert "server_version" in server_metadata
174
+ assert "sdk_version" in server_metadata
175
+ assert "memory_usage_mb" in server_metadata
176
+ assert isinstance(server_metadata["memory_usage_mb"], (int, float))
177
+ assert "is_new_server" in server_metadata
178
+ assert server_metadata["is_new_server"] is False
179
+
166
180
  assert events[2]["name"] == "workflow.execution.fulfilled", events[2]
181
+ assert events[2]["body"]["workflow_definition"]["module"] == ["test", "workflow"]
182
+
183
+ # AND the fulfilled event should have server_metadata with memory usage
184
+ assert "server_metadata" in events[2]["body"], events[2]["body"]
185
+ fulfilled_metadata = events[2]["body"]["server_metadata"]
186
+ assert fulfilled_metadata is not None, "fulfilled server_metadata should not be None"
187
+ assert "memory_usage_mb" in fulfilled_metadata
188
+ assert isinstance(fulfilled_metadata["memory_usage_mb"], (int, float))
167
189
 
168
190
  assert events[3] == {
169
191
  "id": mock.ANY,
@@ -364,9 +386,15 @@ class State(BaseState):
364
386
  def test_stream_workflow_route__bad_indent_in_inputs_file(both_stream_types):
365
387
  # GIVEN a valid request body
366
388
  span_id = uuid4()
389
+ trace_id = uuid4()
390
+ parent_span_id = uuid4()
367
391
  request_body = {
368
392
  "timeout": 360,
369
393
  "execution_id": str(span_id),
394
+ "execution_context": {
395
+ "trace_id": str(trace_id),
396
+ "parent_context": {"span_id": str(parent_span_id)},
397
+ },
370
398
  "inputs": [
371
399
  {"name": "foo", "type": "STRING", "value": "hello"},
372
400
  ],
@@ -403,7 +431,7 @@ from vellum.workflows.inputs import BaseInputs
403
431
 
404
432
  assert events[0] == {
405
433
  "id": mock.ANY,
406
- "trace_id": mock.ANY,
434
+ "trace_id": str(trace_id),
407
435
  "span_id": str(span_id),
408
436
  "timestamp": mock.ANY,
409
437
  "api_version": "2024-10-25",
@@ -417,12 +445,22 @@ from vellum.workflows.inputs import BaseInputs
417
445
  }
418
446
 
419
447
  assert events[1]["name"] == "workflow.execution.initiated"
448
+ assert events[1]["trace_id"] == str(trace_id), "workflow initiated event should use request trace_id"
449
+ assert events[1]["parent"] is not None, "workflow initiated event should have parent context"
450
+ assert events[1]["parent"]["span_id"] == str(
451
+ parent_span_id
452
+ ), "workflow initiated event parent should match request parent_context"
420
453
 
421
454
  assert events[2]["name"] == "workflow.execution.rejected"
455
+ assert events[2]["trace_id"] == str(trace_id), "workflow rejected event should use request trace_id"
422
456
  assert events[2]["span_id"] == events[1]["span_id"]
457
+ assert events[2]["parent"] is not None, "workflow rejected event should have parent context"
458
+ assert events[2]["parent"]["span_id"] == str(
459
+ parent_span_id
460
+ ), "workflow rejected event parent should match request parent_context"
423
461
  assert (
424
- "Failed to initialize workflow: Failed to load workflow module: unexpected indent (inputs.py, line 3)"
425
- in events[2]["body"]["error"]["message"]
462
+ "Syntax Error raised while loading Workflow: "
463
+ "unexpected indent (inputs.py, line 3)" in events[2]["body"]["error"]["message"]
426
464
  )
427
465
 
428
466
  assert events[3] == {
@@ -645,10 +683,16 @@ class TimeoutWorkflow(BaseWorkflow):
645
683
  assert "workflow.execution.initiated" in event_names
646
684
  assert "node.execution.initiated" in event_names
647
685
 
648
- # TODO: Re-enable once solved SDK-side
649
- # assert "node.execution.rejected" in event_names, "Should emit node.execution.rejected on timeout"
686
+ assert "node.execution.rejected" in event_names, "Should emit node.execution.rejected on timeout"
687
+ node_execution_rejected = next(e for e in events if e["name"] == "node.execution.rejected")
688
+ assert "vellum/workflows/runner/runner.py" in node_execution_rejected["body"]["stacktrace"]
650
689
 
651
690
  assert "workflow.execution.rejected" in event_names, "Should emit workflow.execution.rejected on timeout"
691
+ workflow_execution_rejected = next(e for e in events if e["name"] == "workflow.execution.rejected")
692
+ assert workflow_execution_rejected["body"]["error"]["code"] == "WORKFLOW_TIMEOUT"
693
+ # TODO: Uncomment once version 1.8.1 is released
694
+ # assert "stacktrace" in workflow_execution_rejected["body"]
695
+ # assert "vellum/workflows/runner/runner.py" in workflow_execution_rejected["body"]["stacktrace"]
652
696
 
653
697
  assert "vembda.execution.fulfilled" in event_names
654
698
  vembda_fulfilled = next(e for e in events if e["name"] == "vembda.execution.fulfilled")
@@ -1175,7 +1219,7 @@ class InvalidWorkflow(BaseWorkflow):
1175
1219
  # AND the error message should contain information about the invalid graph structure
1176
1220
  error_message = events[2]["body"]["error"]["message"]
1177
1221
  expected_message = (
1178
- "Failed to initialize workflow: Invalid graph structure detected. "
1222
+ "Invalid graph structure detected. "
1179
1223
  "Nested sets or unsupported graph types are not allowed. "
1180
1224
  "Please contact Vellum support for assistance with Workflow configuration."
1181
1225
  )