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.

Files changed (34) hide show
  1. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/PKG-INFO +1 -1
  2. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/pyproject.toml +1 -1
  3. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/workflow_view.py +1 -1
  4. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/config.py +5 -0
  5. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/workflow_executor_context.py +2 -41
  6. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/sentry.py +21 -0
  7. vellum_workflow_server-1.7.5.post3/src/workflow_server/utils/tests/test_sentry_integration.py +143 -0
  8. vellum_workflow_server-1.7.5.post1/src/workflow_server/utils/tests/test_sentry_integration.py +0 -69
  9. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/README.md +0 -0
  10. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/__init__.py +0 -0
  11. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/__init__.py +0 -0
  12. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/auth_middleware.py +0 -0
  13. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/healthz_view.py +0 -0
  14. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/api/tests/__init__.py +0 -0
  15. {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
  16. {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
  17. {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
  18. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/code_exec_runner.py +0 -0
  19. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/__init__.py +0 -0
  20. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/cancel_workflow.py +0 -0
  21. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/events.py +0 -0
  22. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/executor.py +0 -0
  23. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/core/utils.py +0 -0
  24. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/server.py +0 -0
  25. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/start.py +0 -0
  26. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/__init__.py +0 -0
  27. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/exit_handler.py +0 -0
  28. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/log_proxy.py +0 -0
  29. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/oom_killer.py +0 -0
  30. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/system_utils.py +0 -0
  31. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/tests/__init__.py +0 -0
  32. {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
  33. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/tests/test_utils.py +0 -0
  34. {vellum_workflow_server-1.7.5.post1 → vellum_workflow_server-1.7.5.post3}/src/workflow_server/utils/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-workflow-server
3
- Version: 1.7.5.post1
3
+ Version: 1.7.5.post3
4
4
  Summary:
5
5
  License: AGPL
6
6
  Requires-Python: >=3.9.0,<4
@@ -3,7 +3,7 @@ name = "vellum-workflow-server"
3
3
 
4
4
  [tool.poetry]
5
5
  name = "vellum-workflow-server"
6
- version = "1.7.5.post1"
6
+ version = "1.7.5.post3"
7
7
  description = ""
8
8
  readme = "README.md"
9
9
  authors = []
@@ -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, VellumEnvironment
12
+ from vellum import ApiVersionEnum, Vellum
13
13
  from vellum.client.core import UniversalBaseModel
14
14
  from vellum.workflows.context import ExecutionContext
15
- from workflow_server.config import IS_VPC, VELLUM_API_URL_HOST, VELLUM_API_URL_PORT
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"
@@ -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