vellum-workflow-server 0.14.69__py3-none-any.whl → 0.14.70.post1__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: 0.14.69
3
+ Version: 0.14.70.post1
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.69)
31
+ Requires-Dist: vellum-ai (==0.14.70)
32
32
  Description-Content-Type: text/markdown
33
33
 
34
34
  # Vellum Workflow Runner Server
@@ -3,16 +3,17 @@ workflow_server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
3
3
  workflow_server/api/auth_middleware.py,sha256=IlZaCiwZ5nwQqk5sYQorvOFj7lt0p1ZSSEqUxfiFaW0,2458
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
+ workflow_server/api/tests/test_input_display_mapping.py,sha256=drBZqMudFyB5wgiUOcMgRXz7E7ge-Qgxbstw4E4f0zE,2211
6
7
  workflow_server/api/tests/test_workflow_view.py,sha256=wlVFBmKcoI-RdzfGPioeW46k6zaXyUeIerPc6m4aQls,7150
7
- workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=wWW4sukIUmHnL525MLSAB2E5P6CeASnhsCoKQXEgPcI,21297
8
+ workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=5k2caHDaR2pBk-o8JPffnO38VLMB1f1dsSfvztlU_3Q,22826
8
9
  workflow_server/api/workflow_view.py,sha256=duiMnAZ7PRpoPz63s9z37pxUxGR-9yAi3qxG9APXCao,14244
9
10
  workflow_server/code_exec_runner.py,sha256=tfijklTVkX4y45jeFTfrY2hVhdwo0VrLFc3SMeIiVYs,3096
10
11
  workflow_server/config.py,sha256=Jk1kmncI7g2LTIujssIpncD_eQoIowL_5oARQ6XpkJ0,1281
11
12
  workflow_server/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  workflow_server/core/cancel_workflow.py,sha256=Ffkc3mzmrdMEUcD-sHfEhX4IwVrka-E--SxKA1dUfIU,2185
13
14
  workflow_server/core/events.py,sha256=iscGJv8bS7WGEYR-ODnALIANuHpwOs2TdKzqDPrCOh0,1370
14
- workflow_server/core/executor.py,sha256=QO0DrwAfUMC8d2m-5x8JZKsbjcnIxVkxqKRSczoj5Ag,16796
15
- workflow_server/core/workflow_executor_context.py,sha256=FCUWWNoIZcYwlBwt55TsCKd_kcLrIoVQvnaGn7d9tfE,1239
15
+ workflow_server/core/executor.py,sha256=q1xeshQVXYKPuJ0qKIugTkQ-58KRZ9VMKSZ1IyWVIXU,16910
16
+ workflow_server/core/workflow_executor_context.py,sha256=JHGlrEaf7IkCJjrAPMo64M7X0aiwWXe6exwXwxhwow4,1298
16
17
  workflow_server/server.py,sha256=QBU12AaAfAgLqfCDBd24qIJl_mbheiq0-hfcWV7rZM4,1234
17
18
  workflow_server/start.py,sha256=DgtQhuCLc07BIWyJPLPZKZsQ8jwEFsvvfIo7MdwVrpw,1998
18
19
  workflow_server/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -21,9 +22,9 @@ workflow_server/utils/log_proxy.py,sha256=nugi6fOgAYKX2X9DIc39TG366rsmmDUPoEtG3g
21
22
  workflow_server/utils/oom_killer.py,sha256=8WB0nQWjmnjW9QzvNNwfYoBFB3yDHM3_OmnryeC8G3A,3657
22
23
  workflow_server/utils/sentry.py,sha256=Pr3xKvHdk0XFSpXgy-55bWI4J3bbf_36gjDyLOs7oVU,855
23
24
  workflow_server/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- workflow_server/utils/tests/test_utils.py,sha256=MKGY4F_jnRdwqyQ8Krhlk7MpMEPql5MDATcwBaxpIEA,4786
25
- workflow_server/utils/utils.py,sha256=dpFeUQW7cmiH2-aLsO65jAa4RubfvptWNkW1ZkvDlmE,3710
26
- vellum_workflow_server-0.14.69.dist-info/METADATA,sha256=Mw-2DCN-YSs3w4bny4SLReAts1d2VzuuQRJHfx6acyU,2237
27
- vellum_workflow_server-0.14.69.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
28
- vellum_workflow_server-0.14.69.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
29
- vellum_workflow_server-0.14.69.dist-info/RECORD,,
25
+ workflow_server/utils/tests/test_utils.py,sha256=qwK5Rmy3RQyjtlUrYAuGuDlBeRzZKsf1yS-y2IpUizQ,6452
26
+ workflow_server/utils/utils.py,sha256=Wqqn-1l2ugkGgy5paWWdt0AVxAyPMQCYcnRSSOMjXlA,4355
27
+ vellum_workflow_server-0.14.70.post1.dist-info/METADATA,sha256=rb07H2pgqHQF2Yx5ncBt4M2YzgAh8SJNu8FsUOTCv0Q,2243
28
+ vellum_workflow_server-0.14.70.post1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
+ vellum_workflow_server-0.14.70.post1.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
30
+ vellum_workflow_server-0.14.70.post1.dist-info/RECORD,,
@@ -0,0 +1,61 @@
1
+ from uuid import uuid4
2
+
3
+ from workflow_server.server import create_app
4
+
5
+
6
+ def test_input_conversion_with_display_mapping():
7
+ """
8
+ Test that validates input conversion behavior for future WorkflowDisplay refactor.
9
+
10
+ This test demonstrates the expected behavior when inputs are converted from UI names
11
+ to SDK attribute names using WorkflowDisplay's inputs_display mapping, rather than
12
+ the current snake_case conversion approach.
13
+
14
+ The test passes on main branch by using the current conversion logic, but establishes
15
+ the expected input/output patterns for the future refactor.
16
+ """
17
+ span_id = uuid4()
18
+ request_body = {
19
+ "execution_id": str(span_id),
20
+ "inputs": [
21
+ {"name": "User Message", "type": "STRING", "value": "Hello world"},
22
+ {"name": "123-config", "type": "STRING", "value": "config-value"},
23
+ {"name": "API-Key", "type": "STRING", "value": "test-key"},
24
+ ],
25
+ "environment_api_key": "test",
26
+ "module": "workflow",
27
+ "timeout": 360,
28
+ "files": {
29
+ "__init__.py": "",
30
+ "workflow.py": """
31
+ from vellum.workflows import BaseWorkflow
32
+ from vellum.workflows.state import BaseState
33
+ from .inputs import Inputs
34
+
35
+ class Workflow(BaseWorkflow[Inputs, BaseState]):
36
+ class Outputs(BaseWorkflow.Outputs):
37
+ result = "success"
38
+ """,
39
+ "inputs.py": """
40
+ from vellum.workflows.inputs import BaseInputs
41
+
42
+ class Inputs(BaseInputs):
43
+ user_message: str
44
+ input_123_config: str
45
+ api_key: str
46
+ """,
47
+ },
48
+ }
49
+
50
+ flask_app = create_app()
51
+ with flask_app.test_client() as test_client:
52
+ response = test_client.post("/workflow/stream", json=request_body)
53
+ status_code = response.status_code
54
+
55
+ assert status_code == 200, f"Request failed with status {status_code}: {response.data}"
56
+
57
+ response_lines = [line for line in response.data.decode().split("\n") if line and line not in ["WAITING", "END"]]
58
+ assert len(response_lines) > 0, "No response events received"
59
+
60
+ fulfilled_events = [line for line in response_lines if "workflow.execution.fulfilled" in line]
61
+ assert len(fulfilled_events) > 0, "No workflow.execution.fulfilled event received"
@@ -743,3 +743,44 @@ class EndNodeDisplay(BaseNodeDisplay[EndNode]):
743
743
  assert len(events) > 3, json.dumps(events[-1])
744
744
  assert events[2]["name"] == "node.execution.initiated", json.dumps(events[-1])
745
745
  assert events[2]["body"]["inputs"] == {"fruit": "cherry"}
746
+
747
+
748
+ def test_stream_workflow_route__with_environment_variables(both_stream_types):
749
+ # GIVEN a valid request body with environment variables
750
+ span_id = uuid4()
751
+ request_body = {
752
+ "execution_id": str(span_id),
753
+ "inputs": [],
754
+ "environment_api_key": "test",
755
+ "module": "workflow",
756
+ "timeout": 360,
757
+ "environment_variables": {"TEST_ENV_VAR": "test_value", "ANOTHER_VAR": "another_value"},
758
+ "files": {
759
+ "__init__.py": "",
760
+ "workflow.py": """\
761
+ from vellum.workflows import BaseWorkflow
762
+ from vellum.workflows.references import EnvironmentVariableReference
763
+
764
+ class Workflow(BaseWorkflow):
765
+ class Outputs(BaseWorkflow.Outputs):
766
+ env_var_value = EnvironmentVariableReference(name="TEST_ENV_VAR", default="not_found")
767
+ another_var_value = EnvironmentVariableReference(name="ANOTHER_VAR", default="not_found")
768
+ """,
769
+ },
770
+ }
771
+
772
+ # WHEN we call the stream route
773
+ status_code, events = both_stream_types(request_body)
774
+
775
+ # THEN we get a 200 response
776
+ assert status_code == 200, events
777
+
778
+ # THEN we get the expected events
779
+ assert events[0]["name"] == "vembda.execution.initiated"
780
+ assert events[1]["name"] == "workflow.execution.initiated"
781
+ assert events[2]["name"] == "workflow.execution.fulfilled"
782
+
783
+ # AND the environment variables are accessible in the workflow
784
+ outputs = events[2]["body"]["outputs"]
785
+ assert outputs["env_var_value"] == "test_value"
786
+ assert outputs["another_var_value"] == "another_value"
@@ -342,6 +342,9 @@ def _create_workflow_context(executor_context: BaseExecutorContext) -> WorkflowC
342
342
  else:
343
343
  environment = VellumEnvironment.PRODUCTION
344
344
 
345
+ if executor_context.environment_variables:
346
+ os.environ.update(executor_context.environment_variables)
347
+
345
348
  return WorkflowContext(
346
349
  vellum_client=Vellum(
347
350
  api_key=executor_context.environment_api_key,
@@ -22,6 +22,7 @@ class BaseExecutorContext(UniversalBaseModel):
22
22
  stream_start_time: int = 0
23
23
  vembda_public_url: Optional[str] = None
24
24
  node_output_mocks: Optional[list[Any]] = None
25
+ environment_variables: Optional[dict[str, str]] = None
25
26
 
26
27
  @property
27
28
  def container_overhead_latency(self) -> int:
@@ -10,7 +10,11 @@ from vellum import (
10
10
  VellumError,
11
11
  VellumImage,
12
12
  )
13
- from workflow_server.utils.utils import convert_json_inputs_to_vellum
13
+ from workflow_server.utils.utils import (
14
+ convert_json_inputs_to_vellum,
15
+ to_python_safe_snake_case,
16
+ to_valid_python_identifier,
17
+ )
14
18
 
15
19
 
16
20
  @pytest.mark.parametrize(
@@ -134,10 +138,57 @@ def test_input_variables_with_uppercase_gets_sanitized():
134
138
  ]
135
139
 
136
140
  expected = {
137
- "foo": "<example-string-value>",
141
+ "Foo": "<example-string-value>",
138
142
  "foo_var": "<another-example-string-value>",
139
143
  }
140
144
 
141
145
  actual = convert_json_inputs_to_vellum(inputs)
142
146
 
143
147
  assert expected == actual
148
+
149
+
150
+ @pytest.mark.parametrize(
151
+ ["input_string", "safety_prefix", "expected"],
152
+ [
153
+ ("Foo", "input", "Foo"),
154
+ ("test", "input", "test"),
155
+ ("myVariable", "input", "myVariable"),
156
+ ("validName123", "input", "validName123"),
157
+ ("Foo-Var", "input", "foo_var"),
158
+ ("My Variable", "input", "my_variable"),
159
+ ("test-case", "input", "test_case"),
160
+ ("CamelCase", "input", "CamelCase"),
161
+ ("123", "input", "input_123"),
162
+ ("_a", "input", "input_a"),
163
+ ("_123", "input", "input_123"),
164
+ ("test@#$", "input", "test"),
165
+ ("@#$test", "input", "test"),
166
+ ("123", "_", "_123"),
167
+ ("123", "var", "var_123"),
168
+ ("123", "var_", "var_123"),
169
+ ],
170
+ )
171
+ def test_to_valid_python_identifier(input_string, safety_prefix, expected):
172
+ actual = to_valid_python_identifier(input_string, safety_prefix)
173
+ assert expected == actual
174
+
175
+
176
+ @pytest.mark.parametrize(
177
+ ["input_string", "safety_prefix", "expected"],
178
+ [
179
+ ("Foo", "input", "foo"),
180
+ ("Foo-Var", "input", "foo_var"),
181
+ ("CamelCase", "input", "camel_case"),
182
+ ("test", "input", "test"),
183
+ ("My Variable", "input", "my_variable"),
184
+ ("123", "input", "input_123"),
185
+ ("_a", "input", "input_a"),
186
+ ("_123", "input", "input_123"),
187
+ ("123", "_", "_123"),
188
+ ("123", "var", "var_123"),
189
+ ("123", "var_", "var_123"),
190
+ ],
191
+ )
192
+ def test_to_python_safe_snake_case(input_string, safety_prefix, expected):
193
+ actual = to_python_safe_snake_case(input_string, safety_prefix)
194
+ assert expected == actual
@@ -22,7 +22,7 @@ def convert_json_inputs_to_vellum(inputs: List[dict]) -> dict:
22
22
  for input in inputs:
23
23
  value = input["value"]
24
24
  # sync with vellum-python-sdks/ee/codegen/src/context/input-variable-context.ts
25
- name = to_python_safe_snake_case(input["name"], "input")
25
+ name = to_valid_python_identifier(input["name"], "input")
26
26
  type = input["type"]
27
27
 
28
28
  if type == "CHAT_HISTORY":
@@ -58,7 +58,7 @@ def to_python_safe_snake_case(string: str, safety_prefix: str = "_") -> str:
58
58
  # Strip special characters from start of string
59
59
  cleaned_str = re.sub(r"^[^a-zA-Z0-9_]+", "", string)
60
60
 
61
- # Check if cleaned string starts with a number or underscore
61
+ # Check if cleaned string starts with a number or an underscore
62
62
  starts_with_unsafe = bool(re.match(r"^[\d_]", cleaned_str))
63
63
 
64
64
  # Convert to snake case
@@ -74,6 +74,22 @@ def to_python_safe_snake_case(string: str, safety_prefix: str = "_") -> str:
74
74
  return f"{cleaned_safety_prefix}{snake_case}" if starts_with_unsafe else snake_case
75
75
 
76
76
 
77
+ def to_valid_python_identifier(string: str, safety_prefix: str = "_") -> str:
78
+ # Strip special characters from start of string
79
+ cleaned_str = re.sub(r"^[^a-zA-Z0-9_]+", "", string)
80
+
81
+ # Check if cleaned string starts with a number or an underscore
82
+ starts_with_unsafe = bool(re.match(r"^[\d_]", cleaned_str))
83
+
84
+ # Check if the string is already a valid Python identifier (preserve case)
85
+ is_valid_python_identifier = bool(re.match(r"^[a-zA-Z][a-zA-Z0-9]*$", cleaned_str))
86
+
87
+ if is_valid_python_identifier and not starts_with_unsafe:
88
+ return cleaned_str
89
+
90
+ return to_python_safe_snake_case(string, safety_prefix)
91
+
92
+
77
93
  def get_obj_size(obj: Any) -> int:
78
94
  marked = {id(obj)}
79
95
  obj_q = [obj]