vellum-ai 0.10.3__py3-none-any.whl → 0.10.6__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 +1 -1
- vellum/workflows/events/tests/test_event.py +30 -0
- vellum/workflows/events/types.py +57 -3
- vellum/workflows/nodes/__init__.py +6 -7
- vellum/workflows/nodes/bases/base.py +0 -1
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +2 -1
- vellum/workflows/nodes/core/map_node/node.py +1 -1
- vellum/workflows/nodes/core/retry_node/node.py +1 -0
- vellum/workflows/nodes/core/templating_node/node.py +5 -1
- vellum/workflows/nodes/core/try_node/node.py +66 -27
- vellum/workflows/nodes/core/try_node/tests/test_node.py +39 -8
- vellum/workflows/nodes/displayable/__init__.py +2 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +3 -3
- vellum/workflows/nodes/displayable/code_execution_node/node.py +5 -2
- vellum/workflows/nodes/displayable/final_output_node/node.py +6 -2
- vellum/workflows/nodes/displayable/note_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/note_node/node.py +10 -0
- vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +10 -11
- vellum/workflows/nodes/utils.py +2 -0
- vellum/workflows/outputs/base.py +26 -2
- vellum/workflows/runner/runner.py +41 -27
- vellum/workflows/state/tests/test_state.py +2 -0
- vellum/workflows/types/tests/test_utils.py +9 -0
- vellum/workflows/types/utils.py +1 -1
- vellum/workflows/utils/vellum_variables.py +13 -1
- vellum/workflows/workflows/base.py +24 -1
- {vellum_ai-0.10.3.dist-info → vellum_ai-0.10.6.dist-info}/METADATA +8 -6
- {vellum_ai-0.10.3.dist-info → vellum_ai-0.10.6.dist-info}/RECORD +61 -56
- vellum_cli/CONTRIBUTING.md +66 -0
- vellum_cli/README.md +3 -0
- vellum_ee/workflows/display/base.py +2 -1
- vellum_ee/workflows/display/nodes/base_node_display.py +27 -4
- vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/api_node.py +3 -3
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +4 -4
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +86 -41
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +3 -3
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +4 -5
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +9 -9
- vellum_ee/workflows/display/nodes/vellum/map_node.py +5 -5
- vellum_ee/workflows/display/nodes/vellum/note_node.py +32 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +5 -5
- vellum_ee/workflows/display/nodes/vellum/search_node.py +6 -10
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +4 -5
- vellum_ee/workflows/display/nodes/vellum/try_node.py +16 -4
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +7 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +127 -101
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +6 -5
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -64
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +4 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +6 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +6 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +4 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +7 -6
- vellum_ee/workflows/display/workflows/base_workflow_display.py +14 -9
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +2 -7
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +18 -16
- {vellum_ai-0.10.3.dist-info → vellum_ai-0.10.6.dist-info}/LICENSE +0 -0
- {vellum_ai-0.10.3.dist-info → vellum_ai-0.10.6.dist-info}/WHEEL +0 -0
- {vellum_ai-0.10.3.dist-info → vellum_ai-0.10.6.dist-info}/entry_points.txt +0 -0
vellum/workflows/outputs/base.py
CHANGED
@@ -5,6 +5,7 @@ from pydantic import GetCoreSchemaHandler
|
|
5
5
|
from pydantic_core import core_schema
|
6
6
|
|
7
7
|
from vellum.workflows.constants import UNDEF
|
8
|
+
from vellum.workflows.descriptors.base import BaseDescriptor
|
8
9
|
from vellum.workflows.references.output import OutputReference
|
9
10
|
from vellum.workflows.types.utils import get_class_attr_names, infer_types
|
10
11
|
|
@@ -76,6 +77,23 @@ class BaseOutput(Generic[_Delta, _Accumulated]):
|
|
76
77
|
|
77
78
|
return data
|
78
79
|
|
80
|
+
def __repr__(self) -> str:
|
81
|
+
if self.value is not UNDEF:
|
82
|
+
return f"{self.__class__.__name__}({self.name}={self.value})"
|
83
|
+
elif self.delta is not UNDEF:
|
84
|
+
return f"{self.__class__.__name__}({self.name}={self.delta})"
|
85
|
+
else:
|
86
|
+
return f"{self.__class__.__name__}(name='{self.name}')"
|
87
|
+
|
88
|
+
def __eq__(self, other: Any) -> bool:
|
89
|
+
if not isinstance(other, BaseOutput):
|
90
|
+
return False
|
91
|
+
|
92
|
+
return self.name == other.name and self.value == other.value and self.delta == other.delta
|
93
|
+
|
94
|
+
def __hash__(self) -> int:
|
95
|
+
return hash((self._name, self._value, self._value))
|
96
|
+
|
79
97
|
|
80
98
|
@dataclass_transform(kw_only_default=True)
|
81
99
|
class _BaseOutputsMeta(type):
|
@@ -175,7 +193,9 @@ class BaseOutputs(metaclass=_BaseOutputsMeta):
|
|
175
193
|
if not isinstance(other, dict):
|
176
194
|
return super().__eq__(other)
|
177
195
|
|
178
|
-
outputs = {
|
196
|
+
outputs = {
|
197
|
+
name: value for name, value in vars(self).items() if not name.startswith("_") and value is not UNDEF
|
198
|
+
}
|
179
199
|
return outputs == other
|
180
200
|
|
181
201
|
def __repr__(self) -> str:
|
@@ -184,7 +204,11 @@ class BaseOutputs(metaclass=_BaseOutputsMeta):
|
|
184
204
|
|
185
205
|
def __iter__(self) -> Iterator[Tuple[OutputReference, Any]]:
|
186
206
|
for output_descriptor in self.__class__:
|
187
|
-
|
207
|
+
output_value = getattr(self, output_descriptor.name, UNDEF)
|
208
|
+
if isinstance(output_value, BaseDescriptor):
|
209
|
+
output_value = UNDEF
|
210
|
+
|
211
|
+
yield (output_descriptor, output_value)
|
188
212
|
|
189
213
|
def __getitem__(self, key: str) -> Any:
|
190
214
|
return getattr(self, key)
|
@@ -170,32 +170,37 @@ class WorkflowRunner(Generic[StateType]):
|
|
170
170
|
streaming_output_queues: Dict[str, Queue] = {}
|
171
171
|
outputs = node.Outputs()
|
172
172
|
|
173
|
+
def initiate_node_streaming_output(output: BaseOutput) -> None:
|
174
|
+
streaming_output_queues[output.name] = Queue()
|
175
|
+
output_descriptor = OutputReference(
|
176
|
+
name=output.name,
|
177
|
+
types=(type(output.delta),),
|
178
|
+
instance=None,
|
179
|
+
outputs_class=node.Outputs,
|
180
|
+
)
|
181
|
+
node.state.meta.node_outputs[output_descriptor] = streaming_output_queues[output.name]
|
182
|
+
self._work_item_event_queue.put(
|
183
|
+
WorkItemEvent(
|
184
|
+
node=node,
|
185
|
+
event=NodeExecutionStreamingEvent(
|
186
|
+
trace_id=node.state.meta.trace_id,
|
187
|
+
span_id=span_id,
|
188
|
+
body=NodeExecutionStreamingBody(
|
189
|
+
node_definition=node.__class__,
|
190
|
+
output=BaseOutput(name=output.name),
|
191
|
+
),
|
192
|
+
),
|
193
|
+
invoked_ports=invoked_ports,
|
194
|
+
)
|
195
|
+
)
|
196
|
+
|
173
197
|
for output in node_run_response:
|
174
198
|
invoked_ports = output > ports
|
175
|
-
if
|
199
|
+
if output.is_initiated:
|
200
|
+
initiate_node_streaming_output(output)
|
201
|
+
elif output.is_streaming:
|
176
202
|
if output.name not in streaming_output_queues:
|
177
|
-
|
178
|
-
output_descriptor = OutputReference(
|
179
|
-
name=output.name,
|
180
|
-
types=(type(output.delta),),
|
181
|
-
instance=None,
|
182
|
-
outputs_class=node.Outputs,
|
183
|
-
)
|
184
|
-
node.state.meta.node_outputs[output_descriptor] = streaming_output_queues[output.name]
|
185
|
-
self._work_item_event_queue.put(
|
186
|
-
WorkItemEvent(
|
187
|
-
node=node,
|
188
|
-
event=NodeExecutionStreamingEvent(
|
189
|
-
trace_id=node.state.meta.trace_id,
|
190
|
-
span_id=span_id,
|
191
|
-
body=NodeExecutionStreamingBody(
|
192
|
-
node_definition=node.__class__,
|
193
|
-
output=BaseOutput(name=output.name),
|
194
|
-
),
|
195
|
-
),
|
196
|
-
invoked_ports=invoked_ports,
|
197
|
-
)
|
198
|
-
)
|
203
|
+
initiate_node_streaming_output(output)
|
199
204
|
|
200
205
|
streaming_output_queues[output.name].put(output.delta)
|
201
206
|
self._work_item_event_queue.put(
|
@@ -212,7 +217,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
212
217
|
invoked_ports=invoked_ports,
|
213
218
|
)
|
214
219
|
)
|
215
|
-
|
220
|
+
elif output.is_fulfilled:
|
216
221
|
if output.name in streaming_output_queues:
|
217
222
|
streaming_output_queues[output.name].put(UNDEF)
|
218
223
|
|
@@ -233,6 +238,11 @@ class WorkflowRunner(Generic[StateType]):
|
|
233
238
|
)
|
234
239
|
|
235
240
|
for descriptor, output_value in outputs:
|
241
|
+
if output_value is UNDEF:
|
242
|
+
if descriptor in node.state.meta.node_outputs:
|
243
|
+
del node.state.meta.node_outputs[descriptor]
|
244
|
+
continue
|
245
|
+
|
236
246
|
node.state.meta.node_outputs[descriptor] = output_value
|
237
247
|
|
238
248
|
invoked_ports = ports(outputs, node.state)
|
@@ -540,11 +550,15 @@ class WorkflowRunner(Generic[StateType]):
|
|
540
550
|
)
|
541
551
|
|
542
552
|
def stream(self) -> WorkflowEventStream:
|
543
|
-
background_thread = Thread(
|
553
|
+
background_thread = Thread(
|
554
|
+
target=self._run_background_thread, name=f"{self.workflow.__class__.__name__}.background_thread"
|
555
|
+
)
|
544
556
|
background_thread.start()
|
545
557
|
|
546
558
|
if self._cancel_signal:
|
547
|
-
cancel_thread = Thread(
|
559
|
+
cancel_thread = Thread(
|
560
|
+
target=self._run_cancel_thread, name=f"{self.workflow.__class__.__name__}.cancel_thread"
|
561
|
+
)
|
548
562
|
cancel_thread.start()
|
549
563
|
|
550
564
|
event: WorkflowEvent
|
@@ -557,7 +571,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
557
571
|
self._initial_state.meta.is_terminated = False
|
558
572
|
|
559
573
|
# The extra level of indirection prevents the runner from waiting on the caller to consume the event stream
|
560
|
-
stream_thread = Thread(target=self._stream)
|
574
|
+
stream_thread = Thread(target=self._stream, name=f"{self.workflow.__class__.__name__}.stream_thread")
|
561
575
|
stream_thread.start()
|
562
576
|
|
563
577
|
while stream_thread.is_alive():
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import pytest
|
1
2
|
from collections import defaultdict
|
2
3
|
from copy import deepcopy
|
3
4
|
import json
|
@@ -76,6 +77,7 @@ def test_state_deepcopy():
|
|
76
77
|
assert deepcopied_state.meta.node_outputs == state.meta.node_outputs
|
77
78
|
|
78
79
|
|
80
|
+
@pytest.mark.skip(reason="https://app.shortcut.com/vellum/story/5654")
|
79
81
|
def test_state_deepcopy__with_node_output_updates():
|
80
82
|
# GIVEN an initial state instance
|
81
83
|
state = MockState(foo="bar")
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import pytest
|
2
2
|
from typing import ClassVar, Generic, List, TypeVar, Union
|
3
3
|
|
4
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
5
|
+
from vellum.workflows.nodes.core.try_node.node import TryNode
|
4
6
|
from vellum.workflows.outputs.base import BaseOutputs
|
5
7
|
from vellum.workflows.references.output import OutputReference
|
6
8
|
from vellum.workflows.types.utils import get_class_attr_names, infer_types
|
@@ -30,6 +32,11 @@ class ExampleGenericClass(Generic[T]):
|
|
30
32
|
class ExampleInheritedClass(ExampleClass):
|
31
33
|
theta: int
|
32
34
|
|
35
|
+
@TryNode.wrap()
|
36
|
+
class ExampleNode(BaseNode):
|
37
|
+
class Outputs(BaseNode.Outputs):
|
38
|
+
iota: str
|
39
|
+
|
33
40
|
|
34
41
|
@pytest.mark.parametrize(
|
35
42
|
"cls, attr_name, expected_type",
|
@@ -45,6 +52,7 @@ class ExampleInheritedClass(ExampleClass):
|
|
45
52
|
(ExampleInheritedClass, "theta", (int,)),
|
46
53
|
(ExampleInheritedClass, "alpha", (str,)),
|
47
54
|
(ExampleInheritedClass, "beta", (int,)),
|
55
|
+
(ExampleNode.Outputs, "iota", (str,)),
|
48
56
|
],
|
49
57
|
ids=[
|
50
58
|
"str",
|
@@ -58,6 +66,7 @@ class ExampleInheritedClass(ExampleClass):
|
|
58
66
|
"inherited_int",
|
59
67
|
"inherited_parent_annotation",
|
60
68
|
"inherited_parent_class_var",
|
69
|
+
"try_node_output",
|
61
70
|
],
|
62
71
|
)
|
63
72
|
def test_infer_types(cls, attr_name, expected_type):
|
vellum/workflows/types/utils.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from copy import deepcopy
|
2
2
|
from datetime import datetime
|
3
3
|
import importlib
|
4
|
+
import sys
|
4
5
|
from typing import (
|
5
6
|
Any,
|
6
7
|
ClassVar,
|
@@ -18,7 +19,6 @@ from typing import (
|
|
18
19
|
)
|
19
20
|
|
20
21
|
from vellum import ArrayVellumValue, ArrayVellumValueRequest, ChatMessagePromptBlock
|
21
|
-
|
22
22
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
23
23
|
from vellum.workflows.types.core import Json, SpecialGenericAlias, UnderGenericAlias, UnionGenericAlias
|
24
24
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import typing
|
1
2
|
from typing import List, Tuple, Type, Union, get_args, get_origin
|
2
3
|
|
3
4
|
from vellum import (
|
@@ -17,8 +18,8 @@ from vellum import (
|
|
17
18
|
VellumValueRequest,
|
18
19
|
VellumVariableType,
|
19
20
|
)
|
20
|
-
|
21
21
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
22
|
+
from vellum.workflows.types.core import Json
|
22
23
|
|
23
24
|
|
24
25
|
def primitive_type_to_vellum_variable_type(type_: Union[Type, BaseDescriptor]) -> VellumVariableType:
|
@@ -32,6 +33,17 @@ def primitive_type_to_vellum_variable_type(type_: Union[Type, BaseDescriptor]) -
|
|
32
33
|
return "JSON"
|
33
34
|
|
34
35
|
if len(types) != 1:
|
36
|
+
# Check explicitly for our internal JSON type.
|
37
|
+
# Matches the type found at vellum.workflows.utils.vellum_variables.Json
|
38
|
+
if types == [
|
39
|
+
bool,
|
40
|
+
int,
|
41
|
+
float,
|
42
|
+
str,
|
43
|
+
typing.List[typing.ForwardRef('Json')], # type: ignore [misc]
|
44
|
+
typing.Dict[str, typing.ForwardRef('Json')], # type: ignore [misc]
|
45
|
+
]:
|
46
|
+
return "JSON"
|
35
47
|
raise ValueError(f"Expected Descriptor to only have one type, got {types}")
|
36
48
|
|
37
49
|
type_ = type_.types[0]
|
@@ -35,11 +35,17 @@ from vellum.workflows.emitters.base import BaseWorkflowEmitter
|
|
35
35
|
from vellum.workflows.errors import VellumError, VellumErrorCode
|
36
36
|
from vellum.workflows.events.node import (
|
37
37
|
NodeExecutionFulfilledBody,
|
38
|
+
NodeExecutionFulfilledEvent,
|
38
39
|
NodeExecutionInitiatedBody,
|
40
|
+
NodeExecutionInitiatedEvent,
|
39
41
|
NodeExecutionPausedBody,
|
42
|
+
NodeExecutionPausedEvent,
|
40
43
|
NodeExecutionRejectedBody,
|
44
|
+
NodeExecutionRejectedEvent,
|
41
45
|
NodeExecutionResumedBody,
|
46
|
+
NodeExecutionResumedEvent,
|
42
47
|
NodeExecutionStreamingBody,
|
48
|
+
NodeExecutionStreamingEvent,
|
43
49
|
)
|
44
50
|
from vellum.workflows.events.types import WorkflowEventType
|
45
51
|
from vellum.workflows.events.workflow import (
|
@@ -55,6 +61,7 @@ from vellum.workflows.events.workflow import (
|
|
55
61
|
WorkflowExecutionResumedBody,
|
56
62
|
WorkflowExecutionResumedEvent,
|
57
63
|
WorkflowExecutionStreamingBody,
|
64
|
+
WorkflowExecutionStreamingEvent,
|
58
65
|
)
|
59
66
|
from vellum.workflows.graph import Graph
|
60
67
|
from vellum.workflows.inputs.base import BaseInputs
|
@@ -204,7 +211,9 @@ class BaseWorkflow(Generic[WorkflowInputsType, StateType], metaclass=_BaseWorkfl
|
|
204
211
|
trace_id=uuid4(),
|
205
212
|
span_id=uuid4(),
|
206
213
|
body=WorkflowExecutionRejectedBody(
|
207
|
-
error=VellumError(
|
214
|
+
error=VellumError(
|
215
|
+
code=VellumErrorCode.INTERNAL_ERROR, message="Initiated event was never emitted"
|
216
|
+
),
|
208
217
|
workflow_definition=self.__class__,
|
209
218
|
),
|
210
219
|
)
|
@@ -363,3 +372,17 @@ NodeExecutionRejectedBody.model_rebuild()
|
|
363
372
|
NodeExecutionPausedBody.model_rebuild()
|
364
373
|
NodeExecutionResumedBody.model_rebuild()
|
365
374
|
NodeExecutionStreamingBody.model_rebuild()
|
375
|
+
|
376
|
+
WorkflowExecutionInitiatedEvent.model_rebuild()
|
377
|
+
WorkflowExecutionFulfilledEvent.model_rebuild()
|
378
|
+
WorkflowExecutionRejectedEvent.model_rebuild()
|
379
|
+
WorkflowExecutionPausedEvent.model_rebuild()
|
380
|
+
WorkflowExecutionResumedEvent.model_rebuild()
|
381
|
+
WorkflowExecutionStreamingEvent.model_rebuild()
|
382
|
+
|
383
|
+
NodeExecutionInitiatedEvent.model_rebuild()
|
384
|
+
NodeExecutionFulfilledEvent.model_rebuild()
|
385
|
+
NodeExecutionRejectedEvent.model_rebuild()
|
386
|
+
NodeExecutionPausedEvent.model_rebuild()
|
387
|
+
NodeExecutionResumedEvent.model_rebuild()
|
388
|
+
NodeExecutionStreamingEvent.model_rebuild()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: vellum-ai
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.6
|
4
4
|
Summary:
|
5
5
|
License: MIT
|
6
6
|
Requires-Python: >=3.9,<4.0
|
@@ -63,7 +63,6 @@ Description-Content-Type: text/markdown
|
|
63
63
|
|
64
64
|
# Introduction
|
65
65
|
|
66
|
-
|
67
66
|
[Vellum](https://www.vellum.ai/) is the end-to-end development platform for building production-grade AI applications
|
68
67
|
|
69
68
|
### Core Features
|
@@ -85,22 +84,21 @@ Description-Content-Type: text/markdown
|
|
85
84
|
- [Contributing](#contributing)
|
86
85
|
- [Open-source vs paid](#open-source-vs-paid)
|
87
86
|
|
88
|
-
|
89
87
|
## Get Started
|
88
|
+
|
90
89
|
Most functionality within the SDKs here requires a Vellum account and API key. To sign up, [talk to us](https://www.vellum.ai/landing-pages/request-demo)
|
91
90
|
or visit our [pricing page](https://www.vellum.ai/pricing).
|
92
91
|
|
93
92
|
Even without a Vellum account, you can use the Workflows SDK to define the control flow of your AI systems. [Learn
|
94
93
|
more below](#workflows-sdk).
|
95
94
|
|
96
|
-
|
97
|
-
|
98
95
|
## Client SDK
|
96
|
+
|
99
97
|
The Vellum Client SDK, found within `src/client` is a low-level client used to interact directly with the Vellum API.
|
100
98
|
Learn more and get started by visiting the [Vellum Client SDK README](/src/vellum/client/README.md).
|
101
99
|
|
102
|
-
|
103
100
|
## Workflows SDK
|
101
|
+
|
104
102
|
The Vellum Workflows SDK is a high-level framework for defining and debugging the control flow of AI systems. At
|
105
103
|
it's core, it's a powerful workflow engine with syntactic sugar for intuitively defining graphs, the nodes within,
|
106
104
|
and the relationships between them.
|
@@ -111,6 +109,9 @@ and debugging via a UI.
|
|
111
109
|
|
112
110
|
To learn more and get started, visit the [Vellum Workflows SDK README](/src/vellum/workflows/README.md).
|
113
111
|
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
See the [CONTRIBUTING.md](/CONTRIBUTING.md) for information on how to contribute to the Vellum SDKs.
|
114
115
|
|
115
116
|
## Open-Source vs. Paid
|
116
117
|
|
@@ -118,3 +119,4 @@ This repo is available under the [MIT expat license](https://github.com/vellum-a
|
|
118
119
|
for the `ee` directory (which has its [license here](https://github.com/vellum-ai/vellum-python-sdks/blob/main/ee/LICENSE)) if applicable.
|
119
120
|
|
120
121
|
To learn more, [book a demo](https://www.vellum.ai/landing-pages/request-demo) or see our [pricing page](https://www.vellum.ai/pricing).
|
122
|
+
|