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.
- vellum/__init__.py +48 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/resources/workflows/client.py +20 -0
- vellum/client/resources/workflows/raw_client.py +20 -0
- vellum/client/types/__init__.py +48 -0
- vellum/client/types/audio_input.py +30 -0
- vellum/client/types/code_executor_input.py +8 -0
- vellum/client/types/document_input.py +30 -0
- vellum/client/types/image_input.py +30 -0
- vellum/client/types/named_scenario_input_audio_variable_value_request.py +22 -0
- vellum/client/types/named_scenario_input_document_variable_value_request.py +22 -0
- vellum/client/types/named_scenario_input_image_variable_value_request.py +22 -0
- vellum/client/types/named_scenario_input_request.py +8 -0
- vellum/client/types/named_scenario_input_video_variable_value_request.py +22 -0
- vellum/client/types/named_test_case_audio_variable_value.py +26 -0
- vellum/client/types/named_test_case_audio_variable_value_request.py +26 -0
- vellum/client/types/named_test_case_document_variable_value.py +22 -0
- vellum/client/types/named_test_case_document_variable_value_request.py +22 -0
- vellum/client/types/named_test_case_image_variable_value.py +22 -0
- vellum/client/types/named_test_case_image_variable_value_request.py +22 -0
- vellum/client/types/named_test_case_variable_value.py +8 -0
- vellum/client/types/named_test_case_variable_value_request.py +8 -0
- vellum/client/types/named_test_case_video_variable_value.py +22 -0
- vellum/client/types/named_test_case_video_variable_value_request.py +22 -0
- vellum/client/types/node_execution_span_attributes.py +1 -0
- vellum/client/types/scenario_input.py +11 -1
- vellum/client/types/scenario_input_audio_variable_value.py +22 -0
- vellum/client/types/scenario_input_document_variable_value.py +22 -0
- vellum/client/types/scenario_input_image_variable_value.py +22 -0
- vellum/client/types/scenario_input_video_variable_value.py +22 -0
- vellum/client/types/span_link.py +1 -1
- vellum/client/types/span_link_type_enum.py +1 -1
- vellum/client/types/test_case_audio_variable_value.py +27 -0
- vellum/client/types/test_case_document_variable_value.py +27 -0
- vellum/client/types/test_case_image_variable_value.py +27 -0
- vellum/client/types/test_case_variable_value.py +8 -0
- vellum/client/types/test_case_video_variable_value.py +27 -0
- vellum/client/types/video_input.py +30 -0
- vellum/client/types/workflow_push_deployment_config_request.py +1 -0
- vellum/types/audio_input.py +3 -0
- vellum/types/document_input.py +3 -0
- vellum/types/image_input.py +3 -0
- vellum/types/named_scenario_input_audio_variable_value_request.py +3 -0
- vellum/types/named_scenario_input_document_variable_value_request.py +3 -0
- vellum/types/named_scenario_input_image_variable_value_request.py +3 -0
- vellum/types/named_scenario_input_video_variable_value_request.py +3 -0
- vellum/types/named_test_case_audio_variable_value.py +3 -0
- vellum/types/named_test_case_audio_variable_value_request.py +3 -0
- vellum/types/named_test_case_document_variable_value.py +3 -0
- vellum/types/named_test_case_document_variable_value_request.py +3 -0
- vellum/types/named_test_case_image_variable_value.py +3 -0
- vellum/types/named_test_case_image_variable_value_request.py +3 -0
- vellum/types/named_test_case_video_variable_value.py +3 -0
- vellum/types/named_test_case_video_variable_value_request.py +3 -0
- vellum/types/scenario_input_audio_variable_value.py +3 -0
- vellum/types/scenario_input_document_variable_value.py +3 -0
- vellum/types/scenario_input_image_variable_value.py +3 -0
- vellum/types/scenario_input_video_variable_value.py +3 -0
- vellum/types/test_case_audio_variable_value.py +3 -0
- vellum/types/test_case_document_variable_value.py +3 -0
- vellum/types/test_case_image_variable_value.py +3 -0
- vellum/types/test_case_video_variable_value.py +3 -0
- vellum/types/video_input.py +3 -0
- vellum/workflows/events/tests/test_event.py +9 -0
- vellum/workflows/events/types.py +3 -1
- vellum/workflows/integrations/tests/test_mcp_service.py +40 -1
- vellum/workflows/nodes/core/templating_node/node.py +3 -2
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +129 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +12 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +41 -0
- vellum/workflows/nodes/displayable/bases/utils.py +38 -1
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -20
- vellum/workflows/nodes/displayable/inline_prompt_node/node.py +3 -26
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +3 -25
- vellum/workflows/nodes/utils.py +26 -1
- vellum/workflows/resolvers/base.py +18 -1
- vellum/workflows/resolvers/resolver.py +42 -0
- vellum/workflows/resolvers/tests/test_resolver.py +59 -0
- vellum/workflows/types/definition.py +1 -0
- vellum/workflows/utils/functions.py +4 -0
- vellum/workflows/utils/tests/test_functions.py +6 -3
- vellum/workflows/workflows/base.py +3 -0
- {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/METADATA +1 -1
- {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/RECORD +96 -46
- vellum_cli/__init__.py +6 -0
- vellum_cli/config.py +2 -0
- vellum_cli/push.py +3 -0
- vellum_cli/tests/test_pull.py +2 -0
- vellum_cli/tests/test_push.py +39 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +2 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +1 -0
- vellum_ee/workflows/display/utils/events.py +19 -1
- vellum_ee/workflows/display/utils/tests/test_events.py +42 -0
- {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/LICENSE +0 -0
- {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.5.dist-info}/WHEEL +0 -0
- {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
|
@@ -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
|
],
|
vellum/workflows/events/types.py
CHANGED
@@ -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.
|
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=
|
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":
|
vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py
CHANGED
@@ -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
|
-
|
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":
|
53
|
+
"__arg__inputs": wrapped_inputs,
|
71
54
|
"__arg__out": None,
|
72
55
|
"print": _inline_print,
|
73
56
|
}
|