vellum-workflow-server 1.11.21__py3-none-any.whl → 1.12.3__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.
- {vellum_workflow_server-1.11.21.dist-info → vellum_workflow_server-1.12.3.dist-info}/METADATA +2 -2
- {vellum_workflow_server-1.11.21.dist-info → vellum_workflow_server-1.12.3.dist-info}/RECORD +10 -10
- workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +158 -0
- workflow_server/api/workflow_view.py +39 -1
- workflow_server/core/executor.py +1 -0
- workflow_server/core/workflow_executor_context.py +1 -0
- workflow_server/utils/tests/test_utils.py +1 -2
- workflow_server/utils/utils.py +18 -1
- {vellum_workflow_server-1.11.21.dist-info → vellum_workflow_server-1.12.3.dist-info}/WHEEL +0 -0
- {vellum_workflow_server-1.11.21.dist-info → vellum_workflow_server-1.12.3.dist-info}/entry_points.txt +0 -0
{vellum_workflow_server-1.11.21.dist-info → vellum_workflow_server-1.12.3.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vellum-workflow-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.3
|
|
4
4
|
Summary:
|
|
5
5
|
License: AGPL
|
|
6
6
|
Requires-Python: >=3.9.0,<4
|
|
@@ -30,7 +30,7 @@ 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.3)
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
36
36
|
# Vellum Workflow Runner Server
|
|
@@ -7,16 +7,16 @@ workflow_server/api/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
7
7
|
workflow_server/api/tests/test_input_display_mapping.py,sha256=drBZqMudFyB5wgiUOcMgRXz7E7ge-Qgxbstw4E4f0zE,2211
|
|
8
8
|
workflow_server/api/tests/test_workflow_view.py,sha256=I2sd11ptKDqbylzB9rKqkMXeZoh8ttad3zIhNus86vk,32491
|
|
9
9
|
workflow_server/api/tests/test_workflow_view_async_exec.py,sha256=eP_H2xI9SRfJdoJ6HPeynQecnxR50I_8aDCooF-YzIw,11952
|
|
10
|
-
workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=
|
|
11
|
-
workflow_server/api/workflow_view.py,sha256=
|
|
10
|
+
workflow_server/api/tests/test_workflow_view_stream_workflow_route.py,sha256=PLHU7rZUVZqToSEuo6uJI4PTLdmaR1qBYiv9k_86A4w,48140
|
|
11
|
+
workflow_server/api/workflow_view.py,sha256=qPIN6iicMQVngy-Jr7dtOT3wqdFW2Bl3N_hbPdyTCAs,28177
|
|
12
12
|
workflow_server/code_exec_runner.py,sha256=vJlCQ8FkcG8RfCZ34Ea2Xt6J7dNkU5EqA-KxRkbVOeo,2219
|
|
13
13
|
workflow_server/config.py,sha256=I4hfTsjIbHxoSKylPCjKnrysPV0jO5nfRKwpKvEcfAE,2193
|
|
14
14
|
workflow_server/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
workflow_server/core/cancel_workflow.py,sha256=uMPZg_rQ6iKZBLuxgwla1NYwhkcbO0gLh8QYBfU_2_0,2371
|
|
16
16
|
workflow_server/core/events.py,sha256=24MA66DVQuaLJJcZrS8IL1Zq4Ohi9CoouKZ5VgoH3Cs,1402
|
|
17
|
-
workflow_server/core/executor.py,sha256
|
|
17
|
+
workflow_server/core/executor.py,sha256=-FT4x05ijlnERY4BnghFAoF8sxx2yjQBrkrVf_anXHQ,17172
|
|
18
18
|
workflow_server/core/utils.py,sha256=mecVPqQkthrC4mpop3r8J3IWnBmKbDgqfCrSagyzVEg,2021
|
|
19
|
-
workflow_server/core/workflow_executor_context.py,sha256=
|
|
19
|
+
workflow_server/core/workflow_executor_context.py,sha256=SXO5aVgO9rdsp7LSYJZkNIky-GvYwY3lJNBdgWK5KjE,3940
|
|
20
20
|
workflow_server/logging_config.py,sha256=Hvx1t8uhqMMinl-5qcef7ufUvzs6x14VRnCb7YZxEAg,1206
|
|
21
21
|
workflow_server/server.py,sha256=pBl0OQmrLE-PbTDwTgsVmxgz_Ai3TVhFRaMnr6PX6Yk,1849
|
|
22
22
|
workflow_server/start.py,sha256=dvV8EKUH_oaTbOzNmUolF7RpkPWW8IkFwlgqOV9BhZQ,2842
|
|
@@ -29,9 +29,9 @@ workflow_server/utils/system_utils.py,sha256=3jNv113zRkKJ0928i2Vm6TqFHrDulteQu1k
|
|
|
29
29
|
workflow_server/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
workflow_server/utils/tests/test_sentry_integration.py,sha256=14PfuW8AaQNNtqLmBs16EPe5T3f_iTI7YJMCRtiboZk,4502
|
|
31
31
|
workflow_server/utils/tests/test_system_utils.py,sha256=_4GwXvVvU5BrATxUEWwQIPg0bzQXMWBtiBmjP8MTxJM,4314
|
|
32
|
-
workflow_server/utils/tests/test_utils.py,sha256=
|
|
33
|
-
workflow_server/utils/utils.py,sha256=
|
|
34
|
-
vellum_workflow_server-1.
|
|
35
|
-
vellum_workflow_server-1.
|
|
36
|
-
vellum_workflow_server-1.
|
|
37
|
-
vellum_workflow_server-1.
|
|
32
|
+
workflow_server/utils/tests/test_utils.py,sha256=8gbgZyzVdJteDbTdIbpiwJh6q4J2kQBcpkd9yjXnGEc,6882
|
|
33
|
+
workflow_server/utils/utils.py,sha256=QeSDrM-AnniomRM4oqYWxhFkSqEKmGfzx_qpL-bfflU,5690
|
|
34
|
+
vellum_workflow_server-1.12.3.dist-info/METADATA,sha256=KJ_bpPg8nN1vpJhQJlwC2dNYK9LZcRQhCJl7Mp4VrBc,2275
|
|
35
|
+
vellum_workflow_server-1.12.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
36
|
+
vellum_workflow_server-1.12.3.dist-info/entry_points.txt,sha256=uB_0yPkr7YV6RhEXzvFReUM8P4OQBlVXD6TN6eb9-oc,277
|
|
37
|
+
vellum_workflow_server-1.12.3.dist-info/RECORD,,
|
|
@@ -1313,3 +1313,161 @@ class OOMWorkflow(BaseWorkflow):
|
|
|
1313
1313
|
assert (
|
|
1314
1314
|
vembda_fulfilled_event["body"].get("timed_out") is not True
|
|
1315
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"
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
def test_stream_workflow_route__array_input_string_methods(both_stream_types):
|
|
1392
|
+
"""
|
|
1393
|
+
Tests that array inputs of strings can have string methods called on them.
|
|
1394
|
+
|
|
1395
|
+
This is a regression test for APO-2423 where array inputs of strings were being
|
|
1396
|
+
deserialized as VellumValue objects instead of plain strings, causing string
|
|
1397
|
+
methods like .upper() to fail.
|
|
1398
|
+
"""
|
|
1399
|
+
|
|
1400
|
+
# GIVEN a workflow that takes an array of strings and calls .upper() on each item
|
|
1401
|
+
span_id = uuid4()
|
|
1402
|
+
request_body = {
|
|
1403
|
+
"timeout": 360,
|
|
1404
|
+
"execution_id": str(span_id),
|
|
1405
|
+
"inputs": [
|
|
1406
|
+
{
|
|
1407
|
+
"name": "items",
|
|
1408
|
+
"type": "ARRAY",
|
|
1409
|
+
"value": [
|
|
1410
|
+
{"type": "STRING", "value": "hello"},
|
|
1411
|
+
{"type": "STRING", "value": "world"},
|
|
1412
|
+
],
|
|
1413
|
+
},
|
|
1414
|
+
],
|
|
1415
|
+
"environment_api_key": "test",
|
|
1416
|
+
"module": "workflow",
|
|
1417
|
+
"files": {
|
|
1418
|
+
"__init__.py": "",
|
|
1419
|
+
"workflow.py": """\
|
|
1420
|
+
from typing import List
|
|
1421
|
+
|
|
1422
|
+
from vellum.workflows import BaseWorkflow
|
|
1423
|
+
from vellum.workflows.inputs import BaseInputs
|
|
1424
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
1425
|
+
from vellum.workflows.state import BaseState
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
class Inputs(BaseInputs):
|
|
1429
|
+
items: List[str]
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
class UppercaseNode(BaseNode):
|
|
1433
|
+
items = Inputs.items
|
|
1434
|
+
|
|
1435
|
+
class Outputs(BaseNode.Outputs):
|
|
1436
|
+
result: List[str]
|
|
1437
|
+
|
|
1438
|
+
def run(self) -> Outputs:
|
|
1439
|
+
# This should work if items is a list of strings
|
|
1440
|
+
# but will fail if items is a list of VellumValue objects
|
|
1441
|
+
uppercased = [item.upper() for item in self.items]
|
|
1442
|
+
return self.Outputs(result=uppercased)
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
class Workflow(BaseWorkflow[Inputs, BaseState]):
|
|
1446
|
+
graph = UppercaseNode
|
|
1447
|
+
|
|
1448
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
1449
|
+
result = UppercaseNode.Outputs.result
|
|
1450
|
+
""",
|
|
1451
|
+
},
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
# WHEN we call the stream route
|
|
1455
|
+
status_code, events = both_stream_types(request_body)
|
|
1456
|
+
|
|
1457
|
+
# THEN we get a 200 response
|
|
1458
|
+
assert status_code == 200, events
|
|
1459
|
+
|
|
1460
|
+
# AND we should get the expected events without errors
|
|
1461
|
+
event_names = [e["name"] for e in events]
|
|
1462
|
+
assert "vembda.execution.initiated" in event_names
|
|
1463
|
+
assert "workflow.execution.initiated" in event_names
|
|
1464
|
+
assert "workflow.execution.fulfilled" in event_names
|
|
1465
|
+
|
|
1466
|
+
# AND the workflow should NOT be rejected
|
|
1467
|
+
assert "workflow.execution.rejected" not in event_names, (
|
|
1468
|
+
f"Workflow was rejected when it should have succeeded. " f"Events: {events}"
|
|
1469
|
+
)
|
|
1470
|
+
|
|
1471
|
+
# AND the output should be the uppercased strings
|
|
1472
|
+
fulfilled_event = next(e for e in events if e["name"] == "workflow.execution.fulfilled")
|
|
1473
|
+
assert fulfilled_event["body"]["outputs"]["result"] == ["HELLO", "WORLD"]
|
|
@@ -22,6 +22,7 @@ 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
|
|
25
26
|
from vellum.workflows.errors import WorkflowError, WorkflowErrorCode
|
|
26
27
|
from vellum.workflows.events.workflow import (
|
|
27
28
|
WorkflowExecutionInitiatedBody,
|
|
@@ -142,7 +143,7 @@ def stream_workflow_route() -> Response:
|
|
|
142
143
|
# These can happen either from Vembda disconnects (possibily from predict disconnects) or
|
|
143
144
|
# from knative activator gateway timeouts which are caused by idleTimeout or responseStartSeconds
|
|
144
145
|
# being exceeded.
|
|
145
|
-
app.logger.
|
|
146
|
+
app.logger.warning(
|
|
146
147
|
"Client disconnected in the middle of the Workflow Stream",
|
|
147
148
|
extra={
|
|
148
149
|
"sentry_tags": {
|
|
@@ -151,6 +152,11 @@ def stream_workflow_route() -> Response:
|
|
|
151
152
|
}
|
|
152
153
|
},
|
|
153
154
|
)
|
|
155
|
+
_emit_client_disconnect_events(
|
|
156
|
+
context,
|
|
157
|
+
span_id,
|
|
158
|
+
"Client disconnected in the middle of the Workflow Stream",
|
|
159
|
+
)
|
|
154
160
|
return
|
|
155
161
|
except Exception as e:
|
|
156
162
|
logger.exception("Error during workflow response stream generator", extra={"error": e})
|
|
@@ -218,6 +224,38 @@ def _emit_async_error_events(
|
|
|
218
224
|
logger.exception(f"Failed to emit async error events: {e}")
|
|
219
225
|
|
|
220
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
|
+
|
|
221
259
|
@bp.route("/async-exec", methods=["POST"])
|
|
222
260
|
def async_exec_workflow() -> Response:
|
|
223
261
|
data = request.get_json()
|
workflow_server/core/executor.py
CHANGED
|
@@ -194,6 +194,7 @@ def stream_workflow(
|
|
|
194
194
|
timeout=executor_context.timeout,
|
|
195
195
|
trigger=trigger,
|
|
196
196
|
execution_id=executor_context.workflow_span_id,
|
|
197
|
+
event_max_size=executor_context.event_max_size,
|
|
197
198
|
)
|
|
198
199
|
except WorkflowInitializationException as e:
|
|
199
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
|
|
@@ -5,7 +5,6 @@ from vellum import (
|
|
|
5
5
|
FunctionCall,
|
|
6
6
|
SearchResult,
|
|
7
7
|
SearchResultDocument,
|
|
8
|
-
StringVellumValue,
|
|
9
8
|
VellumAudio,
|
|
10
9
|
VellumDocument,
|
|
11
10
|
VellumError,
|
|
@@ -104,7 +103,7 @@ from workflow_server.utils.utils import (
|
|
|
104
103
|
),
|
|
105
104
|
(
|
|
106
105
|
{"type": "ARRAY", "name": "array", "value": [{"type": "STRING", "value": "<example-string-value>"}]},
|
|
107
|
-
{"array": [
|
|
106
|
+
{"array": ["<example-string-value>"]},
|
|
108
107
|
),
|
|
109
108
|
(
|
|
110
109
|
{"type": "NUMBER", "name": "123", "value": 123},
|
workflow_server/utils/utils.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Any, List
|
|
|
6
6
|
|
|
7
7
|
from vellum import (
|
|
8
8
|
ArrayInput,
|
|
9
|
+
ArrayVellumValue,
|
|
9
10
|
ChatHistoryInput,
|
|
10
11
|
ErrorInput,
|
|
11
12
|
FunctionCallInput,
|
|
@@ -13,11 +14,26 @@ from vellum import (
|
|
|
13
14
|
VellumAudio,
|
|
14
15
|
VellumDocument,
|
|
15
16
|
VellumImage,
|
|
17
|
+
VellumValue,
|
|
16
18
|
VellumVideo,
|
|
17
19
|
)
|
|
18
20
|
from workflow_server.config import CONTAINER_IMAGE, is_development
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
def unwrap_vellum_value(item: VellumValue) -> Any:
|
|
24
|
+
"""Recursively unwrap VellumValue objects to their primitive values.
|
|
25
|
+
|
|
26
|
+
This is needed because ArrayInput.value returns List[VellumValue] objects,
|
|
27
|
+
but workflows expect primitive Python values (str, int, etc.).
|
|
28
|
+
"""
|
|
29
|
+
if isinstance(item, ArrayVellumValue):
|
|
30
|
+
if item.value is None:
|
|
31
|
+
return None
|
|
32
|
+
return [unwrap_vellum_value(nested_item) for nested_item in item.value]
|
|
33
|
+
else:
|
|
34
|
+
return item.value
|
|
35
|
+
|
|
36
|
+
|
|
21
37
|
def convert_json_inputs_to_vellum(inputs: List[dict]) -> dict:
|
|
22
38
|
vellum_inputs: dict[str, Any] = {}
|
|
23
39
|
|
|
@@ -41,7 +57,8 @@ def convert_json_inputs_to_vellum(inputs: List[dict]) -> dict:
|
|
|
41
57
|
elif type == "ERROR":
|
|
42
58
|
vellum_inputs[name] = ErrorInput.model_validate(input).value
|
|
43
59
|
elif type == "ARRAY":
|
|
44
|
-
|
|
60
|
+
array_value = ArrayInput.model_validate(input).value
|
|
61
|
+
vellum_inputs[name] = [unwrap_vellum_value(item) for item in array_value]
|
|
45
62
|
# Once we export *Input classes for these two cases, we can add the union to the WorkflowExecutorContext
|
|
46
63
|
# model and simplify this method to just a {to_python_safe_snake_case(input.name): input.value} mapping
|
|
47
64
|
elif type == "IMAGE":
|
|
File without changes
|
|
File without changes
|