vellum-ai 0.12.15__py3-none-any.whl → 0.12.17__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/plugins/vellum_mypy.py +80 -11
- vellum/prompts/blocks/compilation.py +43 -0
- vellum/utils/templating/render.py +3 -0
- vellum/workflows/nodes/bases/base.py +4 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +10 -2
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +16 -0
- vellum/workflows/nodes/core/retry_node/node.py +24 -3
- vellum/workflows/nodes/core/retry_node/tests/test_node.py +40 -0
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +20 -1
- vellum/workflows/nodes/displayable/api_node/node.py +3 -3
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.17.dist-info}/METADATA +10 -8
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.17.dist-info}/RECORD +48 -45
- vellum_cli/__init__.py +14 -0
- vellum_cli/pull.py +7 -4
- vellum_cli/push.py +26 -4
- vellum_cli/tests/conftest.py +4 -2
- vellum_cli/tests/test_push.py +75 -4
- vellum_ee/workflows/display/nodes/vellum/api_node.py +3 -3
- vellum_ee/workflows/display/nodes/vellum/base_node.py +17 -0
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +20 -6
- vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/search_node.py +4 -2
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py +0 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +28 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +123 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +2 -11
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -14
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +1 -7
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +17 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +17 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +1 -9
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +1 -4
- vellum_ee/workflows/display/types.py +5 -1
- vellum_ee/workflows/display/utils/vellum.py +3 -3
- vellum_ee/workflows/display/vellum.py +4 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +44 -16
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +3 -0
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +7 -8
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.17.dist-info}/LICENSE +0 -0
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.17.dist-info}/WHEEL +0 -0
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.17.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.
|
21
|
+
"X-Fern-SDK-Version": "0.12.17",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
vellum/plugins/vellum_mypy.py
CHANGED
@@ -4,6 +4,7 @@ from typing import Callable, Dict, List, Optional, Set, Type
|
|
4
4
|
from mypy.nodes import (
|
5
5
|
AssignmentStmt,
|
6
6
|
CallExpr,
|
7
|
+
ClassDef,
|
7
8
|
Decorator,
|
8
9
|
MemberExpr,
|
9
10
|
NameExpr,
|
@@ -198,13 +199,27 @@ class VellumMypyPlugin(Plugin):
|
|
198
199
|
return
|
199
200
|
|
200
201
|
current_node_outputs = node_info.names.get("Outputs")
|
201
|
-
if not current_node_outputs:
|
202
|
+
if not current_node_outputs and isinstance(base_node_outputs.node, TypeInfo):
|
202
203
|
node_info.names["Outputs"] = base_node_outputs.copy()
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
204
|
+
|
205
|
+
new_outputs_fullname = f"{node_info.fullname}.Outputs"
|
206
|
+
new_outputs_defn_raw = base_node_outputs.node.defn.serialize()
|
207
|
+
new_outputs_defn_raw["fullname"] = new_outputs_fullname
|
208
|
+
new_outputs_defn = ClassDef.deserialize(new_outputs_defn_raw)
|
209
|
+
new_outputs_sym = TypeInfo(
|
210
|
+
names=base_node_outputs.node.names.copy(),
|
211
|
+
defn=new_outputs_defn,
|
212
|
+
module_name=node_info.module_name,
|
213
|
+
)
|
214
|
+
|
215
|
+
base_result_sym = base_node_outputs.node.names[attribute_name].node
|
216
|
+
if isinstance(base_result_sym, Var):
|
217
|
+
new_result_sym = Var.deserialize(base_result_sym.serialize())
|
218
|
+
new_result_sym._fullname = f"{new_outputs_fullname}.{attribute_name}"
|
219
|
+
new_result_sym.type = base_node_resolved_type
|
220
|
+
new_outputs_sym.names[attribute_name].node = new_result_sym
|
221
|
+
new_outputs_sym.bases.append(Instance(base_node_outputs.node, []))
|
222
|
+
node_info.names["Outputs"].node = new_outputs_sym
|
208
223
|
|
209
224
|
def _base_node_class_hook(self, ctx: ClassDefContext) -> None:
|
210
225
|
"""
|
@@ -472,10 +487,22 @@ class VellumMypyPlugin(Plugin):
|
|
472
487
|
|
473
488
|
def _run_method_hook(self, ctx: MethodContext) -> MypyType:
|
474
489
|
"""
|
475
|
-
We use this to target
|
476
|
-
|
490
|
+
We use this to target:
|
491
|
+
- `BaseWorkflow.run()`, so that the WorkflowExecutionFulfilledEvent is properly typed
|
492
|
+
using the `Outputs` class defined on the user-defined subclass of `Workflow`.
|
493
|
+
- `BaseNode.run()`, so that the `Outputs` class defined on the user-defined subclass of `Node`
|
494
|
+
is properly typed.
|
477
495
|
"""
|
478
496
|
|
497
|
+
if isinstance(ctx.default_return_type, TypeAliasType):
|
498
|
+
return self._workflow_run_method_hook(ctx)
|
499
|
+
|
500
|
+
if isinstance(ctx.default_return_type, Instance):
|
501
|
+
return self._node_run_method_hook(ctx)
|
502
|
+
|
503
|
+
return ctx.default_return_type
|
504
|
+
|
505
|
+
def _workflow_run_method_hook(self, ctx: MethodContext) -> MypyType:
|
479
506
|
if not isinstance(ctx.default_return_type, TypeAliasType):
|
480
507
|
return ctx.default_return_type
|
481
508
|
|
@@ -494,7 +521,7 @@ class VellumMypyPlugin(Plugin):
|
|
494
521
|
if fulfilled_event.type.fullname != "vellum.workflows.events.workflow.WorkflowExecutionFulfilledEvent":
|
495
522
|
return ctx.default_return_type
|
496
523
|
|
497
|
-
outputs_node = self.
|
524
|
+
outputs_node = self._get_workflow_outputs_type_info(ctx)
|
498
525
|
if not outputs_node:
|
499
526
|
return ctx.default_return_type
|
500
527
|
|
@@ -513,6 +540,19 @@ class VellumMypyPlugin(Plugin):
|
|
513
540
|
column=ctx.default_return_type.column,
|
514
541
|
)
|
515
542
|
|
543
|
+
def _node_run_method_hook(self, ctx: MethodContext) -> MypyType:
|
544
|
+
if not isinstance(ctx.default_return_type, Instance):
|
545
|
+
return ctx.default_return_type
|
546
|
+
|
547
|
+
if not _is_subclass(ctx.default_return_type.type, "vellum.workflows.nodes.bases.base.BaseNode.Outputs"):
|
548
|
+
return ctx.default_return_type
|
549
|
+
|
550
|
+
outputs_node = self._get_node_outputs_type_info(ctx)
|
551
|
+
if not outputs_node:
|
552
|
+
return ctx.default_return_type
|
553
|
+
|
554
|
+
return Instance(outputs_node, [])
|
555
|
+
|
516
556
|
def _stream_method_hook(self, ctx: MethodContext) -> MypyType:
|
517
557
|
"""
|
518
558
|
We use this to target `Workflow.stream()` so that the WorkflowExecutionFulfilledEvent is properly typed
|
@@ -557,7 +597,7 @@ class VellumMypyPlugin(Plugin):
|
|
557
597
|
if fulfilled_event_index == -1 or not fulfilled_event:
|
558
598
|
return ctx.default_return_type
|
559
599
|
|
560
|
-
outputs_node = self.
|
600
|
+
outputs_node = self._get_workflow_outputs_type_info(ctx)
|
561
601
|
if not outputs_node:
|
562
602
|
return ctx.default_return_type
|
563
603
|
|
@@ -594,7 +634,7 @@ class VellumMypyPlugin(Plugin):
|
|
594
634
|
column=ctx.default_return_type.column,
|
595
635
|
)
|
596
636
|
|
597
|
-
def
|
637
|
+
def _get_workflow_outputs_type_info(self, ctx: MethodContext) -> Optional[TypeInfo]:
|
598
638
|
if not isinstance(ctx.context, CallExpr):
|
599
639
|
return None
|
600
640
|
|
@@ -624,6 +664,35 @@ class VellumMypyPlugin(Plugin):
|
|
624
664
|
|
625
665
|
return resolved_outputs_node.node
|
626
666
|
|
667
|
+
def _get_node_outputs_type_info(self, ctx: MethodContext) -> Optional[TypeInfo]:
|
668
|
+
if not isinstance(ctx.context, CallExpr):
|
669
|
+
return None
|
670
|
+
|
671
|
+
if not isinstance(ctx.context.callee, MemberExpr):
|
672
|
+
return None
|
673
|
+
|
674
|
+
expr = ctx.context.callee.expr
|
675
|
+
instance = ctx.api.get_expression_type(expr)
|
676
|
+
if not isinstance(instance, Instance) or not _is_subclass(
|
677
|
+
instance.type, "vellum.workflows.nodes.bases.base.BaseNode"
|
678
|
+
):
|
679
|
+
return None
|
680
|
+
|
681
|
+
outputs_node = instance.type.names.get("Outputs")
|
682
|
+
|
683
|
+
if (
|
684
|
+
not outputs_node
|
685
|
+
or not isinstance(outputs_node.node, TypeInfo)
|
686
|
+
or not _is_subclass(outputs_node.node, "vellum.workflows.outputs.base.BaseOutputs")
|
687
|
+
):
|
688
|
+
return None
|
689
|
+
|
690
|
+
# TODO: For some reason, returning the correct Outputs type info is causing `result` to not
|
691
|
+
# be found. `test_templating_node.py` is the best place to test this.
|
692
|
+
# https://app.shortcut.com/vellum/story/6132
|
693
|
+
# return outputs_node.node
|
694
|
+
return None
|
695
|
+
|
627
696
|
def _resolve_descriptors_in_outputs(self, type_info: SymbolTableNode) -> SymbolTableNode:
|
628
697
|
new_type_info = type_info.copy()
|
629
698
|
if not isinstance(new_type_info.node, TypeInfo):
|
@@ -10,6 +10,12 @@ from vellum import (
|
|
10
10
|
StringVellumValue,
|
11
11
|
VellumVariable,
|
12
12
|
)
|
13
|
+
from vellum.client.types.audio_vellum_value import AudioVellumValue
|
14
|
+
from vellum.client.types.function_call import FunctionCall
|
15
|
+
from vellum.client.types.function_call_vellum_value import FunctionCallVellumValue
|
16
|
+
from vellum.client.types.image_vellum_value import ImageVellumValue
|
17
|
+
from vellum.client.types.vellum_audio import VellumAudio
|
18
|
+
from vellum.client.types.vellum_image import VellumImage
|
13
19
|
from vellum.prompts.blocks.exceptions import PromptCompilationError
|
14
20
|
from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock, CompiledPromptBlock, CompiledValuePromptBlock
|
15
21
|
from vellum.utils.templating.constants import DEFAULT_JINJA_CUSTOM_FILTERS
|
@@ -112,6 +118,43 @@ def compile_prompt_blocks(
|
|
112
118
|
|
113
119
|
elif block.block_type == "FUNCTION_DEFINITION":
|
114
120
|
raise PromptCompilationError("Function definitions shouldn't go through compilation process")
|
121
|
+
|
122
|
+
elif block.block_type == "FUNCTION_CALL":
|
123
|
+
function_call_block = CompiledValuePromptBlock(
|
124
|
+
content=FunctionCallVellumValue(
|
125
|
+
value=FunctionCall(
|
126
|
+
id=block.id,
|
127
|
+
name=block.name,
|
128
|
+
arguments=block.arguments,
|
129
|
+
),
|
130
|
+
),
|
131
|
+
cache_config=block.cache_config,
|
132
|
+
)
|
133
|
+
compiled_blocks.append(function_call_block)
|
134
|
+
|
135
|
+
elif block.block_type == "IMAGE":
|
136
|
+
image_block = CompiledValuePromptBlock(
|
137
|
+
content=ImageVellumValue(
|
138
|
+
value=VellumImage(
|
139
|
+
src=block.src,
|
140
|
+
metadata=block.metadata,
|
141
|
+
),
|
142
|
+
),
|
143
|
+
cache_config=block.cache_config,
|
144
|
+
)
|
145
|
+
compiled_blocks.append(image_block)
|
146
|
+
|
147
|
+
elif block.block_type == "AUDIO":
|
148
|
+
audio_block = CompiledValuePromptBlock(
|
149
|
+
content=AudioVellumValue(
|
150
|
+
value=VellumAudio(
|
151
|
+
src=block.src,
|
152
|
+
metadata=block.metadata,
|
153
|
+
),
|
154
|
+
),
|
155
|
+
cache_config=block.cache_config,
|
156
|
+
)
|
157
|
+
compiled_blocks.append(audio_block)
|
115
158
|
else:
|
116
159
|
raise PromptCompilationError(f"Unknown block_type: {block.block_type}")
|
117
160
|
|
@@ -28,6 +28,9 @@ def render_sandboxed_jinja_template(
|
|
28
28
|
keep_trailing_newline=True,
|
29
29
|
finalize=finalize,
|
30
30
|
)
|
31
|
+
environment.policies["json.dumps_kwargs"] = {
|
32
|
+
"cls": DefaultStateEncoder,
|
33
|
+
}
|
31
34
|
|
32
35
|
if jinja_custom_filters:
|
33
36
|
environment.filters.update(jinja_custom_filters)
|
@@ -324,6 +324,10 @@ class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
|
|
324
324
|
if not descriptor.instance:
|
325
325
|
continue
|
326
326
|
|
327
|
+
if any(isinstance(t, type) and issubclass(t, BaseDescriptor) for t in descriptor.types):
|
328
|
+
# We don't want to resolve attributes that are _meant_ to be descriptors
|
329
|
+
continue
|
330
|
+
|
327
331
|
resolved_value = resolve_value(descriptor.instance, self.state, path=descriptor.name, memo=inputs)
|
328
332
|
setattr(self, descriptor.name, resolved_value)
|
329
333
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from typing import TYPE_CHECKING, ClassVar, Generic, Iterator, Optional, Set, Type, TypeVar, Union
|
2
2
|
|
3
|
+
from vellum.workflows.constants import UNDEF
|
3
4
|
from vellum.workflows.context import execution_context, get_parent_context
|
4
5
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
5
6
|
from vellum.workflows.exceptions import NodeException
|
@@ -27,7 +28,7 @@ class InlineSubworkflowNode(BaseNode[StateType], Generic[StateType, WorkflowInpu
|
|
27
28
|
"""
|
28
29
|
|
29
30
|
subworkflow: Type["BaseWorkflow[WorkflowInputsType, InnerStateType]"]
|
30
|
-
subworkflow_inputs: ClassVar[Union[EntityInputsInterface, BaseInputs]] =
|
31
|
+
subworkflow_inputs: ClassVar[Union[EntityInputsInterface, BaseInputs, Type[UNDEF]]] = UNDEF
|
31
32
|
|
32
33
|
def run(self) -> Iterator[BaseOutput]:
|
33
34
|
with execution_context(parent_context=get_parent_context() or self._context.parent_context):
|
@@ -71,7 +72,14 @@ class InlineSubworkflowNode(BaseNode[StateType], Generic[StateType, WorkflowInpu
|
|
71
72
|
|
72
73
|
def _compile_subworkflow_inputs(self) -> WorkflowInputsType:
|
73
74
|
inputs_class = self.subworkflow.get_inputs_class()
|
74
|
-
if
|
75
|
+
if self.subworkflow_inputs is UNDEF:
|
76
|
+
inputs_dict = {}
|
77
|
+
for descriptor in inputs_class:
|
78
|
+
if hasattr(self, descriptor.name):
|
79
|
+
inputs_dict[descriptor.name] = getattr(self, descriptor.name)
|
80
|
+
|
81
|
+
return inputs_class(**inputs_dict)
|
82
|
+
elif isinstance(self.subworkflow_inputs, dict):
|
75
83
|
return inputs_class(**self.subworkflow_inputs)
|
76
84
|
elif isinstance(self.subworkflow_inputs, inputs_class):
|
77
85
|
return self.subworkflow_inputs
|
@@ -39,3 +39,19 @@ def test_inline_subworkflow_node__inputs(inputs):
|
|
39
39
|
assert events == [
|
40
40
|
BaseOutput(name="out", value="bar"),
|
41
41
|
]
|
42
|
+
|
43
|
+
|
44
|
+
def test_inline_subworkflow_node__support_inputs_as_attributes():
|
45
|
+
# GIVEN a node setup with subworkflow inputs
|
46
|
+
class MyNode(InlineSubworkflowNode):
|
47
|
+
subworkflow = MySubworkflow
|
48
|
+
foo = "bar"
|
49
|
+
|
50
|
+
# WHEN the node is run
|
51
|
+
node = MyNode()
|
52
|
+
events = list(node.run())
|
53
|
+
|
54
|
+
# THEN the output is as expected
|
55
|
+
assert events == [
|
56
|
+
BaseOutput(name="out", value="bar"),
|
57
|
+
]
|
@@ -1,5 +1,7 @@
|
|
1
1
|
from typing import Callable, Generic, Optional, Type
|
2
2
|
|
3
|
+
from vellum.workflows.descriptors.base import BaseDescriptor
|
4
|
+
from vellum.workflows.descriptors.utils import resolve_value
|
3
5
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
4
6
|
from vellum.workflows.exceptions import NodeException
|
5
7
|
from vellum.workflows.inputs.base import BaseInputs
|
@@ -21,6 +23,7 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
21
23
|
|
22
24
|
max_attempts: int
|
23
25
|
retry_on_error_code: Optional[WorkflowErrorCode] = None
|
26
|
+
retry_on_condition: Optional[BaseDescriptor] = None
|
24
27
|
|
25
28
|
class SubworkflowInputs(BaseInputs):
|
26
29
|
attempt_number: int
|
@@ -55,18 +58,36 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
55
58
|
last_exception = NodeException(
|
56
59
|
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
57
60
|
message=f"""Unexpected rejection on attempt {attempt_number}: {terminal_event.error.code.value}.
|
61
|
+
Message: {terminal_event.error.message}""",
|
62
|
+
)
|
63
|
+
break
|
64
|
+
elif self.retry_on_condition and not resolve_value(self.retry_on_condition, self.state):
|
65
|
+
last_exception = NodeException(
|
66
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
67
|
+
message=f"""Rejection failed on attempt {attempt_number}: {terminal_event.error.code.value}.
|
58
68
|
Message: {terminal_event.error.message}""",
|
59
69
|
)
|
60
70
|
break
|
61
71
|
else:
|
62
|
-
last_exception =
|
72
|
+
last_exception = NodeException(
|
73
|
+
terminal_event.error.message,
|
74
|
+
code=terminal_event.error.code,
|
75
|
+
)
|
63
76
|
|
64
77
|
raise last_exception
|
65
78
|
|
66
79
|
@classmethod
|
67
80
|
def wrap(
|
68
|
-
cls,
|
81
|
+
cls,
|
82
|
+
max_attempts: int,
|
83
|
+
retry_on_error_code: Optional[WorkflowErrorCode] = None,
|
84
|
+
retry_on_condition: Optional[BaseDescriptor] = None,
|
69
85
|
) -> Callable[..., Type["RetryNode"]]:
|
70
86
|
return create_adornment(
|
71
|
-
cls,
|
87
|
+
cls,
|
88
|
+
attributes={
|
89
|
+
"max_attempts": max_attempts,
|
90
|
+
"retry_on_error_code": retry_on_error_code,
|
91
|
+
"retry_on_condition": retry_on_condition,
|
92
|
+
},
|
72
93
|
)
|
@@ -6,6 +6,7 @@ from vellum.workflows.inputs.base import BaseInputs
|
|
6
6
|
from vellum.workflows.nodes.bases import BaseNode
|
7
7
|
from vellum.workflows.nodes.core.retry_node.node import RetryNode
|
8
8
|
from vellum.workflows.outputs import BaseOutputs
|
9
|
+
from vellum.workflows.references.lazy import LazyReference
|
9
10
|
from vellum.workflows.state.base import BaseState, StateMeta
|
10
11
|
|
11
12
|
|
@@ -91,3 +92,42 @@ def test_retry_node__use_parent_inputs_and_state():
|
|
91
92
|
|
92
93
|
# THEN the data is used successfully
|
93
94
|
assert outputs.value == "foo bar"
|
95
|
+
|
96
|
+
|
97
|
+
def test_retry_node__condition_arg_successfully_retries():
|
98
|
+
# GIVEN workflow Inputs and State
|
99
|
+
class State(BaseState):
|
100
|
+
count = 0
|
101
|
+
|
102
|
+
# AND a retry node that retries on a condition
|
103
|
+
@RetryNode.wrap(
|
104
|
+
max_attempts=5,
|
105
|
+
retry_on_condition=LazyReference(lambda: State.count.less_than(3)),
|
106
|
+
)
|
107
|
+
class TestNode(BaseNode[State]):
|
108
|
+
attempt_number = RetryNode.SubworkflowInputs.attempt_number
|
109
|
+
|
110
|
+
class Outputs(BaseOutputs):
|
111
|
+
value: str
|
112
|
+
|
113
|
+
def run(self) -> Outputs:
|
114
|
+
if not isinstance(self.state.meta.parent, State):
|
115
|
+
raise NodeException(message="Failed to resolve parent state")
|
116
|
+
|
117
|
+
self.state.meta.parent.count += 1
|
118
|
+
raise NodeException(message=f"This is failure attempt {self.attempt_number}")
|
119
|
+
|
120
|
+
# WHEN the node is run
|
121
|
+
node = TestNode(state=State())
|
122
|
+
with pytest.raises(NodeException) as exc_info:
|
123
|
+
node.run()
|
124
|
+
|
125
|
+
# THEN the exception raised is the last one
|
126
|
+
assert (
|
127
|
+
exc_info.value.message
|
128
|
+
== """Rejection failed on attempt 3: INTERNAL_ERROR.
|
129
|
+
Message: This is failure attempt 3"""
|
130
|
+
)
|
131
|
+
|
132
|
+
# AND the state was updated each time
|
133
|
+
assert node.state.count == 3
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
|
3
|
+
from vellum.client.types.function_call import FunctionCall
|
3
4
|
from vellum.workflows.nodes.bases.base import BaseNode
|
4
5
|
from vellum.workflows.nodes.core.templating_node.node import TemplatingNode
|
5
6
|
from vellum.workflows.state import BaseState
|
@@ -21,7 +22,9 @@ def test_templating_node__dict_output():
|
|
21
22
|
outputs = node.run()
|
22
23
|
|
23
24
|
# THEN the output is json serializable
|
24
|
-
|
25
|
+
# https://app.shortcut.com/vellum/story/6132
|
26
|
+
dump: str = outputs.result # type: ignore[assignment]
|
27
|
+
assert json.loads(dump) == {"key": "value"}
|
25
28
|
|
26
29
|
|
27
30
|
def test_templating_node__int_output():
|
@@ -106,3 +109,19 @@ def test_templating_node__execution_count_reference():
|
|
106
109
|
|
107
110
|
# THEN the output is just the total
|
108
111
|
assert outputs.result == "0"
|
112
|
+
|
113
|
+
|
114
|
+
def test_templating_node__pydantic_to_json():
|
115
|
+
# GIVEN a templating node that uses tojson on a pydantic model
|
116
|
+
class JSONTemplateNode(TemplatingNode[BaseState, Json]):
|
117
|
+
template = "{{ function_call | tojson }}"
|
118
|
+
inputs = {
|
119
|
+
"function_call": FunctionCall(name="test", arguments={"key": "value"}),
|
120
|
+
}
|
121
|
+
|
122
|
+
# WHEN the node is run
|
123
|
+
node = JSONTemplateNode()
|
124
|
+
outputs = node.run()
|
125
|
+
|
126
|
+
# THEN the output is the expected JSON
|
127
|
+
assert outputs.result == {"name": "test", "arguments": {"key": "value"}, "id": None}
|
@@ -2,7 +2,7 @@ from typing import Optional, Union
|
|
2
2
|
|
3
3
|
from vellum.workflows.constants import AuthorizationType
|
4
4
|
from vellum.workflows.nodes.displayable.bases.api_node import BaseAPINode
|
5
|
-
from vellum.workflows.
|
5
|
+
from vellum.workflows.types.core import VellumSecret
|
6
6
|
|
7
7
|
|
8
8
|
class APINode(BaseAPINode):
|
@@ -24,8 +24,8 @@ class APINode(BaseAPINode):
|
|
24
24
|
|
25
25
|
authorization_type: Optional[AuthorizationType] = None
|
26
26
|
api_key_header_key: Optional[str] = None
|
27
|
-
api_key_header_value: Optional[Union[str,
|
28
|
-
bearer_token_value: Optional[Union[str,
|
27
|
+
api_key_header_value: Optional[Union[str, VellumSecret]] = None
|
28
|
+
bearer_token_value: Optional[Union[str, VellumSecret]] = None
|
29
29
|
|
30
30
|
def run(self) -> BaseAPINode.Outputs:
|
31
31
|
headers = self.headers or {}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: vellum-ai
|
3
|
-
Version: 0.12.
|
3
|
+
Version: 0.12.17
|
4
4
|
Summary:
|
5
5
|
License: MIT
|
6
6
|
Requires-Python: >=3.9,<4.0
|
@@ -40,9 +40,11 @@ Requires-Dist: typing_extensions (>=4.0.0)
|
|
40
40
|
Description-Content-Type: text/markdown
|
41
41
|
|
42
42
|
<p align="center">
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
<a href="https://vellum.ai">
|
44
|
+
<picture>
|
45
|
+
<img alt="Vellum README banner" src="https://storage.googleapis.com/vellum-public/assets/github-readme-banner.png" height="128">
|
46
|
+
</picture>
|
47
|
+
</a>
|
46
48
|
<p align="center">
|
47
49
|
<a href="https://vellum.ai">Learn more</a>
|
48
50
|
·
|
@@ -64,13 +66,13 @@ Description-Content-Type: text/markdown
|
|
64
66
|
|
65
67
|
# Introduction
|
66
68
|
|
67
|
-
[Vellum](https://www.vellum.ai/) is the end-to-end development platform for building production-grade AI applications
|
69
|
+
[Vellum](https://www.vellum.ai/) is the end-to-end development platform for building production-grade AI applications.
|
68
70
|
|
69
71
|
### Core Features
|
70
72
|
|
71
73
|
- **Orchestration:** A powerful SDK and IDE for defining and debugging the control flow of your AI applications
|
72
74
|
- **Prompting:** A best-in-class prompt playground for iterating on and refining prompts between models from any provider
|
73
|
-
- **Evaluations**: An evaluations framework that makes it easy to measure the quality of your AI systems at scale
|
75
|
+
- **Evaluations**: An evaluations framework that makes it easy to measure the quality of your AI systems at scale
|
74
76
|
- **Retrieval:** A ready-to-go service for turning unstructured content into intelligent, context-aware solutions
|
75
77
|
optimized for AI systems
|
76
78
|
- **Deployment:** Decouple updates to your AI systems from your application code with an easy integration +
|
@@ -87,7 +89,7 @@ Description-Content-Type: text/markdown
|
|
87
89
|
|
88
90
|
## Get Started
|
89
91
|
|
90
|
-
Most functionality within the
|
92
|
+
Most functionality within the SDK requires a Vellum account and API key. To sign up, [talk to us](https://www.vellum.ai/landing-pages/request-demo)
|
91
93
|
or visit our [pricing page](https://www.vellum.ai/pricing).
|
92
94
|
|
93
95
|
Even without a Vellum account, you can use the Workflows SDK to define the control flow of your AI systems. [Learn
|
@@ -101,7 +103,7 @@ Learn more and get started by visiting the [Vellum Client SDK README](/src/vellu
|
|
101
103
|
## Workflows SDK
|
102
104
|
|
103
105
|
The Vellum Workflows SDK is a high-level framework for defining and debugging the control flow of AI systems. At
|
104
|
-
it's core, it's a powerful workflow engine with syntactic sugar for
|
106
|
+
it's core, it's a powerful workflow engine with syntactic sugar for declaratively defining graphs, the nodes within,
|
105
107
|
and the relationships between them.
|
106
108
|
|
107
109
|
The Workflows SDK can be used with or without a Vellum account, but a Vellum account is required to use certain
|