vellum-ai 1.1.5__py3-none-any.whl → 1.2.1__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/__init__.py +18 -1
- vellum/client/__init__.py +3 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/errors/__init__.py +10 -1
- vellum/client/errors/too_many_requests_error.py +11 -0
- vellum/client/errors/unauthorized_error.py +11 -0
- vellum/client/reference.md +94 -0
- vellum/client/resources/__init__.py +2 -0
- vellum/client/resources/events/__init__.py +4 -0
- vellum/client/resources/events/client.py +165 -0
- vellum/client/resources/events/raw_client.py +207 -0
- vellum/client/types/__init__.py +6 -0
- vellum/client/types/error_detail_response.py +22 -0
- vellum/client/types/event_create_response.py +26 -0
- vellum/client/types/execution_thinking_vellum_value.py +1 -1
- vellum/client/types/thinking_vellum_value.py +1 -1
- vellum/client/types/thinking_vellum_value_request.py +1 -1
- vellum/client/types/workflow_event.py +33 -0
- vellum/errors/too_many_requests_error.py +3 -0
- vellum/errors/unauthorized_error.py +3 -0
- vellum/resources/events/__init__.py +3 -0
- vellum/resources/events/client.py +3 -0
- vellum/resources/events/raw_client.py +3 -0
- vellum/types/error_detail_response.py +3 -0
- vellum/types/event_create_response.py +3 -0
- vellum/types/workflow_event.py +3 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +4 -0
- vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py +26 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +6 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +22 -0
- vellum/workflows/sandbox.py +28 -8
- vellum/workflows/state/encoder.py +19 -1
- vellum/workflows/utils/hmac.py +44 -0
- {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/METADATA +1 -1
- {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/RECORD +61 -43
- vellum_ee/workflows/display/nodes/base_node_display.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +37 -7
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +314 -2
- vellum_ee/workflows/display/nodes/vellum/try_node.py +1 -1
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +53 -1
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +9 -9
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +9 -9
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +14 -15
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +58 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +2 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
- vellum_ee/workflows/display/utils/expressions.py +9 -1
- vellum_ee/workflows/display/utils/registry.py +46 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +21 -1
- vellum_ee/workflows/tests/test_registry.py +169 -0
- vellum_ee/workflows/tests/test_serialize_module.py +31 -0
- vellum_ee/workflows/tests/test_server.py +72 -0
- {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/LICENSE +0 -0
- {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/WHEEL +0 -0
- {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
import typing
|
4
|
+
|
5
|
+
import pydantic
|
6
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
|
7
|
+
|
8
|
+
|
9
|
+
class ErrorDetailResponse(UniversalBaseModel):
|
10
|
+
detail: str = pydantic.Field()
|
11
|
+
"""
|
12
|
+
Message informing the user of the error.
|
13
|
+
"""
|
14
|
+
|
15
|
+
if IS_PYDANTIC_V2:
|
16
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
17
|
+
else:
|
18
|
+
|
19
|
+
class Config:
|
20
|
+
frozen = True
|
21
|
+
smart_union = True
|
22
|
+
extra = pydantic.Extra.allow
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
import typing
|
4
|
+
|
5
|
+
import pydantic
|
6
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
|
7
|
+
|
8
|
+
|
9
|
+
class EventCreateResponse(UniversalBaseModel):
|
10
|
+
"""
|
11
|
+
Response serializer for successful event creation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
success: typing.Optional[bool] = pydantic.Field(default=None)
|
15
|
+
"""
|
16
|
+
Indicates whether the event was published successfully.
|
17
|
+
"""
|
18
|
+
|
19
|
+
if IS_PYDANTIC_V2:
|
20
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
21
|
+
else:
|
22
|
+
|
23
|
+
class Config:
|
24
|
+
frozen = True
|
25
|
+
smart_union = True
|
26
|
+
extra = pydantic.Extra.allow
|
@@ -19,7 +19,7 @@ class ExecutionThinkingVellumValue(UniversalBaseModel):
|
|
19
19
|
|
20
20
|
name: str
|
21
21
|
type: typing.Literal["THINKING"] = "THINKING"
|
22
|
-
value: StringVellumValue
|
22
|
+
value: typing.Optional[StringVellumValue] = None
|
23
23
|
|
24
24
|
if IS_PYDANTIC_V2:
|
25
25
|
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
@@ -13,7 +13,7 @@ class ThinkingVellumValue(UniversalBaseModel):
|
|
13
13
|
"""
|
14
14
|
|
15
15
|
type: typing.Literal["THINKING"] = "THINKING"
|
16
|
-
value: StringVellumValue
|
16
|
+
value: typing.Optional[StringVellumValue] = None
|
17
17
|
|
18
18
|
if IS_PYDANTIC_V2:
|
19
19
|
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
@@ -13,7 +13,7 @@ class ThinkingVellumValueRequest(UniversalBaseModel):
|
|
13
13
|
"""
|
14
14
|
|
15
15
|
type: typing.Literal["THINKING"] = "THINKING"
|
16
|
-
value: StringVellumValueRequest
|
16
|
+
value: typing.Optional[StringVellumValueRequest] = None
|
17
17
|
|
18
18
|
if IS_PYDANTIC_V2:
|
19
19
|
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
import typing
|
4
|
+
|
5
|
+
from .node_execution_fulfilled_event import NodeExecutionFulfilledEvent
|
6
|
+
from .node_execution_initiated_event import NodeExecutionInitiatedEvent
|
7
|
+
from .node_execution_paused_event import NodeExecutionPausedEvent
|
8
|
+
from .node_execution_rejected_event import NodeExecutionRejectedEvent
|
9
|
+
from .node_execution_resumed_event import NodeExecutionResumedEvent
|
10
|
+
from .node_execution_streaming_event import NodeExecutionStreamingEvent
|
11
|
+
from .workflow_execution_fulfilled_event import WorkflowExecutionFulfilledEvent
|
12
|
+
from .workflow_execution_initiated_event import WorkflowExecutionInitiatedEvent
|
13
|
+
from .workflow_execution_paused_event import WorkflowExecutionPausedEvent
|
14
|
+
from .workflow_execution_rejected_event import WorkflowExecutionRejectedEvent
|
15
|
+
from .workflow_execution_resumed_event import WorkflowExecutionResumedEvent
|
16
|
+
from .workflow_execution_snapshotted_event import WorkflowExecutionSnapshottedEvent
|
17
|
+
from .workflow_execution_streaming_event import WorkflowExecutionStreamingEvent
|
18
|
+
|
19
|
+
WorkflowEvent = typing.Union[
|
20
|
+
NodeExecutionInitiatedEvent,
|
21
|
+
NodeExecutionStreamingEvent,
|
22
|
+
NodeExecutionFulfilledEvent,
|
23
|
+
NodeExecutionRejectedEvent,
|
24
|
+
NodeExecutionPausedEvent,
|
25
|
+
NodeExecutionResumedEvent,
|
26
|
+
WorkflowExecutionInitiatedEvent,
|
27
|
+
WorkflowExecutionStreamingEvent,
|
28
|
+
WorkflowExecutionRejectedEvent,
|
29
|
+
WorkflowExecutionFulfilledEvent,
|
30
|
+
WorkflowExecutionPausedEvent,
|
31
|
+
WorkflowExecutionResumedEvent,
|
32
|
+
WorkflowExecutionSnapshottedEvent,
|
33
|
+
]
|
@@ -14,6 +14,7 @@ from vellum.workflows.nodes.bases import BaseNode
|
|
14
14
|
from vellum.workflows.outputs import BaseOutputs
|
15
15
|
from vellum.workflows.types.core import Json, MergeBehavior, VellumSecret
|
16
16
|
from vellum.workflows.types.generics import StateType
|
17
|
+
from vellum.workflows.utils.hmac import sign_request_with_env_secret
|
17
18
|
|
18
19
|
|
19
20
|
class BaseAPINode(BaseNode, Generic[StateType]):
|
@@ -105,6 +106,9 @@ class BaseAPINode(BaseNode, Generic[StateType]):
|
|
105
106
|
prepped = Request(method=method, url=url, headers=headers).prepare()
|
106
107
|
except Exception as e:
|
107
108
|
raise NodeException(f"Failed to prepare HTTP request: {e}", code=WorkflowErrorCode.PROVIDER_ERROR)
|
109
|
+
|
110
|
+
sign_request_with_env_secret(prepped)
|
111
|
+
|
108
112
|
try:
|
109
113
|
with Session() as session:
|
110
114
|
response = session.send(prepped, timeout=timeout)
|
@@ -1,4 +1,6 @@
|
|
1
1
|
import pytest
|
2
|
+
import os
|
3
|
+
from unittest.mock import patch
|
2
4
|
|
3
5
|
from vellum.client.types.execute_api_response import ExecuteApiResponse
|
4
6
|
from vellum.workflows.constants import APIRequestMethod
|
@@ -122,3 +124,27 @@ def test_api_node_preserves_custom_user_agent_header(requests_mock):
|
|
122
124
|
assert response_mock.last_request.headers.get("User-Agent") == "Custom-Agent/1.0"
|
123
125
|
|
124
126
|
assert result.status_code == 200
|
127
|
+
|
128
|
+
|
129
|
+
def test_local_execute_api_with_hmac_secret(requests_mock):
|
130
|
+
"""Test that _local_execute_api adds HMAC headers when VELLUM_HMAC_SECRET is set."""
|
131
|
+
|
132
|
+
class TestAPINode(BaseAPINode):
|
133
|
+
method = APIRequestMethod.POST
|
134
|
+
url = "https://example.com/test"
|
135
|
+
json = {"test": "data"}
|
136
|
+
|
137
|
+
response_mock = requests_mock.post(
|
138
|
+
"https://example.com/test",
|
139
|
+
json={"result": "success"},
|
140
|
+
status_code=200,
|
141
|
+
)
|
142
|
+
|
143
|
+
with patch.dict(os.environ, {"VELLUM_HMAC_SECRET": "test-secret"}):
|
144
|
+
node = TestAPINode()
|
145
|
+
result = node.run()
|
146
|
+
|
147
|
+
assert response_mock.last_request
|
148
|
+
assert "X-Vellum-Timestamp" in response_mock.last_request.headers
|
149
|
+
assert "X-Vellum-Signature" in response_mock.last_request.headers
|
150
|
+
assert result.status_code == 200
|
@@ -122,8 +122,13 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
122
122
|
)
|
123
123
|
elif is_workflow_class(function):
|
124
124
|
normalized_functions.append(compile_inline_workflow_function_definition(function))
|
125
|
-
|
125
|
+
elif callable(function):
|
126
126
|
normalized_functions.append(compile_function_definition(function))
|
127
|
+
else:
|
128
|
+
raise NodeException(
|
129
|
+
message=f"`{function}` is not a valid function definition",
|
130
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
131
|
+
)
|
127
132
|
|
128
133
|
if self.settings and not self.settings.stream_enabled:
|
129
134
|
# This endpoint is returning a single event, so we need to wrap it in a generator
|
vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py
CHANGED
@@ -662,3 +662,25 @@ def test_inline_prompt_node__dict_blocks_error(vellum_adhoc_prompt_client):
|
|
662
662
|
# THEN the node should raise the correct NodeException
|
663
663
|
assert excinfo.value.code == WorkflowErrorCode.INVALID_INPUTS
|
664
664
|
assert "Failed to compile blocks" == str(excinfo.value)
|
665
|
+
|
666
|
+
|
667
|
+
def test_inline_prompt_node__invalid_function_type():
|
668
|
+
"""Test that the node raises an error when an invalid function type is passed."""
|
669
|
+
|
670
|
+
# GIVEN a node that has an invalid function type (not dict or callable)
|
671
|
+
class MyInlinePromptNode(InlinePromptNode):
|
672
|
+
ml_model = "gpt-4o"
|
673
|
+
blocks = []
|
674
|
+
prompt_inputs = {}
|
675
|
+
functions = ["not_a_function"] # type: ignore
|
676
|
+
|
677
|
+
# WHEN the node is created
|
678
|
+
node = MyInlinePromptNode()
|
679
|
+
|
680
|
+
# THEN the node should raise a NodeException with the correct error code
|
681
|
+
with pytest.raises(NodeException) as excinfo:
|
682
|
+
list(node.run())
|
683
|
+
|
684
|
+
# AND the error should have the correct code and message
|
685
|
+
assert excinfo.value.code == WorkflowErrorCode.INVALID_INPUTS
|
686
|
+
assert "`not_a_function` is not a valid function definition" == str(excinfo.value)
|
vellum/workflows/sandbox.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Generic, Sequence
|
1
|
+
from typing import Generic, Optional, Sequence
|
2
2
|
|
3
3
|
import dotenv
|
4
4
|
|
@@ -10,16 +10,36 @@ from vellum.workflows.workflows.event_filters import root_workflow_event_filter
|
|
10
10
|
|
11
11
|
|
12
12
|
class WorkflowSandboxRunner(Generic[WorkflowType]):
|
13
|
-
def __init__(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
workflow: WorkflowType,
|
16
|
+
inputs: Optional[Sequence[BaseInputs]] = None, # DEPRECATED - remove in v2.0.0
|
17
|
+
dataset: Optional[Sequence[BaseInputs]] = None,
|
18
|
+
):
|
20
19
|
dotenv.load_dotenv()
|
21
20
|
self._logger = load_logger()
|
22
21
|
|
22
|
+
if dataset is not None and inputs is not None:
|
23
|
+
raise ValueError(
|
24
|
+
"Cannot specify both 'dataset' and 'inputs' parameters. " "Use 'dataset' as 'inputs' is deprecated."
|
25
|
+
)
|
26
|
+
|
27
|
+
if dataset is not None:
|
28
|
+
actual_inputs = dataset
|
29
|
+
elif inputs is not None:
|
30
|
+
self._logger.warning(
|
31
|
+
"The 'inputs' parameter is deprecated and will be removed in v2.0.0. " "Please use 'dataset' instead."
|
32
|
+
)
|
33
|
+
actual_inputs = inputs
|
34
|
+
else:
|
35
|
+
raise ValueError("Either 'dataset' or 'inputs' parameter is required")
|
36
|
+
|
37
|
+
if not actual_inputs:
|
38
|
+
raise ValueError("Dataset/inputs are required to have at least one defined input")
|
39
|
+
|
40
|
+
self._workflow = workflow
|
41
|
+
self._inputs = actual_inputs
|
42
|
+
|
23
43
|
def run(self, index: int = 0):
|
24
44
|
if index < 0:
|
25
45
|
self._logger.warning("Index is less than 0, running first input")
|
@@ -2,8 +2,10 @@ from dataclasses import asdict, is_dataclass
|
|
2
2
|
from datetime import datetime
|
3
3
|
import enum
|
4
4
|
import inspect
|
5
|
+
from io import StringIO
|
5
6
|
from json import JSONEncoder
|
6
7
|
from queue import Queue
|
8
|
+
import sys
|
7
9
|
from uuid import UUID
|
8
10
|
from typing import Any, Callable, Dict, Type
|
9
11
|
|
@@ -18,6 +20,22 @@ from vellum.workflows.state.base import BaseState, NodeExecutionCache
|
|
18
20
|
from vellum.workflows.utils.functions import compile_function_definition
|
19
21
|
|
20
22
|
|
23
|
+
def virtual_open(file_path: str, mode: str = "r"):
|
24
|
+
"""
|
25
|
+
Open a file, checking VirtualFileFinder instances first before falling back to regular open().
|
26
|
+
"""
|
27
|
+
for finder in sys.meta_path:
|
28
|
+
if hasattr(finder, "loader") and hasattr(finder.loader, "_get_code"):
|
29
|
+
namespace = finder.loader.namespace
|
30
|
+
if file_path.startswith(namespace + "/"):
|
31
|
+
relative_path = file_path[len(namespace) + 1 :]
|
32
|
+
content = finder.loader._get_code(relative_path)
|
33
|
+
if content is not None:
|
34
|
+
return StringIO(content)
|
35
|
+
|
36
|
+
return open(file_path, mode)
|
37
|
+
|
38
|
+
|
21
39
|
class DefaultStateEncoder(JSONEncoder):
|
22
40
|
encoders: Dict[Type, Callable] = {}
|
23
41
|
|
@@ -66,7 +84,7 @@ class DefaultStateEncoder(JSONEncoder):
|
|
66
84
|
function_definition = compile_function_definition(obj)
|
67
85
|
source_path = inspect.getsourcefile(obj)
|
68
86
|
if source_path is not None:
|
69
|
-
with
|
87
|
+
with virtual_open(source_path) as f:
|
70
88
|
source_code = f.read()
|
71
89
|
else:
|
72
90
|
source_code = f"# Error: Source code not available for {obj.__name__}"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import hashlib
|
2
|
+
import hmac
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
|
6
|
+
from requests import PreparedRequest
|
7
|
+
|
8
|
+
|
9
|
+
def _sign_request(request: PreparedRequest, secret: str) -> None:
|
10
|
+
"""
|
11
|
+
Sign a request with HMAC using the same pattern as Django implementation.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
request: The prepared request to sign
|
15
|
+
secret: The HMAC secret string
|
16
|
+
"""
|
17
|
+
timestamp = str(int(time.time()))
|
18
|
+
|
19
|
+
body = request.body or b""
|
20
|
+
if isinstance(body, str):
|
21
|
+
body = body.encode()
|
22
|
+
|
23
|
+
message = f"{timestamp}\n{request.method}\n{request.url}\n".encode() + body
|
24
|
+
|
25
|
+
signature = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
|
26
|
+
|
27
|
+
hmac_headers = {
|
28
|
+
"X-Vellum-Timestamp": timestamp,
|
29
|
+
"X-Vellum-Signature": signature,
|
30
|
+
}
|
31
|
+
|
32
|
+
request.headers.update(hmac_headers)
|
33
|
+
|
34
|
+
|
35
|
+
def sign_request_with_env_secret(request: PreparedRequest) -> None:
|
36
|
+
"""
|
37
|
+
Sign a request using VELLUM_HMAC_SECRET environment variable if available.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
request: The prepared request to sign
|
41
|
+
"""
|
42
|
+
hmac_secret = os.environ.get("VELLUM_HMAC_SECRET")
|
43
|
+
if hmac_secret:
|
44
|
+
_sign_request(request, hmac_secret)
|