vellum-ai 0.14.37__py3-none-any.whl → 0.14.39__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 (49) hide show
  1. vellum/__init__.py +10 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/reference.md +6272 -0
  4. vellum/client/types/__init__.py +10 -0
  5. vellum/client/types/ad_hoc_fulfilled_prompt_execution_meta.py +2 -0
  6. vellum/client/types/fulfilled_prompt_execution_meta.py +2 -0
  7. vellum/client/types/test_suite_run_exec_config_request.py +4 -0
  8. vellum/client/types/test_suite_run_progress.py +20 -0
  9. vellum/client/types/test_suite_run_prompt_sandbox_exec_config_data_request.py +27 -0
  10. vellum/client/types/test_suite_run_prompt_sandbox_exec_config_request.py +29 -0
  11. vellum/client/types/test_suite_run_read.py +3 -0
  12. vellum/client/types/test_suite_run_workflow_sandbox_exec_config_data_request.py +22 -0
  13. vellum/client/types/test_suite_run_workflow_sandbox_exec_config_request.py +29 -0
  14. vellum/client/types/vellum_sdk_error_code_enum.py +1 -0
  15. vellum/client/types/workflow_execution_event_error_code.py +1 -0
  16. vellum/plugins/pydantic.py +1 -1
  17. vellum/types/test_suite_run_progress.py +3 -0
  18. vellum/types/test_suite_run_prompt_sandbox_exec_config_data_request.py +3 -0
  19. vellum/types/test_suite_run_prompt_sandbox_exec_config_request.py +3 -0
  20. vellum/types/test_suite_run_workflow_sandbox_exec_config_data_request.py +3 -0
  21. vellum/types/test_suite_run_workflow_sandbox_exec_config_request.py +3 -0
  22. vellum/workflows/errors/types.py +1 -0
  23. vellum/workflows/events/node.py +2 -1
  24. vellum/workflows/events/tests/test_event.py +1 -0
  25. vellum/workflows/events/types.py +3 -40
  26. vellum/workflows/events/workflow.py +15 -4
  27. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +7 -1
  28. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +94 -3
  29. vellum/workflows/nodes/displayable/conftest.py +2 -6
  30. vellum/workflows/nodes/displayable/guardrail_node/node.py +1 -1
  31. vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py +0 -0
  32. vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py +50 -0
  33. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +6 -1
  34. vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py +323 -0
  35. vellum/workflows/runner/runner.py +78 -57
  36. vellum/workflows/state/base.py +177 -50
  37. vellum/workflows/state/tests/test_state.py +26 -20
  38. vellum/workflows/types/definition.py +71 -0
  39. vellum/workflows/types/generics.py +34 -1
  40. vellum/workflows/workflows/base.py +26 -19
  41. vellum/workflows/workflows/tests/test_base_workflow.py +232 -1
  42. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/METADATA +1 -1
  43. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/RECORD +49 -35
  44. vellum_cli/push.py +2 -3
  45. vellum_cli/tests/test_push.py +52 -0
  46. vellum_ee/workflows/display/vellum.py +0 -5
  47. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/LICENSE +0 -0
  48. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/WHEEL +0 -0
  49. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,50 @@
1
+ from vellum import TestSuiteRunMetricNumberOutput
2
+ from vellum.client.types.chat_history_input import ChatHistoryInput
3
+ from vellum.client.types.chat_message import ChatMessage
4
+ from vellum.client.types.json_input import JsonInput
5
+ from vellum.client.types.metric_definition_execution import MetricDefinitionExecution
6
+ from vellum.client.types.number_input import NumberInput
7
+ from vellum.client.types.string_input import StringInput
8
+ from vellum.workflows.nodes.displayable.guardrail_node.node import GuardrailNode
9
+
10
+
11
+ def test_guardrail_node__inputs(vellum_client):
12
+ """Test that GuardrailNode correctly handles inputs."""
13
+
14
+ # GIVEN a Guardrail Node with inputs
15
+ class MyGuard(GuardrailNode):
16
+ metric_definition = "example_metric_definition"
17
+ metric_inputs = {
18
+ "a_string": "hello",
19
+ "a_chat_history": [ChatMessage(role="USER", text="Hello, how are you?")],
20
+ "a_dict": {"foo": "bar"},
21
+ "a_int": 42,
22
+ "a_float": 3.14,
23
+ }
24
+
25
+ vellum_client.metric_definitions.execute_metric_definition.return_value = MetricDefinitionExecution(
26
+ outputs=[
27
+ TestSuiteRunMetricNumberOutput(
28
+ name="score",
29
+ value=1.0,
30
+ ),
31
+ ],
32
+ )
33
+
34
+ # WHEN the node is run
35
+ MyGuard().run()
36
+
37
+ # THEN the metric_definitions.execute_metric_definition method should be called with the correct inputs
38
+ mock_api = vellum_client.metric_definitions.execute_metric_definition
39
+ assert mock_api.call_count == 1
40
+
41
+ assert mock_api.call_args.kwargs["inputs"] == [
42
+ StringInput(name="a_string", type="STRING", value="hello"),
43
+ ChatHistoryInput(
44
+ name="a_chat_history", type="CHAT_HISTORY", value=[ChatMessage(role="USER", text="Hello, how are you?")]
45
+ ),
46
+ JsonInput(name="a_dict", type="JSON", value={"foo": "bar"}),
47
+ NumberInput(name="a_int", type="NUMBER", value=42.0),
48
+ NumberInput(name="a_float", type="NUMBER", value=3.14),
49
+ ]
50
+ assert len(mock_api.call_args.kwargs["inputs"]) == 5
@@ -170,8 +170,13 @@ def test_inline_prompt_node__function_definitions(vellum_adhoc_prompt_client):
170
170
  WorkflowErrorCode.INTERNAL_ERROR,
171
171
  "Failed to execute Prompt",
172
172
  ),
173
+ (
174
+ ApiError(status_code=403, body={"detail": "Provider credentials is missing or unavailable"}),
175
+ WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE,
176
+ "Provider credentials is missing or unavailable",
177
+ ),
173
178
  ],
174
- ids=["404", "invalid_dict", "invalid_body", "no_status_code", "500"],
179
+ ids=["404", "invalid_dict", "invalid_body", "no_status_code", "500", "403"],
175
180
  )
176
181
  def test_inline_prompt_node__api_error__invalid_inputs_node_exception(
177
182
  vellum_adhoc_prompt_client, exception, expected_code, expected_message
@@ -5,6 +5,8 @@ from typing import Any, Iterator, List
5
5
 
6
6
  from httpx import Response
7
7
 
8
+ from vellum import RejectedExecutePromptEvent
9
+ from vellum.client import ApiError
8
10
  from vellum.client.types.chat_history_input_request import ChatHistoryInputRequest
9
11
  from vellum.client.types.chat_message import ChatMessage
10
12
  from vellum.client.types.chat_message_request import ChatMessageRequest
@@ -15,6 +17,8 @@ from vellum.client.types.json_input_request import JsonInputRequest
15
17
  from vellum.client.types.prompt_output import PromptOutput
16
18
  from vellum.client.types.string_vellum_value import StringVellumValue
17
19
  from vellum.workflows.context import execution_context
20
+ from vellum.workflows.errors import WorkflowErrorCode
21
+ from vellum.workflows.exceptions import NodeException
18
22
  from vellum.workflows.nodes.displayable.prompt_deployment_node.node import PromptDeploymentNode
19
23
 
20
24
 
@@ -194,3 +198,322 @@ def test_prompt_deployment_node__json_output(vellum_client):
194
198
  json_output = outputs[2]
195
199
  assert json_output.name == "json"
196
200
  assert json_output.value == expected_json
201
+
202
+
203
+ def test_prompt_deployment_node__all_fallbacks_fail(vellum_client):
204
+ # GIVEN a Prompt Deployment Node with fallback models
205
+ class TestPromptDeploymentNode(PromptDeploymentNode):
206
+ deployment = "test_deployment"
207
+ prompt_inputs = {"query": "test query"}
208
+ ml_model_fallbacks = ["fallback_model_1", "fallback_model_2"]
209
+
210
+ # AND all models fail with 404 errors
211
+ primary_error = ApiError(
212
+ body={"detail": "Failed to find model 'primary_model'"},
213
+ status_code=404,
214
+ )
215
+ fallback1_error = ApiError(
216
+ body={"detail": "Failed to find model 'fallback_model_1'"},
217
+ status_code=404,
218
+ )
219
+ fallback2_error = ApiError(
220
+ body={"detail": "Failed to find model 'fallback_model_2'"},
221
+ status_code=404,
222
+ )
223
+
224
+ vellum_client.execute_prompt_stream.side_effect = [primary_error, fallback1_error, fallback2_error]
225
+
226
+ # WHEN we run the node
227
+ node = TestPromptDeploymentNode()
228
+
229
+ # THEN an exception should be raised
230
+ with pytest.raises(NodeException) as exc_info:
231
+ list(node.run())
232
+
233
+ # AND the client should have been called three times
234
+ assert vellum_client.execute_prompt_stream.call_count == 3
235
+
236
+ # AND we get the expected error message
237
+ assert (
238
+ exc_info.value.message
239
+ == "Failed to execute prompts with these fallbacks: ['fallback_model_1', 'fallback_model_2']"
240
+ )
241
+
242
+
243
+ def test_prompt_deployment_node__fallback_success(vellum_client):
244
+ # GIVEN a Prompt Deployment Node with fallback models
245
+ class TestPromptDeploymentNode(PromptDeploymentNode):
246
+ deployment = "test_deployment"
247
+ prompt_inputs = {"query": "test query"}
248
+ ml_model_fallbacks = ["fallback_model_1", "fallback_model_2"]
249
+
250
+ # AND the primary model fails with a 404 error
251
+ primary_error = ApiError(
252
+ body={"detail": "Failed to find model 'primary_model'"},
253
+ status_code=404,
254
+ )
255
+
256
+ # AND the first fallback model succeeds
257
+ def generate_successful_stream():
258
+ execution_id = str(uuid4())
259
+ events = [
260
+ InitiatedExecutePromptEvent(execution_id=execution_id),
261
+ FulfilledExecutePromptEvent(
262
+ execution_id=execution_id, outputs=[StringVellumValue(value="Fallback response")]
263
+ ),
264
+ ]
265
+ return iter(events)
266
+
267
+ # Set up the mock to fail on primary but succeed on first fallback
268
+ vellum_client.execute_prompt_stream.side_effect = [primary_error, generate_successful_stream()]
269
+
270
+ # WHEN we run the node
271
+ node = TestPromptDeploymentNode()
272
+ outputs = list(node.run())
273
+
274
+ # THEN the node should complete successfully using the fallback model
275
+ assert len(outputs) > 0
276
+ assert outputs[-1].value == "Fallback response"
277
+
278
+ # AND the client should have been called twice (once for primary, once for fallback)
279
+ assert vellum_client.execute_prompt_stream.call_count == 2
280
+
281
+ # AND the second call should include the fallback model override
282
+ second_call_kwargs = vellum_client.execute_prompt_stream.call_args_list[1][1]
283
+ body_params = second_call_kwargs["request_options"]["additional_body_parameters"]
284
+ assert body_params["overrides"]["ml_model_fallback"] == "fallback_model_1"
285
+
286
+
287
+ def test_prompt_deployment_node__provider_error_with_fallbacks(vellum_client):
288
+ # GIVEN a Prompt Deployment Node with fallback models
289
+ class TestPromptDeploymentNode(PromptDeploymentNode):
290
+ deployment = "test_deployment"
291
+ prompt_inputs = {}
292
+ ml_model_fallbacks = ["gpt-4o", "gemini-1.5-flash-latest"]
293
+
294
+ # AND the primary model starts but then fails with a provider error
295
+ def generate_primary_events():
296
+ execution_id = str(uuid4())
297
+ events = [
298
+ InitiatedExecutePromptEvent(execution_id=execution_id),
299
+ RejectedExecutePromptEvent(
300
+ execution_id=execution_id,
301
+ error={
302
+ "code": "PROVIDER_ERROR",
303
+ "message": "The model provider encountered an error",
304
+ },
305
+ ),
306
+ ]
307
+ return iter(events)
308
+
309
+ # AND the fallback model succeeds
310
+ def generate_fallback_events():
311
+ execution_id = str(uuid4())
312
+ expected_outputs: List[PromptOutput] = [StringVellumValue(value="Fallback response")]
313
+ events = [
314
+ InitiatedExecutePromptEvent(execution_id=execution_id),
315
+ FulfilledExecutePromptEvent(execution_id=execution_id, outputs=expected_outputs),
316
+ ]
317
+ return iter(events)
318
+
319
+ vellum_client.execute_prompt_stream.side_effect = [generate_primary_events(), generate_fallback_events()]
320
+
321
+ # WHEN we run the node
322
+ node = TestPromptDeploymentNode()
323
+ outputs = list(node.run())
324
+
325
+ # THEN the node should complete successfully using the fallback model
326
+ assert len(outputs) > 0
327
+ assert outputs[-1].value == "Fallback response"
328
+
329
+ # AND the client should have been called twice
330
+ assert vellum_client.execute_prompt_stream.call_count == 2
331
+
332
+ # AND the second call should include the fallback model override
333
+ second_call_kwargs = vellum_client.execute_prompt_stream.call_args_list[1][1]
334
+ body_params = second_call_kwargs["request_options"]["additional_body_parameters"]
335
+ assert body_params["overrides"]["ml_model_fallback"] == "gpt-4o"
336
+
337
+
338
+ def test_prompt_deployment_node__multiple_fallbacks_mixed_errors(vellum_client):
339
+ """
340
+ This test case is when the primary model fails with an api error and
341
+ the first fallback fails with a provider error
342
+ """
343
+
344
+ # GIVEN a Prompt Deployment Node with multiple fallback models
345
+ class TestPromptDeploymentNode(PromptDeploymentNode):
346
+ deployment = "test_deployment"
347
+ prompt_inputs = {}
348
+ ml_model_fallbacks = ["gpt-4o", "gemini-1.5-flash-latest"]
349
+
350
+ # AND the primary model fails with an API error
351
+ primary_error = ApiError(
352
+ body={"detail": "Failed to find model 'primary_model'"},
353
+ status_code=404,
354
+ )
355
+
356
+ # AND the first fallback model fails with a provider error
357
+ def generate_fallback1_events():
358
+ execution_id = str(uuid4())
359
+ events = [
360
+ InitiatedExecutePromptEvent(execution_id=execution_id),
361
+ RejectedExecutePromptEvent(
362
+ execution_id=execution_id,
363
+ error={
364
+ "code": "PROVIDER_ERROR",
365
+ "message": "The first fallback provider encountered an error",
366
+ },
367
+ ),
368
+ ]
369
+ return iter(events)
370
+
371
+ # AND the second fallback model succeeds
372
+ def generate_fallback2_events():
373
+ execution_id = str(uuid4())
374
+ expected_outputs: List[PromptOutput] = [StringVellumValue(value="Second fallback response")]
375
+ events = [
376
+ InitiatedExecutePromptEvent(execution_id=execution_id),
377
+ FulfilledExecutePromptEvent(execution_id=execution_id, outputs=expected_outputs),
378
+ ]
379
+ return iter(events)
380
+
381
+ vellum_client.execute_prompt_stream.side_effect = [
382
+ primary_error,
383
+ generate_fallback1_events(),
384
+ generate_fallback2_events(),
385
+ ]
386
+
387
+ # WHEN we run the node
388
+ node = TestPromptDeploymentNode()
389
+ outputs = list(node.run())
390
+
391
+ # THEN the node should complete successfully using the second fallback model
392
+ assert len(outputs) > 0
393
+ assert outputs[-1].value == "Second fallback response"
394
+
395
+ # AND the client should have been called three times
396
+ assert vellum_client.execute_prompt_stream.call_count == 3
397
+
398
+ # AND the calls should include the correct model overrides
399
+ first_fallback_call = vellum_client.execute_prompt_stream.call_args_list[1][1]
400
+ first_fallback_params = first_fallback_call["request_options"]["additional_body_parameters"]
401
+ assert first_fallback_params["overrides"]["ml_model_fallback"] == "gpt-4o"
402
+
403
+ second_fallback_call = vellum_client.execute_prompt_stream.call_args_list[2][1]
404
+ second_fallback_params = second_fallback_call["request_options"]["additional_body_parameters"]
405
+ assert second_fallback_params["overrides"]["ml_model_fallback"] == "gemini-1.5-flash-latest"
406
+
407
+
408
+ def test_prompt_deployment_node_multiple_provider_errors(vellum_client):
409
+ # GIVEN a Prompt Deployment Node with a single fallback model
410
+ class TestPromptDeploymentNode(PromptDeploymentNode):
411
+ deployment = "test_deployment"
412
+ prompt_inputs = {}
413
+ ml_model_fallbacks = ["gpt-4o"]
414
+
415
+ # AND the primary model fails with a provider error
416
+ def generate_primary_events():
417
+ execution_id = str(uuid4())
418
+ events = [
419
+ InitiatedExecutePromptEvent(execution_id=execution_id),
420
+ RejectedExecutePromptEvent(
421
+ execution_id=execution_id,
422
+ error={
423
+ "code": "PROVIDER_ERROR",
424
+ "message": "The primary provider encountered an error",
425
+ },
426
+ ),
427
+ ]
428
+ return iter(events)
429
+
430
+ # AND the fallback model also fails with a provider error
431
+ def generate_fallback1_events():
432
+ execution_id = str(uuid4())
433
+ events = [
434
+ InitiatedExecutePromptEvent(execution_id=execution_id),
435
+ RejectedExecutePromptEvent(
436
+ execution_id=execution_id,
437
+ error={
438
+ "code": "PROVIDER_ERROR",
439
+ "message": "The first fallback provider encountered an error",
440
+ },
441
+ ),
442
+ ]
443
+ return iter(events)
444
+
445
+ vellum_client.execute_prompt_stream.side_effect = [
446
+ generate_primary_events(),
447
+ generate_fallback1_events(),
448
+ ]
449
+
450
+ # WHEN we run the node
451
+ with pytest.raises(NodeException) as exc_info:
452
+ node = TestPromptDeploymentNode()
453
+ list(node.run())
454
+
455
+ # THEN we should get an exception
456
+ assert exc_info.value.message == "Failed to execute prompts with these fallbacks: ['gpt-4o']"
457
+
458
+ # AND the client should have been called two times
459
+ assert vellum_client.execute_prompt_stream.call_count == 2
460
+
461
+ # AND the calls should include the correct model overrides
462
+ first_fallback_call = vellum_client.execute_prompt_stream.call_args_list[1][1]
463
+ first_fallback_params = first_fallback_call["request_options"]["additional_body_parameters"]
464
+ assert first_fallback_params["overrides"]["ml_model_fallback"] == "gpt-4o"
465
+
466
+
467
+ def test_prompt_deployment_node__no_fallbacks(vellum_client):
468
+ # GIVEN a Prompt Deployment Node with no fallback models
469
+ class TestPromptDeploymentNode(PromptDeploymentNode):
470
+ deployment = "test_deployment"
471
+ prompt_inputs = {}
472
+
473
+ # AND the primary model fails with an API error
474
+ primary_error = ApiError(
475
+ body={"detail": "Failed to find model 'primary_model'"},
476
+ status_code=404,
477
+ )
478
+
479
+ vellum_client.execute_prompt_stream.side_effect = primary_error
480
+
481
+ # WHEN we run the node
482
+ node = TestPromptDeploymentNode()
483
+
484
+ # THEN the node should raise an exception
485
+ with pytest.raises(NodeException) as exc_info:
486
+ list(node.run())
487
+
488
+ # AND the exception should contain the original error message
489
+ assert exc_info.value.message == "Failed to find model 'primary_model'"
490
+ assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
491
+
492
+ # AND the client should have been called only once (for the primary model)
493
+ assert vellum_client.execute_prompt_stream.call_count == 1
494
+
495
+
496
+ def test_prompt_deployment_node__provider_credentials_missing(vellum_client):
497
+ # GIVEN a Prompt Deployment Node
498
+ class TestPromptDeploymentNode(PromptDeploymentNode):
499
+ deployment = "test_deployment"
500
+ prompt_inputs = {}
501
+
502
+ # AND the client responds with a 403 error of provider credentials missing
503
+ primary_error = ApiError(
504
+ body={"detail": "Provider credentials is missing or unavailable"},
505
+ status_code=403,
506
+ )
507
+
508
+ vellum_client.execute_prompt_stream.side_effect = primary_error
509
+
510
+ # WHEN we run the node
511
+ node = TestPromptDeploymentNode()
512
+
513
+ # THEN the node should raise an exception
514
+ with pytest.raises(NodeException) as exc_info:
515
+ list(node.run())
516
+
517
+ # AND the exception should contain the original error message
518
+ assert exc_info.value.message == "Provider credentials is missing or unavailable"
519
+ assert exc_info.value.code == WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE