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.
Files changed (61) hide show
  1. vellum/__init__.py +18 -1
  2. vellum/client/__init__.py +3 -0
  3. vellum/client/core/client_wrapper.py +2 -2
  4. vellum/client/errors/__init__.py +10 -1
  5. vellum/client/errors/too_many_requests_error.py +11 -0
  6. vellum/client/errors/unauthorized_error.py +11 -0
  7. vellum/client/reference.md +94 -0
  8. vellum/client/resources/__init__.py +2 -0
  9. vellum/client/resources/events/__init__.py +4 -0
  10. vellum/client/resources/events/client.py +165 -0
  11. vellum/client/resources/events/raw_client.py +207 -0
  12. vellum/client/types/__init__.py +6 -0
  13. vellum/client/types/error_detail_response.py +22 -0
  14. vellum/client/types/event_create_response.py +26 -0
  15. vellum/client/types/execution_thinking_vellum_value.py +1 -1
  16. vellum/client/types/thinking_vellum_value.py +1 -1
  17. vellum/client/types/thinking_vellum_value_request.py +1 -1
  18. vellum/client/types/workflow_event.py +33 -0
  19. vellum/errors/too_many_requests_error.py +3 -0
  20. vellum/errors/unauthorized_error.py +3 -0
  21. vellum/resources/events/__init__.py +3 -0
  22. vellum/resources/events/client.py +3 -0
  23. vellum/resources/events/raw_client.py +3 -0
  24. vellum/types/error_detail_response.py +3 -0
  25. vellum/types/event_create_response.py +3 -0
  26. vellum/types/workflow_event.py +3 -0
  27. vellum/workflows/nodes/displayable/bases/api_node/node.py +4 -0
  28. vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py +26 -0
  29. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +6 -1
  30. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +22 -0
  31. vellum/workflows/sandbox.py +28 -8
  32. vellum/workflows/state/encoder.py +19 -1
  33. vellum/workflows/utils/hmac.py +44 -0
  34. {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/METADATA +1 -1
  35. {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/RECORD +61 -43
  36. vellum_ee/workflows/display/nodes/base_node_display.py +2 -2
  37. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +37 -7
  38. vellum_ee/workflows/display/nodes/vellum/retry_node.py +1 -1
  39. vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py +1 -1
  40. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +314 -2
  41. vellum_ee/workflows/display/nodes/vellum/try_node.py +1 -1
  42. vellum_ee/workflows/display/tests/test_base_workflow_display.py +53 -1
  43. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +9 -9
  44. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +9 -9
  45. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -3
  46. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +14 -15
  47. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +58 -3
  48. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +1 -1
  49. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +4 -0
  50. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +1 -1
  51. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +2 -2
  52. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
  53. vellum_ee/workflows/display/utils/expressions.py +9 -1
  54. vellum_ee/workflows/display/utils/registry.py +46 -0
  55. vellum_ee/workflows/display/workflows/base_workflow_display.py +21 -1
  56. vellum_ee/workflows/tests/test_registry.py +169 -0
  57. vellum_ee/workflows/tests/test_serialize_module.py +31 -0
  58. vellum_ee/workflows/tests/test_server.py +72 -0
  59. {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/LICENSE +0 -0
  60. {vellum_ai-1.1.5.dist-info → vellum_ai-1.2.1.dist-info}/WHEEL +0 -0
  61. {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
+ ]
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.errors.too_many_requests_error import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.errors.unauthorized_error import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.resources.events import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.resources.events.client import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.resources.events.raw_client import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.error_detail_response import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.event_create_response import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.workflow_event import *
@@ -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
- else:
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
@@ -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)
@@ -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__(self, workflow: WorkflowType, inputs: Sequence[BaseInputs]):
14
- if not inputs:
15
- raise ValueError("Inputs are required to have at least one defined inputs")
16
-
17
- self._workflow = workflow
18
- self._inputs = inputs
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 open(source_path) as f:
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.1.5
3
+ Version: 1.2.1
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0