vellum-ai 1.0.2__py3-none-any.whl → 1.0.4__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 (34) hide show
  1. vellum/__init__.py +6 -0
  2. vellum/client/__init__.py +4 -0
  3. vellum/client/core/client_wrapper.py +2 -2
  4. vellum/client/reference.md +58 -0
  5. vellum/client/resources/__init__.py +2 -0
  6. vellum/client/resources/workflow_executions/__init__.py +2 -0
  7. vellum/client/resources/workflow_executions/client.py +125 -0
  8. vellum/client/types/__init__.py +4 -0
  9. vellum/client/types/node_output_compiled_thinking_value.py +28 -0
  10. vellum/client/types/node_output_compiled_value.py +2 -0
  11. vellum/client/types/organization_limit_config.py +1 -0
  12. vellum/client/types/workflow_execution_detail.py +42 -0
  13. vellum/prompts/blocks/compilation.py +5 -1
  14. vellum/prompts/blocks/tests/test_compilation.py +64 -0
  15. vellum/resources/workflow_executions/__init__.py +3 -0
  16. vellum/resources/workflow_executions/client.py +3 -0
  17. vellum/types/node_output_compiled_thinking_value.py +3 -0
  18. vellum/types/workflow_execution_detail.py +3 -0
  19. vellum/workflows/descriptors/base.py +12 -0
  20. vellum/workflows/expressions/concat.py +32 -0
  21. vellum/workflows/expressions/tests/test_concat.py +53 -0
  22. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +1 -2
  23. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +1 -2
  24. vellum/workflows/nodes/displayable/tool_calling_node/composio_service.py +83 -0
  25. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py +122 -0
  26. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +28 -6
  27. vellum/workflows/types/core.py +2 -2
  28. vellum/workflows/types/definition.py +20 -1
  29. vellum/workflows/types/tests/test_definition.py +14 -1
  30. {vellum_ai-1.0.2.dist-info → vellum_ai-1.0.4.dist-info}/METADATA +3 -1
  31. {vellum_ai-1.0.2.dist-info → vellum_ai-1.0.4.dist-info}/RECORD +34 -22
  32. {vellum_ai-1.0.2.dist-info → vellum_ai-1.0.4.dist-info}/LICENSE +0 -0
  33. {vellum_ai-1.0.2.dist-info → vellum_ai-1.0.4.dist-info}/WHEEL +0 -0
  34. {vellum_ai-1.0.2.dist-info → vellum_ai-1.0.4.dist-info}/entry_points.txt +0 -0
vellum/__init__.py CHANGED
@@ -272,6 +272,7 @@ from .client.types import (
272
272
  NodeOutputCompiledNumberValue,
273
273
  NodeOutputCompiledSearchResultsValue,
274
274
  NodeOutputCompiledStringValue,
275
+ NodeOutputCompiledThinkingValue,
275
276
  NodeOutputCompiledValue,
276
277
  NodeParentContext,
277
278
  NormalizedLogProbs,
@@ -541,6 +542,7 @@ from .client.types import (
541
542
  WorkflowExecutionActualChatHistoryRequest,
542
543
  WorkflowExecutionActualJsonRequest,
543
544
  WorkflowExecutionActualStringRequest,
545
+ WorkflowExecutionDetail,
544
546
  WorkflowExecutionEventErrorCode,
545
547
  WorkflowExecutionEventType,
546
548
  WorkflowExecutionFulfilledBody,
@@ -631,6 +633,7 @@ from .resources import (
631
633
  test_suite_runs,
632
634
  test_suites,
633
635
  workflow_deployments,
636
+ workflow_executions,
634
637
  workflow_sandboxes,
635
638
  workflows,
636
639
  workspace_secrets,
@@ -919,6 +922,7 @@ __all__ = [
919
922
  "NodeOutputCompiledNumberValue",
920
923
  "NodeOutputCompiledSearchResultsValue",
921
924
  "NodeOutputCompiledStringValue",
925
+ "NodeOutputCompiledThinkingValue",
922
926
  "NodeOutputCompiledValue",
923
927
  "NodeParentContext",
924
928
  "NormalizedLogProbs",
@@ -1192,6 +1196,7 @@ __all__ = [
1192
1196
  "WorkflowExecutionActualChatHistoryRequest",
1193
1197
  "WorkflowExecutionActualJsonRequest",
1194
1198
  "WorkflowExecutionActualStringRequest",
1199
+ "WorkflowExecutionDetail",
1195
1200
  "WorkflowExecutionEventErrorCode",
1196
1201
  "WorkflowExecutionEventType",
1197
1202
  "WorkflowExecutionFulfilledBody",
@@ -1273,6 +1278,7 @@ __all__ = [
1273
1278
  "test_suite_runs",
1274
1279
  "test_suites",
1275
1280
  "workflow_deployments",
1281
+ "workflow_executions",
1276
1282
  "workflow_sandboxes",
1277
1283
  "workflows",
1278
1284
  "workspace_secrets",
vellum/client/__init__.py CHANGED
@@ -21,6 +21,7 @@ from .resources.test_suite_runs.client import TestSuiteRunsClient
21
21
  from .resources.test_suites.client import TestSuitesClient
22
22
  from .resources.workflow_deployments.client import WorkflowDeploymentsClient
23
23
  from .resources.release_reviews.client import ReleaseReviewsClient
24
+ from .resources.workflow_executions.client import WorkflowExecutionsClient
24
25
  from .resources.workflow_sandboxes.client import WorkflowSandboxesClient
25
26
  from .resources.workflows.client import WorkflowsClient
26
27
  from .resources.workspace_secrets.client import WorkspaceSecretsClient
@@ -79,6 +80,7 @@ from .resources.test_suite_runs.client import AsyncTestSuiteRunsClient
79
80
  from .resources.test_suites.client import AsyncTestSuitesClient
80
81
  from .resources.workflow_deployments.client import AsyncWorkflowDeploymentsClient
81
82
  from .resources.release_reviews.client import AsyncReleaseReviewsClient
83
+ from .resources.workflow_executions.client import AsyncWorkflowExecutionsClient
82
84
  from .resources.workflow_sandboxes.client import AsyncWorkflowSandboxesClient
83
85
  from .resources.workflows.client import AsyncWorkflowsClient
84
86
  from .resources.workspace_secrets.client import AsyncWorkspaceSecretsClient
@@ -163,6 +165,7 @@ class Vellum:
163
165
  self.test_suites = TestSuitesClient(client_wrapper=self._client_wrapper)
164
166
  self.workflow_deployments = WorkflowDeploymentsClient(client_wrapper=self._client_wrapper)
165
167
  self.release_reviews = ReleaseReviewsClient(client_wrapper=self._client_wrapper)
168
+ self.workflow_executions = WorkflowExecutionsClient(client_wrapper=self._client_wrapper)
166
169
  self.workflow_sandboxes = WorkflowSandboxesClient(client_wrapper=self._client_wrapper)
167
170
  self.workflows = WorkflowsClient(client_wrapper=self._client_wrapper)
168
171
  self.workspace_secrets = WorkspaceSecretsClient(client_wrapper=self._client_wrapper)
@@ -1597,6 +1600,7 @@ class AsyncVellum:
1597
1600
  self.test_suites = AsyncTestSuitesClient(client_wrapper=self._client_wrapper)
1598
1601
  self.workflow_deployments = AsyncWorkflowDeploymentsClient(client_wrapper=self._client_wrapper)
1599
1602
  self.release_reviews = AsyncReleaseReviewsClient(client_wrapper=self._client_wrapper)
1603
+ self.workflow_executions = AsyncWorkflowExecutionsClient(client_wrapper=self._client_wrapper)
1600
1604
  self.workflow_sandboxes = AsyncWorkflowSandboxesClient(client_wrapper=self._client_wrapper)
1601
1605
  self.workflows = AsyncWorkflowsClient(client_wrapper=self._client_wrapper)
1602
1606
  self.workspace_secrets = AsyncWorkspaceSecretsClient(client_wrapper=self._client_wrapper)
@@ -25,10 +25,10 @@ class BaseClientWrapper:
25
25
 
26
26
  def get_headers(self) -> typing.Dict[str, str]:
27
27
  headers: typing.Dict[str, str] = {
28
- "User-Agent": "vellum-ai/1.0.2",
28
+ "User-Agent": "vellum-ai/1.0.4",
29
29
  "X-Fern-Language": "Python",
30
30
  "X-Fern-SDK-Name": "vellum-ai",
31
- "X-Fern-SDK-Version": "1.0.2",
31
+ "X-Fern-SDK-Version": "1.0.4",
32
32
  }
33
33
  if self._api_version is not None:
34
34
  headers["X-API-Version"] = self._api_version
@@ -6029,6 +6029,64 @@ client.release_reviews.retrieve_workflow_deployment_release(
6029
6029
  </dl>
6030
6030
 
6031
6031
 
6032
+ </dd>
6033
+ </dl>
6034
+ </details>
6035
+
6036
+ ## WorkflowExecutions
6037
+ <details><summary><code>client.workflow_executions.<a href="src/vellum/resources/workflow_executions/client.py">retrieve_workflow_execution_detail</a>(...)</code></summary>
6038
+ <dl>
6039
+ <dd>
6040
+
6041
+ #### 🔌 Usage
6042
+
6043
+ <dl>
6044
+ <dd>
6045
+
6046
+ <dl>
6047
+ <dd>
6048
+
6049
+ ```python
6050
+ from vellum import Vellum
6051
+
6052
+ client = Vellum(
6053
+ api_version="YOUR_API_VERSION",
6054
+ api_key="YOUR_API_KEY",
6055
+ )
6056
+ client.workflow_executions.retrieve_workflow_execution_detail(
6057
+ execution_id="execution_id",
6058
+ )
6059
+
6060
+ ```
6061
+ </dd>
6062
+ </dl>
6063
+ </dd>
6064
+ </dl>
6065
+
6066
+ #### ⚙️ Parameters
6067
+
6068
+ <dl>
6069
+ <dd>
6070
+
6071
+ <dl>
6072
+ <dd>
6073
+
6074
+ **execution_id:** `str`
6075
+
6076
+ </dd>
6077
+ </dl>
6078
+
6079
+ <dl>
6080
+ <dd>
6081
+
6082
+ **request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
6083
+
6084
+ </dd>
6085
+ </dl>
6086
+ </dd>
6087
+ </dl>
6088
+
6089
+
6032
6090
  </dd>
6033
6091
  </dl>
6034
6092
  </details>
@@ -16,6 +16,7 @@ from . import (
16
16
  test_suite_runs,
17
17
  test_suites,
18
18
  workflow_deployments,
19
+ workflow_executions,
19
20
  workflow_sandboxes,
20
21
  workflows,
21
22
  workspace_secrets,
@@ -50,6 +51,7 @@ __all__ = [
50
51
  "test_suite_runs",
51
52
  "test_suites",
52
53
  "workflow_deployments",
54
+ "workflow_executions",
53
55
  "workflow_sandboxes",
54
56
  "workflows",
55
57
  "workspace_secrets",
@@ -0,0 +1,2 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
@@ -0,0 +1,125 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from ...core.client_wrapper import SyncClientWrapper
4
+ import typing
5
+ from ...core.request_options import RequestOptions
6
+ from ...types.workflow_execution_detail import WorkflowExecutionDetail
7
+ from ...core.jsonable_encoder import jsonable_encoder
8
+ from ...core.pydantic_utilities import parse_obj_as
9
+ from json.decoder import JSONDecodeError
10
+ from ...core.api_error import ApiError
11
+ from ...core.client_wrapper import AsyncClientWrapper
12
+
13
+
14
+ class WorkflowExecutionsClient:
15
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
16
+ self._client_wrapper = client_wrapper
17
+
18
+ def retrieve_workflow_execution_detail(
19
+ self, execution_id: str, *, request_options: typing.Optional[RequestOptions] = None
20
+ ) -> WorkflowExecutionDetail:
21
+ """
22
+ Parameters
23
+ ----------
24
+ execution_id : str
25
+
26
+ request_options : typing.Optional[RequestOptions]
27
+ Request-specific configuration.
28
+
29
+ Returns
30
+ -------
31
+ WorkflowExecutionDetail
32
+
33
+
34
+ Examples
35
+ --------
36
+ from vellum import Vellum
37
+
38
+ client = Vellum(
39
+ api_version="YOUR_API_VERSION",
40
+ api_key="YOUR_API_KEY",
41
+ )
42
+ client.workflow_executions.retrieve_workflow_execution_detail(
43
+ execution_id="execution_id",
44
+ )
45
+ """
46
+ _response = self._client_wrapper.httpx_client.request(
47
+ f"v1/workflow-executions/{jsonable_encoder(execution_id)}/detail",
48
+ base_url=self._client_wrapper.get_environment().default,
49
+ method="GET",
50
+ request_options=request_options,
51
+ )
52
+ try:
53
+ if 200 <= _response.status_code < 300:
54
+ return typing.cast(
55
+ WorkflowExecutionDetail,
56
+ parse_obj_as(
57
+ type_=WorkflowExecutionDetail, # type: ignore
58
+ object_=_response.json(),
59
+ ),
60
+ )
61
+ _response_json = _response.json()
62
+ except JSONDecodeError:
63
+ raise ApiError(status_code=_response.status_code, body=_response.text)
64
+ raise ApiError(status_code=_response.status_code, body=_response_json)
65
+
66
+
67
+ class AsyncWorkflowExecutionsClient:
68
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
69
+ self._client_wrapper = client_wrapper
70
+
71
+ async def retrieve_workflow_execution_detail(
72
+ self, execution_id: str, *, request_options: typing.Optional[RequestOptions] = None
73
+ ) -> WorkflowExecutionDetail:
74
+ """
75
+ Parameters
76
+ ----------
77
+ execution_id : str
78
+
79
+ request_options : typing.Optional[RequestOptions]
80
+ Request-specific configuration.
81
+
82
+ Returns
83
+ -------
84
+ WorkflowExecutionDetail
85
+
86
+
87
+ Examples
88
+ --------
89
+ import asyncio
90
+
91
+ from vellum import AsyncVellum
92
+
93
+ client = AsyncVellum(
94
+ api_version="YOUR_API_VERSION",
95
+ api_key="YOUR_API_KEY",
96
+ )
97
+
98
+
99
+ async def main() -> None:
100
+ await client.workflow_executions.retrieve_workflow_execution_detail(
101
+ execution_id="execution_id",
102
+ )
103
+
104
+
105
+ asyncio.run(main())
106
+ """
107
+ _response = await self._client_wrapper.httpx_client.request(
108
+ f"v1/workflow-executions/{jsonable_encoder(execution_id)}/detail",
109
+ base_url=self._client_wrapper.get_environment().default,
110
+ method="GET",
111
+ request_options=request_options,
112
+ )
113
+ try:
114
+ if 200 <= _response.status_code < 300:
115
+ return typing.cast(
116
+ WorkflowExecutionDetail,
117
+ parse_obj_as(
118
+ type_=WorkflowExecutionDetail, # type: ignore
119
+ object_=_response.json(),
120
+ ),
121
+ )
122
+ _response_json = _response.json()
123
+ except JSONDecodeError:
124
+ raise ApiError(status_code=_response.status_code, body=_response.text)
125
+ raise ApiError(status_code=_response.status_code, body=_response_json)
@@ -280,6 +280,7 @@ from .node_output_compiled_json_value import NodeOutputCompiledJsonValue
280
280
  from .node_output_compiled_number_value import NodeOutputCompiledNumberValue
281
281
  from .node_output_compiled_search_results_value import NodeOutputCompiledSearchResultsValue
282
282
  from .node_output_compiled_string_value import NodeOutputCompiledStringValue
283
+ from .node_output_compiled_thinking_value import NodeOutputCompiledThinkingValue
283
284
  from .node_output_compiled_value import NodeOutputCompiledValue
284
285
  from .node_parent_context import NodeParentContext
285
286
  from .normalized_log_probs import NormalizedLogProbs
@@ -565,6 +566,7 @@ from .workflow_execution_actual import WorkflowExecutionActual
565
566
  from .workflow_execution_actual_chat_history_request import WorkflowExecutionActualChatHistoryRequest
566
567
  from .workflow_execution_actual_json_request import WorkflowExecutionActualJsonRequest
567
568
  from .workflow_execution_actual_string_request import WorkflowExecutionActualStringRequest
569
+ from .workflow_execution_detail import WorkflowExecutionDetail
568
570
  from .workflow_execution_event_error_code import WorkflowExecutionEventErrorCode
569
571
  from .workflow_execution_event_type import WorkflowExecutionEventType
570
572
  from .workflow_execution_fulfilled_body import WorkflowExecutionFulfilledBody
@@ -900,6 +902,7 @@ __all__ = [
900
902
  "NodeOutputCompiledNumberValue",
901
903
  "NodeOutputCompiledSearchResultsValue",
902
904
  "NodeOutputCompiledStringValue",
905
+ "NodeOutputCompiledThinkingValue",
903
906
  "NodeOutputCompiledValue",
904
907
  "NodeParentContext",
905
908
  "NormalizedLogProbs",
@@ -1169,6 +1172,7 @@ __all__ = [
1169
1172
  "WorkflowExecutionActualChatHistoryRequest",
1170
1173
  "WorkflowExecutionActualJsonRequest",
1171
1174
  "WorkflowExecutionActualStringRequest",
1175
+ "WorkflowExecutionDetail",
1172
1176
  "WorkflowExecutionEventErrorCode",
1173
1177
  "WorkflowExecutionEventType",
1174
1178
  "WorkflowExecutionFulfilledBody",
@@ -0,0 +1,28 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from ..core.pydantic_utilities import UniversalBaseModel
4
+ import typing
5
+ from .string_vellum_value import StringVellumValue
6
+ from .workflow_node_result_event_state import WorkflowNodeResultEventState
7
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
8
+ import pydantic
9
+
10
+
11
+ class NodeOutputCompiledThinkingValue(UniversalBaseModel):
12
+ """
13
+ An output returned by a node that is of type THINKING.
14
+ """
15
+
16
+ type: typing.Literal["THINKING"] = "THINKING"
17
+ value: typing.Optional[StringVellumValue] = None
18
+ node_output_id: str
19
+ state: typing.Optional[WorkflowNodeResultEventState] = None
20
+
21
+ if IS_PYDANTIC_V2:
22
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
23
+ else:
24
+
25
+ class Config:
26
+ frozen = True
27
+ smart_union = True
28
+ extra = pydantic.Extra.allow
@@ -9,6 +9,7 @@ from .node_output_compiled_search_results_value import NodeOutputCompiledSearchR
9
9
  from .node_output_compiled_error_value import NodeOutputCompiledErrorValue
10
10
  from .node_output_compiled_array_value import NodeOutputCompiledArrayValue
11
11
  from .node_output_compiled_function_call_value import NodeOutputCompiledFunctionCallValue
12
+ from .node_output_compiled_thinking_value import NodeOutputCompiledThinkingValue
12
13
 
13
14
  NodeOutputCompiledValue = typing.Union[
14
15
  NodeOutputCompiledStringValue,
@@ -19,4 +20,5 @@ NodeOutputCompiledValue = typing.Union[
19
20
  NodeOutputCompiledErrorValue,
20
21
  NodeOutputCompiledArrayValue,
21
22
  NodeOutputCompiledFunctionCallValue,
23
+ NodeOutputCompiledThinkingValue,
22
24
  ]
@@ -13,6 +13,7 @@ class OrganizationLimitConfig(UniversalBaseModel):
13
13
  prompt_executions_quota: typing.Optional[Quota] = None
14
14
  workflow_executions_quota: typing.Optional[Quota] = None
15
15
  workflow_runtime_seconds_quota: typing.Optional[Quota] = None
16
+ max_workflow_runtime_seconds: typing.Optional[int] = None
16
17
 
17
18
  if IS_PYDANTIC_V2:
18
19
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -0,0 +1,42 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from ..core.pydantic_utilities import UniversalBaseModel
4
+ from .api_request_parent_context import ApiRequestParentContext
5
+ from .node_parent_context import NodeParentContext
6
+ from .prompt_deployment_parent_context import PromptDeploymentParentContext
7
+ from .span_link import SpanLink
8
+ from .workflow_deployment_parent_context import WorkflowDeploymentParentContext
9
+ from .workflow_parent_context import WorkflowParentContext
10
+ from .workflow_sandbox_parent_context import WorkflowSandboxParentContext
11
+ from .array_vellum_value import ArrayVellumValue
12
+ import typing
13
+ from .parent_context import ParentContext
14
+ import datetime as dt
15
+ from .execution_vellum_value import ExecutionVellumValue
16
+ from .workflow_error import WorkflowError
17
+ from .workflow_execution_usage_result import WorkflowExecutionUsageResult
18
+ from .vellum_span import VellumSpan
19
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
20
+ import pydantic
21
+
22
+
23
+ class WorkflowExecutionDetail(UniversalBaseModel):
24
+ span_id: str
25
+ parent_context: typing.Optional[ParentContext] = None
26
+ start: dt.datetime
27
+ end: typing.Optional[dt.datetime] = None
28
+ inputs: typing.List[ExecutionVellumValue]
29
+ outputs: typing.List[ExecutionVellumValue]
30
+ error: typing.Optional[WorkflowError] = None
31
+ usage_results: typing.Optional[typing.List[WorkflowExecutionUsageResult]] = None
32
+ spans: typing.List[VellumSpan]
33
+ state: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None
34
+
35
+ if IS_PYDANTIC_V2:
36
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
37
+ else:
38
+
39
+ class Config:
40
+ frozen = True
41
+ smart_union = True
42
+ extra = pydantic.Extra.allow
@@ -105,7 +105,11 @@ def compile_prompt_blocks(
105
105
  cache_config=block.cache_config,
106
106
  )
107
107
  )
108
- elif compiled_input == "JSON":
108
+ elif compiled_input.type == "JSON":
109
+ # Skip empty JSON arrays when there are chat message blocks present
110
+ if compiled_input.value == [] and any(block.block_type == "CHAT_MESSAGE" for block in compiled_blocks):
111
+ continue
112
+
109
113
  compiled_blocks.append(
110
114
  CompiledValuePromptBlock(
111
115
  content=JsonVellumValue(value=compiled_input.value),
@@ -10,7 +10,10 @@ from vellum import (
10
10
  VariablePromptBlock,
11
11
  VellumVariable,
12
12
  )
13
+ from vellum.client.types.json_vellum_value import JsonVellumValue
13
14
  from vellum.client.types.number_input import NumberInput
15
+ from vellum.client.types.prompt_block import PromptBlock
16
+ from vellum.client.types.prompt_request_json_input import PromptRequestJsonInput
14
17
  from vellum.prompts.blocks.compilation import compile_prompt_blocks
15
18
  from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock, CompiledValuePromptBlock
16
19
 
@@ -146,3 +149,64 @@ def test_compile_prompt_blocks__happy(blocks, inputs, input_variables, expected)
146
149
  actual = compile_prompt_blocks(blocks=blocks, inputs=inputs, input_variables=input_variables)
147
150
 
148
151
  assert actual == expected
152
+
153
+
154
+ def test_compile_prompt_blocks__empty_json_variable_with_chat_message_blocks():
155
+ """Test JSON variable handling logic, specifically the empty array skipping behavior."""
156
+
157
+ # GIVEN empty array with chat message blocks
158
+ blocks_with_chat: list[PromptBlock] = [
159
+ ChatMessagePromptBlock(
160
+ chat_role="USER",
161
+ blocks=[RichTextPromptBlock(blocks=[PlainTextPromptBlock(text="User message")])],
162
+ ),
163
+ VariablePromptBlock(input_variable="json_data"),
164
+ ]
165
+
166
+ inputs_with_empty_json = [PromptRequestJsonInput(key="json_data", value=[], type="JSON")]
167
+
168
+ input_variables = [VellumVariable(id="901ec2d6-430c-4341-b963-ca689006f5cc", type="JSON", key="json_data")]
169
+
170
+ # THEN the empty JSON array should be skipped when there are chat message blocks
171
+ expected_with_chat = [
172
+ CompiledChatMessagePromptBlock(
173
+ role="USER",
174
+ blocks=[CompiledValuePromptBlock(content=StringVellumValue(value="User message"))],
175
+ ),
176
+ ]
177
+
178
+ actual = compile_prompt_blocks(
179
+ blocks=blocks_with_chat, inputs=inputs_with_empty_json, input_variables=input_variables
180
+ )
181
+ assert actual == expected_with_chat
182
+
183
+
184
+ def test_compile_prompt_blocks__non_empty_json_variable_with_chat_message_blocks():
185
+ """Test that non-empty JSON variables are included even when there are chat message blocks."""
186
+
187
+ # GIVEN non-empty JSON with chat message blocks
188
+ blocks_with_chat: list[PromptBlock] = [
189
+ ChatMessagePromptBlock(
190
+ chat_role="USER",
191
+ blocks=[RichTextPromptBlock(blocks=[PlainTextPromptBlock(text="User message")])],
192
+ ),
193
+ VariablePromptBlock(input_variable="json_data"),
194
+ ]
195
+
196
+ inputs_with_non_empty_json = [PromptRequestJsonInput(key="json_data", value={"key": "value"}, type="JSON")]
197
+
198
+ input_variables = [VellumVariable(id="901ec2d6-430c-4341-b963-ca689006f5cc", type="JSON", key="json_data")]
199
+
200
+ # THEN the non-empty JSON should be included
201
+ expected_with_non_empty = [
202
+ CompiledChatMessagePromptBlock(
203
+ role="USER",
204
+ blocks=[CompiledValuePromptBlock(content=StringVellumValue(value="User message"))],
205
+ ),
206
+ CompiledValuePromptBlock(content=JsonVellumValue(value={"key": "value"})),
207
+ ]
208
+
209
+ actual = compile_prompt_blocks(
210
+ blocks=blocks_with_chat, inputs=inputs_with_non_empty_json, input_variables=input_variables
211
+ )
212
+ assert actual == expected_with_non_empty
@@ -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.resources.workflow_executions 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.resources.workflow_executions.client 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.node_output_compiled_thinking_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.workflow_execution_detail import *
@@ -6,6 +6,7 @@ if TYPE_CHECKING:
6
6
  from vellum.workflows.expressions.begins_with import BeginsWithExpression
7
7
  from vellum.workflows.expressions.between import BetweenExpression
8
8
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
9
+ from vellum.workflows.expressions.concat import ConcatExpression
9
10
  from vellum.workflows.expressions.contains import ContainsExpression
10
11
  from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
11
12
  from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
@@ -364,3 +365,14 @@ class BaseDescriptor(Generic[_T]):
364
365
  from vellum.workflows.expressions.is_error import IsErrorExpression
365
366
 
366
367
  return IsErrorExpression(expression=self)
368
+
369
+ @overload
370
+ def concat(self, other: "BaseDescriptor[_O]") -> "ConcatExpression[_T, _O]": ...
371
+
372
+ @overload
373
+ def concat(self, other: _O) -> "ConcatExpression[_T, _O]": ...
374
+
375
+ def concat(self, other: "Union[BaseDescriptor[_O], _O]") -> "ConcatExpression[_T, _O]":
376
+ from vellum.workflows.expressions.concat import ConcatExpression
377
+
378
+ return ConcatExpression(lhs=self, rhs=other)
@@ -0,0 +1,32 @@
1
+ from typing import Generic, TypeVar, Union
2
+
3
+ from vellum.workflows.descriptors.base import BaseDescriptor
4
+ from vellum.workflows.descriptors.exceptions import InvalidExpressionException
5
+ from vellum.workflows.descriptors.utils import resolve_value
6
+ from vellum.workflows.state.base import BaseState
7
+
8
+ LHS = TypeVar("LHS")
9
+ RHS = TypeVar("RHS")
10
+
11
+
12
+ class ConcatExpression(BaseDescriptor[list], Generic[LHS, RHS]):
13
+ def __init__(
14
+ self,
15
+ *,
16
+ lhs: Union[BaseDescriptor[LHS], LHS],
17
+ rhs: Union[BaseDescriptor[RHS], RHS],
18
+ ) -> None:
19
+ super().__init__(name=f"{lhs} + {rhs}", types=(list,))
20
+ self._lhs = lhs
21
+ self._rhs = rhs
22
+
23
+ def resolve(self, state: "BaseState") -> list:
24
+ lval = resolve_value(self._lhs, state)
25
+ rval = resolve_value(self._rhs, state)
26
+
27
+ if not isinstance(lval, list):
28
+ raise InvalidExpressionException(f"Expected LHS to be a list, got {type(lval)}")
29
+ if not isinstance(rval, list):
30
+ raise InvalidExpressionException(f"Expected RHS to be a list, got {type(rval)}")
31
+
32
+ return lval + rval
@@ -0,0 +1,53 @@
1
+ import pytest
2
+
3
+ from vellum.workflows.descriptors.exceptions import InvalidExpressionException
4
+ from vellum.workflows.references.constant import ConstantValueReference
5
+ from vellum.workflows.state.base import BaseState
6
+
7
+
8
+ class TestState(BaseState):
9
+ pass
10
+
11
+
12
+ def test_concat_expression_happy_path():
13
+ # GIVEN two lists
14
+ state = TestState()
15
+ lhs_ref = ConstantValueReference([1, 2, 3])
16
+ rhs_ref = ConstantValueReference([4, 5, 6])
17
+ concat_expr = lhs_ref.concat(rhs_ref)
18
+
19
+ # WHEN we resolve the expression
20
+ result = concat_expr.resolve(state)
21
+
22
+ # THEN the lists should be concatenated
23
+ assert result == [1, 2, 3, 4, 5, 6]
24
+
25
+
26
+ def test_concat_expression_lhs_fail():
27
+ # GIVEN a non-list lhs and a list rhs
28
+ state = TestState()
29
+ lhs_ref = ConstantValueReference(0)
30
+ rhs_ref = ConstantValueReference([4, 5, 6])
31
+ concat_expr = lhs_ref.concat(rhs_ref)
32
+
33
+ # WHEN we attempt to resolve the expression
34
+ with pytest.raises(InvalidExpressionException) as exc_info:
35
+ concat_expr.resolve(state)
36
+
37
+ # THEN an exception should be raised
38
+ assert "Expected LHS to be a list, got <class 'int'>" in str(exc_info.value)
39
+
40
+
41
+ def test_concat_expression_rhs_fail():
42
+ # GIVEN a list lhs and a non-list rhs
43
+ state = TestState()
44
+ lhs_ref = ConstantValueReference([1, 2, 3])
45
+ rhs_ref = ConstantValueReference(False)
46
+ concat_expr = lhs_ref.concat(rhs_ref)
47
+
48
+ # WHEN we attempt to resolve the expression
49
+ with pytest.raises(InvalidExpressionException) as exc_info:
50
+ concat_expr.resolve(state)
51
+
52
+ # THEN an exception should be raised
53
+ assert "Expected RHS to be a list, got <class 'bool'>" in str(exc_info.value)
@@ -64,8 +64,7 @@ class InlinePromptNode(BaseInlinePromptNode[StateType]):
64
64
  elif output.type == "FUNCTION_CALL":
65
65
  string_outputs.append(output.value.model_dump_json(indent=4))
66
66
  elif output.type == "THINKING":
67
- if output.value.type == "STRING":
68
- string_outputs.append(output.value.value)
67
+ continue
69
68
  else:
70
69
  string_outputs.append(output.value.message)
71
70