vellum-ai 1.2.3__py3-none-any.whl → 1.2.5__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.
Files changed (96) hide show
  1. vellum/__init__.py +48 -0
  2. vellum/client/core/client_wrapper.py +2 -2
  3. vellum/client/resources/workflows/client.py +20 -0
  4. vellum/client/resources/workflows/raw_client.py +20 -0
  5. vellum/client/types/__init__.py +48 -0
  6. vellum/client/types/audio_input.py +30 -0
  7. vellum/client/types/code_executor_input.py +8 -0
  8. vellum/client/types/document_input.py +30 -0
  9. vellum/client/types/image_input.py +30 -0
  10. vellum/client/types/named_scenario_input_audio_variable_value_request.py +22 -0
  11. vellum/client/types/named_scenario_input_document_variable_value_request.py +22 -0
  12. vellum/client/types/named_scenario_input_image_variable_value_request.py +22 -0
  13. vellum/client/types/named_scenario_input_request.py +8 -0
  14. vellum/client/types/named_scenario_input_video_variable_value_request.py +22 -0
  15. vellum/client/types/named_test_case_audio_variable_value.py +26 -0
  16. vellum/client/types/named_test_case_audio_variable_value_request.py +26 -0
  17. vellum/client/types/named_test_case_document_variable_value.py +22 -0
  18. vellum/client/types/named_test_case_document_variable_value_request.py +22 -0
  19. vellum/client/types/named_test_case_image_variable_value.py +22 -0
  20. vellum/client/types/named_test_case_image_variable_value_request.py +22 -0
  21. vellum/client/types/named_test_case_variable_value.py +8 -0
  22. vellum/client/types/named_test_case_variable_value_request.py +8 -0
  23. vellum/client/types/named_test_case_video_variable_value.py +22 -0
  24. vellum/client/types/named_test_case_video_variable_value_request.py +22 -0
  25. vellum/client/types/node_execution_span_attributes.py +1 -0
  26. vellum/client/types/scenario_input.py +11 -1
  27. vellum/client/types/scenario_input_audio_variable_value.py +22 -0
  28. vellum/client/types/scenario_input_document_variable_value.py +22 -0
  29. vellum/client/types/scenario_input_image_variable_value.py +22 -0
  30. vellum/client/types/scenario_input_video_variable_value.py +22 -0
  31. vellum/client/types/span_link.py +1 -1
  32. vellum/client/types/span_link_type_enum.py +1 -1
  33. vellum/client/types/test_case_audio_variable_value.py +27 -0
  34. vellum/client/types/test_case_document_variable_value.py +27 -0
  35. vellum/client/types/test_case_image_variable_value.py +27 -0
  36. vellum/client/types/test_case_variable_value.py +8 -0
  37. vellum/client/types/test_case_video_variable_value.py +27 -0
  38. vellum/client/types/video_input.py +30 -0
  39. vellum/client/types/workflow_push_deployment_config_request.py +1 -0
  40. vellum/types/audio_input.py +3 -0
  41. vellum/types/document_input.py +3 -0
  42. vellum/types/image_input.py +3 -0
  43. vellum/types/named_scenario_input_audio_variable_value_request.py +3 -0
  44. vellum/types/named_scenario_input_document_variable_value_request.py +3 -0
  45. vellum/types/named_scenario_input_image_variable_value_request.py +3 -0
  46. vellum/types/named_scenario_input_video_variable_value_request.py +3 -0
  47. vellum/types/named_test_case_audio_variable_value.py +3 -0
  48. vellum/types/named_test_case_audio_variable_value_request.py +3 -0
  49. vellum/types/named_test_case_document_variable_value.py +3 -0
  50. vellum/types/named_test_case_document_variable_value_request.py +3 -0
  51. vellum/types/named_test_case_image_variable_value.py +3 -0
  52. vellum/types/named_test_case_image_variable_value_request.py +3 -0
  53. vellum/types/named_test_case_video_variable_value.py +3 -0
  54. vellum/types/named_test_case_video_variable_value_request.py +3 -0
  55. vellum/types/scenario_input_audio_variable_value.py +3 -0
  56. vellum/types/scenario_input_document_variable_value.py +3 -0
  57. vellum/types/scenario_input_image_variable_value.py +3 -0
  58. vellum/types/scenario_input_video_variable_value.py +3 -0
  59. vellum/types/test_case_audio_variable_value.py +3 -0
  60. vellum/types/test_case_document_variable_value.py +3 -0
  61. vellum/types/test_case_image_variable_value.py +3 -0
  62. vellum/types/test_case_video_variable_value.py +3 -0
  63. vellum/types/video_input.py +3 -0
  64. vellum/workflows/events/tests/test_event.py +9 -0
  65. vellum/workflows/events/types.py +3 -1
  66. vellum/workflows/integrations/tests/test_mcp_service.py +40 -1
  67. vellum/workflows/nodes/core/templating_node/node.py +3 -2
  68. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +129 -0
  69. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +12 -0
  70. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +41 -0
  71. vellum/workflows/nodes/displayable/bases/utils.py +38 -1
  72. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -20
  73. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +3 -26
  74. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +3 -25
  75. vellum/workflows/nodes/utils.py +26 -1
  76. vellum/workflows/resolvers/base.py +18 -1
  77. vellum/workflows/resolvers/resolver.py +42 -0
  78. vellum/workflows/resolvers/tests/test_resolver.py +59 -0
  79. vellum/workflows/types/definition.py +1 -0
  80. vellum/workflows/utils/functions.py +4 -0
  81. vellum/workflows/utils/tests/test_functions.py +6 -3
  82. vellum/workflows/workflows/base.py +3 -0
  83. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/METADATA +1 -1
  84. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/RECORD +96 -46
  85. vellum_cli/__init__.py +6 -0
  86. vellum_cli/config.py +2 -0
  87. vellum_cli/push.py +3 -0
  88. vellum_cli/tests/test_pull.py +2 -0
  89. vellum_cli/tests/test_push.py +39 -0
  90. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +2 -0
  91. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +1 -0
  92. vellum_ee/workflows/display/utils/events.py +19 -1
  93. vellum_ee/workflows/display/utils/tests/test_events.py +42 -0
  94. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/LICENSE +0 -0
  95. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/WHEEL +0 -0
  96. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,30 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ import pydantic
6
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
7
+ from .vellum_video import VellumVideo
8
+
9
+
10
+ class VideoInput(UniversalBaseModel):
11
+ """
12
+ A user input representing a Vellum Video value
13
+ """
14
+
15
+ name: str = pydantic.Field()
16
+ """
17
+ The variable's name
18
+ """
19
+
20
+ type: typing.Literal["VIDEO"] = "VIDEO"
21
+ value: VellumVideo
22
+
23
+ if IS_PYDANTIC_V2:
24
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
25
+ else:
26
+
27
+ class Config:
28
+ frozen = True
29
+ smart_union = True
30
+ extra = pydantic.Extra.allow
@@ -11,6 +11,7 @@ class WorkflowPushDeploymentConfigRequest(UniversalBaseModel):
11
11
  name: typing.Optional[str] = None
12
12
  description: typing.Optional[str] = None
13
13
  release_tags: typing.Optional[typing.List[str]] = None
14
+ release_description: typing.Optional[str] = None
14
15
 
15
16
  if IS_PYDANTIC_V2:
16
17
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.audio_input import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.document_input import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.image_input import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_scenario_input_audio_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_scenario_input_document_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_scenario_input_image_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_scenario_input_video_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_audio_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_audio_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_document_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_document_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_image_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_image_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_video_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.named_test_case_video_variable_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.scenario_input_audio_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.scenario_input_document_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.scenario_input_image_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.scenario_input_video_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.test_case_audio_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.test_case_document_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.test_case_image_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.test_case_video_variable_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.video_input import *
@@ -93,6 +93,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
93
93
  "workflow_version_exec_config": None,
94
94
  },
95
95
  "parent": None,
96
+ "links": None,
96
97
  },
97
98
  ),
98
99
  (
@@ -156,6 +157,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
156
157
  "type": "WORKFLOW_NODE",
157
158
  "span_id": "123e4567-e89b-12d3-a456-426614174000",
158
159
  },
160
+ "links": None,
159
161
  },
160
162
  ),
161
163
  (
@@ -191,6 +193,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
191
193
  },
192
194
  },
193
195
  "parent": None,
196
+ "links": None,
194
197
  },
195
198
  ),
196
199
  (
@@ -224,6 +227,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
224
227
  },
225
228
  },
226
229
  "parent": None,
230
+ "links": None,
227
231
  },
228
232
  ),
229
233
  (
@@ -259,6 +263,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
259
263
  },
260
264
  },
261
265
  "parent": None,
266
+ "links": None,
262
267
  },
263
268
  ),
264
269
  (
@@ -294,6 +299,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
294
299
  },
295
300
  },
296
301
  "parent": None,
302
+ "links": None,
297
303
  },
298
304
  ),
299
305
  (
@@ -334,6 +340,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
334
340
  "mocked": None,
335
341
  },
336
342
  "parent": None,
343
+ "links": None,
337
344
  },
338
345
  ),
339
346
  (
@@ -372,6 +379,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
372
379
  "mocked": None,
373
380
  },
374
381
  "parent": None,
382
+ "links": None,
375
383
  },
376
384
  ),
377
385
  (
@@ -405,6 +413,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
405
413
  "mocked": True,
406
414
  },
407
415
  "parent": None,
416
+ "links": None,
408
417
  },
409
418
  ),
410
419
  ],
@@ -1,12 +1,13 @@
1
1
  from datetime import datetime
2
2
  import json
3
3
  from uuid import UUID, uuid4
4
- from typing import Annotated, Any, Literal, Optional, Union, get_args
4
+ from typing import Annotated, Any, List, Literal, Optional, Union, get_args
5
5
 
6
6
  from pydantic import Field, GetCoreSchemaHandler, Tag, ValidationInfo
7
7
  from pydantic_core import CoreSchema, core_schema
8
8
 
9
9
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
10
+ from vellum.client.types.span_link import SpanLink
10
11
  from vellum.workflows.state.encoder import DefaultStateEncoder
11
12
  from vellum.workflows.types.definition import VellumCodeResourceDefinition
12
13
  from vellum.workflows.types.utils import datetime_now
@@ -164,3 +165,4 @@ class BaseEvent(UniversalBaseModel):
164
165
  trace_id: UUID
165
166
  span_id: UUID
166
167
  parent: Optional[ParentContext] = None
168
+ links: Optional[List[SpanLink]] = None
@@ -2,7 +2,9 @@ import asyncio
2
2
  import json
3
3
  from unittest import mock
4
4
 
5
- from vellum.workflows.integrations.mcp_service import MCPHttpClient
5
+ from vellum.workflows.constants import AuthorizationType
6
+ from vellum.workflows.integrations.mcp_service import MCPHttpClient, MCPService
7
+ from vellum.workflows.types.definition import MCPServer
6
8
 
7
9
 
8
10
  def test_mcp_http_client_sse_response():
@@ -79,3 +81,40 @@ def test_mcp_http_client_json_response():
79
81
 
80
82
  # THEN the JSON response should be returned as expected
81
83
  assert result == sample_json_response
84
+
85
+
86
+ def test_mcp_service_bearer_token_auth():
87
+ """Test that bearer token auth headers are set correctly"""
88
+ # GIVEN an MCP server with bearer token auth
89
+ server = MCPServer(
90
+ name="test-server",
91
+ url="https://test.server.com",
92
+ authorization_type=AuthorizationType.BEARER_TOKEN,
93
+ bearer_token_value="test-token-123",
94
+ )
95
+
96
+ # WHEN we get auth headers
97
+ service = MCPService()
98
+ headers = service._get_auth_headers(server)
99
+
100
+ # THEN the Authorization header should be set correctly
101
+ assert headers == {"Authorization": "Bearer test-token-123"}
102
+
103
+
104
+ def test_mcp_service_api_key_auth():
105
+ """Test that API key auth headers are set correctly"""
106
+ # GIVEN an MCP server with API key auth
107
+ server = MCPServer(
108
+ name="test-server",
109
+ url="https://test.server.com",
110
+ authorization_type=AuthorizationType.API_KEY,
111
+ api_key_header_key="X-API-Key",
112
+ api_key_header_value="api-key-123",
113
+ )
114
+
115
+ # WHEN we get auth headers
116
+ service = MCPService()
117
+ headers = service._get_auth_headers(server)
118
+
119
+ # THEN the custom API key header should be set correctly
120
+ assert headers == {"X-API-Key": "api-key-123"}
@@ -7,7 +7,7 @@ from vellum.workflows.errors import WorkflowErrorCode
7
7
  from vellum.workflows.exceptions import NodeException
8
8
  from vellum.workflows.nodes.bases import BaseNode
9
9
  from vellum.workflows.nodes.bases.base import BaseNodeMeta
10
- from vellum.workflows.nodes.utils import parse_type_from_str
10
+ from vellum.workflows.nodes.utils import parse_type_from_str, wrap_inputs_for_backward_compatibility
11
11
  from vellum.workflows.types.core import EntityInputsInterface
12
12
  from vellum.workflows.types.generics import StateType
13
13
  from vellum.workflows.types.utils import get_original_base
@@ -86,9 +86,10 @@ class TemplatingNode(BaseNode[StateType], Generic[StateType, _OutputType], metac
86
86
 
87
87
  def _render_template(self) -> str:
88
88
  try:
89
+ wrapped_inputs = wrap_inputs_for_backward_compatibility(self.inputs)
89
90
  return render_sandboxed_jinja_template(
90
91
  template=self.template,
91
- input_values=self.inputs,
92
+ input_values=wrapped_inputs,
92
93
  jinja_custom_filters={**self.jinja_custom_filters},
93
94
  jinja_globals=self.jinja_globals,
94
95
  )
@@ -317,3 +317,132 @@ def test_api_error_templating_node():
317
317
 
318
318
  # THEN the output should be empty string
319
319
  assert outputs.result == ""
320
+
321
+
322
+ @pytest.mark.parametrize(
323
+ "template,inputs,expected_result",
324
+ [
325
+ # String value access
326
+ ("{{ text_input.value }}", {"text_input": "hello world"}, "hello world"),
327
+ # Function call value access
328
+ (
329
+ "{{ func.value.name }}",
330
+ {"func": FunctionCall(name="test_function", arguments={"key": "value"})},
331
+ "test_function",
332
+ ),
333
+ # Array item value access
334
+ ("{{ items[0].value }}", {"items": ["apple"]}, "apple"),
335
+ ],
336
+ ids=["string_value", "function_call_value", "array_item_value"],
337
+ )
338
+ def test_templating_node__value_access_patterns_str(template, inputs, expected_result):
339
+ # GIVEN a templating node that accesses wrapper value properties
340
+ class TemplateNode(TemplatingNode[BaseState, str]):
341
+ pass
342
+
343
+ # Set template and inputs dynamically
344
+ TemplateNode.template = template
345
+ TemplateNode.inputs = inputs
346
+
347
+ # WHEN the node is run
348
+ node = TemplateNode()
349
+ outputs = node.run()
350
+
351
+ # THEN the value is accessible
352
+ assert outputs.result == expected_result
353
+
354
+
355
+ @pytest.mark.parametrize(
356
+ "template,inputs,expected_result",
357
+ [
358
+ # Dict value access
359
+ ("{{ data.value }}", {"data": {"name": "test", "score": 42}}, {"name": "test", "score": 42}),
360
+ # List value access
361
+ ("{{ items.value }}", {"items": ["item1", "item2", "item3"]}, ["item1", "item2", "item3"]),
362
+ ],
363
+ ids=["dict_value", "list_value"],
364
+ )
365
+ def test_templating_node__value_access_patterns_json(template, inputs, expected_result):
366
+ # GIVEN a templating node that accesses wrapper value properties
367
+ class TemplateNode(TemplatingNode[BaseState, Json]):
368
+ pass
369
+
370
+ # Set template and inputs dynamically
371
+ TemplateNode.template = template
372
+ TemplateNode.inputs = inputs
373
+
374
+ # WHEN the node is run
375
+ node = TemplateNode()
376
+ outputs = node.run()
377
+
378
+ # THEN the value is accessible
379
+ assert outputs.result == expected_result
380
+
381
+
382
+ @pytest.mark.parametrize(
383
+ "template,inputs,expected_result",
384
+ [
385
+ # String type access
386
+ ("{{ text_input.type }}", {"text_input": "hello world"}, "STRING"),
387
+ # Function call type access
388
+ ("{{ func.type }}", {"func": FunctionCall(name="test_function", arguments={"key": "value"})}, "FUNCTION_CALL"),
389
+ ],
390
+ ids=["string_type", "function_call_type"],
391
+ )
392
+ def test_templating_node__type_access_patterns(template, inputs, expected_result):
393
+ # GIVEN a templating node that accesses wrapper type properties
394
+ class TemplateNode(TemplatingNode[BaseState, str]):
395
+ pass
396
+
397
+ # Set template and inputs dynamically
398
+ TemplateNode.template = template
399
+ TemplateNode.inputs = inputs
400
+
401
+ # WHEN the node is run
402
+ node = TemplateNode()
403
+ outputs = node.run()
404
+
405
+ # THEN the type is accessible
406
+ assert outputs.result == expected_result
407
+
408
+
409
+ def test_templating_node__nested_dict_access():
410
+ # GIVEN a templating node with nested dict access
411
+ class TemplateNode(TemplatingNode[BaseState, str]):
412
+ template = "{{ data.user.name }}"
413
+ inputs = {"data": {"user": {"name": "John Doe", "age": 30}, "status": "active"}}
414
+
415
+ # WHEN the node is run
416
+ node = TemplateNode()
417
+ outputs = node.run()
418
+
419
+ # THEN nested properties are accessible
420
+ assert outputs.result == "John Doe"
421
+
422
+
423
+ def test_templating_node__list_iteration_wrapper_access():
424
+ # GIVEN a templating node that iterates over list with wrapper access
425
+ class TemplateNode(TemplatingNode[BaseState, str]):
426
+ template = "{% for item in items %}{{ item.value }}{% if not loop.last %},{% endif %}{% endfor %}"
427
+ inputs = {"items": ["apple", "banana", "cherry"]}
428
+
429
+ # WHEN the node is run
430
+ node = TemplateNode()
431
+ outputs = node.run()
432
+
433
+ # THEN list iteration with wrapper access works
434
+ assert outputs.result == "apple,banana,cherry"
435
+
436
+
437
+ def test_templating_node__conditional_type_checking():
438
+ # GIVEN a templating node with conditional type checking
439
+ class TemplateNode(TemplatingNode[BaseState, str]):
440
+ template = "{% if input.type == 'STRING' %}{{ input.value }}{% else %}unknown{% endif %}"
441
+ inputs = {"input": "test string"}
442
+
443
+ # WHEN the node is run
444
+ node = TemplateNode()
445
+ outputs = node.run()
446
+
447
+ # THEN conditional type checking works
448
+ assert outputs.result == "test string"
@@ -28,6 +28,7 @@ from vellum.workflows.errors.types import vellum_error_to_workflow_error
28
28
  from vellum.workflows.events.types import default_serializer
29
29
  from vellum.workflows.exceptions import NodeException
30
30
  from vellum.workflows.nodes.displayable.bases.base_prompt_node import BasePromptNode
31
+ from vellum.workflows.nodes.displayable.bases.utils import process_additional_prompt_outputs
31
32
  from vellum.workflows.outputs import BaseOutput
32
33
  from vellum.workflows.types import MergeBehavior
33
34
  from vellum.workflows.types.definition import DeploymentDefinition
@@ -197,6 +198,17 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
197
198
  elif event.state == "STREAMING":
198
199
  yield BaseOutput(name="results", delta=event.output.value)
199
200
  elif event.state == "FULFILLED":
201
+ if event.meta and event.meta.finish_reason == "LENGTH":
202
+ text_value, json_value = process_additional_prompt_outputs(event.outputs)
203
+ if text_value == "":
204
+ raise NodeException(
205
+ message=(
206
+ "Maximum tokens reached before model could output any content. "
207
+ "Consider increasing the max_tokens Prompt Parameter."
208
+ ),
209
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
210
+ )
211
+
200
212
  outputs = event.outputs
201
213
  yield BaseOutput(name="results", value=event.outputs)
202
214
  elif event.state == "REJECTED":
@@ -20,6 +20,7 @@ from vellum import (
20
20
  )
21
21
  from vellum.client.types.execute_prompt_event import ExecutePromptEvent
22
22
  from vellum.client.types.fulfilled_execute_prompt_event import FulfilledExecutePromptEvent
23
+ from vellum.client.types.fulfilled_prompt_execution_meta import FulfilledPromptExecutionMeta
23
24
  from vellum.client.types.initiated_execute_prompt_event import InitiatedExecutePromptEvent
24
25
  from vellum.client.types.prompt_output import PromptOutput
25
26
  from vellum.client.types.prompt_request_string_input import PromptRequestStringInput
@@ -684,3 +685,43 @@ def test_inline_prompt_node__invalid_function_type():
684
685
  # AND the error should have the correct code and message
685
686
  assert excinfo.value.code == WorkflowErrorCode.INVALID_INPUTS
686
687
  assert "`not_a_function` is not a valid function definition" == str(excinfo.value)
688
+
689
+
690
+ def test_inline_prompt_node__empty_string_output_with_length_finish_reason(vellum_adhoc_prompt_client):
691
+ """
692
+ Tests that InlinePromptNode raises NodeException for empty string output with LENGTH finish_reason.
693
+ """
694
+
695
+ # GIVEN an InlinePromptNode with basic configuration
696
+ class TestNode(InlinePromptNode):
697
+ ml_model = "test-model"
698
+ blocks = []
699
+ prompt_inputs = {}
700
+
701
+ expected_outputs: List[PromptOutput] = [
702
+ StringVellumValue(value=""),
703
+ ]
704
+
705
+ def generate_prompt_events(*args: Any, **kwargs: Any) -> Iterator[ExecutePromptEvent]:
706
+ execution_id = str(uuid4())
707
+ events: List[ExecutePromptEvent] = [
708
+ InitiatedExecutePromptEvent(execution_id=execution_id),
709
+ FulfilledExecutePromptEvent(
710
+ execution_id=execution_id,
711
+ outputs=expected_outputs,
712
+ meta=FulfilledPromptExecutionMeta(finish_reason="LENGTH"),
713
+ ),
714
+ ]
715
+ yield from events
716
+
717
+ vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
718
+
719
+ # WHEN the node is run
720
+ node = TestNode()
721
+
722
+ # THEN it should raise a NodeException with INVALID_OUTPUTS error code
723
+ with pytest.raises(NodeException) as excinfo:
724
+ list(node.run())
725
+
726
+ # AND the exception should have the correct error code
727
+ assert excinfo.value.code == WorkflowErrorCode.INVALID_OUTPUTS
@@ -1,7 +1,8 @@
1
1
  import enum
2
2
  import json
3
- from typing import Any, List, Union, cast
3
+ from typing import Any, List, Optional, Tuple, Union, cast
4
4
 
5
+ from vellum import PromptOutput
5
6
  from vellum.client.types.array_vellum_value import ArrayVellumValue
6
7
  from vellum.client.types.array_vellum_value_request import ArrayVellumValueRequest
7
8
  from vellum.client.types.audio_vellum_value import AudioVellumValue
@@ -123,3 +124,39 @@ def primitive_to_vellum_value_request(value: Any) -> VellumValueRequest:
123
124
  raise ValueError(f"Unsupported variable type: {vellum_value.__class__.__name__}")
124
125
 
125
126
  return vellum_value_request_class.model_validate(vellum_value.model_dump())
127
+
128
+
129
+ def process_additional_prompt_outputs(outputs: List[PromptOutput]) -> Tuple[str, Optional[Any]]:
130
+ """
131
+ Process prompt outputs using the same logic as prompt nodes to determine text output.
132
+
133
+ Args:
134
+ outputs: List of prompt outputs to process
135
+
136
+ Returns:
137
+ The text representation of the outputs joined with newlines
138
+ The JSON representation of the outputs
139
+ """
140
+ string_outputs = []
141
+ json_output = None
142
+ for output in outputs:
143
+ if output.value is None:
144
+ continue
145
+
146
+ if output.type == "STRING":
147
+ string_outputs.append(output.value)
148
+ try:
149
+ json_output = json.loads(output.value)
150
+ except (json.JSONDecodeError, TypeError):
151
+ pass
152
+ elif output.type == "JSON":
153
+ string_outputs.append(json.dumps(output.value, indent=4))
154
+ json_output = output.value
155
+ elif output.type == "FUNCTION_CALL":
156
+ string_outputs.append(output.value.model_dump_json(indent=4))
157
+ elif output.type == "THINKING":
158
+ continue
159
+ else:
160
+ string_outputs.append(output.value.message)
161
+
162
+ return "\n".join(string_outputs), json_output
@@ -4,13 +4,10 @@ import sys
4
4
  import traceback
5
5
  from typing import Any, Optional, Tuple, Union
6
6
 
7
- from pydantic import BaseModel
8
-
9
7
  from vellum.workflows.errors.types import WorkflowErrorCode
10
8
  from vellum.workflows.exceptions import NodeException
11
- from vellum.workflows.nodes.utils import cast_to_output_type
9
+ from vellum.workflows.nodes.utils import cast_to_output_type, wrap_inputs_for_backward_compatibility
12
10
  from vellum.workflows.state.context import WorkflowContext
13
- from vellum.workflows.types.code_execution_node_wrappers import ListWrapper, clean_for_dict_wrapper
14
11
  from vellum.workflows.types.core import EntityInputsInterface
15
12
 
16
13
 
@@ -51,23 +48,9 @@ def run_code_inline(
51
48
  print_line = f"{' '.join(str_args)}\n"
52
49
  log_buffer.write(print_line)
53
50
 
54
- def wrap_value(value):
55
- if isinstance(value, list):
56
- return ListWrapper(
57
- [
58
- # Convert VellumValue to dict with its fields
59
- (
60
- item.model_dump()
61
- if isinstance(item, BaseModel)
62
- else clean_for_dict_wrapper(item) if isinstance(item, (dict, list, str)) else item
63
- )
64
- for item in value
65
- ]
66
- )
67
- return clean_for_dict_wrapper(value)
68
-
51
+ wrapped_inputs = wrap_inputs_for_backward_compatibility(inputs)
69
52
  exec_globals = {
70
- "__arg__inputs": {name: wrap_value(value) for name, value in inputs.items()},
53
+ "__arg__inputs": wrapped_inputs,
71
54
  "__arg__out": None,
72
55
  "print": _inline_print,
73
56
  }