vellum-workflow-server 1.3.8__py3-none-any.whl → 1.3.9.post2__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-workflow-server
3
- Version: 1.3.8
3
+ Version: 1.3.9.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.3.8)
32
+ Requires-Dist: vellum-ai (==1.3.9)
33
33
  Description-Content-Type: text/markdown
34
34
 
35
35
  # Vellum Workflow Runner Server
@@ -4,19 +4,19 @@ workflow_server/api/auth_middleware.py,sha256=IlZaCiwZ5nwQqk5sYQorvOFj7lt0p1ZSSE
4
4
  workflow_server/api/healthz_view.py,sha256=itiRvBDBXncrw8Kbbc73UZLwqMAhgHOR3uSre_dAfgY,404
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
- workflow_server/api/tests/test_workflow_view.py,sha256=gwb53E44LBm06XHY1UwfHqKG-UfwwICh_IYPtowN_kE,19085
7
+ workflow_server/api/tests/test_workflow_view.py,sha256=RlAw1tHeIlnOXGrFQN-w3EOLPZkhp6Dfy6d1r7kU5oc,22573
8
8
  workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=Qo8u6mPyRCmE2jamY1yIh8l44hgo4-Nwlq03z61ND5g,27031
9
- workflow_server/api/workflow_view.py,sha256=gSKqlMR2r2urSBYm_jFlcpXHOk5jQsDMSDwpSahp5A8,21144
9
+ workflow_server/api/workflow_view.py,sha256=Ufc49WJ4LON3FFxzY2MeP4q7uQyorMNMu9zLRuJvdp4,21523
10
10
  workflow_server/code_exec_runner.py,sha256=lBnMIorPZL8zZBye6TjeCIs06WTJM7P2HR07B1fjJJI,2533
11
11
  workflow_server/config.py,sha256=DyTty8NrAwvtx-esM3KthnpsNh-nKdWNlovWQOgiGpg,1417
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=TTpMifrIZuzXxOUtX3E3CgLOb8eRXul2aGPpRCfWIco,18368
16
- workflow_server/core/utils.py,sha256=cmwHbKCfXqtUutBD3akGus0Ga7a1xG3zlOw-jEMx6mI,1795
15
+ workflow_server/core/executor.py,sha256=qAAQcwN0BfNL--IVZAn9jWr_KeOYGqB8sPP7dm8cFQk,17548
16
+ workflow_server/core/utils.py,sha256=aIpSINstLGslP2PIoDLM82_1GlJ1uC_0AIrP-V7Yobo,3230
17
17
  workflow_server/core/workflow_executor_context.py,sha256=w3OhV_AXpgh7AxpjEsc0vo-IJypgJcgr5DXJCqGptOU,1587
18
18
  workflow_server/server.py,sha256=QBU12AaAfAgLqfCDBd24qIJl_mbheiq0-hfcWV7rZM4,1234
19
- workflow_server/start.py,sha256=qpIg0SgIgz8RNyc8Cu9LxyzXdOXZRv9qq3M3uSBbgD0,2180
19
+ workflow_server/start.py,sha256=pkwRcms6I4tkVHP06LdrZY6rG_DFHfBx4ioY5X91W5k,2264
20
20
  workflow_server/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  workflow_server/utils/exit_handler.py,sha256=_FacDVi4zc3bfTA3D2mJsISePlJ8jpLrnGVo5-xZQFs,743
22
22
  workflow_server/utils/log_proxy.py,sha256=nugi6fOgAYKX2X9DIc39TG366rsmmDUPoEtG3gzma_Y,3088
@@ -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=qwK5Rmy3RQyjtlUrYAuGuDlBeRzZKsf1yS-y2IpUizQ,6452
30
30
  workflow_server/utils/utils.py,sha256=zcjnY6tSPf90VZOBm8w0NfxYWPQYAL9TCNlza231Odg,4535
31
- vellum_workflow_server-1.3.8.dist-info/METADATA,sha256=XpStc7y5oUXAvWcPnz60GZCzjgXjbMITkuIUfFhfdSM,2267
32
- vellum_workflow_server-1.3.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
33
- vellum_workflow_server-1.3.8.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
34
- vellum_workflow_server-1.3.8.dist-info/RECORD,,
31
+ vellum_workflow_server-1.3.9.post2.dist-info/METADATA,sha256=PsYb7bP17M8X4L_d4-3XTGOeL4vQQHU8CLGJ7hqF2uI,2273
32
+ vellum_workflow_server-1.3.9.post2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
33
+ vellum_workflow_server-1.3.9.post2.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
34
+ vellum_workflow_server-1.3.9.post2.dist-info/RECORD,,
@@ -392,6 +392,7 @@ class MyAdditionNode(BaseNode):
392
392
  "ports": [{"id": "9a9e4ef6-febf-4093-a515-217bbb1373db", "name": "default", "type": "DEFAULT"}],
393
393
  "trigger": {"id": "a5298668-d808-4a45-a62e-790943948e8a", "merge_behavior": "AWAIT_ATTRIBUTES"},
394
394
  "type": "GENERIC",
395
+ "should_file_merge": True,
395
396
  }
396
397
 
397
398
 
@@ -472,3 +473,64 @@ def test_serialize_route__with__workflow():
472
473
 
473
474
  # AND at least some of the expected nodes should be present
474
475
  assert not DeepDiff(node_labels, expected_nodes, ignore_order=True)
476
+
477
+
478
+ def test_serialize_route__with_workspace_api_key():
479
+ """
480
+ Tests that the serialize route accepts workspace_api_key and passes it through serialization.
481
+ """
482
+ # GIVEN a Flask application
483
+ flask_app = create_app()
484
+
485
+ # AND a complete workflow with multiple files
486
+ workflow_files = {
487
+ "__init__.py": "# flake8: noqa: F401, F403\n\n",
488
+ "inputs.py": "from typing import Any, Optional\n\nfrom vellum.workflows.inputs import BaseInputs\n\n\nclass Inputs(BaseInputs):\n text: str\n var_1: Optional[Any]\n", # noqa: E501
489
+ "workflow.py": "from vellum.workflows import BaseWorkflow\nfrom vellum.workflows.state import BaseState\n\nfrom .inputs import Inputs\nfrom .nodes.final_output import FinalOutput\nfrom .nodes.templating_node import TemplatingNode\n\n\nclass Workflow(BaseWorkflow[Inputs, BaseState]):\n graph = TemplatingNode >> FinalOutput\n\n class Outputs(BaseWorkflow.Outputs):\n final_output = FinalOutput.Outputs.value\n", # noqa: E501
490
+ "nodes/__init__.py": 'from .final_output import FinalOutput\nfrom .templating_node import TemplatingNode\n\n__all__ = [\n "FinalOutput",\n "TemplatingNode",\n]\n', # noqa: E501
491
+ "nodes/templating_node.py": 'from vellum.workflows.nodes.displayable import TemplatingNode as BaseTemplatingNode\nfrom vellum.workflows.state import BaseState\n\nfrom ..inputs import Inputs\n\n\nclass TemplatingNode(BaseTemplatingNode[BaseState, str]):\n template = """{{ text }}"""\n inputs = {\n "text": Inputs.text,\n }\n', # noqa: E501
492
+ "nodes/final_output.py": "from vellum.workflows.nodes.displayable import FinalOutputNode\nfrom vellum.workflows.state import BaseState\n\nfrom .templating_node import TemplatingNode\n\n\nclass FinalOutput(FinalOutputNode[BaseState, str]):\n class Outputs(FinalOutputNode.Outputs):\n value = TemplatingNode.Outputs.result\n", # noqa: E501
493
+ }
494
+
495
+ # WHEN we make a request to the serialize route with workspace_api_key
496
+ with flask_app.test_client() as test_client:
497
+ response = test_client.post(
498
+ "/workflow/serialize", json={"files": workflow_files, "workspace_api_key": "test_workspace_key"}
499
+ )
500
+
501
+ # THEN we should get a successful response
502
+ assert response.status_code == 200
503
+
504
+ # AND the response should contain the full WorkflowSerializationResult
505
+ assert "exec_config" in response.json
506
+ assert "errors" in response.json
507
+
508
+
509
+ def test_serialize_route__with_invalid_workspace_api_key():
510
+ """
511
+ Tests that the serialize route handles invalid workspace_api_key gracefully.
512
+ """
513
+ # GIVEN a Flask application
514
+ flask_app = create_app()
515
+
516
+ workflow_files = {
517
+ "__init__.py": "",
518
+ "workflow.py": (
519
+ "from vellum.workflows import BaseWorkflow\n\n"
520
+ "class Workflow(BaseWorkflow):\n"
521
+ " class Outputs(BaseWorkflow.Outputs):\n"
522
+ " foo = 'hello'\n"
523
+ ),
524
+ }
525
+
526
+ # WHEN we make a request with an invalid workspace_api_key
527
+ with flask_app.test_client() as test_client:
528
+ response = test_client.post(
529
+ "/workflow/serialize", json={"files": workflow_files, "workspace_api_key": ""} # Invalid empty key
530
+ )
531
+
532
+ # THEN we should still get a successful response (graceful degradation)
533
+ assert response.status_code == 200
534
+
535
+ # AND the response should contain the serialization result
536
+ assert "exec_config" in response.json
@@ -37,6 +37,7 @@ from workflow_server.core.events import (
37
37
  )
38
38
  from workflow_server.core.executor import stream_node_pebble_timeout, stream_workflow, stream_workflow_process_timeout
39
39
  from workflow_server.core.utils import (
40
+ create_vellum_client,
40
41
  create_vembda_rejected_event,
41
42
  is_events_emitting_enabled,
42
43
  serialize_vembda_rejected_event,
@@ -55,11 +56,10 @@ from workflow_server.utils.system_utils import (
55
56
  from workflow_server.utils.utils import convert_json_inputs_to_vellum, get_version
56
57
 
57
58
  bp = Blueprint("exec", __name__)
59
+ logger = logging.getLogger(__name__)
58
60
 
59
61
  set_start_method("fork", force=True)
60
62
 
61
- logger = logging.getLogger(__name__)
62
-
63
63
  CUSTOM_NODES_DIRECTORY = "vellum_custom_nodes"
64
64
  WORKFLOW_INITIATION_TIMEOUT_SECONDS = 60
65
65
 
@@ -425,6 +425,7 @@ def serialize_route() -> Response:
425
425
  data = request.get_json()
426
426
 
427
427
  files = data.get("files", {})
428
+ workspace_api_key = data.get("workspace_api_key")
428
429
 
429
430
  if not files:
430
431
  return Response(
@@ -434,6 +435,13 @@ def serialize_route() -> Response:
434
435
  )
435
436
 
436
437
  try:
438
+ client = None
439
+ if workspace_api_key:
440
+ try:
441
+ client = create_vellum_client(api_key=workspace_api_key)
442
+ except Exception as e:
443
+ logger.warning(f"Failed to create VellumClient with provided workspace_api_key: {str(e)}")
444
+
437
445
  # Generate a unique namespace for this serialization request
438
446
  namespace = "".join(random.choice(string.ascii_letters + string.digits) for i in range(14))
439
447
  virtual_finder = VirtualFileFinder(files, namespace)
@@ -441,7 +449,7 @@ def serialize_route() -> Response:
441
449
  try:
442
450
  sys.meta_path.append(virtual_finder)
443
451
  try:
444
- result = BaseWorkflowDisplay.serialize_module(namespace)
452
+ result = BaseWorkflowDisplay.serialize_module(namespace, client=client)
445
453
  except Exception as e:
446
454
  raise WorkflowInitializationException(str(e)) from e
447
455
 
@@ -20,7 +20,6 @@ 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, VellumEnvironment
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
@@ -36,7 +35,6 @@ from vellum.workflows.state.base import BaseState, StateMeta
36
35
  from vellum.workflows.state.context import WorkflowContext
37
36
  from vellum.workflows.state.store import EmptyStore
38
37
  from vellum.workflows.workflows.event_filters import all_workflow_event_filter
39
- from workflow_server.config import IS_VPC, VELLUM_API_URL_HOST, VELLUM_API_URL_PORT
40
38
  from workflow_server.core.cancel_workflow import CancelWorkflowWatcherThread
41
39
  from workflow_server.core.events import (
42
40
  SPAN_ID_EVENT,
@@ -44,7 +42,7 @@ from workflow_server.core.events import (
44
42
  VembdaExecutionFulfilledBody,
45
43
  VembdaExecutionFulfilledEvent,
46
44
  )
47
- from workflow_server.core.utils import is_events_emitting_enabled, serialize_vembda_rejected_event
45
+ from workflow_server.core.utils import create_vellum_client, is_events_emitting_enabled, serialize_vembda_rejected_event
48
46
  from workflow_server.core.workflow_executor_context import (
49
47
  DEFAULT_TIMEOUT_SECONDS,
50
48
  BaseExecutorContext,
@@ -374,21 +372,10 @@ def _create_workflow(executor_context: WorkflowExecutorContext, namespace: str)
374
372
 
375
373
 
376
374
  def _create_workflow_context(executor_context: BaseExecutorContext) -> WorkflowContext:
377
- if IS_VPC:
378
- environment = VellumEnvironment(
379
- default=os.getenv("VELLUM_DEFAULT_API_URL", VellumEnvironment.PRODUCTION.default),
380
- documents=os.getenv("VELLUM_DOCUMENTS_API_URL", VellumEnvironment.PRODUCTION.documents),
381
- predict=os.getenv("VELLUM_PREDICT_API_URL", VellumEnvironment.PRODUCTION.predict),
382
- )
383
- elif os.getenv("USE_LOCAL_VELLUM_API") == "true":
384
- VELLUM_API_URL = f"http://{VELLUM_API_URL_HOST}:{VELLUM_API_URL_PORT}"
385
- environment = VellumEnvironment(
386
- default=VELLUM_API_URL,
387
- documents=VELLUM_API_URL,
388
- predict=VELLUM_API_URL,
389
- )
390
- else:
391
- environment = VellumEnvironment.PRODUCTION
375
+ vellum_client = create_vellum_client(
376
+ api_key=executor_context.environment_api_key,
377
+ api_version=executor_context.api_version,
378
+ )
392
379
 
393
380
  if executor_context.environment_variables:
394
381
  os.environ.update(executor_context.environment_variables)
@@ -396,11 +383,7 @@ def _create_workflow_context(executor_context: BaseExecutorContext) -> WorkflowC
396
383
  namespace = _get_file_namespace(executor_context)
397
384
 
398
385
  return WorkflowContext(
399
- vellum_client=Vellum(
400
- api_key=executor_context.environment_api_key,
401
- environment=environment,
402
- api_version=executor_context.api_version,
403
- ),
386
+ vellum_client=vellum_client,
404
387
  execution_context=executor_context.execution_context,
405
388
  generated_files=executor_context.files,
406
389
  namespace=namespace,
@@ -1,7 +1,10 @@
1
1
  from datetime import datetime
2
+ import os
2
3
  from uuid import uuid4
3
4
  from typing import Optional
4
5
 
6
+ from vellum import ApiVersionEnum, Vellum, VellumEnvironment
7
+ from workflow_server.config import IS_VPC, VELLUM_API_URL_HOST, VELLUM_API_URL_PORT
5
8
  from workflow_server.core.events import VembdaExecutionFulfilledBody, VembdaExecutionFulfilledEvent
6
9
  from workflow_server.core.workflow_executor_context import BaseExecutorContext
7
10
 
@@ -48,3 +51,42 @@ def is_events_emitting_enabled(executor_context: Optional[BaseExecutorContext])
48
51
  return False
49
52
 
50
53
  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
+ )
workflow_server/start.py CHANGED
@@ -49,8 +49,9 @@ def start() -> None:
49
49
  "bind": f"0.0.0.0:{PORT}",
50
50
  "workers": int(os.getenv("GUNICORN_WORKERS", 2)),
51
51
  "threads": int(os.getenv("GUNICORN_THREADS", 9 if ENABLE_PROCESS_WRAPPER else 6)),
52
- # Try to avoid memory leaks when using non process mode
53
- "max_requests": 0 if ENABLE_PROCESS_WRAPPER else 20,
52
+ # Aggressively try to avoid memory leaks when using non process mode
53
+ "max_requests": 120 if ENABLE_PROCESS_WRAPPER else 20,
54
+ "max_requests_jitter": 30 if ENABLE_PROCESS_WRAPPER else 10,
54
55
  "worker_class": "gthread",
55
56
  "timeout": int(os.getenv("GUNICORN_TIMEOUT", 1800)),
56
57
  "logger_class": CustomGunicornLogger,