vellum-ai 0.14.83__py3-none-any.whl → 0.14.85__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vellum/client/core/client_wrapper.py +2 -2
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +12 -1
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +54 -21
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +1 -1
- vellum/workflows/nodes/experimental/tool_calling_node/node.py +3 -0
- vellum/workflows/runner/runner.py +34 -2
- vellum/workflows/state/base.py +92 -30
- vellum/workflows/state/delta.py +20 -0
- vellum/workflows/state/tests/test_state.py +6 -5
- {vellum_ai-0.14.83.dist-info → vellum_ai-0.14.85.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.83.dist-info → vellum_ai-0.14.85.dist-info}/RECORD +14 -12
- {vellum_ai-0.14.83.dist-info → vellum_ai-0.14.85.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.83.dist-info → vellum_ai-0.14.85.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.83.dist-info → vellum_ai-0.14.85.dist-info}/entry_points.txt +0 -0
@@ -16,10 +16,10 @@ class BaseClientWrapper:
|
|
16
16
|
|
17
17
|
def get_headers(self) -> typing.Dict[str, str]:
|
18
18
|
headers: typing.Dict[str, str] = {
|
19
|
-
"User-Agent": "vellum-ai/0.14.
|
19
|
+
"User-Agent": "vellum-ai/0.14.85",
|
20
20
|
"X-Fern-Language": "Python",
|
21
21
|
"X-Fern-SDK-Name": "vellum-ai",
|
22
|
-
"X-Fern-SDK-Version": "0.14.
|
22
|
+
"X-Fern-SDK-Version": "0.14.85",
|
23
23
|
}
|
24
24
|
headers["X-API-KEY"] = self.api_key
|
25
25
|
return headers
|
@@ -84,10 +84,13 @@ class InlineSubworkflowNode(
|
|
84
84
|
)
|
85
85
|
|
86
86
|
outputs: Optional[BaseOutputs] = None
|
87
|
+
exception: Optional[NodeException] = None
|
87
88
|
fulfilled_output_names: Set[str] = set()
|
88
89
|
|
89
90
|
for event in subworkflow_stream:
|
90
91
|
self._context._emit_subworkflow_event(event)
|
92
|
+
if exception:
|
93
|
+
continue
|
91
94
|
|
92
95
|
if not is_workflow_event(event):
|
93
96
|
continue
|
@@ -101,7 +104,15 @@ class InlineSubworkflowNode(
|
|
101
104
|
elif event.name == "workflow.execution.fulfilled":
|
102
105
|
outputs = event.outputs
|
103
106
|
elif event.name == "workflow.execution.rejected":
|
104
|
-
|
107
|
+
exception = NodeException.of(event.error)
|
108
|
+
elif event.name == "workflow.execution.paused":
|
109
|
+
exception = NodeException(
|
110
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
111
|
+
message="Subworkflow unexpectedly paused",
|
112
|
+
)
|
113
|
+
|
114
|
+
if exception:
|
115
|
+
raise exception
|
105
116
|
|
106
117
|
if outputs is None:
|
107
118
|
raise NodeException(
|
@@ -1,8 +1,9 @@
|
|
1
|
-
from typing import ClassVar, List, Optional
|
1
|
+
from typing import ClassVar, Iterator, List, Optional, Set
|
2
2
|
|
3
3
|
from vellum import ChatMessage, PromptBlock
|
4
4
|
from vellum.workflows.context import execution_context, get_parent_context
|
5
5
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
6
|
+
from vellum.workflows.events.workflow import is_workflow_event
|
6
7
|
from vellum.workflows.exceptions import NodeException
|
7
8
|
from vellum.workflows.graph.graph import Graph
|
8
9
|
from vellum.workflows.inputs.base import BaseInputs
|
@@ -12,10 +13,11 @@ from vellum.workflows.nodes.displayable.tool_calling_node.utils import (
|
|
12
13
|
create_tool_router_node,
|
13
14
|
get_function_name,
|
14
15
|
)
|
15
|
-
from vellum.workflows.outputs.base import BaseOutputs
|
16
|
+
from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
|
16
17
|
from vellum.workflows.state.base import BaseState
|
17
18
|
from vellum.workflows.state.context import WorkflowContext
|
18
19
|
from vellum.workflows.types.core import EntityInputsInterface, Tool
|
20
|
+
from vellum.workflows.workflows.event_filters import all_workflow_event_filter
|
19
21
|
|
20
22
|
|
21
23
|
class ToolCallingNode(BaseNode):
|
@@ -47,12 +49,12 @@ class ToolCallingNode(BaseNode):
|
|
47
49
|
text: str
|
48
50
|
chat_history: List[ChatMessage]
|
49
51
|
|
50
|
-
def run(self) ->
|
52
|
+
def run(self) -> Iterator[BaseOutput]:
|
51
53
|
"""
|
52
|
-
Run the tool calling workflow.
|
54
|
+
Run the tool calling workflow with streaming support.
|
53
55
|
|
54
56
|
This dynamically builds a graph with router and function nodes,
|
55
|
-
then executes the workflow.
|
57
|
+
then executes the workflow and streams chat_history updates.
|
56
58
|
"""
|
57
59
|
|
58
60
|
self._build_graph()
|
@@ -77,24 +79,55 @@ class ToolCallingNode(BaseNode):
|
|
77
79
|
context=WorkflowContext.create_from(self._context),
|
78
80
|
)
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
85
|
-
message="Subworkflow unexpectedly paused",
|
86
|
-
)
|
87
|
-
elif terminal_event.name == "workflow.execution.fulfilled":
|
88
|
-
node_outputs = self.Outputs(
|
89
|
-
text=terminal_event.outputs.text,
|
90
|
-
chat_history=terminal_event.outputs.chat_history,
|
91
|
-
)
|
82
|
+
subworkflow_stream = subworkflow.stream(
|
83
|
+
event_filter=all_workflow_event_filter,
|
84
|
+
node_output_mocks=self._context._get_all_node_output_mocks(),
|
85
|
+
)
|
92
86
|
|
93
|
-
|
94
|
-
|
95
|
-
|
87
|
+
outputs: Optional[BaseOutputs] = None
|
88
|
+
exception: Optional[NodeException] = None
|
89
|
+
fulfilled_output_names: Set[str] = set()
|
90
|
+
|
91
|
+
# Yield initiated event for chat_history
|
92
|
+
yield BaseOutput(name="chat_history")
|
93
|
+
|
94
|
+
for event in subworkflow_stream:
|
95
|
+
self._context._emit_subworkflow_event(event)
|
96
|
+
|
97
|
+
if not is_workflow_event(event):
|
98
|
+
continue
|
99
|
+
if event.workflow_definition != ToolCallingWorkflow:
|
100
|
+
continue
|
101
|
+
|
102
|
+
if event.name == "workflow.execution.streaming":
|
103
|
+
if event.output.name == "chat_history":
|
104
|
+
if event.output.is_fulfilled:
|
105
|
+
fulfilled_output_names.add(event.output.name)
|
106
|
+
yield event.output
|
107
|
+
elif event.output.name == "text":
|
108
|
+
if event.output.is_fulfilled:
|
109
|
+
fulfilled_output_names.add(event.output.name)
|
110
|
+
yield event.output
|
111
|
+
elif event.name == "workflow.execution.fulfilled":
|
112
|
+
outputs = event.outputs
|
113
|
+
elif event.name == "workflow.execution.rejected":
|
114
|
+
exception = NodeException.of(event.error)
|
115
|
+
|
116
|
+
if exception:
|
117
|
+
raise exception
|
118
|
+
|
119
|
+
if outputs is None:
|
120
|
+
raise NodeException(
|
121
|
+
message="Expected to receive outputs from Tool Calling Workflow",
|
122
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
123
|
+
)
|
96
124
|
|
97
|
-
|
125
|
+
for output_descriptor, output_value in outputs:
|
126
|
+
if output_descriptor.name not in fulfilled_output_names:
|
127
|
+
yield BaseOutput(
|
128
|
+
name=output_descriptor.name,
|
129
|
+
value=output_value,
|
130
|
+
)
|
98
131
|
|
99
132
|
def _build_graph(self) -> None:
|
100
133
|
self.tool_router_node = create_tool_router_node(
|
@@ -114,7 +114,7 @@ def test_tool_calling_node_inline_workflow_context():
|
|
114
114
|
function_node.state = TestState(meta=StateMeta(node_outputs={tool_router_node.Outputs.text: '{"arguments": {}}'}))
|
115
115
|
|
116
116
|
# WHEN the function node runs
|
117
|
-
outputs = function_node.run()
|
117
|
+
outputs = list(function_node.run())
|
118
118
|
|
119
119
|
# THEN the workflow should have run successfully
|
120
120
|
assert outputs is not None
|
@@ -15,6 +15,7 @@ from typing import (
|
|
15
15
|
Generic,
|
16
16
|
Iterable,
|
17
17
|
Iterator,
|
18
|
+
List,
|
18
19
|
Optional,
|
19
20
|
Sequence,
|
20
21
|
Set,
|
@@ -67,7 +68,9 @@ from vellum.workflows.outputs import BaseOutputs
|
|
67
68
|
from vellum.workflows.outputs.base import BaseOutput
|
68
69
|
from vellum.workflows.ports.port import Port
|
69
70
|
from vellum.workflows.references import ExternalInputReference, OutputReference
|
71
|
+
from vellum.workflows.references.state_value import StateValueReference
|
70
72
|
from vellum.workflows.state.base import BaseState
|
73
|
+
from vellum.workflows.state.delta import StateDelta
|
71
74
|
from vellum.workflows.types.generics import InputsType, OutputsType, StateType
|
72
75
|
|
73
76
|
if TYPE_CHECKING:
|
@@ -175,12 +178,16 @@ class WorkflowRunner(Generic[StateType]):
|
|
175
178
|
setattr(
|
176
179
|
self._initial_state,
|
177
180
|
"__snapshot_callback__",
|
178
|
-
lambda s: self._snapshot_state(s),
|
181
|
+
lambda s, d: self._snapshot_state(s, d),
|
179
182
|
)
|
180
183
|
self.workflow.context._register_event_queue(self._workflow_event_inner_queue)
|
181
184
|
self.workflow.context._register_node_output_mocks(node_output_mocks or [])
|
182
185
|
|
183
|
-
|
186
|
+
self._outputs_listening_to_state = [
|
187
|
+
descriptor for descriptor in self.workflow.Outputs if isinstance(descriptor.instance, StateValueReference)
|
188
|
+
]
|
189
|
+
|
190
|
+
def _snapshot_state(self, state: StateType, deltas: List[StateDelta]) -> StateType:
|
184
191
|
self._workflow_event_inner_queue.put(
|
185
192
|
WorkflowExecutionSnapshottedEvent(
|
186
193
|
trace_id=self._execution_context.trace_id,
|
@@ -192,6 +199,31 @@ class WorkflowRunner(Generic[StateType]):
|
|
192
199
|
parent=self._execution_context.parent_context,
|
193
200
|
)
|
194
201
|
)
|
202
|
+
|
203
|
+
delta_names = [d.name for d in deltas]
|
204
|
+
for descriptor in self._outputs_listening_to_state:
|
205
|
+
if not isinstance(descriptor.instance, StateValueReference):
|
206
|
+
continue
|
207
|
+
|
208
|
+
if descriptor.instance.name not in delta_names:
|
209
|
+
continue
|
210
|
+
|
211
|
+
resolved_delta = descriptor.instance.resolve(state)
|
212
|
+
if resolved_delta is undefined:
|
213
|
+
continue
|
214
|
+
|
215
|
+
self._workflow_event_outer_queue.put(
|
216
|
+
self._stream_workflow_event(
|
217
|
+
BaseOutput(
|
218
|
+
name=descriptor.name,
|
219
|
+
# We may want to switch this to using the delta from the state delta
|
220
|
+
# instead of the state value. In the short term, we need to show
|
221
|
+
# the full conversation of the tool calling node's delta's.
|
222
|
+
delta=resolved_delta,
|
223
|
+
)
|
224
|
+
)
|
225
|
+
)
|
226
|
+
|
195
227
|
self.workflow._store.append_state_snapshot(state)
|
196
228
|
self._background_thread_queue.put(state)
|
197
229
|
return state
|
vellum/workflows/state/base.py
CHANGED
@@ -7,7 +7,21 @@ import logging
|
|
7
7
|
from queue import Queue
|
8
8
|
from threading import Lock
|
9
9
|
from uuid import UUID, uuid4
|
10
|
-
from typing import
|
10
|
+
from typing import (
|
11
|
+
TYPE_CHECKING,
|
12
|
+
Any,
|
13
|
+
Callable,
|
14
|
+
Dict,
|
15
|
+
Iterator,
|
16
|
+
List,
|
17
|
+
Optional,
|
18
|
+
Set,
|
19
|
+
SupportsIndex,
|
20
|
+
Tuple,
|
21
|
+
Type,
|
22
|
+
Union,
|
23
|
+
cast,
|
24
|
+
)
|
11
25
|
from typing_extensions import dataclass_transform
|
12
26
|
|
13
27
|
from pydantic import GetCoreSchemaHandler, ValidationInfo, field_serializer, field_validator
|
@@ -18,6 +32,7 @@ from vellum.utils.uuid import is_valid_uuid
|
|
18
32
|
from vellum.workflows.constants import undefined
|
19
33
|
from vellum.workflows.inputs.base import BaseInputs
|
20
34
|
from vellum.workflows.references import ExternalInputReference, OutputReference, StateValueReference
|
35
|
+
from vellum.workflows.state.delta import AppendStateDelta, SetStateDelta, StateDelta
|
21
36
|
from vellum.workflows.types.definition import CodeResourceDefinition, serialize_type_encoder_with_id
|
22
37
|
from vellum.workflows.types.generics import StateType, import_workflow_class, is_workflow_class
|
23
38
|
from vellum.workflows.types.stack import Stack
|
@@ -31,16 +46,12 @@ logger = logging.getLogger(__name__)
|
|
31
46
|
|
32
47
|
|
33
48
|
class _Snapshottable:
|
34
|
-
_snapshot_callback: Callable[[], None]
|
49
|
+
_snapshot_callback: Callable[[Optional[StateDelta]], None]
|
50
|
+
_path: str
|
35
51
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
memo=memo,
|
40
|
-
exclusions={
|
41
|
-
"_snapshot_callback": self._snapshot_callback,
|
42
|
-
},
|
43
|
-
)
|
52
|
+
def __bind__(self, path: str, snapshot_callback: Callable[[Optional[StateDelta]], None]) -> None:
|
53
|
+
self._snapshot_callback = snapshot_callback
|
54
|
+
self._path = path
|
44
55
|
|
45
56
|
|
46
57
|
@dataclass_transform(kw_only_default=True)
|
@@ -71,10 +82,44 @@ class _BaseStateMeta(type):
|
|
71
82
|
class _SnapshottableDict(dict, _Snapshottable):
|
72
83
|
def __setitem__(self, key: Any, value: Any) -> None:
|
73
84
|
super().__setitem__(key, value)
|
74
|
-
self._snapshot_callback()
|
85
|
+
self._snapshot_callback(SetStateDelta(name=f"{self._path}.{key}", delta=value))
|
86
|
+
|
87
|
+
def __deepcopy__(self, memo: Any) -> "_SnapshottableDict":
|
88
|
+
y: dict = {}
|
89
|
+
memo[id(self)] = y
|
90
|
+
for key, value in self.items():
|
91
|
+
y[deepcopy(key, memo)] = deepcopy(value, memo)
|
92
|
+
|
93
|
+
y = _SnapshottableDict(y)
|
94
|
+
y.__bind__(self._path, self._snapshot_callback)
|
95
|
+
memo[id(self)] = y
|
96
|
+
return y
|
97
|
+
|
98
|
+
|
99
|
+
class _SnapshottableList(list, _Snapshottable):
|
100
|
+
def __setitem__(self, index: Union[SupportsIndex, slice], value: Any) -> None:
|
101
|
+
super().__setitem__(index, value)
|
102
|
+
if isinstance(index, int):
|
103
|
+
self._snapshot_callback(SetStateDelta(name=f"{self._path}.{index}", delta=value))
|
104
|
+
|
105
|
+
def append(self, value: Any) -> None:
|
106
|
+
super().append(value)
|
107
|
+
self._snapshot_callback(AppendStateDelta(name=self._path, delta=value))
|
108
|
+
|
109
|
+
def __deepcopy__(self, memo: Any) -> "_SnapshottableList":
|
110
|
+
y: list = []
|
111
|
+
memo[id(self)] = y
|
112
|
+
append = y.append
|
113
|
+
for a in self:
|
114
|
+
append(deepcopy(a, memo))
|
75
115
|
|
116
|
+
y = _SnapshottableList(y)
|
117
|
+
y.__bind__(self._path, self._snapshot_callback)
|
118
|
+
memo[id(self)] = y
|
119
|
+
return y
|
76
120
|
|
77
|
-
|
121
|
+
|
122
|
+
def _make_snapshottable(path: str, value: Any, snapshot_callback: Callable[[Optional[StateDelta]], None]) -> Any:
|
78
123
|
"""
|
79
124
|
Edits any value to make it snapshottable on edit. Made as a separate function from `BaseState` to
|
80
125
|
avoid namespace conflicts with subclasses.
|
@@ -84,9 +129,14 @@ def _make_snapshottable(value: Any, snapshot_callback: Callable[[], None]) -> An
|
|
84
129
|
|
85
130
|
if isinstance(value, dict):
|
86
131
|
snapshottable_dict = _SnapshottableDict(value)
|
87
|
-
snapshottable_dict.
|
132
|
+
snapshottable_dict.__bind__(path, snapshot_callback)
|
88
133
|
return snapshottable_dict
|
89
134
|
|
135
|
+
if isinstance(value, list):
|
136
|
+
snapshottable_list = _SnapshottableList(value)
|
137
|
+
snapshottable_list.__bind__(path, snapshot_callback)
|
138
|
+
return snapshottable_list
|
139
|
+
|
90
140
|
return value
|
91
141
|
|
92
142
|
|
@@ -253,14 +303,14 @@ class StateMeta(UniversalBaseModel):
|
|
253
303
|
node_outputs: Dict[OutputReference, Any] = field(default_factory=dict)
|
254
304
|
node_execution_cache: NodeExecutionCache = field(default_factory=NodeExecutionCache)
|
255
305
|
parent: Optional["BaseState"] = None
|
256
|
-
__snapshot_callback__: Optional[Callable[[], None]] = field(init=False, default=None)
|
306
|
+
__snapshot_callback__: Optional[Callable[[Optional[StateDelta]], None]] = field(init=False, default=None)
|
257
307
|
|
258
308
|
def model_post_init(self, context: Any) -> None:
|
259
309
|
self.__snapshot_callback__ = None
|
260
310
|
|
261
|
-
def add_snapshot_callback(self, callback: Callable[[], None]) -> None:
|
262
|
-
self.node_outputs = _make_snapshottable(self.node_outputs, callback)
|
263
|
-
self.external_inputs = _make_snapshottable(self.external_inputs, callback)
|
311
|
+
def add_snapshot_callback(self, callback: Callable[[Optional[StateDelta]], None]) -> None:
|
312
|
+
self.node_outputs = _make_snapshottable("meta.node_outputs", self.node_outputs, callback)
|
313
|
+
self.external_inputs = _make_snapshottable("meta.external_inputs", self.external_inputs, callback)
|
264
314
|
self.__snapshot_callback__ = callback
|
265
315
|
|
266
316
|
def __setattr__(self, name: str, value: Any) -> None:
|
@@ -270,7 +320,7 @@ class StateMeta(UniversalBaseModel):
|
|
270
320
|
|
271
321
|
super().__setattr__(name, value)
|
272
322
|
if callable(self.__snapshot_callback__):
|
273
|
-
self.__snapshot_callback__()
|
323
|
+
self.__snapshot_callback__(SetStateDelta(name=f"meta.{name}", delta=value))
|
274
324
|
|
275
325
|
@field_serializer("workflow_definition")
|
276
326
|
def serialize_workflow_definition(self, workflow_definition: Type["BaseWorkflow"], _info: Any) -> Dict[str, Any]:
|
@@ -428,11 +478,15 @@ class BaseState(metaclass=_BaseStateMeta):
|
|
428
478
|
|
429
479
|
__lock__: Lock = field(init=False)
|
430
480
|
__is_quiet__: bool = field(init=False)
|
431
|
-
|
481
|
+
__is_atomic__: bool = field(init=False)
|
482
|
+
__snapshot_callback__: Callable[["BaseState", List[StateDelta]], None] = field(init=False)
|
483
|
+
__deltas__: List[StateDelta] = field(init=False)
|
432
484
|
|
433
485
|
def __init__(self, meta: Optional[StateMeta] = None, **kwargs: Any) -> None:
|
434
486
|
self.__is_quiet__ = True
|
435
|
-
self.
|
487
|
+
self.__is_atomic__ = False
|
488
|
+
self.__snapshot_callback__ = lambda state, deltas: None
|
489
|
+
self.__deltas__ = []
|
436
490
|
self.__lock__ = Lock()
|
437
491
|
|
438
492
|
self.meta = meta or StateMeta()
|
@@ -442,7 +496,7 @@ class BaseState(metaclass=_BaseStateMeta):
|
|
442
496
|
for name, value in self.__class__.__dict__.items():
|
443
497
|
if not name.startswith("_") and name != "meta":
|
444
498
|
# Bypass __is_quiet__ instead of `setattr`
|
445
|
-
snapshottable_value = _make_snapshottable(value, self.__snapshot__)
|
499
|
+
snapshottable_value = _make_snapshottable(name, value, self.__snapshot__)
|
446
500
|
super().__setattr__(name, snapshottable_value)
|
447
501
|
|
448
502
|
for name, value in kwargs.items():
|
@@ -494,15 +548,15 @@ class BaseState(metaclass=_BaseStateMeta):
|
|
494
548
|
def __getitem__(self, key: str) -> Any:
|
495
549
|
return self.__dict__[key]
|
496
550
|
|
497
|
-
def __setattr__(self, name: str,
|
551
|
+
def __setattr__(self, name: str, delta: Any) -> None:
|
498
552
|
if name.startswith("_"):
|
499
|
-
super().__setattr__(name,
|
553
|
+
super().__setattr__(name, delta)
|
500
554
|
return
|
501
555
|
|
502
|
-
snapshottable_value = _make_snapshottable(
|
556
|
+
snapshottable_value = _make_snapshottable(name, delta, self.__snapshot__)
|
503
557
|
super().__setattr__(name, snapshottable_value)
|
504
558
|
self.meta.updated_ts = datetime_now()
|
505
|
-
self.__snapshot__()
|
559
|
+
self.__snapshot__(SetStateDelta(name=name, delta=delta))
|
506
560
|
|
507
561
|
def __add__(self, other: StateType) -> StateType:
|
508
562
|
"""
|
@@ -531,7 +585,7 @@ class BaseState(metaclass=_BaseStateMeta):
|
|
531
585
|
|
532
586
|
return cast(StateType, latest_state)
|
533
587
|
|
534
|
-
def __snapshot__(self) -> None:
|
588
|
+
def __snapshot__(self, delta: Optional[StateDelta] = None) -> None:
|
535
589
|
"""
|
536
590
|
Snapshots the current state to the workflow emitter. The invoked callback is overridden by the
|
537
591
|
workflow runner.
|
@@ -539,11 +593,19 @@ class BaseState(metaclass=_BaseStateMeta):
|
|
539
593
|
if self.__is_quiet__:
|
540
594
|
return
|
541
595
|
|
596
|
+
if delta:
|
597
|
+
self.__deltas__.append(delta)
|
598
|
+
|
599
|
+
if self.__is_atomic__:
|
600
|
+
return
|
601
|
+
|
542
602
|
try:
|
543
|
-
self.__snapshot_callback__(deepcopy(self))
|
603
|
+
self.__snapshot_callback__(deepcopy(self), self.__deltas__)
|
544
604
|
except Exception:
|
545
605
|
logger.exception("Failed to snapshot Workflow state.")
|
546
606
|
|
607
|
+
self.__deltas__.clear()
|
608
|
+
|
547
609
|
@contextmanager
|
548
610
|
def __quiet__(self):
|
549
611
|
prev = self.__is_quiet__
|
@@ -555,12 +617,12 @@ class BaseState(metaclass=_BaseStateMeta):
|
|
555
617
|
|
556
618
|
@contextmanager
|
557
619
|
def __atomic__(self):
|
558
|
-
prev = self.
|
559
|
-
self.
|
620
|
+
prev = self.__is_atomic__
|
621
|
+
self.__is_atomic__ = True
|
560
622
|
try:
|
561
623
|
yield
|
562
624
|
finally:
|
563
|
-
self.
|
625
|
+
self.__is_atomic__ = prev
|
564
626
|
self.__snapshot__()
|
565
627
|
|
566
628
|
@classmethod
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from typing import Any, Literal, Union
|
2
|
+
|
3
|
+
from vellum.core.pydantic_utilities import UniversalBaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class BaseStateDelta(UniversalBaseModel):
|
7
|
+
name: str
|
8
|
+
|
9
|
+
|
10
|
+
class SetStateDelta(BaseStateDelta):
|
11
|
+
delta: Any
|
12
|
+
delta_type: Literal["SET_STATE"] = "SET_STATE"
|
13
|
+
|
14
|
+
|
15
|
+
class AppendStateDelta(BaseStateDelta):
|
16
|
+
delta: Any
|
17
|
+
delta_type: Literal["APPEND_STATE"] = "APPEND_STATE"
|
18
|
+
|
19
|
+
|
20
|
+
StateDelta = Union[SetStateDelta, AppendStateDelta]
|
@@ -2,11 +2,12 @@ import pytest
|
|
2
2
|
from copy import deepcopy
|
3
3
|
import json
|
4
4
|
from queue import Queue
|
5
|
-
from typing import Dict, cast
|
5
|
+
from typing import Dict, List, cast
|
6
6
|
|
7
7
|
from vellum.workflows.nodes.bases import BaseNode
|
8
8
|
from vellum.workflows.outputs.base import BaseOutputs
|
9
9
|
from vellum.workflows.state.base import BaseState
|
10
|
+
from vellum.workflows.state.delta import SetStateDelta, StateDelta
|
10
11
|
from vellum.workflows.state.encoder import DefaultStateEncoder
|
11
12
|
|
12
13
|
|
@@ -28,7 +29,7 @@ class MockState(BaseState):
|
|
28
29
|
|
29
30
|
def __init__(self, *args, **kwargs) -> None:
|
30
31
|
super().__init__(*args, **kwargs)
|
31
|
-
self.__snapshot_callback__ = lambda _: self.__mock_snapshot__()
|
32
|
+
self.__snapshot_callback__ = lambda _, __: self.__mock_snapshot__()
|
32
33
|
|
33
34
|
def __mock_snapshot__(self) -> None:
|
34
35
|
self.__snapshot_count__ += 1
|
@@ -201,7 +202,7 @@ def test_state_snapshot__deepcopy_fails__logs_error(mock_deepcopy, mock_logger):
|
|
201
202
|
# AND we have a snapshot callback that we are tracking
|
202
203
|
snapshot_count = 0
|
203
204
|
|
204
|
-
def __snapshot_callback__(state: "BaseState") -> None:
|
205
|
+
def __snapshot_callback__(state: "BaseState", deltas: List[StateDelta]) -> None:
|
205
206
|
nonlocal snapshot_count
|
206
207
|
snapshot_count += 1
|
207
208
|
|
@@ -220,8 +221,8 @@ def test_state_snapshot__deepcopy_fails__logs_error(mock_deepcopy, mock_logger):
|
|
220
221
|
mock_deepcopy.side_effect = side_effect
|
221
222
|
|
222
223
|
# WHEN we snapshot the state twice
|
223
|
-
state.__snapshot__()
|
224
|
-
state.__snapshot__()
|
224
|
+
state.__snapshot__(SetStateDelta(name="foo", delta="bar"))
|
225
|
+
state.__snapshot__(SetStateDelta(name="foo", delta="baz"))
|
225
226
|
|
226
227
|
# THEN we were able to invoke the callback once
|
227
228
|
assert snapshot_count == 1
|
@@ -144,7 +144,7 @@ vellum/client/README.md,sha256=47bNYmRLSISR1ING58kXXZ88nFLPGFv0bAspBtuXG3g,4306
|
|
144
144
|
vellum/client/__init__.py,sha256=tKLc-F8I8_62RSZg7J7Lvo1dUQ_or7DGsDhbMyhWfGA,120958
|
145
145
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
146
146
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
147
|
-
vellum/client/core/client_wrapper.py,sha256=
|
147
|
+
vellum/client/core/client_wrapper.py,sha256=X9SbAOmZu48N8-Sgi-VoDoEM3tro2SNf1grdzvosVww,1916
|
148
148
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
149
149
|
vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
|
150
150
|
vellum/client/core/http_client.py,sha256=cKs2w0ybDBk1wHQf-fTALm_MmvaMe3cZKcYJxqmCxkE,19539
|
@@ -1595,7 +1595,7 @@ vellum/workflows/nodes/core/__init__.py,sha256=5zDMCmyt1v0HTJzlUBwq3U9L825yZGZhT
|
|
1595
1595
|
vellum/workflows/nodes/core/error_node/__init__.py,sha256=g7RRnlHhqu4qByfLjBwCunmgGA8dI5gNsjS3h6TwlSI,60
|
1596
1596
|
vellum/workflows/nodes/core/error_node/node.py,sha256=MFHU5vITYSK-L9CuMZ49In2ZeNLWnhZD0f8r5dWvb5Y,1270
|
1597
1597
|
vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py,sha256=nKNEH1QTl-1PcvmYoqSWEl0-t6gAur8GLTXHzklRQfM,84
|
1598
|
-
vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=
|
1598
|
+
vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=TCmO0wPbt7kc89K1iGqeeoZy-yn1z0iioYSSN6mErdY,6997
|
1599
1599
|
vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1600
1600
|
vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=kUqwcRMMxjTHALbwGUXCJT_aJBrHS1bkg49oL8R0JO8,4337
|
1601
1601
|
vellum/workflows/nodes/core/map_node/__init__.py,sha256=MXpZYmGfhsMJHqqlpd64WiJRtbAtAMQz-_3fCU_cLV0,56
|
@@ -1679,9 +1679,9 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
|
|
1679
1679
|
vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
|
1680
1680
|
vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=dc3EEn1sOICpr3GdS8eyeFtExaGwWWcw9eHSdkRhQJU,2584
|
1681
1681
|
vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
|
1682
|
-
vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=
|
1682
|
+
vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=7MqEtw-RejpJ5Uer11tIFKRtklC4DfiWVx2-tpnA1Gg,6310
|
1683
1683
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1684
|
-
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=
|
1684
|
+
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=ZWxicmEpBUAIudqUcKi1Li1f-Z2thYq8xI_Kf6YstMg,5077
|
1685
1685
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=eu6WTyENhGLg9pGp_j69rysZjf_qiQXske1YdZn9PzU,1718
|
1686
1686
|
vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=f2hCLciTqjHXj9tVdnnTAVY0BLUCMBoH0emQeOJCB7I,11309
|
1687
1687
|
vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
|
@@ -1689,6 +1689,7 @@ vellum/workflows/nodes/experimental/__init__.py,sha256=k7VQEyvgEdnrEZ-icXx3fiByP
|
|
1689
1689
|
vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
|
1690
1690
|
vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=cKI2Ls25L-JVt4z4a2ozQa-YBeVy21Z7BQ32Sj7iBPE,10460
|
1691
1691
|
vellum/workflows/nodes/experimental/tool_calling_node/__init__.py,sha256=8R5HQtDDfo0hgFX6VdM45-FiyxK-IMTI9B9LL4TRUbw,112
|
1692
|
+
vellum/workflows/nodes/experimental/tool_calling_node/node.py,sha256=8R5HQtDDfo0hgFX6VdM45-FiyxK-IMTI9B9LL4TRUbw,112
|
1692
1693
|
vellum/workflows/nodes/mocks.py,sha256=a1FjWEIocseMfjzM-i8DNozpUsaW0IONRpZmXBoWlyc,10455
|
1693
1694
|
vellum/workflows/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1694
1695
|
vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9TmQxpdyFKDdO60,7837
|
@@ -1716,15 +1717,16 @@ vellum/workflows/references/workflow_input.py,sha256=W3rOK1EPd2gYHb04WJwmNm1CUSd
|
|
1716
1717
|
vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPycOQevJxQnI,82
|
1717
1718
|
vellum/workflows/resolvers/base.py,sha256=WHra9LRtlTuB1jmuNqkfVE2JUgB61Cyntn8f0b0WZg4,411
|
1718
1719
|
vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
|
1719
|
-
vellum/workflows/runner/runner.py,sha256=
|
1720
|
+
vellum/workflows/runner/runner.py,sha256=t9Y3k82YjeML-Z7f1W2sj_1nAjJEpsnJUComDBebagU,36339
|
1720
1721
|
vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
|
1721
1722
|
vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
|
1722
|
-
vellum/workflows/state/base.py,sha256=
|
1723
|
+
vellum/workflows/state/base.py,sha256=F53r9n1tnipe6a7BmI4qcqgBRDkYzuEsN_mb-65Dazw,24276
|
1723
1724
|
vellum/workflows/state/context.py,sha256=KOAI1wEGn8dGmhmAemJaf4SZbitP3jpIBcwKfznQaRE,3076
|
1725
|
+
vellum/workflows/state/delta.py,sha256=7p0jJ3wquaFPGktZZz1vLM1ydv4CI7kuYumIaNbiSF4,433
|
1724
1726
|
vellum/workflows/state/encoder.py,sha256=sV80h-lAo66isD2ezK1_YLzj75Aa5dAtF2YA1c3BYKo,2883
|
1725
1727
|
vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
|
1726
1728
|
vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1727
|
-
vellum/workflows/state/tests/test_state.py,sha256=
|
1729
|
+
vellum/workflows/state/tests/test_state.py,sha256=SjQZgovETrosPUeFohaPB9egAkSVe8ptJO5O4Fk2E04,6920
|
1728
1730
|
vellum/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1729
1731
|
vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVik6ZVTfVOA,1826
|
1730
1732
|
vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
|
@@ -1756,8 +1758,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
|
|
1756
1758
|
vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1757
1759
|
vellum/workflows/workflows/tests/test_base_workflow.py,sha256=fROqff6AZpCIzaSwOKSdtYy4XR0UZQ6ejxL3RJOSJVs,20447
|
1758
1760
|
vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
|
1759
|
-
vellum_ai-0.14.
|
1760
|
-
vellum_ai-0.14.
|
1761
|
-
vellum_ai-0.14.
|
1762
|
-
vellum_ai-0.14.
|
1763
|
-
vellum_ai-0.14.
|
1761
|
+
vellum_ai-0.14.85.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1762
|
+
vellum_ai-0.14.85.dist-info/METADATA,sha256=YJsUjCnTdDuvxb-FOhV6litvxWf_owaPkWBUv8RXjq4,5556
|
1763
|
+
vellum_ai-0.14.85.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1764
|
+
vellum_ai-0.14.85.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1765
|
+
vellum_ai-0.14.85.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|