vellum-ai 0.12.6__py3-none-any.whl → 0.12.8__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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