vellum-workflow-server 1.7.5.post1__tar.gz → 1.7.5.post3__tar.gz
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.
Potentially problematic release.
This version of vellum-workflow-server might be problematic. Click here for more details.
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/PKG-INFO +1 -1
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/pyproject.toml +1 -1
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/workflow_view.py +1 -1
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/config.py +5 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/workflow_executor_context.py +2 -41
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/sentry.py +21 -0
- vellum_workflow_server-1.7.5.post3/src/workflow_server/utils/tests/test_sentry_integration.py +143 -0
- vellum_workflow_server-1.7.5.post1/src/workflow_server/utils/tests/test_sentry_integration.py +0 -69
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/README.md +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/__init__.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/__init__.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/auth_middleware.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/healthz_view.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/tests/__init__.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/tests/test_input_display_mapping.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/tests/test_workflow_view.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/code_exec_runner.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/__init__.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/cancel_workflow.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/events.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/executor.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/utils.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/server.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/start.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/__init__.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/exit_handler.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/log_proxy.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/oom_killer.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/system_utils.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/tests/__init__.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/tests/test_system_utils.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/tests/test_utils.py +0 -0
- {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/utils.py +0 -0
|
@@ -22,6 +22,7 @@ from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
|
|
22
22
|
|
|
23
23
|
from vellum.workflows.exceptions import WorkflowInitializationException
|
|
24
24
|
from vellum.workflows.nodes import BaseNode
|
|
25
|
+
from vellum.workflows.vellum_client import create_vellum_client
|
|
25
26
|
from workflow_server.config import ENABLE_PROCESS_WRAPPER, MEMORY_LIMIT_MB
|
|
26
27
|
from workflow_server.core.events import (
|
|
27
28
|
SPAN_ID_EVENT,
|
|
@@ -47,7 +48,6 @@ from workflow_server.core.workflow_executor_context import (
|
|
|
47
48
|
DEFAULT_TIMEOUT_SECONDS,
|
|
48
49
|
NodeExecutorContext,
|
|
49
50
|
WorkflowExecutorContext,
|
|
50
|
-
create_vellum_client,
|
|
51
51
|
)
|
|
52
52
|
from workflow_server.utils.oom_killer import get_is_oom_killed
|
|
53
53
|
from workflow_server.utils.system_utils import (
|
|
@@ -27,6 +27,11 @@ MEMORY_LIMIT_MB = int(os.getenv("MEMORY_LIMIT_MB", "2048"))
|
|
|
27
27
|
PORT = os.getenv("PORT", "8000")
|
|
28
28
|
VELLUM_API_URL_HOST = os.getenv("VELLUM_API_URL_HOST", "localhost")
|
|
29
29
|
VELLUM_API_URL_PORT = os.getenv("VELLUM_API_URL_PORT", 8000)
|
|
30
|
+
|
|
31
|
+
# Set VELLUM_API_URL for SDK when using local Vellum API
|
|
32
|
+
if os.getenv("USE_LOCAL_VELLUM_API") == "true" and "VELLUM_API_URL" not in os.environ:
|
|
33
|
+
os.environ["VELLUM_API_URL"] = "http://{}:{}".format(VELLUM_API_URL_HOST, VELLUM_API_URL_PORT)
|
|
34
|
+
|
|
30
35
|
CONCURRENCY = int(os.getenv("CONCURRENCY", "8"))
|
|
31
36
|
CONTAINER_IMAGE = os.getenv("CONTAINER_IMAGE", "python-workflow-runtime:latest")
|
|
32
37
|
ENABLE_PROCESS_WRAPPER = os.getenv("ENABLE_PROCESS_WRAPPER", "true").lower() == "true"
|
|
@@ -9,54 +9,15 @@ from typing_extensions import Self
|
|
|
9
9
|
from flask import has_request_context, request
|
|
10
10
|
from pydantic import Field, field_validator, model_validator
|
|
11
11
|
|
|
12
|
-
from vellum import ApiVersionEnum, Vellum
|
|
12
|
+
from vellum import ApiVersionEnum, Vellum
|
|
13
13
|
from vellum.client.core import UniversalBaseModel
|
|
14
14
|
from vellum.workflows.context import ExecutionContext
|
|
15
|
-
from
|
|
15
|
+
from vellum.workflows.vellum_client import create_vellum_client
|
|
16
16
|
from workflow_server.utils.utils import convert_json_inputs_to_vellum
|
|
17
17
|
|
|
18
18
|
DEFAULT_TIMEOUT_SECONDS = int(os.getenv("MAX_WORKFLOW_RUNTIME_SECONDS", 1800))
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def create_vellum_client(
|
|
22
|
-
api_key: str,
|
|
23
|
-
api_version: Optional[ApiVersionEnum] = None,
|
|
24
|
-
) -> Vellum:
|
|
25
|
-
"""
|
|
26
|
-
Create a VellumClient with proper environment configuration.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
api_key: The API key for the Vellum client
|
|
30
|
-
api_version: Optional API version to use
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Configured Vellum client instance
|
|
34
|
-
|
|
35
|
-
Note: Ideally we replace this with `vellum.workflows.vellum_client.create_vellum_client`
|
|
36
|
-
"""
|
|
37
|
-
if IS_VPC:
|
|
38
|
-
environment = VellumEnvironment(
|
|
39
|
-
default=os.getenv("VELLUM_DEFAULT_API_URL", VellumEnvironment.PRODUCTION.default),
|
|
40
|
-
documents=os.getenv("VELLUM_DOCUMENTS_API_URL", VellumEnvironment.PRODUCTION.documents),
|
|
41
|
-
predict=os.getenv("VELLUM_PREDICT_API_URL", VellumEnvironment.PRODUCTION.predict),
|
|
42
|
-
)
|
|
43
|
-
elif os.getenv("USE_LOCAL_VELLUM_API") == "true":
|
|
44
|
-
VELLUM_API_URL = f"http://{VELLUM_API_URL_HOST}:{VELLUM_API_URL_PORT}"
|
|
45
|
-
environment = VellumEnvironment(
|
|
46
|
-
default=VELLUM_API_URL,
|
|
47
|
-
documents=VELLUM_API_URL,
|
|
48
|
-
predict=VELLUM_API_URL,
|
|
49
|
-
)
|
|
50
|
-
else:
|
|
51
|
-
environment = VellumEnvironment.PRODUCTION
|
|
52
|
-
|
|
53
|
-
return Vellum(
|
|
54
|
-
api_key=api_key,
|
|
55
|
-
environment=environment,
|
|
56
|
-
api_version=api_version,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
21
|
class BaseExecutorContext(UniversalBaseModel):
|
|
61
22
|
inputs: dict = Field(default_factory=dict)
|
|
62
23
|
state: Optional[dict] = None
|
|
@@ -37,11 +37,32 @@ def _tag_trace_id(event: dict) -> None:
|
|
|
37
37
|
event["tags"]["vellum_trace_id"] = trace_id
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def _apply_custom_tags(event: dict, hint: dict) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Extracts 'sentry_tags' from logger exception's extra data and adds them to the event tags.
|
|
43
|
+
|
|
44
|
+
Modifies the event dictionary in place.
|
|
45
|
+
"""
|
|
46
|
+
record = hint.get("log_record")
|
|
47
|
+
if not record:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
sentry_tags = getattr(record, "sentry_tags", None)
|
|
51
|
+
if not sentry_tags or not isinstance(sentry_tags, dict):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if "tags" not in event:
|
|
55
|
+
event["tags"] = {}
|
|
56
|
+
|
|
57
|
+
event["tags"].update(sentry_tags)
|
|
58
|
+
|
|
59
|
+
|
|
40
60
|
def before_send(event: dict, hint: dict) -> Optional[dict]:
|
|
41
61
|
if "exc_info" in hint:
|
|
42
62
|
_, _, _ = hint["exc_info"]
|
|
43
63
|
|
|
44
64
|
_tag_trace_id(event)
|
|
65
|
+
_apply_custom_tags(event, hint)
|
|
45
66
|
|
|
46
67
|
return event
|
|
47
68
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import logging
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from workflow_server.server import create_app
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def mock_sentry_capture_envelope(mocker):
|
|
10
|
+
mock_transport = mocker.patch("sentry_sdk.client.make_transport")
|
|
11
|
+
return mock_transport.return_value.capture_envelope
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_sentry_integration_with_workflow_endpoints(monkeypatch, mock_sentry_capture_envelope):
|
|
15
|
+
# GIVEN sentry is configured
|
|
16
|
+
monkeypatch.setenv("SENTRY_DSN", "https://test-dsn@sentry.io/1234567890")
|
|
17
|
+
|
|
18
|
+
# AND our /workflow/stream endpoint raises an exception
|
|
19
|
+
def mock_get_version():
|
|
20
|
+
raise Exception("Test exception")
|
|
21
|
+
|
|
22
|
+
monkeypatch.setattr("workflow_server.api.workflow_view.get_version", mock_get_version)
|
|
23
|
+
|
|
24
|
+
# AND we have a mock trace_id
|
|
25
|
+
trace_id = str(uuid4())
|
|
26
|
+
|
|
27
|
+
# AND we have a mock request body
|
|
28
|
+
body = {
|
|
29
|
+
"execution_id": uuid4(),
|
|
30
|
+
"inputs": [],
|
|
31
|
+
"environment_api_key": "test",
|
|
32
|
+
"module": "workflow",
|
|
33
|
+
"timeout": 360,
|
|
34
|
+
"files": {
|
|
35
|
+
"__init__.py": "",
|
|
36
|
+
"workflow.py": """\
|
|
37
|
+
from vellum.workflows import BaseWorkflow
|
|
38
|
+
|
|
39
|
+
class Workflow(BaseWorkflow):
|
|
40
|
+
pass
|
|
41
|
+
""",
|
|
42
|
+
},
|
|
43
|
+
"execution_context": {
|
|
44
|
+
"trace_id": trace_id,
|
|
45
|
+
"parent_context": {
|
|
46
|
+
"type": "API_REQUEST",
|
|
47
|
+
"span_id": str(uuid4()),
|
|
48
|
+
"parent": None,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# WHEN we call the /workflow/version endpoint
|
|
54
|
+
flask_app = create_app()
|
|
55
|
+
|
|
56
|
+
with flask_app.test_client() as test_client:
|
|
57
|
+
response = test_client.post("/workflow/stream", json=body)
|
|
58
|
+
|
|
59
|
+
# THEN we get a 500 error
|
|
60
|
+
assert response.status_code == 500
|
|
61
|
+
|
|
62
|
+
# AND sentry captures the error with the correct data
|
|
63
|
+
assert mock_sentry_capture_envelope.call_count == 1
|
|
64
|
+
envelope = mock_sentry_capture_envelope.call_args[0][0]
|
|
65
|
+
event = envelope.get_event()
|
|
66
|
+
assert event["level"] == "error"
|
|
67
|
+
assert "Test exception" in event["exception"]["values"][0]["value"]
|
|
68
|
+
|
|
69
|
+
# AND the trace_id is tagged
|
|
70
|
+
assert event["tags"]["vellum_trace_id"] == trace_id
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_sentry_integration_applies_custom_tags_from_logger_extra(monkeypatch, mock_sentry_capture_envelope):
|
|
74
|
+
"""Test that Sentry events include custom tags from logger exception extra data."""
|
|
75
|
+
|
|
76
|
+
# GIVEN sentry is configured
|
|
77
|
+
monkeypatch.setenv("SENTRY_DSN", "https://test-dsn@sentry.io/1234567890")
|
|
78
|
+
|
|
79
|
+
# AND we have a function that will log with custom sentry_tags when called
|
|
80
|
+
def mock_get_version():
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
try:
|
|
83
|
+
raise Exception("Test exception with custom tags")
|
|
84
|
+
except Exception:
|
|
85
|
+
logger.exception(
|
|
86
|
+
"Failed during workflow execution",
|
|
87
|
+
extra={
|
|
88
|
+
"sentry_tags": {
|
|
89
|
+
"operation": "stream",
|
|
90
|
+
"test_tag": "test_value",
|
|
91
|
+
"numeric_tag": "12345",
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr("workflow_server.api.workflow_view.get_version", mock_get_version)
|
|
98
|
+
|
|
99
|
+
# AND we have a valid request body
|
|
100
|
+
body = {
|
|
101
|
+
"execution_id": str(uuid4()),
|
|
102
|
+
"inputs": [],
|
|
103
|
+
"environment_api_key": "test",
|
|
104
|
+
"module": "workflow",
|
|
105
|
+
"timeout": 360,
|
|
106
|
+
"files": {
|
|
107
|
+
"__init__.py": "",
|
|
108
|
+
"workflow.py": """\
|
|
109
|
+
from vellum.workflows import BaseWorkflow
|
|
110
|
+
|
|
111
|
+
class Workflow(BaseWorkflow):
|
|
112
|
+
pass
|
|
113
|
+
""",
|
|
114
|
+
},
|
|
115
|
+
"execution_context": {
|
|
116
|
+
"trace_id": str(uuid4()),
|
|
117
|
+
"parent_context": {
|
|
118
|
+
"type": "API_REQUEST",
|
|
119
|
+
"span_id": str(uuid4()),
|
|
120
|
+
"parent": None,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# WHEN we call an endpoint that triggers the error
|
|
126
|
+
flask_app = create_app()
|
|
127
|
+
|
|
128
|
+
with flask_app.test_client() as test_client:
|
|
129
|
+
response = test_client.post("/workflow/stream", json=body)
|
|
130
|
+
|
|
131
|
+
# THEN we get a 500 error
|
|
132
|
+
assert response.status_code == 500
|
|
133
|
+
|
|
134
|
+
# AND sentry captures the error
|
|
135
|
+
assert mock_sentry_capture_envelope.call_count == 1
|
|
136
|
+
envelope = mock_sentry_capture_envelope.call_args[0][0]
|
|
137
|
+
event = envelope.get_event()
|
|
138
|
+
|
|
139
|
+
# AND the custom tags are included in the event
|
|
140
|
+
assert "tags" in event
|
|
141
|
+
assert event["tags"]["operation"] == "stream"
|
|
142
|
+
assert event["tags"]["test_tag"] == "test_value"
|
|
143
|
+
assert event["tags"]["numeric_tag"] == "12345"
|
vellum_workflow_server-1.7.5.post1/src/workflow_server/utils/tests/test_sentry_integration.py
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from uuid import uuid4
|
|
3
|
-
|
|
4
|
-
from workflow_server.server import create_app
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@pytest.fixture
|
|
8
|
-
def mock_sentry_capture_envelope(mocker):
|
|
9
|
-
mock_transport = mocker.patch("sentry_sdk.client.make_transport")
|
|
10
|
-
return mock_transport.return_value.capture_envelope
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_sentry_integration_with_workflow_endpoints(monkeypatch, mock_sentry_capture_envelope):
|
|
14
|
-
# GIVEN sentry is configured
|
|
15
|
-
monkeypatch.setenv("SENTRY_DSN", "https://test-dsn@sentry.io/1234567890")
|
|
16
|
-
|
|
17
|
-
# AND our /workflow/stream endpoint raises an exception
|
|
18
|
-
def mock_get_version():
|
|
19
|
-
raise Exception("Test exception")
|
|
20
|
-
|
|
21
|
-
monkeypatch.setattr("workflow_server.api.workflow_view.get_version", mock_get_version)
|
|
22
|
-
|
|
23
|
-
# AND we have a mock trace_id
|
|
24
|
-
trace_id = str(uuid4())
|
|
25
|
-
|
|
26
|
-
# AND we have a mock request body
|
|
27
|
-
body = {
|
|
28
|
-
"execution_id": uuid4(),
|
|
29
|
-
"inputs": [],
|
|
30
|
-
"environment_api_key": "test",
|
|
31
|
-
"module": "workflow",
|
|
32
|
-
"timeout": 360,
|
|
33
|
-
"files": {
|
|
34
|
-
"__init__.py": "",
|
|
35
|
-
"workflow.py": """\
|
|
36
|
-
from vellum.workflows import BaseWorkflow
|
|
37
|
-
|
|
38
|
-
class Workflow(BaseWorkflow):
|
|
39
|
-
pass
|
|
40
|
-
""",
|
|
41
|
-
},
|
|
42
|
-
"execution_context": {
|
|
43
|
-
"trace_id": trace_id,
|
|
44
|
-
"parent_context": {
|
|
45
|
-
"type": "API_REQUEST",
|
|
46
|
-
"span_id": str(uuid4()),
|
|
47
|
-
"parent": None,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# WHEN we call the /workflow/version endpoint
|
|
53
|
-
flask_app = create_app()
|
|
54
|
-
|
|
55
|
-
with flask_app.test_client() as test_client:
|
|
56
|
-
response = test_client.post("/workflow/stream", json=body)
|
|
57
|
-
|
|
58
|
-
# THEN we get a 500 error
|
|
59
|
-
assert response.status_code == 500
|
|
60
|
-
|
|
61
|
-
# AND sentry captures the error with the correct data
|
|
62
|
-
assert mock_sentry_capture_envelope.call_count == 1
|
|
63
|
-
envelope = mock_sentry_capture_envelope.call_args[0][0]
|
|
64
|
-
event = envelope.get_event()
|
|
65
|
-
assert event["level"] == "error"
|
|
66
|
-
assert "Test exception" in event["exception"]["values"][0]["value"]
|
|
67
|
-
|
|
68
|
-
# AND the trace_id is tagged
|
|
69
|
-
assert event["tags"]["vellum_trace_id"] == trace_id
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|