vellum-ai 0.14.68__py3-none-any.whl → 0.14.70__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 +4 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/types/__init__.py +4 -0
- vellum/client/types/fast_embed_vectorizer_baai_bge_small_en_v_15.py +23 -0
- vellum/client/types/fast_embed_vectorizer_baai_bge_small_en_v_15_request.py +23 -0
- vellum/client/types/folder_entity_document_index_data.py +2 -0
- vellum/client/types/indexing_config_vectorizer.py +2 -0
- vellum/client/types/indexing_config_vectorizer_request.py +2 -0
- vellum/types/fast_embed_vectorizer_baai_bge_small_en_v_15.py +3 -0
- vellum/types/fast_embed_vectorizer_baai_bge_small_en_v_15_request.py +3 -0
- vellum/workflows/environment/__init__.py +2 -1
- vellum/workflows/environment/environment.py +5 -1
- vellum/workflows/nodes/displayable/bases/search_node.py +15 -3
- vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py +215 -0
- vellum/workflows/nodes/experimental/tool_calling_node/tests/test_node.py +77 -1
- vellum/workflows/nodes/experimental/tool_calling_node/utils.py +2 -2
- vellum/workflows/references/environment_variable.py +2 -3
- {vellum_ai-0.14.68.dist-info → vellum_ai-0.14.70.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.68.dist-info → vellum_ai-0.14.70.dist-info}/RECORD +54 -45
- vellum_cli/__init__.py +5 -2
- vellum_cli/image_push.py +24 -1
- vellum_cli/tests/test_image_push.py +103 -12
- vellum_ee/workflows/display/nodes/base_node_display.py +1 -1
- vellum_ee/workflows/display/nodes/utils.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/api_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/error_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +4 -4
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +9 -1
- vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/note_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_inline_subworkflow_node.py +88 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +16 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +81 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +9 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +59 -297
- vellum_ee/workflows/display/utils/auto_layout.py +130 -0
- vellum_ee/workflows/display/utils/expressions.py +7 -0
- vellum_ee/workflows/display/utils/tests/__init__.py +0 -0
- vellum_ee/workflows/display/utils/tests/test_auto_layout.py +56 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +15 -10
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +41 -0
- {vellum_ai-0.14.68.dist-info → vellum_ai-0.14.70.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.68.dist-info → vellum_ai-0.14.70.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.68.dist-info → vellum_ai-0.14.70.dist-info}/entry_points.txt +0 -0
vellum/__init__.py
CHANGED
@@ -123,6 +123,8 @@ from .types import (
|
|
123
123
|
ExternalInputDescriptor,
|
124
124
|
ExternalTestCaseExecution,
|
125
125
|
ExternalTestCaseExecutionRequest,
|
126
|
+
FastEmbedVectorizerBaaiBgeSmallEnV15,
|
127
|
+
FastEmbedVectorizerBaaiBgeSmallEnV15Request,
|
126
128
|
FinishReasonEnum,
|
127
129
|
FolderEntity,
|
128
130
|
FolderEntityDocumentIndex,
|
@@ -750,6 +752,8 @@ __all__ = [
|
|
750
752
|
"ExternalInputDescriptor",
|
751
753
|
"ExternalTestCaseExecution",
|
752
754
|
"ExternalTestCaseExecutionRequest",
|
755
|
+
"FastEmbedVectorizerBaaiBgeSmallEnV15",
|
756
|
+
"FastEmbedVectorizerBaaiBgeSmallEnV15Request",
|
753
757
|
"FinishReasonEnum",
|
754
758
|
"FolderEntitiesListRequestEntityStatus",
|
755
759
|
"FolderEntity",
|
@@ -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.14.
|
21
|
+
"X-Fern-SDK-Version": "0.14.70",
|
22
22
|
}
|
23
23
|
headers["X-API-KEY"] = self.api_key
|
24
24
|
return headers
|
vellum/client/types/__init__.py
CHANGED
@@ -127,6 +127,8 @@ from .execution_vellum_value import ExecutionVellumValue
|
|
127
127
|
from .external_input_descriptor import ExternalInputDescriptor
|
128
128
|
from .external_test_case_execution import ExternalTestCaseExecution
|
129
129
|
from .external_test_case_execution_request import ExternalTestCaseExecutionRequest
|
130
|
+
from .fast_embed_vectorizer_baai_bge_small_en_v_15 import FastEmbedVectorizerBaaiBgeSmallEnV15
|
131
|
+
from .fast_embed_vectorizer_baai_bge_small_en_v_15_request import FastEmbedVectorizerBaaiBgeSmallEnV15Request
|
130
132
|
from .finish_reason_enum import FinishReasonEnum
|
131
133
|
from .folder_entity import FolderEntity
|
132
134
|
from .folder_entity_document_index import FolderEntityDocumentIndex
|
@@ -737,6 +739,8 @@ __all__ = [
|
|
737
739
|
"ExternalInputDescriptor",
|
738
740
|
"ExternalTestCaseExecution",
|
739
741
|
"ExternalTestCaseExecutionRequest",
|
742
|
+
"FastEmbedVectorizerBaaiBgeSmallEnV15",
|
743
|
+
"FastEmbedVectorizerBaaiBgeSmallEnV15Request",
|
740
744
|
"FinishReasonEnum",
|
741
745
|
"FolderEntity",
|
742
746
|
"FolderEntityDocumentIndex",
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
import typing
|
5
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
6
|
+
import pydantic
|
7
|
+
|
8
|
+
|
9
|
+
class FastEmbedVectorizerBaaiBgeSmallEnV15(UniversalBaseModel):
|
10
|
+
"""
|
11
|
+
FastEmbed vectorizer for BAAI/bge-small-en-v1.5.
|
12
|
+
"""
|
13
|
+
|
14
|
+
model_name: typing.Literal["BAAI/bge-small-en-v1.5"] = "BAAI/bge-small-en-v1.5"
|
15
|
+
|
16
|
+
if IS_PYDANTIC_V2:
|
17
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
18
|
+
else:
|
19
|
+
|
20
|
+
class Config:
|
21
|
+
frozen = True
|
22
|
+
smart_union = True
|
23
|
+
extra = pydantic.Extra.allow
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
import typing
|
5
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
6
|
+
import pydantic
|
7
|
+
|
8
|
+
|
9
|
+
class FastEmbedVectorizerBaaiBgeSmallEnV15Request(UniversalBaseModel):
|
10
|
+
"""
|
11
|
+
FastEmbed vectorizer for BAAI/bge-small-en-v1.5.
|
12
|
+
"""
|
13
|
+
|
14
|
+
model_name: typing.Literal["BAAI/bge-small-en-v1.5"] = "BAAI/bge-small-en-v1.5"
|
15
|
+
|
16
|
+
if IS_PYDANTIC_V2:
|
17
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
18
|
+
else:
|
19
|
+
|
20
|
+
class Config:
|
21
|
+
frozen = True
|
22
|
+
smart_union = True
|
23
|
+
extra = pydantic.Extra.allow
|
@@ -3,6 +3,7 @@
|
|
3
3
|
from ..core.pydantic_utilities import UniversalBaseModel
|
4
4
|
import datetime as dt
|
5
5
|
from .entity_status import EntityStatus
|
6
|
+
from .document_index_indexing_config import DocumentIndexIndexingConfig
|
6
7
|
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
7
8
|
import typing
|
8
9
|
import pydantic
|
@@ -14,6 +15,7 @@ class FolderEntityDocumentIndexData(UniversalBaseModel):
|
|
14
15
|
created: dt.datetime
|
15
16
|
modified: dt.datetime
|
16
17
|
status: EntityStatus
|
18
|
+
indexing_config: DocumentIndexIndexingConfig
|
17
19
|
|
18
20
|
if IS_PYDANTIC_V2:
|
19
21
|
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
@@ -16,6 +16,7 @@ from .google_vertex_ai_vectorizer_text_embedding_004 import GoogleVertexAiVector
|
|
16
16
|
from .google_vertex_ai_vectorizer_text_multilingual_embedding_002 import (
|
17
17
|
GoogleVertexAiVectorizerTextMultilingualEmbedding002,
|
18
18
|
)
|
19
|
+
from .fast_embed_vectorizer_baai_bge_small_en_v_15 import FastEmbedVectorizerBaaiBgeSmallEnV15
|
19
20
|
|
20
21
|
IndexingConfigVectorizer = typing.Union[
|
21
22
|
OpenAiVectorizerTextEmbedding3Small,
|
@@ -27,4 +28,5 @@ IndexingConfigVectorizer = typing.Union[
|
|
27
28
|
HkunlpInstructorXlVectorizer,
|
28
29
|
GoogleVertexAiVectorizerTextEmbedding004,
|
29
30
|
GoogleVertexAiVectorizerTextMultilingualEmbedding002,
|
31
|
+
FastEmbedVectorizerBaaiBgeSmallEnV15,
|
30
32
|
]
|
@@ -16,6 +16,7 @@ from .google_vertex_ai_vectorizer_text_embedding_004_request import GoogleVertex
|
|
16
16
|
from .google_vertex_ai_vectorizer_text_multilingual_embedding_002_request import (
|
17
17
|
GoogleVertexAiVectorizerTextMultilingualEmbedding002Request,
|
18
18
|
)
|
19
|
+
from .fast_embed_vectorizer_baai_bge_small_en_v_15_request import FastEmbedVectorizerBaaiBgeSmallEnV15Request
|
19
20
|
|
20
21
|
IndexingConfigVectorizerRequest = typing.Union[
|
21
22
|
OpenAiVectorizerTextEmbedding3SmallRequest,
|
@@ -27,4 +28,5 @@ IndexingConfigVectorizerRequest = typing.Union[
|
|
27
28
|
HkunlpInstructorXlVectorizerRequest,
|
28
29
|
GoogleVertexAiVectorizerTextEmbedding004Request,
|
29
30
|
GoogleVertexAiVectorizerTextMultilingualEmbedding002Request,
|
31
|
+
FastEmbedVectorizerBaaiBgeSmallEnV15Request,
|
30
32
|
]
|
@@ -3,7 +3,11 @@ from typing import Optional
|
|
3
3
|
from vellum.workflows.references import EnvironmentVariableReference
|
4
4
|
|
5
5
|
|
6
|
-
class
|
6
|
+
class EnvironmentVariables:
|
7
7
|
@staticmethod
|
8
8
|
def get(name: str, default: Optional[str] = None) -> EnvironmentVariableReference:
|
9
9
|
return EnvironmentVariableReference(name=name, default=default)
|
10
|
+
|
11
|
+
|
12
|
+
# Deprecated: Use EnvironmentVariables instead. Will be removed in v0.15.0
|
13
|
+
Environment = EnvironmentVariables
|
@@ -106,11 +106,23 @@ class BaseSearchNode(BaseNode[StateType], Generic[StateType]):
|
|
106
106
|
message=f"Document Index '{self.document_index}' not found",
|
107
107
|
code=WorkflowErrorCode.INVALID_INPUTS,
|
108
108
|
)
|
109
|
-
except ApiError:
|
109
|
+
except ApiError as e:
|
110
|
+
if e.status_code and e.status_code == 403 and isinstance(e.body, dict):
|
111
|
+
raise NodeException(
|
112
|
+
message=e.body.get("detail", "Provider credentials is missing or unavailable"),
|
113
|
+
code=WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE,
|
114
|
+
)
|
115
|
+
elif e.status_code and e.status_code >= 400 and e.status_code < 500 and isinstance(e.body, dict):
|
116
|
+
raise NodeException(
|
117
|
+
message=e.body.get(
|
118
|
+
"detail", f"An error occurred while searching against Document Index '{self.document_index}'"
|
119
|
+
),
|
120
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
121
|
+
) from e
|
110
122
|
raise NodeException(
|
111
|
-
message=f"An error occurred while searching against Document Index '{self.document_index}'",
|
123
|
+
message=f"An error occurred while searching against Document Index '{self.document_index}'",
|
112
124
|
code=WorkflowErrorCode.INTERNAL_ERROR,
|
113
|
-
)
|
125
|
+
) from e
|
114
126
|
|
115
127
|
def _get_options_request(self) -> SearchRequestOptionsRequest:
|
116
128
|
return SearchRequestOptionsRequest(
|
@@ -0,0 +1,215 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from vellum.client.core.api_error import ApiError
|
4
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
5
|
+
from vellum.workflows.exceptions import NodeException
|
6
|
+
from vellum.workflows.inputs import BaseInputs
|
7
|
+
from vellum.workflows.nodes.displayable.search_node import SearchNode as BaseSearchNode
|
8
|
+
from vellum.workflows.state import BaseState
|
9
|
+
from vellum.workflows.state.base import StateMeta
|
10
|
+
|
11
|
+
|
12
|
+
def test_search_node_handles_403_error(vellum_client):
|
13
|
+
"""Test that SearchNode properly handles 403 API errors with user-facing messages."""
|
14
|
+
|
15
|
+
class Inputs(BaseInputs):
|
16
|
+
query: str
|
17
|
+
document_index: str
|
18
|
+
|
19
|
+
class State(BaseState):
|
20
|
+
pass
|
21
|
+
|
22
|
+
class SearchNode(BaseSearchNode):
|
23
|
+
query = Inputs.query
|
24
|
+
document_index = Inputs.document_index
|
25
|
+
|
26
|
+
vellum_client.search.side_effect = ApiError(
|
27
|
+
status_code=403, body={"detail": "Provider credentials is missing or unavailable"}
|
28
|
+
)
|
29
|
+
|
30
|
+
node = SearchNode(
|
31
|
+
state=State(
|
32
|
+
meta=StateMeta(
|
33
|
+
workflow_inputs=Inputs(
|
34
|
+
query="test query",
|
35
|
+
document_index="test-index",
|
36
|
+
)
|
37
|
+
),
|
38
|
+
)
|
39
|
+
)
|
40
|
+
|
41
|
+
with pytest.raises(NodeException) as exc_info:
|
42
|
+
node.run()
|
43
|
+
|
44
|
+
assert exc_info.value.code == WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE
|
45
|
+
assert exc_info.value.message == "Provider credentials is missing or unavailable"
|
46
|
+
|
47
|
+
|
48
|
+
def test_search_node_handles_403_error_with_custom_detail(vellum_client):
|
49
|
+
"""Test that SearchNode properly handles 403 API errors with custom detail message."""
|
50
|
+
|
51
|
+
class Inputs(BaseInputs):
|
52
|
+
query: str
|
53
|
+
document_index: str
|
54
|
+
|
55
|
+
class State(BaseState):
|
56
|
+
pass
|
57
|
+
|
58
|
+
class SearchNode(BaseSearchNode):
|
59
|
+
query = Inputs.query
|
60
|
+
document_index = Inputs.document_index
|
61
|
+
|
62
|
+
vellum_client.search.side_effect = ApiError(status_code=403, body={"detail": "Access denied to document index"})
|
63
|
+
|
64
|
+
node = SearchNode(
|
65
|
+
state=State(
|
66
|
+
meta=StateMeta(
|
67
|
+
workflow_inputs=Inputs(
|
68
|
+
query="test query",
|
69
|
+
document_index="test-index",
|
70
|
+
)
|
71
|
+
),
|
72
|
+
)
|
73
|
+
)
|
74
|
+
|
75
|
+
with pytest.raises(NodeException) as exc_info:
|
76
|
+
node.run()
|
77
|
+
|
78
|
+
assert exc_info.value.code == WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE
|
79
|
+
assert exc_info.value.message == "Access denied to document index"
|
80
|
+
|
81
|
+
|
82
|
+
def test_search_node_handles_403_error_without_detail(vellum_client):
|
83
|
+
"""Test that SearchNode properly handles 403 API errors without detail in body."""
|
84
|
+
|
85
|
+
class Inputs(BaseInputs):
|
86
|
+
query: str
|
87
|
+
document_index: str
|
88
|
+
|
89
|
+
class State(BaseState):
|
90
|
+
pass
|
91
|
+
|
92
|
+
class SearchNode(BaseSearchNode):
|
93
|
+
query = Inputs.query
|
94
|
+
document_index = Inputs.document_index
|
95
|
+
|
96
|
+
vellum_client.search.side_effect = ApiError(status_code=403, body={})
|
97
|
+
|
98
|
+
node = SearchNode(
|
99
|
+
state=State(
|
100
|
+
meta=StateMeta(
|
101
|
+
workflow_inputs=Inputs(
|
102
|
+
query="test query",
|
103
|
+
document_index="test-index",
|
104
|
+
)
|
105
|
+
),
|
106
|
+
)
|
107
|
+
)
|
108
|
+
|
109
|
+
with pytest.raises(NodeException) as exc_info:
|
110
|
+
node.run()
|
111
|
+
|
112
|
+
assert exc_info.value.code == WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE
|
113
|
+
assert exc_info.value.message == "Provider credentials is missing or unavailable"
|
114
|
+
|
115
|
+
|
116
|
+
def test_search_node_handles_other_4xx_errors(vellum_client):
|
117
|
+
"""Test that SearchNode properly handles other 4xx API errors."""
|
118
|
+
|
119
|
+
class Inputs(BaseInputs):
|
120
|
+
query: str
|
121
|
+
document_index: str
|
122
|
+
|
123
|
+
class State(BaseState):
|
124
|
+
pass
|
125
|
+
|
126
|
+
class SearchNode(BaseSearchNode):
|
127
|
+
query = Inputs.query
|
128
|
+
document_index = Inputs.document_index
|
129
|
+
|
130
|
+
vellum_client.search.side_effect = ApiError(status_code=400, body={"detail": "Invalid request parameters"})
|
131
|
+
|
132
|
+
node = SearchNode(
|
133
|
+
state=State(
|
134
|
+
meta=StateMeta(
|
135
|
+
workflow_inputs=Inputs(
|
136
|
+
query="test query",
|
137
|
+
document_index="test-index",
|
138
|
+
)
|
139
|
+
),
|
140
|
+
)
|
141
|
+
)
|
142
|
+
|
143
|
+
with pytest.raises(NodeException) as exc_info:
|
144
|
+
node.run()
|
145
|
+
|
146
|
+
assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
|
147
|
+
assert "Invalid request parameters" in exc_info.value.message
|
148
|
+
|
149
|
+
|
150
|
+
def test_search_node_handles_5xx_errors(vellum_client):
|
151
|
+
"""Test that SearchNode properly handles 5xx API errors as internal errors."""
|
152
|
+
|
153
|
+
class Inputs(BaseInputs):
|
154
|
+
query: str
|
155
|
+
document_index: str
|
156
|
+
|
157
|
+
class State(BaseState):
|
158
|
+
pass
|
159
|
+
|
160
|
+
class SearchNode(BaseSearchNode):
|
161
|
+
query = Inputs.query
|
162
|
+
document_index = Inputs.document_index
|
163
|
+
|
164
|
+
vellum_client.search.side_effect = ApiError(status_code=500, body={"detail": "Internal server error"})
|
165
|
+
|
166
|
+
node = SearchNode(
|
167
|
+
state=State(
|
168
|
+
meta=StateMeta(
|
169
|
+
workflow_inputs=Inputs(
|
170
|
+
query="test query",
|
171
|
+
document_index="test-index",
|
172
|
+
)
|
173
|
+
),
|
174
|
+
)
|
175
|
+
)
|
176
|
+
|
177
|
+
with pytest.raises(NodeException) as exc_info:
|
178
|
+
node.run()
|
179
|
+
|
180
|
+
assert exc_info.value.code == WorkflowErrorCode.INTERNAL_ERROR
|
181
|
+
assert "An error occurred while searching against Document Index 'test-index'" in exc_info.value.message
|
182
|
+
|
183
|
+
|
184
|
+
def test_search_node_handles_api_error_without_status_code(vellum_client):
|
185
|
+
"""Test that SearchNode properly handles API errors without status code."""
|
186
|
+
|
187
|
+
class Inputs(BaseInputs):
|
188
|
+
query: str
|
189
|
+
document_index: str
|
190
|
+
|
191
|
+
class State(BaseState):
|
192
|
+
pass
|
193
|
+
|
194
|
+
class SearchNode(BaseSearchNode):
|
195
|
+
query = Inputs.query
|
196
|
+
document_index = Inputs.document_index
|
197
|
+
|
198
|
+
vellum_client.search.side_effect = ApiError(status_code=None, body={"detail": "Unknown error"})
|
199
|
+
|
200
|
+
node = SearchNode(
|
201
|
+
state=State(
|
202
|
+
meta=StateMeta(
|
203
|
+
workflow_inputs=Inputs(
|
204
|
+
query="test query",
|
205
|
+
document_index="test-index",
|
206
|
+
)
|
207
|
+
),
|
208
|
+
)
|
209
|
+
)
|
210
|
+
|
211
|
+
with pytest.raises(NodeException) as exc_info:
|
212
|
+
node.run()
|
213
|
+
|
214
|
+
assert exc_info.value.code == WorkflowErrorCode.INTERNAL_ERROR
|
215
|
+
assert "An error occurred while searching against Document Index 'test-index'" in exc_info.value.message
|
@@ -1,7 +1,17 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any, List
|
3
|
+
|
4
|
+
from vellum import ChatMessage
|
1
5
|
from vellum.client.types.function_call import FunctionCall
|
2
6
|
from vellum.client.types.function_call_vellum_value import FunctionCallVellumValue
|
3
|
-
from vellum.
|
7
|
+
from vellum.client.types.string_chat_message_content import StringChatMessageContent
|
8
|
+
from vellum.workflows import BaseWorkflow
|
9
|
+
from vellum.workflows.inputs.base import BaseInputs
|
10
|
+
from vellum.workflows.nodes.bases import BaseNode
|
11
|
+
from vellum.workflows.nodes.experimental.tool_calling_node.utils import create_function_node, create_tool_router_node
|
12
|
+
from vellum.workflows.outputs.base import BaseOutputs
|
4
13
|
from vellum.workflows.state.base import BaseState, StateMeta
|
14
|
+
from vellum.workflows.state.context import WorkflowContext
|
5
15
|
|
6
16
|
|
7
17
|
def first_function() -> str:
|
@@ -51,3 +61,69 @@ def test_port_condition_match_function_name():
|
|
51
61
|
# AND the default port should be false
|
52
62
|
default_port = getattr(router_node.Ports, "default")
|
53
63
|
assert default_port.resolve_condition(state) is False
|
64
|
+
|
65
|
+
|
66
|
+
def test_tool_calling_node_inline_workflow_context():
|
67
|
+
"""
|
68
|
+
Test that the tool calling node correctly passes the context to the inline workflow.
|
69
|
+
This specifically tests that inline workflows receive the correct context.
|
70
|
+
"""
|
71
|
+
|
72
|
+
# GIVEN a test workflow that captures its context
|
73
|
+
class MyNode(BaseNode):
|
74
|
+
class Outputs(BaseOutputs):
|
75
|
+
generated_files: Any
|
76
|
+
|
77
|
+
def run(self) -> Outputs:
|
78
|
+
return self.Outputs(generated_files=self._context.generated_files)
|
79
|
+
|
80
|
+
class MyWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
81
|
+
graph = MyNode
|
82
|
+
|
83
|
+
class Outputs(BaseOutputs):
|
84
|
+
generated_files = MyNode.Outputs.generated_files
|
85
|
+
|
86
|
+
# GIVEN a tool router node
|
87
|
+
tool_router_node = create_tool_router_node(
|
88
|
+
ml_model="test-model",
|
89
|
+
blocks=[],
|
90
|
+
functions=[MyWorkflow],
|
91
|
+
prompt_inputs=None,
|
92
|
+
)
|
93
|
+
|
94
|
+
# WHEN we create a function node for the workflow
|
95
|
+
function_node_class = create_function_node(
|
96
|
+
function=MyWorkflow,
|
97
|
+
tool_router_node=tool_router_node,
|
98
|
+
)
|
99
|
+
|
100
|
+
# AND we create an instance with a context containing generated_files
|
101
|
+
function_node = function_node_class()
|
102
|
+
|
103
|
+
# Create a parent context with test data
|
104
|
+
parent_context = WorkflowContext(
|
105
|
+
generated_files={"script.py": "print('hello world')"},
|
106
|
+
)
|
107
|
+
function_node._context = parent_context
|
108
|
+
|
109
|
+
# Create a state with chat_history for the function node
|
110
|
+
class TestState(BaseState):
|
111
|
+
chat_history: List[ChatMessage] = []
|
112
|
+
|
113
|
+
function_node.state = TestState(meta=StateMeta(node_outputs={tool_router_node.Outputs.text: '{"arguments": {}}'}))
|
114
|
+
|
115
|
+
# WHEN the function node runs
|
116
|
+
outputs = function_node.run()
|
117
|
+
|
118
|
+
# THEN the workflow should have run successfully
|
119
|
+
assert outputs is not None
|
120
|
+
|
121
|
+
# AND the chat history should contain a function response
|
122
|
+
assert len(function_node.state.chat_history) == 1
|
123
|
+
function_response = function_node.state.chat_history[0]
|
124
|
+
assert function_response.role == "FUNCTION"
|
125
|
+
|
126
|
+
# AND the response should contain the generated files
|
127
|
+
assert isinstance(function_response.content, StringChatMessageContent)
|
128
|
+
data = json.loads(function_response.content.value)
|
129
|
+
assert data["generated_files"] == {"script.py": "print('hello world')"}
|
@@ -22,6 +22,7 @@ from vellum.workflows.outputs.base import BaseOutput
|
|
22
22
|
from vellum.workflows.ports.port import Port
|
23
23
|
from vellum.workflows.references.lazy import LazyReference
|
24
24
|
from vellum.workflows.state.base import BaseState
|
25
|
+
from vellum.workflows.state.context import WorkflowContext
|
25
26
|
from vellum.workflows.state.encoder import DefaultStateEncoder
|
26
27
|
from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior
|
27
28
|
from vellum.workflows.types.generics import is_workflow_class
|
@@ -152,8 +153,7 @@ def create_function_node(
|
|
152
153
|
|
153
154
|
# Call the function based on its type
|
154
155
|
inputs_instance = function.get_inputs_class()(**arguments)
|
155
|
-
|
156
|
-
workflow = function()
|
156
|
+
workflow = function(context=WorkflowContext.create_from(self._context))
|
157
157
|
terminal_event = workflow.run(
|
158
158
|
inputs=inputs_instance,
|
159
159
|
)
|
@@ -8,7 +8,7 @@ if TYPE_CHECKING:
|
|
8
8
|
|
9
9
|
|
10
10
|
class EnvironmentVariableReference(BaseDescriptor[str]):
|
11
|
-
def __init__(self, *, name: str, default: Optional[str]):
|
11
|
+
def __init__(self, *, name: str, default: Optional[str] = None):
|
12
12
|
super().__init__(name=name, types=(str,))
|
13
13
|
self._default = default
|
14
14
|
|
@@ -20,5 +20,4 @@ class EnvironmentVariableReference(BaseDescriptor[str]):
|
|
20
20
|
if self._default is not None:
|
21
21
|
return self._default
|
22
22
|
|
23
|
-
|
24
|
-
raise ValueError(f"No environment variable named '{self.name}' found")
|
23
|
+
return ""
|