vellum-ai 0.12.6__py3-none-any.whl → 0.12.8__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 (32) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/evaluations/resources.py +1 -1
  3. vellum/prompts/__init__.py +0 -0
  4. vellum/prompts/blocks/__init__.py +0 -0
  5. vellum/prompts/blocks/compilation.py +190 -0
  6. vellum/prompts/blocks/exceptions.py +2 -0
  7. vellum/prompts/blocks/tests/__init__.py +0 -0
  8. vellum/prompts/blocks/tests/test_compilation.py +110 -0
  9. vellum/prompts/blocks/types.py +36 -0
  10. vellum/utils/__init__.py +0 -0
  11. vellum/utils/templating/__init__.py +0 -0
  12. vellum/utils/templating/constants.py +28 -0
  13. vellum/{workflows/nodes/core/templating_node → utils/templating}/render.py +1 -1
  14. vellum/workflows/nodes/core/templating_node/node.py +5 -31
  15. vellum/workflows/nodes/experimental/README.md +6 -0
  16. vellum/workflows/nodes/experimental/__init__.py +0 -0
  17. vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py +5 -0
  18. vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py +260 -0
  19. vellum/workflows/sandbox.py +3 -0
  20. {vellum_ai-0.12.6.dist-info → vellum_ai-0.12.8.dist-info}/METADATA +2 -1
  21. {vellum_ai-0.12.6.dist-info → vellum_ai-0.12.8.dist-info}/RECORD +32 -18
  22. vellum_cli/__init__.py +31 -0
  23. vellum_cli/pull.py +14 -0
  24. vellum_cli/tests/test_pull.py +29 -0
  25. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
  26. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +1 -2
  27. /vellum/{workflows/nodes/core/templating_node → utils/templating}/custom_filters.py +0 -0
  28. /vellum/{workflows/nodes/core/templating_node → utils/templating}/exceptions.py +0 -0
  29. /vellum/{evaluations/utils → utils}/typing.py +0 -0
  30. {vellum_ai-0.12.6.dist-info → vellum_ai-0.12.8.dist-info}/LICENSE +0 -0
  31. {vellum_ai-0.12.6.dist-info → vellum_ai-0.12.8.dist-info}/WHEEL +0 -0
  32. {vellum_ai-0.12.6.dist-info → vellum_ai-0.12.8.dist-info}/entry_points.txt +0 -0
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.12.6",
21
+ "X-Fern-SDK-Version": "0.12.8",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -12,7 +12,6 @@ from vellum.evaluations.constants import DEFAULT_MAX_POLLING_DURATION_MS, DEFAUL
12
12
  from vellum.evaluations.exceptions import TestSuiteRunResultsException
13
13
  from vellum.evaluations.utils.env import get_api_key
14
14
  from vellum.evaluations.utils.paginator import PaginatedResults, get_all_results
15
- from vellum.evaluations.utils.typing import cast_not_optional
16
15
  from vellum.evaluations.utils.uuid import is_valid_uuid
17
16
  from vellum.types import (
18
17
  ExternalTestCaseExecutionRequest,
@@ -24,6 +23,7 @@ from vellum.types import (
24
23
  TestSuiteRunMetricOutput,
25
24
  TestSuiteRunState,
26
25
  )
26
+ from vellum.utils.typing import cast_not_optional
27
27
 
28
28
  logger = logging.getLogger(__name__)
29
29
 
File without changes
File without changes
@@ -0,0 +1,190 @@
1
+ import json
2
+ from typing import Optional, cast
3
+
4
+ from vellum import (
5
+ ChatMessage,
6
+ JsonVellumValue,
7
+ PromptBlock,
8
+ PromptRequestInput,
9
+ RichTextPromptBlock,
10
+ StringVellumValue,
11
+ VellumVariable,
12
+ )
13
+ from vellum.prompts.blocks.exceptions import PromptCompilationError
14
+ from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock, CompiledPromptBlock, CompiledValuePromptBlock
15
+ from vellum.utils.templating.constants import DEFAULT_JINJA_CUSTOM_FILTERS
16
+ from vellum.utils.templating.render import render_sandboxed_jinja_template
17
+ from vellum.utils.typing import cast_not_optional
18
+
19
+
20
+ def compile_prompt_blocks(
21
+ blocks: list[PromptBlock],
22
+ inputs: list[PromptRequestInput],
23
+ input_variables: list[VellumVariable],
24
+ ) -> list[CompiledPromptBlock]:
25
+ """Compiles a list of Prompt Blocks, performing all variable substitutions and Jinja templating needed."""
26
+
27
+ sanitized_inputs = _sanitize_inputs(inputs)
28
+
29
+ compiled_blocks: list[CompiledPromptBlock] = []
30
+ for block in blocks:
31
+ if block.state == "DISABLED":
32
+ continue
33
+
34
+ if block.block_type == "CHAT_MESSAGE":
35
+ chat_role = cast_not_optional(block.chat_role)
36
+ inner_blocks = cast_not_optional(block.blocks)
37
+ unterminated = block.chat_message_unterminated or False
38
+
39
+ inner_prompt_blocks = compile_prompt_blocks(
40
+ inner_blocks,
41
+ sanitized_inputs,
42
+ input_variables,
43
+ )
44
+ if not inner_prompt_blocks:
45
+ continue
46
+
47
+ compiled_blocks.append(
48
+ CompiledChatMessagePromptBlock(
49
+ role=chat_role,
50
+ unterminated=unterminated,
51
+ source=block.chat_source,
52
+ blocks=[inner for inner in inner_prompt_blocks if inner.block_type == "VALUE"],
53
+ cache_config=block.cache_config,
54
+ )
55
+ )
56
+
57
+ elif block.block_type == "JINJA":
58
+ if block.template is None:
59
+ continue
60
+
61
+ rendered_template = render_sandboxed_jinja_template(
62
+ template=block.template,
63
+ input_values={input_.key: input_.value for input_ in sanitized_inputs},
64
+ jinja_custom_filters=DEFAULT_JINJA_CUSTOM_FILTERS,
65
+ jinja_globals=DEFAULT_JINJA_CUSTOM_FILTERS,
66
+ )
67
+ jinja_content = StringVellumValue(value=rendered_template)
68
+
69
+ compiled_blocks.append(
70
+ CompiledValuePromptBlock(
71
+ content=jinja_content,
72
+ cache_config=block.cache_config,
73
+ )
74
+ )
75
+
76
+ elif block.block_type == "VARIABLE":
77
+ compiled_input: Optional[PromptRequestInput] = next(
78
+ (input_ for input_ in sanitized_inputs if input_.key == str(block.input_variable)), None
79
+ )
80
+ if compiled_input is None:
81
+ raise PromptCompilationError(f"Input variable '{block.input_variable}' not found")
82
+
83
+ if compiled_input.type == "CHAT_HISTORY":
84
+ history = cast(list[ChatMessage], compiled_input.value)
85
+ chat_message_blocks = _compile_chat_messages_as_prompt_blocks(history)
86
+ compiled_blocks.extend(chat_message_blocks)
87
+ continue
88
+
89
+ if compiled_input.type == "STRING":
90
+ compiled_blocks.append(
91
+ CompiledValuePromptBlock(
92
+ content=StringVellumValue(value=compiled_input.value),
93
+ cache_config=block.cache_config,
94
+ )
95
+ )
96
+ elif compiled_input == "JSON":
97
+ compiled_blocks.append(
98
+ CompiledValuePromptBlock(
99
+ content=JsonVellumValue(value=compiled_input.value),
100
+ cache_config=block.cache_config,
101
+ )
102
+ )
103
+ elif compiled_input.type == "CHAT_HISTORY":
104
+ chat_message_blocks = _compile_chat_messages_as_prompt_blocks(compiled_input.value)
105
+ compiled_blocks.extend(chat_message_blocks)
106
+ else:
107
+ raise ValueError(f"Invalid input type for variable block: {compiled_input.type}")
108
+
109
+ elif block.block_type == "RICH_TEXT":
110
+ value_block = _compile_rich_text_block_as_value_block(block=block, inputs=sanitized_inputs)
111
+ compiled_blocks.append(value_block)
112
+
113
+ elif block.block_type == "FUNCTION_DEFINITION":
114
+ raise ValueError("Function definitions shouldn't go through compilation process")
115
+ else:
116
+ raise ValueError(f"Unknown block_type: {block.block_type}")
117
+
118
+ return compiled_blocks
119
+
120
+
121
+ def _compile_chat_messages_as_prompt_blocks(chat_messages: list[ChatMessage]) -> list[CompiledChatMessagePromptBlock]:
122
+ blocks: list[CompiledChatMessagePromptBlock] = []
123
+ for chat_message in chat_messages:
124
+ if chat_message.content is None:
125
+ continue
126
+
127
+ chat_message_blocks = (
128
+ [
129
+ CompiledValuePromptBlock(
130
+ content=item,
131
+ )
132
+ for item in chat_message.content.value
133
+ ]
134
+ if chat_message.content.type == "ARRAY"
135
+ else [
136
+ CompiledValuePromptBlock(
137
+ content=chat_message.content,
138
+ )
139
+ ]
140
+ )
141
+
142
+ blocks.append(
143
+ CompiledChatMessagePromptBlock(
144
+ role=chat_message.role,
145
+ unterminated=False,
146
+ blocks=chat_message_blocks,
147
+ source=chat_message.source,
148
+ )
149
+ )
150
+
151
+ return blocks
152
+
153
+
154
+ def _compile_rich_text_block_as_value_block(
155
+ block: RichTextPromptBlock,
156
+ inputs: list[PromptRequestInput],
157
+ ) -> CompiledValuePromptBlock:
158
+ value: str = ""
159
+ for child_block in block.blocks:
160
+ if child_block.block_type == "PLAIN_TEXT":
161
+ value += child_block.text
162
+ elif child_block.block_type == "VARIABLE":
163
+ variable = next((input_ for input_ in inputs if input_.key == str(child_block.input_variable)), None)
164
+ if variable is None:
165
+ raise PromptCompilationError(f"Input variable '{child_block.input_variable}' not found")
166
+ elif variable.type == "STRING":
167
+ value += str(variable.value)
168
+ elif variable.type == "JSON":
169
+ value += json.dumps(variable.value, indent=4)
170
+ else:
171
+ raise PromptCompilationError(
172
+ f"Input variable '{child_block.input_variable}' must be of type STRING or JSON"
173
+ )
174
+ else:
175
+ raise ValueError(f"Invalid child block_type for RICH_TEXT: {child_block.block_type}")
176
+
177
+ return CompiledValuePromptBlock(content=StringVellumValue(value=value), cache_config=block.cache_config)
178
+
179
+
180
+ def _sanitize_inputs(inputs: list[PromptRequestInput]) -> list[PromptRequestInput]:
181
+ sanitized_inputs: list[PromptRequestInput] = []
182
+ for input_ in inputs:
183
+ if input_.type == "CHAT_HISTORY" and input_.value is None:
184
+ sanitized_inputs.append(input_.model_copy(update={"value": cast(list[ChatMessage], [])}))
185
+ elif input_.type == "STRING" and input_.value is None:
186
+ sanitized_inputs.append(input_.model_copy(update={"value": ""}))
187
+ else:
188
+ sanitized_inputs.append(input_)
189
+
190
+ return sanitized_inputs
@@ -0,0 +1,2 @@
1
+ class PromptCompilationError(Exception):
2
+ pass
File without changes
@@ -0,0 +1,110 @@
1
+ import pytest
2
+
3
+ from vellum import (
4
+ ChatMessagePromptBlock,
5
+ JinjaPromptBlock,
6
+ PlainTextPromptBlock,
7
+ PromptRequestStringInput,
8
+ RichTextPromptBlock,
9
+ StringVellumValue,
10
+ VariablePromptBlock,
11
+ VellumVariable,
12
+ )
13
+ from vellum.prompts.blocks.compilation import compile_prompt_blocks
14
+ from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock, CompiledValuePromptBlock
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ ["blocks", "inputs", "input_variables", "expected"],
19
+ [
20
+ # Empty
21
+ ([], [], [], []),
22
+ # Jinja
23
+ (
24
+ [JinjaPromptBlock(template="Hello, world!")],
25
+ [],
26
+ [],
27
+ [
28
+ CompiledValuePromptBlock(content=StringVellumValue(value="Hello, world!")),
29
+ ],
30
+ ),
31
+ (
32
+ [JinjaPromptBlock(template="Repeat back to me {{ echo }}")],
33
+ [PromptRequestStringInput(key="echo", value="Hello, world!")],
34
+ [VellumVariable(id="1", type="STRING", key="echo")],
35
+ [
36
+ CompiledValuePromptBlock(content=StringVellumValue(value="Repeat back to me Hello, world!")),
37
+ ],
38
+ ),
39
+ # Rich Text
40
+ (
41
+ [
42
+ RichTextPromptBlock(
43
+ blocks=[
44
+ PlainTextPromptBlock(text="Hello, world!"),
45
+ ]
46
+ )
47
+ ],
48
+ [],
49
+ [],
50
+ [
51
+ CompiledValuePromptBlock(content=StringVellumValue(value="Hello, world!")),
52
+ ],
53
+ ),
54
+ (
55
+ [
56
+ RichTextPromptBlock(
57
+ blocks=[
58
+ PlainTextPromptBlock(text='Repeat back to me "'),
59
+ VariablePromptBlock(input_variable="echo"),
60
+ PlainTextPromptBlock(text='".'),
61
+ ]
62
+ )
63
+ ],
64
+ [PromptRequestStringInput(key="echo", value="Hello, world!")],
65
+ [VellumVariable(id="901ec2d6-430c-4341-b963-ca689006f5cc", type="STRING", key="echo")],
66
+ [
67
+ CompiledValuePromptBlock(content=StringVellumValue(value='Repeat back to me "Hello, world!".')),
68
+ ],
69
+ ),
70
+ # Chat Message
71
+ (
72
+ [
73
+ ChatMessagePromptBlock(
74
+ chat_role="USER",
75
+ blocks=[
76
+ RichTextPromptBlock(
77
+ blocks=[
78
+ PlainTextPromptBlock(text='Repeat back to me "'),
79
+ VariablePromptBlock(input_variable="echo"),
80
+ PlainTextPromptBlock(text='".'),
81
+ ]
82
+ )
83
+ ],
84
+ )
85
+ ],
86
+ [PromptRequestStringInput(key="echo", value="Hello, world!")],
87
+ [VellumVariable(id="901ec2d6-430c-4341-b963-ca689006f5cc", type="STRING", key="echo")],
88
+ [
89
+ CompiledChatMessagePromptBlock(
90
+ role="USER",
91
+ blocks=[
92
+ CompiledValuePromptBlock(content=StringVellumValue(value='Repeat back to me "Hello, world!".'))
93
+ ],
94
+ ),
95
+ ],
96
+ ),
97
+ ],
98
+ ids=[
99
+ "empty",
100
+ "jinja-no-variables",
101
+ "jinja-with-variables",
102
+ "rich-text-no-variables",
103
+ "rich-text-with-variables",
104
+ "chat-message",
105
+ ],
106
+ )
107
+ def test_compile_prompt_blocks__happy(blocks, inputs, input_variables, expected):
108
+ actual = compile_prompt_blocks(blocks=blocks, inputs=inputs, input_variables=input_variables)
109
+
110
+ assert actual == expected
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated, Literal, Optional, Union
4
+
5
+ from vellum import ArrayVellumValue, ChatMessageRole, EphemeralPromptCacheConfig, VellumValue
6
+ from vellum.client.core import UniversalBaseModel
7
+
8
+
9
+ class BaseCompiledPromptBlock(UniversalBaseModel):
10
+ cache_config: Optional[EphemeralPromptCacheConfig] = None
11
+
12
+
13
+ class CompiledValuePromptBlock(BaseCompiledPromptBlock):
14
+ block_type: Literal["VALUE"] = "VALUE"
15
+ content: VellumValue
16
+
17
+
18
+ class CompiledChatMessagePromptBlock(BaseCompiledPromptBlock):
19
+ block_type: Literal["CHAT_MESSAGE"] = "CHAT_MESSAGE"
20
+ role: ChatMessageRole = "ASSISTANT"
21
+ unterminated: bool = False
22
+ blocks: list[CompiledValuePromptBlock] = []
23
+ source: Optional[str] = None
24
+
25
+
26
+ CompiledPromptBlock = Annotated[
27
+ Union[
28
+ CompiledValuePromptBlock,
29
+ CompiledChatMessagePromptBlock,
30
+ ],
31
+ "block_type",
32
+ ]
33
+
34
+ ArrayVellumValue.model_rebuild()
35
+
36
+ CompiledValuePromptBlock.model_rebuild()
File without changes
File without changes
@@ -0,0 +1,28 @@
1
+ import datetime
2
+ import itertools
3
+ import json
4
+ import random
5
+ import re
6
+ from typing import Any, Callable, Dict, Union
7
+
8
+ import dateutil
9
+ import pydash
10
+ import pytz
11
+ import yaml
12
+
13
+ from vellum.utils.templating.custom_filters import is_valid_json_string
14
+
15
+ DEFAULT_JINJA_GLOBALS: Dict[str, Any] = {
16
+ "datetime": datetime,
17
+ "dateutil": dateutil,
18
+ "itertools": itertools,
19
+ "json": json,
20
+ "pydash": pydash,
21
+ "pytz": pytz,
22
+ "random": random,
23
+ "re": re,
24
+ "yaml": yaml,
25
+ }
26
+ DEFAULT_JINJA_CUSTOM_FILTERS: Dict[str, Callable[[Union[str, bytes]], bool]] = {
27
+ "is_valid_json_string": is_valid_json_string,
28
+ }
@@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, Optional, Union
3
3
 
4
4
  from jinja2.sandbox import SandboxedEnvironment
5
5
 
6
- from vellum.workflows.nodes.core.templating_node.exceptions import JinjaTemplateError
6
+ from vellum.utils.templating.exceptions import JinjaTemplateError
7
7
  from vellum.workflows.state.encoder import DefaultStateEncoder
8
8
 
9
9
 
@@ -1,42 +1,16 @@
1
- import datetime
2
- import itertools
3
- import json
4
- import random
5
- import re
6
1
  from typing import Any, Callable, ClassVar, Dict, Generic, Mapping, Tuple, Type, TypeVar, Union, get_args
7
2
 
8
- import dateutil.parser
9
- import pydash
10
- import pytz
11
- import yaml
12
-
3
+ from vellum.utils.templating.constants import DEFAULT_JINJA_CUSTOM_FILTERS, DEFAULT_JINJA_GLOBALS
4
+ from vellum.utils.templating.exceptions import JinjaTemplateError
5
+ from vellum.utils.templating.render import render_sandboxed_jinja_template
13
6
  from vellum.workflows.errors import WorkflowErrorCode
14
7
  from vellum.workflows.exceptions import NodeException
15
8
  from vellum.workflows.nodes.bases import BaseNode
16
9
  from vellum.workflows.nodes.bases.base import BaseNodeMeta
17
- from vellum.workflows.nodes.core.templating_node.custom_filters import is_valid_json_string
18
- from vellum.workflows.nodes.core.templating_node.exceptions import JinjaTemplateError
19
- from vellum.workflows.nodes.core.templating_node.render import render_sandboxed_jinja_template
20
10
  from vellum.workflows.types.core import EntityInputsInterface
21
11
  from vellum.workflows.types.generics import StateType
22
12
  from vellum.workflows.types.utils import get_original_base
23
13
 
24
- _DEFAULT_JINJA_GLOBALS: Dict[str, Any] = {
25
- "datetime": datetime,
26
- "dateutil": dateutil,
27
- "itertools": itertools,
28
- "json": json,
29
- "pydash": pydash,
30
- "pytz": pytz,
31
- "random": random,
32
- "re": re,
33
- "yaml": yaml,
34
- }
35
-
36
- _DEFAULT_JINJA_CUSTOM_FILTERS: Dict[str, Callable[[Union[str, bytes]], bool]] = {
37
- "is_valid_json_string": is_valid_json_string,
38
- }
39
-
40
14
  _OutputType = TypeVar("_OutputType")
41
15
 
42
16
 
@@ -78,8 +52,8 @@ class TemplatingNode(BaseNode[StateType], Generic[StateType, _OutputType], metac
78
52
  # The inputs to render the template with.
79
53
  inputs: ClassVar[EntityInputsInterface]
80
54
 
81
- jinja_globals: Dict[str, Any] = _DEFAULT_JINJA_GLOBALS
82
- jinja_custom_filters: Mapping[str, Callable[[Union[str, bytes]], bool]] = _DEFAULT_JINJA_CUSTOM_FILTERS
55
+ jinja_globals: Dict[str, Any] = DEFAULT_JINJA_GLOBALS
56
+ jinja_custom_filters: Mapping[str, Callable[[Union[str, bytes]], bool]] = DEFAULT_JINJA_CUSTOM_FILTERS
83
57
 
84
58
  class Outputs(BaseNode.Outputs):
85
59
  """
@@ -0,0 +1,6 @@
1
+ # 🧪 Experimental
2
+
3
+ This section is a proofing ground for new ideas and concepts. It's not meant to be used in production and is
4
+ subject to breaking changes at any time.
5
+
6
+ If a concept within is sufficiently interesting and validated, then it may be introduced as a first-class node.
File without changes
@@ -0,0 +1,5 @@
1
+ from .node import OpenAIChatCompletionNode
2
+
3
+ __all__ = [
4
+ "OpenAIChatCompletionNode",
5
+ ]
@@ -0,0 +1,260 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from uuid import uuid4
5
+ from typing import Any, Iterable, Iterator, List, Literal, Union, cast
6
+
7
+ from openai import OpenAI
8
+ from openai.types.chat import (
9
+ ChatCompletionAssistantMessageParam,
10
+ ChatCompletionContentPartImageParam,
11
+ ChatCompletionContentPartInputAudioParam,
12
+ ChatCompletionContentPartParam,
13
+ ChatCompletionContentPartRefusalParam,
14
+ ChatCompletionContentPartTextParam,
15
+ ChatCompletionMessageParam,
16
+ ChatCompletionSystemMessageParam,
17
+ ChatCompletionUserMessageParam,
18
+ )
19
+ from openai.types.chat.chat_completion_chunk import Choice
20
+
21
+ from vellum import (
22
+ AdHocExecutePromptEvent,
23
+ FulfilledAdHocExecutePromptEvent,
24
+ InitiatedAdHocExecutePromptEvent,
25
+ RejectedAdHocExecutePromptEvent,
26
+ StreamingAdHocExecutePromptEvent,
27
+ StringVellumValue,
28
+ VellumAudio,
29
+ VellumError,
30
+ )
31
+ from vellum.prompts.blocks.compilation import compile_prompt_blocks
32
+ from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock
33
+ from vellum.workflows.errors import WorkflowErrorCode
34
+ from vellum.workflows.exceptions import NodeException
35
+ from vellum.workflows.nodes import InlinePromptNode
36
+ from vellum.workflows.types.generics import StateType
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ class OpenAIChatCompletionNode(InlinePromptNode[StateType]):
42
+ """
43
+ Used to execute a Prompt using the OpenAI API.
44
+ """
45
+
46
+ # Override
47
+ def _get_prompt_event_stream(self) -> Iterator[AdHocExecutePromptEvent]:
48
+ client = self._get_client()
49
+
50
+ execution_id = str(uuid4())
51
+
52
+ yield InitiatedAdHocExecutePromptEvent(
53
+ execution_id=execution_id,
54
+ )
55
+
56
+ try:
57
+ stream = client.chat.completions.create(
58
+ messages=self._get_messages(),
59
+ model=self.ml_model,
60
+ # TODO: Add support for additional parameters
61
+ stream=True,
62
+ )
63
+ except Exception as exc:
64
+ yield RejectedAdHocExecutePromptEvent(
65
+ error=VellumError(
66
+ code=WorkflowErrorCode.PROVIDER_ERROR,
67
+ message=exc.args[0],
68
+ ),
69
+ execution_id=execution_id,
70
+ )
71
+ return
72
+
73
+ combined_delta_content = ""
74
+ for chunk in stream:
75
+ choices: List[Choice] = chunk.choices
76
+ if len(choices) != 1:
77
+ yield RejectedAdHocExecutePromptEvent(
78
+ error=VellumError(
79
+ code=WorkflowErrorCode.PROVIDER_ERROR,
80
+ message="Expected one choice per chunk, but found more than one.",
81
+ ),
82
+ execution_id=execution_id,
83
+ )
84
+ return
85
+
86
+ choice = choices[0]
87
+ delta = choice.delta
88
+
89
+ if delta.tool_calls:
90
+ # TODO: Add support for tool calls
91
+ raise NotImplementedError("This node hasn't been extended to support tool calling yet.")
92
+
93
+ if delta.content:
94
+ combined_delta_content += delta.content
95
+
96
+ StreamingAdHocExecutePromptEvent(
97
+ output=StringVellumValue(value=delta.content),
98
+ # TODO: Add support for multiple outputs
99
+ output_index=1,
100
+ execution_id=execution_id,
101
+ )
102
+
103
+ yield FulfilledAdHocExecutePromptEvent(
104
+ # TODO: Add support for multiple outputs
105
+ outputs=[
106
+ StringVellumValue(value=combined_delta_content),
107
+ ],
108
+ execution_id=execution_id,
109
+ )
110
+
111
+ def _get_client(self) -> OpenAI:
112
+ """Used to retrieve an API client for interacting with the OpenAI API.
113
+
114
+ Note: This method can be overridden if you'd like to use your own API client that conforms to the same
115
+ interfaces as that of OpenAI.
116
+ """
117
+
118
+ openai_api_key = os.environ.get("OPENAI_API_KEY")
119
+
120
+ if not openai_api_key:
121
+ raise NodeException(
122
+ code=WorkflowErrorCode.INTERNAL_ERROR,
123
+ message="Unable to determine an OpenAI API key.",
124
+ )
125
+
126
+ client = OpenAI(api_key=openai_api_key)
127
+ return client
128
+
129
+ def _get_messages(self) -> Iterable[ChatCompletionMessageParam]:
130
+ input_variables, input_values = self._compile_prompt_inputs()
131
+
132
+ compiled_blocks = compile_prompt_blocks(
133
+ blocks=self.blocks, inputs=input_values, input_variables=input_variables
134
+ )
135
+
136
+ chat_message_blocks: list[CompiledChatMessagePromptBlock] = [
137
+ block for block in compiled_blocks if block.block_type == "CHAT_MESSAGE"
138
+ ]
139
+ messages = [self._create_message(block) for block in chat_message_blocks]
140
+
141
+ return messages
142
+
143
+ @classmethod
144
+ def _create_message(cls, chat_message_block: CompiledChatMessagePromptBlock) -> ChatCompletionMessageParam:
145
+ name = chat_message_block.source
146
+ content = cls._create_message_content(chat_message_block)
147
+
148
+ if chat_message_block.role == "SYSTEM":
149
+ relevant_system_content = [
150
+ cast(ChatCompletionContentPartTextParam, c) for c in content if c["type"] == "text"
151
+ ]
152
+ system_message: ChatCompletionSystemMessageParam = {
153
+ "content": relevant_system_content,
154
+ "role": "system",
155
+ }
156
+ if name:
157
+ system_message["name"] = name
158
+
159
+ return system_message
160
+ elif chat_message_block.role == "USER":
161
+ user_message: ChatCompletionUserMessageParam = {
162
+ "content": content,
163
+ "role": "user",
164
+ }
165
+ if name:
166
+ user_message["name"] = name
167
+
168
+ return user_message
169
+ elif chat_message_block.role == "ASSISTANT":
170
+ relevant_assistant_content = [
171
+ cast(Union[ChatCompletionContentPartTextParam, ChatCompletionContentPartRefusalParam], c)
172
+ for c in content
173
+ if c["type"] in ["text", "refusal"]
174
+ ]
175
+ assistant_message: ChatCompletionAssistantMessageParam = {
176
+ "content": relevant_assistant_content,
177
+ "role": "assistant",
178
+ }
179
+ if name:
180
+ assistant_message["name"] = name
181
+
182
+ return assistant_message
183
+ else:
184
+ logger.error(f"Unexpected role: {chat_message_block.role}")
185
+ raise NodeException(
186
+ code=WorkflowErrorCode.INTERNAL_ERROR, message="Unexpected role found when compiling prompt blocks"
187
+ )
188
+
189
+ @classmethod
190
+ def _create_message_content(
191
+ cls,
192
+ chat_message_block: CompiledChatMessagePromptBlock,
193
+ ) -> List[ChatCompletionContentPartParam]:
194
+ content: List[ChatCompletionContentPartParam] = []
195
+ for block in chat_message_block.blocks:
196
+ if block.content.type == "STRING":
197
+ string_value = cast(str, block.content.value)
198
+ string_content_item: ChatCompletionContentPartTextParam = {"type": "text", "text": string_value}
199
+ content.append(string_content_item)
200
+ elif block.content.type == "JSON":
201
+ json_value = cast(Any, block.content.value)
202
+ json_content_item: ChatCompletionContentPartTextParam = {"type": "text", "text": json.dumps(json_value)}
203
+ content.append(json_content_item)
204
+ elif block.content.type == "IMAGE":
205
+ image_value = cast(VellumAudio, block.content.value)
206
+ image_content_item: ChatCompletionContentPartImageParam = {
207
+ "type": "image_url",
208
+ "image_url": {"url": image_value.src},
209
+ }
210
+ if image_value.metadata and image_value.metadata.get("detail"):
211
+ detail = image_value.metadata["detail"]
212
+
213
+ if detail not in ["auto", "low", "high"]:
214
+ raise NodeException(
215
+ code=WorkflowErrorCode.INTERNAL_ERROR,
216
+ message="Image detail must be one of 'auto', 'low', or 'high.",
217
+ )
218
+
219
+ image_content_item["image_url"]["detail"] = cast(Literal["auto", "low", "high"], detail)
220
+
221
+ content.append(image_content_item)
222
+ elif block.content.type == "AUDIO":
223
+ audio_value = cast(VellumAudio, block.content.value)
224
+ audio_value_src_parts = audio_value.src.split(",")
225
+ if len(audio_value_src_parts) != 2:
226
+ raise NodeException(
227
+ code=WorkflowErrorCode.INTERNAL_ERROR, message="Audio data is not properly encoded."
228
+ )
229
+ _, cleaned_audio_value = audio_value_src_parts
230
+ if not audio_value.metadata:
231
+ raise NodeException(
232
+ code=WorkflowErrorCode.INTERNAL_ERROR, message="Audio metadata is required for audio input."
233
+ )
234
+ audio_format = audio_value.metadata.get("format")
235
+ if not audio_format:
236
+ raise NodeException(
237
+ code=WorkflowErrorCode.INTERNAL_ERROR, message="Audio format is required for audio input."
238
+ )
239
+ if audio_format not in {"wav", "mp3"}:
240
+ raise NodeException(
241
+ code=WorkflowErrorCode.INTERNAL_ERROR,
242
+ message="Audio format must be one of 'wav' or 'mp3'.",
243
+ )
244
+
245
+ audio_content_item: ChatCompletionContentPartInputAudioParam = {
246
+ "type": "input_audio",
247
+ "input_audio": {
248
+ "data": cleaned_audio_value,
249
+ "format": cast(Literal["wav", "mp3"], audio_format),
250
+ },
251
+ }
252
+
253
+ content.append(audio_content_item)
254
+ else:
255
+ raise NodeException(
256
+ code=WorkflowErrorCode.INTERNAL_ERROR,
257
+ message=f"Failed to parse chat message block {block.content.type}",
258
+ )
259
+
260
+ return content
@@ -49,3 +49,6 @@ class SandboxRunner(Generic[WorkflowType]):
49
49
  self._logger.info(f"Just started Node: {event.node_definition.__name__}")
50
50
  elif event.name == "node.execution.fulfilled":
51
51
  self._logger.info(f"Just finished Node: {event.node_definition.__name__}")
52
+ elif event.name == "node.execution.rejected":
53
+ self._logger.debug(f"Error: {event.error}")
54
+ self._logger.error(f"Failed to run Node: {event.node_definition.__name__}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.12.6
3
+ Version: 0.12.8
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -25,6 +25,7 @@ Requires-Dist: cdktf (>=0.20.5,<0.21.0)
25
25
  Requires-Dist: click (==8.1.7)
26
26
  Requires-Dist: docker (==7.1.0)
27
27
  Requires-Dist: httpx (>=0.21.2)
28
+ Requires-Dist: openai (>=1.0.0)
28
29
  Requires-Dist: orderly-set (>=5.2.2,<6.0.0)
29
30
  Requires-Dist: publication (==0.0.3)
30
31
  Requires-Dist: pydantic (>=1.9.2)
@@ -1,17 +1,17 @@
1
1
  vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,2935
2
2
  vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
- vellum_cli/__init__.py,sha256=A9uo9OE7xQACNEtX4k0c-rxypDqS5V8kA8u4BNN0azM,7402
3
+ vellum_cli/__init__.py,sha256=iims87aL6HbwggCAh8JfLBNf3C7tN3TGDmlg7V5W9Lg,8506
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=wJQnv3tCgu1BOugg0AOP94yQ-x1yAg8juX_QoFN9Y7w,5223
6
6
  vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
7
7
  vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
8
- vellum_cli/pull.py,sha256=q68fr1o5H9l8Dvc8BTY1GARJYjAV1i6Fg-Lg4Oo4FDw,6155
8
+ vellum_cli/pull.py,sha256=AfSQLHwTeq_T22iRbGyefnmrPiddIKb_fqPMSoTlN0Y,6832
9
9
  vellum_cli/push.py,sha256=gcYhIogeYejZIhNm5Cjp0VBECaXLmVQEvZjrPH0-TSU,5337
10
10
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  vellum_cli/tests/conftest.py,sha256=eFGwBxib3Nki830lIFintB0b6r4x8T_KMnmzhlTY5x0,1337
12
12
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
13
13
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
14
- vellum_cli/tests/test_pull.py,sha256=P2JFNHU1hE6iydYl7rW35h7c8_DSrLiAS7gNsFMy1JU,17829
14
+ vellum_cli/tests/test_pull.py,sha256=Bnf21VjfiRb_j495idz5N8afucagtiktNCtVMvU8tGs,18977
15
15
  vellum_cli/tests/test_push.py,sha256=V2iGcskh2X3OHj2uV5Vx_BhmtyfmUkyx0lrp8DDOExc,5824
16
16
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,12 +28,12 @@ vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6m
28
28
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
29
29
  vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nmPLj8vkbVCS46XQqmHq8Xj8Mr36wCK_vWf26A9KIkw,1505
30
30
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=4SSQGecKWHuoGy5YIGJeOZVHGKwTs_8Y-gf3GvsHb0M,8506
31
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=XqizRn5bLwT8LMwgyvfbln8inhCxzTi1EkD22Fx-5-U,4222
31
+ vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=qrf2Ne8n2aaoa2xgK3ExCEGI9BA-2g1zmvjSHyzCWmQ,4230
32
32
  vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=EtdqJfhYw03PuT2iyJ6mSAZK4RsQqDie_2AnJAtMelk,13625
33
33
  vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=ygTjSjYDI4DtkxADWub5rhBnRWItMKWF6fezBrgpOKA,1979
34
34
  vellum_ee/workflows/display/nodes/vellum/final_output_node.py,sha256=t5iJQVoRT5g-v2IiUb4kFYdvUVKch0zn27016pzDZoo,2761
35
35
  vellum_ee/workflows/display/nodes/vellum/guardrail_node.py,sha256=3TJvHX_Uuf_gr94VkYc_zmNH8I5p71ChIeoAbJZ3ddY,2158
36
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py,sha256=E7U4B05-KSlPvTp0nYQTtGPkNa5563VxrNBQmKwvTqw,7267
36
+ vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py,sha256=DYhxHgFT06CNCpovO4Z-wFPqCjFKxyl89ygFs8AucRQ,7189
37
37
  vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py,sha256=x5wiuWbRjxNcPGu8BoBEKHwPeqCpHE-vrGjAdM5TJOs,4721
38
38
  vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=AqUlItgSZij12qRKguKVmDbbaLuDy3Cdom5uOlJPqrc,3640
39
39
  vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=jzO63B9KiEAncnBqmz2ZTcxjmEHozMEe7WnfpcpsQYg,3195
@@ -76,7 +76,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
76
76
  vellum/client/__init__.py,sha256=o4m7iRZWEV8rP3GkdaztHAjNmjxjWERlarviFoHzuKI,110927
77
77
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
78
78
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
79
- vellum/client/core/client_wrapper.py,sha256=Djd_7GxqAd6z6spaztPvuwOWM8nCLYHOM1pxOFHfmrc,1868
79
+ vellum/client/core/client_wrapper.py,sha256=oWk19_A_3mvqQM_W3wJgQnObX6YIE918NEp9olVAuEo,1868
80
80
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
81
81
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
82
82
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -649,17 +649,23 @@ vellum/errors/not_found_error.py,sha256=gC71YBdPyHR46l3RNTs0v9taVvAY0gWRFrcKpKzb
649
649
  vellum/evaluations/__init__.py,sha256=hNsLoHSykqXDJP-MwFvu2lExImxo9KEyEJjt_fdAzpE,77
650
650
  vellum/evaluations/constants.py,sha256=Vteml4_csZsMgo_q3-71E3JRCAoN6308TXLu5nfLhmU,116
651
651
  vellum/evaluations/exceptions.py,sha256=6Xacoyv43fJvVf6Dt6Io5a-f9vF12Tx51jzsQRNSqhY,56
652
- vellum/evaluations/resources.py,sha256=8-gqAxLnVtDQrHYy73RAGIzomeD6uX_3b_yhmFR9I3g,12688
652
+ vellum/evaluations/resources.py,sha256=33IDRHOjrRsC20JHs0BtRlaSYCY9Gg-AkA6qkPjBrWo,12676
653
653
  vellum/evaluations/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
654
654
  vellum/evaluations/utils/env.py,sha256=Xj_nxsoU5ox06EOTjRopR4lrigQI6Le6qbWGltYoEGU,276
655
655
  vellum/evaluations/utils/exceptions.py,sha256=dXMAkzqbHV_AP5FjjbegPlfUE0zQDlpA3qOsoOJUxfg,49
656
656
  vellum/evaluations/utils/paginator.py,sha256=rEED_BJAXAM6tM1yMwHePNzszjq_tTq4NbQvi1jWQ_Q,697
657
- vellum/evaluations/utils/typing.py,sha256=wx_daFqD69cYkuJTVnvNrpjhqC3uuhbnyJ9_bIwC9OU,327
658
657
  vellum/evaluations/utils/uuid.py,sha256=Ch6wWRgwICxLxJCTl5iE3EdRlZj2zADR-zUMUtjcMWM,214
659
658
  vellum/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
660
659
  vellum/plugins/pydantic.py,sha256=EbI0pJMhUS9rLPSkzmAELfnCHrWCJzOrU06T8ommwdw,2334
661
660
  vellum/plugins/utils.py,sha256=U9ZY9KdE3RRvbcG01hXxu9CvfJD6Fo7nJDgcHjQn0FI,606
662
661
  vellum/plugins/vellum_mypy.py,sha256=VC15EzjTsXOb9uF1bky4rcxePP-0epMVmCsLB2z4Dh8,24816
662
+ vellum/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
663
+ vellum/prompts/blocks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
664
+ vellum/prompts/blocks/compilation.py,sha256=hBN_ajq77tSkVyUIcDlO-Qu77PSeJ1OzVhp70NmQd2k,7458
665
+ vellum/prompts/blocks/exceptions.py,sha256=vmk5PV6Vyw9nKjZYQDUDW0LH8MfQNIgFvFb_mFWdIRI,50
666
+ vellum/prompts/blocks/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
667
+ vellum/prompts/blocks/tests/test_compilation.py,sha256=0DhMoc4huHR6YnNL-0aBLmWSyUfw2BpRq_gEdKsQmAc,3693
668
+ vellum/prompts/blocks/types.py,sha256=6aSJQco-5kKeadfKVVXF_SrQPlIJgMYVNc-C7so1sY8,975
663
669
  vellum/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
664
670
  vellum/resources/__init__.py,sha256=sQWK7g_Z4EM7pa7fy6vy3d_DMdTJ4wVcozBn3Lx4Qpo,141
665
671
  vellum/resources/ad_hoc/__init__.py,sha256=UD01D9nS_M7sRKmMbEg4Tv9SlfFj3cWahVxwUEaSLAY,148
@@ -1197,6 +1203,13 @@ vellum/types/workflow_result_event_output_data_search_results.py,sha256=UNfCHLQ0
1197
1203
  vellum/types/workflow_result_event_output_data_string.py,sha256=rHEVbN0nyf-xoDoSIUEKlUKh6DDoguer4w0iN18JQ2I,178
1198
1204
  vellum/types/workflow_stream_event.py,sha256=PjHGgN0eJm5w-5FJ6__ASC1FU94Gsav_ko5JWkpVvK8,159
1199
1205
  vellum/types/workspace_secret_read.py,sha256=Z6QNXHxVHRdrLXSI31KxngePRwJTVoJYMXVbtPQwrxs,159
1206
+ vellum/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1207
+ vellum/utils/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1208
+ vellum/utils/templating/constants.py,sha256=XTNmDsKa7byjw4GMZmzx2dUeYUTeMLZrPgRHcc80Kvc,613
1209
+ vellum/utils/templating/custom_filters.py,sha256=Q0DahYRHP4KfaUXDt9XxN-DFLBrAxlv90yaVqxScoUw,264
1210
+ vellum/utils/templating/exceptions.py,sha256=cDp140PP4OnInW4qAvg3KqiSiF70C71UyEAKRBR1Abo,46
1211
+ vellum/utils/templating/render.py,sha256=0vgkwhu2A6o64aT4fUdTSLFCEMbeRjAKAuvv2k2LYGY,1772
1212
+ vellum/utils/typing.py,sha256=wx_daFqD69cYkuJTVnvNrpjhqC3uuhbnyJ9_bIwC9OU,327
1200
1213
  vellum/version.py,sha256=jq-1PlAYxN9AXuaZqbYk9ak27SgE2lw9Ia5gx1b1gVI,76
1201
1214
  vellum/workflows/README.md,sha256=MLNm-ihc0ao6I8gwwOhXQQBf0jOf-EsA9C519ALYI1o,3610
1202
1215
  vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,71
@@ -1278,10 +1291,7 @@ vellum/workflows/nodes/core/retry_node/node.py,sha256=IjNcpzFmHyBUjOHEoULLbKf85B
1278
1291
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1279
1292
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=QXTnHwmJHISxXjvZMeuuEo0iVugVMJyaJoggI8yKXfI,3132
1280
1293
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1281
- vellum/workflows/nodes/core/templating_node/custom_filters.py,sha256=Q0DahYRHP4KfaUXDt9XxN-DFLBrAxlv90yaVqxScoUw,264
1282
- vellum/workflows/nodes/core/templating_node/exceptions.py,sha256=cDp140PP4OnInW4qAvg3KqiSiF70C71UyEAKRBR1Abo,46
1283
- vellum/workflows/nodes/core/templating_node/node.py,sha256=Q5U5lAW1nhGFJPc94Gxnq1s5RWrbOo7G_AY4n-ABmGg,4504
1284
- vellum/workflows/nodes/core/templating_node/render.py,sha256=OpJp0NAH6qcEL6K9lxR0qjpFb75TYNttJR5iCos8tmg,1792
1294
+ vellum/workflows/nodes/core/templating_node/node.py,sha256=mWNzsa6TLosrD3higKIsiqEv54OIfNY7YzFDkuQgm6k,3964
1285
1295
  vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=0BtXeSix7KGIuKzlPFTMLATpNnFPhut1UV_srGptkt0,1120
1286
1296
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1287
1297
  vellum/workflows/nodes/core/try_node/node.py,sha256=_xkpUNIfHXuwjivMBxdU5DegsNge2ITW5nBifWmBPTY,6670
@@ -1331,6 +1341,10 @@ vellum/workflows/nodes/displayable/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
1331
1341
  vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py,sha256=UI_RMmXn9qwB-StnFPvkDd9FctBQAg43wrfouqvPepk,4701
1332
1342
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=4CMwDtXwTaEvFfDpA6j2iLqc7S6IICSkvVZOobEpeps,6954
1333
1343
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=KqKJtJ0vuNoPuUPMdILmBTt4a2fBBxxun-nmOI7T8jo,2585
1344
+ vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
1345
+ vellum/workflows/nodes/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1346
+ vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
1347
+ vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=1EGeiaT-Zoo6pttQFKKBcdf3dmhAbjKGaErYD5FFwlc,10185
1334
1348
  vellum/workflows/nodes/utils.py,sha256=EZt7CzJmgQBR_GWFpZr8d-oaoti3tolTd2Cv9wm7dKo,1087
1335
1349
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1336
1350
  vellum/workflows/outputs/base.py,sha256=a7W6rNSDSawwGAXYjNTF2iHb9lnZu7WFSOagZIyy__k,7976
@@ -1353,7 +1367,7 @@ vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPyc
1353
1367
  vellum/workflows/resolvers/base.py,sha256=WHra9LRtlTuB1jmuNqkfVE2JUgB61Cyntn8f0b0WZg4,411
1354
1368
  vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
1355
1369
  vellum/workflows/runner/runner.py,sha256=RXnLEmSJFbp0u4vKF7rvD2fscuYfcRYkspIJINnvFAI,27607
1356
- vellum/workflows/sandbox.py,sha256=wNyOfd3gb6-O85EQcBIHNCnSYPH7Oufh2z4hQnR2HFU,2059
1370
+ vellum/workflows/sandbox.py,sha256=1aL5BoUgJX6dbIN3pqui20Wk3VyzXV16BUaZz-clmFs,2269
1357
1371
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1358
1372
  vellum/workflows/state/base.py,sha256=jpSzF1OQd3-fqi6dMGlNsQl-7JnJxCdzWIigmX8Wz-I,14425
1359
1373
  vellum/workflows/state/context.py,sha256=oXiEdNsWJi1coRB85IreTgUeR6_CrWWBXndtLff9S7M,1272
@@ -1384,8 +1398,8 @@ vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528
1384
1398
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1385
1399
  vellum/workflows/workflows/base.py,sha256=zpspOEdO5Ye_0ZvN-Wkzv9iQSiF1sD201ba8lhbnPbs,17086
1386
1400
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1387
- vellum_ai-0.12.6.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1388
- vellum_ai-0.12.6.dist-info/METADATA,sha256=C1VU1fn4OEzUUW4pv7NTqN6hMP4XCwxUZldj2hAtths,5128
1389
- vellum_ai-0.12.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1390
- vellum_ai-0.12.6.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1391
- vellum_ai-0.12.6.dist-info/RECORD,,
1401
+ vellum_ai-0.12.8.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1402
+ vellum_ai-0.12.8.dist-info/METADATA,sha256=FI7BG5Gx3Dd3exq5uqBWnHu8kuqFrVpE_9tcdUuYwdk,5160
1403
+ vellum_ai-0.12.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1404
+ vellum_ai-0.12.8.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1405
+ vellum_ai-0.12.8.dist-info/RECORD,,
vellum_cli/__init__.py CHANGED
@@ -130,11 +130,18 @@ Should only be used for debugging purposes.""",
130
130
  is_flag=True,
131
131
  help="""Raises an error immediately if there are any issues with the pulling of the Resource.""",
132
132
  )
133
+ @click.option(
134
+ "--include-sandbox",
135
+ is_flag=True,
136
+ help="""Generates a runnable sandbox.py file containing test data from the Resource's sandbox. \
137
+ Helpful for running and debugging workflows locally.""",
138
+ )
133
139
  def pull(
134
140
  ctx: click.Context,
135
141
  include_json: Optional[bool],
136
142
  exclude_code: Optional[bool],
137
143
  strict: Optional[bool],
144
+ include_sandbox: Optional[bool],
138
145
  ) -> None:
139
146
  """Pull Resources from Vellum"""
140
147
 
@@ -143,6 +150,7 @@ def pull(
143
150
  include_json=include_json,
144
151
  exclude_code=exclude_code,
145
152
  strict=strict,
153
+ include_sandbox=include_sandbox,
146
154
  )
147
155
 
148
156
 
@@ -171,6 +179,12 @@ Should only be used for debugging purposes.""",
171
179
  is_flag=True,
172
180
  help="""Raises an error immediately if there are any issues with the pulling of the Workflow.""",
173
181
  )
182
+ @click.option(
183
+ "--include-sandbox",
184
+ is_flag=True,
185
+ help="""Generates a runnable sandbox.py file containing test data from the Resource's sandbox. \
186
+ Helpful for running and debugging workflows locally.""",
187
+ )
174
188
  def workflows_pull(
175
189
  module: Optional[str],
176
190
  include_json: Optional[bool],
@@ -178,6 +192,7 @@ def workflows_pull(
178
192
  workflow_deployment: Optional[str],
179
193
  exclude_code: Optional[bool],
180
194
  strict: Optional[bool],
195
+ include_sandbox: Optional[bool],
181
196
  ) -> None:
182
197
  """
183
198
  Pull Workflows from Vellum. If a module is provided, only the Workflow for that module will be pulled.
@@ -191,6 +206,7 @@ def workflows_pull(
191
206
  workflow_deployment=workflow_deployment,
192
207
  exclude_code=exclude_code,
193
208
  strict=strict,
209
+ include_sandbox=include_sandbox,
194
210
  )
195
211
 
196
212
 
@@ -208,10 +224,23 @@ Should only be used for debugging purposes.""",
208
224
  help="""Exclude the code definition of the Resource from the pull response. \
209
225
  Should only be used for debugging purposes.""",
210
226
  )
227
+ @click.option(
228
+ "--strict",
229
+ is_flag=True,
230
+ help="""Raises an error immediately if there are any issues with the pulling of the Resource.""",
231
+ )
232
+ @click.option(
233
+ "--include-sandbox",
234
+ is_flag=True,
235
+ help="""Generates a runnable sandbox.py file containing test data from the Resource's sandbox. \
236
+ Helpful for running and debugging resources locally.""",
237
+ )
211
238
  def pull_module(
212
239
  ctx: click.Context,
213
240
  include_json: Optional[bool],
214
241
  exclude_code: Optional[bool],
242
+ strict: Optional[bool],
243
+ include_sandbox: Optional[bool],
215
244
  ) -> None:
216
245
  """Pull a specific module from Vellum"""
217
246
 
@@ -220,6 +249,8 @@ def pull_module(
220
249
  module=ctx.parent.invoked_subcommand,
221
250
  include_json=include_json,
222
251
  exclude_code=exclude_code,
252
+ strict=strict,
253
+ include_sandbox=include_sandbox,
223
254
  )
224
255
 
225
256
 
vellum_cli/pull.py CHANGED
@@ -84,6 +84,7 @@ def pull_command(
84
84
  include_json: Optional[bool] = None,
85
85
  exclude_code: Optional[bool] = None,
86
86
  strict: Optional[bool] = None,
87
+ include_sandbox: Optional[bool] = None,
87
88
  ) -> None:
88
89
  load_dotenv()
89
90
  logger = load_cli_logger()
@@ -114,6 +115,8 @@ def pull_command(
114
115
  query_parameters["exclude_code"] = exclude_code
115
116
  if strict:
116
117
  query_parameters["strict"] = strict
118
+ if include_sandbox:
119
+ query_parameters["include_sandbox"] = include_sandbox
117
120
 
118
121
  response = client.workflows.pull(
119
122
  pk,
@@ -166,6 +169,17 @@ def pull_command(
166
169
  Its schema should be considered unstable and subject to change at any time."""
167
170
  )
168
171
 
172
+ if include_sandbox:
173
+ if not workflow_config.ignore:
174
+ workflow_config.ignore = "sandbox.py"
175
+ save_lock_file = True
176
+ elif isinstance(workflow_config.ignore, str) and "sandbox.py" != workflow_config.ignore:
177
+ workflow_config.ignore = [workflow_config.ignore, "sandbox.py"]
178
+ save_lock_file = True
179
+ elif isinstance(workflow_config.ignore, list) and "sandbox.py" not in workflow_config.ignore:
180
+ workflow_config.ignore.append("sandbox.py")
181
+ save_lock_file = True
182
+
169
183
  if save_lock_file:
170
184
  config.save()
171
185
 
@@ -486,3 +486,32 @@ def test_pull__strict__with_error(vellum_client, mock_module):
486
486
  vellum_client.workflows.pull.assert_called_once()
487
487
  call_args = vellum_client.workflows.pull.call_args.kwargs
488
488
  assert call_args["request_options"]["additional_query_parameters"] == {"strict": True}
489
+
490
+
491
+ def test_pull__include_sandbox(vellum_client, mock_module):
492
+ # GIVEN a module on the user's filesystem
493
+ module = mock_module.module
494
+ temp_dir = mock_module.temp_dir
495
+
496
+ # AND the workflow pull API call returns a zip file
497
+ vellum_client.workflows.pull.return_value = iter(
498
+ [_zip_file_map({"workflow.py": "print('hello')", "sandbox.py": "print('hello')"})]
499
+ )
500
+
501
+ # WHEN the user runs the pull command
502
+ runner = CliRunner()
503
+ result = runner.invoke(cli_main, ["pull", module, "--include-sandbox"])
504
+
505
+ # THEN the command returns successfully
506
+ assert result.exit_code == 0, result.output
507
+
508
+ # AND the pull api is called with include_sandbox=True
509
+ vellum_client.workflows.pull.assert_called_once()
510
+ call_args = vellum_client.workflows.pull.call_args.kwargs
511
+ assert call_args["request_options"]["additional_query_parameters"] == {"include_sandbox": True}
512
+
513
+ # AND the sandbox.py should be added to the ignore list
514
+ lock_json = os.path.join(temp_dir, "vellum.lock.json")
515
+ with open(lock_json) as f:
516
+ lock_data = json.load(f)
517
+ assert lock_data["workflows"][0]["ignore"] == "sandbox.py"
@@ -27,8 +27,8 @@ class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType]
27
27
  node = self._node
28
28
  node_id = self.node_id
29
29
  raw_code = raise_if_descriptor(node.code)
30
- code_value = None
31
30
 
31
+ code_value: Optional[str]
32
32
  if raw_code:
33
33
  code_value = raw_code
34
34
  else:
@@ -104,13 +104,12 @@ class BaseInlinePromptNodeDisplay(BaseNodeVellumDisplay[_InlinePromptNodeType],
104
104
  chat_properties: JsonObject = {
105
105
  "chat_role": prompt_block.chat_role,
106
106
  "chat_source": prompt_block.chat_source,
107
+ "chat_message_unterminated": bool(prompt_block.chat_message_unterminated),
107
108
  "blocks": [
108
109
  self._generate_prompt_block(block, input_variable_id_by_name, path + [i])
109
110
  for i, block in enumerate(prompt_block.blocks)
110
111
  ],
111
112
  }
112
- if prompt_block.chat_message_unterminated is not None:
113
- chat_properties["chat_message_unterminated"] = prompt_block.chat_message_unterminated
114
113
 
115
114
  block = {
116
115
  "block_type": "CHAT_MESSAGE",
File without changes