agenta 0.70.1__py3-none-any.whl → 0.75.0__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 (46) hide show
  1. agenta/__init__.py +9 -3
  2. agenta/sdk/__init__.py +2 -4
  3. agenta/sdk/agenta_init.py +22 -75
  4. agenta/sdk/assets.py +57 -0
  5. agenta/sdk/context/serving.py +2 -0
  6. agenta/sdk/contexts/routing.py +2 -0
  7. agenta/sdk/contexts/running.py +3 -2
  8. agenta/sdk/decorators/running.py +8 -4
  9. agenta/sdk/decorators/serving.py +65 -26
  10. agenta/sdk/decorators/tracing.py +51 -30
  11. agenta/sdk/engines/tracing/inline.py +8 -1
  12. agenta/sdk/engines/tracing/processors.py +23 -12
  13. agenta/sdk/evaluations/preview/evaluate.py +36 -8
  14. agenta/sdk/evaluations/runs.py +2 -1
  15. agenta/sdk/litellm/mockllm.py +2 -2
  16. agenta/sdk/managers/config.py +3 -1
  17. agenta/sdk/managers/secrets.py +25 -8
  18. agenta/sdk/managers/testsets.py +143 -227
  19. agenta/sdk/middleware/config.py +3 -1
  20. agenta/sdk/middleware/otel.py +3 -1
  21. agenta/sdk/middleware/vault.py +33 -18
  22. agenta/sdk/middlewares/routing/otel.py +1 -1
  23. agenta/sdk/middlewares/running/vault.py +33 -17
  24. agenta/sdk/router.py +30 -5
  25. agenta/sdk/tracing/inline.py +8 -1
  26. agenta/sdk/tracing/processors.py +8 -3
  27. agenta/sdk/tracing/propagation.py +9 -12
  28. agenta/sdk/types.py +19 -21
  29. agenta/sdk/utils/client.py +10 -9
  30. agenta/sdk/utils/lazy.py +253 -0
  31. agenta/sdk/workflows/builtin.py +2 -0
  32. agenta/sdk/workflows/configurations.py +1 -0
  33. agenta/sdk/workflows/handlers.py +236 -81
  34. agenta/sdk/workflows/interfaces.py +47 -0
  35. agenta/sdk/workflows/runners/base.py +6 -2
  36. agenta/sdk/workflows/runners/daytona.py +250 -131
  37. agenta/sdk/workflows/runners/local.py +22 -56
  38. agenta/sdk/workflows/runners/registry.py +1 -1
  39. agenta/sdk/workflows/sandbox.py +17 -5
  40. agenta/sdk/workflows/templates.py +81 -0
  41. agenta/sdk/workflows/utils.py +6 -0
  42. {agenta-0.70.1.dist-info → agenta-0.75.0.dist-info}/METADATA +4 -8
  43. {agenta-0.70.1.dist-info → agenta-0.75.0.dist-info}/RECORD +44 -44
  44. agenta/config.py +0 -25
  45. agenta/config.toml +0 -4
  46. {agenta-0.70.1.dist-info → agenta-0.75.0.dist-info}/WHEEL +0 -0
@@ -36,7 +36,7 @@ _CACHE_ENABLED = (
36
36
  _cache = TTLLRUCache()
37
37
 
38
38
 
39
- async def get_secrets(api_url, credentials) -> list:
39
+ async def get_secrets(api_url, credentials) -> tuple[list, list, list]:
40
40
  headers = None
41
41
  if credentials:
42
42
  headers = {"Authorization": credentials}
@@ -53,8 +53,13 @@ async def get_secrets(api_url, credentials) -> list:
53
53
 
54
54
  if secrets_cache:
55
55
  secrets = secrets_cache.get("secrets")
56
+ vault_secrets = secrets_cache.get("vault_secrets")
57
+ local_secrets = secrets_cache.get("local_secrets")
56
58
 
57
- return secrets
59
+ if vault_secrets is None or local_secrets is None:
60
+ return secrets, [], []
61
+
62
+ return secrets, vault_secrets, local_secrets
58
63
 
59
64
  local_secrets: List[Dict[str, Any]] = []
60
65
 
@@ -76,7 +81,7 @@ async def get_secrets(api_url, credentials) -> list:
76
81
  )
77
82
 
78
83
  local_secrets.append(secret.model_dump())
79
- except: # pylint: disable=bare-except
84
+ except Exception: # pylint: disable=bare-except
80
85
  display_exception("Vault: Local Secrets Exception")
81
86
 
82
87
  vault_secrets: List[Dict[str, Any]] = []
@@ -93,32 +98,38 @@ async def get_secrets(api_url, credentials) -> list:
93
98
 
94
99
  else:
95
100
  vault_secrets = response.json()
96
- except: # pylint: disable=bare-except
101
+ except Exception: # pylint: disable=bare-except
97
102
  display_exception("Vault: Vault Secrets Exception")
98
103
 
99
- secrets = local_secrets + vault_secrets
100
-
101
- standard_secrets = {}
102
- custom_secrets = []
104
+ local_standard = {}
105
+ vault_standard = {}
106
+ vault_custom = []
103
107
 
104
108
  if local_secrets:
105
109
  for secret in local_secrets:
106
- standard_secrets[secret["data"]["kind"]] = secret # type: ignore
110
+ local_standard[secret["data"]["kind"]] = secret # type: ignore
107
111
 
108
112
  if vault_secrets:
109
113
  for secret in vault_secrets:
110
114
  if secret["kind"] == "provider_key": # type: ignore
111
- standard_secrets[secret["data"]["kind"]] = secret # type: ignore
115
+ vault_standard[secret["data"]["kind"]] = secret # type: ignore
112
116
  elif secret["kind"] == "custom_provider": # type: ignore
113
- custom_secrets.append(secret)
117
+ vault_custom.append(secret)
114
118
 
115
- standard_secrets = list(standard_secrets.values())
119
+ combined_standard = {**local_standard, **vault_standard}
120
+ combined_vault = list(vault_standard.values()) + vault_custom
121
+ secrets = list(combined_standard.values()) + vault_custom
116
122
 
117
- secrets = standard_secrets + custom_secrets
118
-
119
- _cache.put(_hash, {"secrets": secrets})
123
+ _cache.put(
124
+ _hash,
125
+ {
126
+ "secrets": secrets,
127
+ "vault_secrets": combined_vault,
128
+ "local_secrets": local_secrets,
129
+ },
130
+ )
120
131
 
121
- return secrets
132
+ return secrets, combined_vault, local_secrets
122
133
 
123
134
 
124
135
  class VaultMiddleware:
@@ -133,8 +144,13 @@ class VaultMiddleware:
133
144
  ctx = RunningContext.get()
134
145
  credentials = ctx.credentials
135
146
 
136
- secrets = await get_secrets(api_url, credentials)
147
+ secrets, vault_secrets, local_secrets = await get_secrets(
148
+ api_url,
149
+ credentials,
150
+ )
137
151
 
138
152
  ctx.secrets = secrets
153
+ ctx.vault_secrets = vault_secrets
154
+ ctx.local_secrets = local_secrets
139
155
 
140
156
  return await call_next(request)
agenta/sdk/router.py CHANGED
@@ -1,8 +1,33 @@
1
- from fastapi import APIRouter
1
+ from typing import TYPE_CHECKING, Optional
2
2
 
3
- router = APIRouter()
3
+ if TYPE_CHECKING:
4
+ from fastapi import APIRouter
4
5
 
6
+ from agenta.sdk.utils.lazy import _load_fastapi
5
7
 
6
- @router.get("/health")
7
- def health():
8
- return {"status": "ok"}
8
+ _router: Optional["APIRouter"] = None
9
+
10
+
11
+ class _LazyRouter:
12
+ def __getattr__(self, name):
13
+ return getattr(get_router(), name)
14
+
15
+ def __call__(self, *args, **kwargs):
16
+ return get_router()(*args, **kwargs)
17
+
18
+
19
+ def get_router() -> "APIRouter":
20
+ global _router # pylint: disable=global-statement
21
+
22
+ if _router is None:
23
+ fastapi = _load_fastapi()
24
+ _router = fastapi.APIRouter()
25
+
26
+ @_router.get("/health")
27
+ def health():
28
+ return {"status": "ok"}
29
+
30
+ return _router
31
+
32
+
33
+ router = _LazyRouter()
@@ -949,9 +949,10 @@ def parse_to_agenta_span_dto(
949
949
  ########################################
950
950
 
951
951
 
952
- from litellm import cost_calculator
953
952
  from opentelemetry.sdk.trace import ReadableSpan
954
953
 
954
+ from agenta.sdk.utils.lazy import _load_litellm
955
+
955
956
  from agenta.sdk.types import AgentaNodeDto, AgentaNodesResponse
956
957
 
957
958
 
@@ -1112,6 +1113,12 @@ TYPES_WITH_COSTS = [
1112
1113
 
1113
1114
 
1114
1115
  def calculate_costs(span_idx: Dict[str, SpanDTO]):
1116
+ litellm = _load_litellm()
1117
+ if not litellm:
1118
+ return
1119
+
1120
+ cost_calculator = litellm.cost_calculator
1121
+
1115
1122
  for span in span_idx.values():
1116
1123
  if (
1117
1124
  span.node.type
@@ -1,13 +1,16 @@
1
- from typing import Optional, Dict, List
2
1
  from threading import Lock
2
+ from typing import Dict, List, Optional
3
3
 
4
+
5
+ from agenta.sdk.contexts.tracing import TracingContext
6
+ from agenta.sdk.utils.logging import get_module_logger
4
7
  from opentelemetry.baggage import get_all as get_baggage
5
8
  from opentelemetry.context import Context
6
9
  from opentelemetry.sdk.trace import Span, SpanProcessor
7
10
  from opentelemetry.sdk.trace.export import (
8
- SpanExporter,
9
- ReadableSpan,
10
11
  BatchSpanProcessor,
12
+ ReadableSpan,
13
+ SpanExporter,
11
14
  )
12
15
  from opentelemetry.trace import SpanContext
13
16
 
@@ -88,6 +91,8 @@ class TraceProcessor(SpanProcessor):
88
91
  if isinstance(ref, dict):
89
92
  for field, val in ref.items():
90
93
  span.set_attribute(f"{key}.{field}", str(val))
94
+ elif isinstance(ref, (str, bool, int, float, bytes)):
95
+ span.set_attribute(key, ref)
91
96
  else:
92
97
  # Not a reference - only set if it's a valid attribute type
93
98
  if isinstance(value, (str, bool, int, float, bytes)):
@@ -1,14 +1,10 @@
1
- from typing import Tuple, Optional, Dict, Any
1
+ from typing import Any, Dict, Optional, Tuple
2
2
 
3
- from opentelemetry.trace import Span, set_span_in_context, get_current_span
4
- from opentelemetry.baggage.propagation import W3CBaggagePropagator
5
- from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
3
+ from agenta.sdk.contexts.tracing import TracingContext
6
4
  from opentelemetry.baggage import set_baggage
5
+ from opentelemetry.baggage.propagation import W3CBaggagePropagator
7
6
  from opentelemetry.context import get_current
8
-
9
- from agenta.sdk.contexts.tracing import TracingContext
10
-
11
- import agenta as ag
7
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
12
8
 
13
9
 
14
10
  def extract(
@@ -47,11 +43,12 @@ def extract(
47
43
  baggage = {}
48
44
 
49
45
  try:
50
- _carrier = {
51
- "baggage": headers.get("Baggage") # Uppercase
46
+ raw_baggage = (
47
+ headers.get("Baggage") # Uppercase
52
48
  or headers.get("baggage") # Lowercase
53
- or "",
54
- }
49
+ or ""
50
+ )
51
+ _carrier = {"baggage": raw_baggage}
55
52
 
56
53
  _context = W3CBaggagePropagator().extract(_carrier)
57
54
 
agenta/sdk/types.py CHANGED
@@ -8,7 +8,7 @@ from pydantic import BaseModel, Field, model_validator, AliasChoices
8
8
  from starlette.responses import StreamingResponse
9
9
 
10
10
 
11
- from agenta.sdk.assets import supported_llm_models
11
+ from agenta.sdk.assets import supported_llm_models, model_metadata
12
12
  from agenta.client.backend.types import AgentaNodesResponse, AgentaNodeDto
13
13
 
14
14
 
@@ -23,7 +23,11 @@ def MCField( # pylint: disable=invalid-name
23
23
  ) -> Field:
24
24
  # Pydantic 2.12+ no longer allows post-creation mutation of field properties
25
25
  if isinstance(choices, dict):
26
- json_extra = {"choices": choices, "x-parameter": "grouped_choice"}
26
+ json_extra = {
27
+ "choices": choices,
28
+ "x-parameter": "grouped_choice",
29
+ "x-model-metadata": model_metadata,
30
+ }
27
31
  elif isinstance(choices, list):
28
32
  json_extra = {"choices": choices, "x-parameter": "choice"}
29
33
  else:
@@ -492,17 +496,11 @@ class TemplateFormatError(PromptTemplateError):
492
496
  super().__init__(message)
493
497
 
494
498
 
495
- import json
496
499
  import re
497
500
  from typing import Any, Dict, Iterable, Tuple, Optional
498
501
 
499
- # --- Optional dependency: python-jsonpath (provides JSONPath + JSON Pointer) ---
500
- try:
501
- import jsonpath # ✅ use module API
502
- from jsonpath import JSONPointer # pointer class is fine to use
503
- except Exception:
504
- jsonpath = None
505
- JSONPointer = None
502
+ from agenta.sdk.utils.lazy import _load_jinja2, _load_jsonpath
503
+
506
504
 
507
505
  # ========= Scheme detection =========
508
506
 
@@ -545,7 +543,8 @@ def resolve_dot_notation(expr: str, data: dict) -> object:
545
543
 
546
544
 
547
545
  def resolve_json_path(expr: str, data: dict) -> object:
548
- if jsonpath is None:
546
+ json_path, _ = _load_jsonpath()
547
+ if json_path is None:
549
548
  raise ImportError("python-jsonpath is required for json-path ($...)")
550
549
 
551
550
  if not (expr == "$" or expr.startswith("$.") or expr.startswith("$[")):
@@ -555,15 +554,16 @@ def resolve_json_path(expr: str, data: dict) -> object:
555
554
  )
556
555
 
557
556
  # Use package-level APIf
558
- results = jsonpath.findall(expr, data) # always returns a list
557
+ results = json_path.findall(expr, data) # always returns a list
559
558
  return results[0] if len(results) == 1 else results
560
559
 
561
560
 
562
561
  def resolve_json_pointer(expr: str, data: Dict[str, Any]) -> Any:
563
562
  """Resolve a JSON Pointer; returns a single value."""
564
- if JSONPointer is None:
563
+ _, json_pointer = _load_jsonpath()
564
+ if json_pointer is None:
565
565
  raise ImportError("python-jsonpath is required for json-pointer (/...)")
566
- return JSONPointer(expr).resolve(data)
566
+ return json_pointer(expr).resolve(data)
567
567
 
568
568
 
569
569
  def resolve_any(expr: str, data: Dict[str, Any]) -> Any:
@@ -631,12 +631,10 @@ def compute_truly_unreplaced(original: set, rendered: str) -> set:
631
631
 
632
632
  def missing_lib_hints(unreplaced: set) -> Optional[str]:
633
633
  """Suggest installing python-jsonpath if placeholders indicate json-path or json-pointer usage."""
634
- if any(expr.startswith("$") or expr.startswith("/") for expr in unreplaced) and (
635
- jsonpath is None or JSONPointer is None
636
- ):
637
- return (
638
- "Install python-jsonpath to enable json-path ($...) and json-pointer (/...)"
639
- )
634
+ if any(expr.startswith("$") or expr.startswith("/") for expr in unreplaced):
635
+ json_path, json_pointer = _load_jsonpath()
636
+ if json_path is None or json_pointer is None:
637
+ return "Install python-jsonpath to enable json-path ($...) and json-pointer (/...)"
640
638
  return None
641
639
 
642
640
 
@@ -688,7 +686,7 @@ class PromptTemplate(BaseModel):
688
686
  return content.format(**kwargs)
689
687
 
690
688
  elif self.template_format == "jinja2":
691
- from jinja2 import Template, TemplateError
689
+ Template, TemplateError = _load_jinja2()
692
690
 
693
691
  try:
694
692
  return Template(content).render(**kwargs)
@@ -1,4 +1,4 @@
1
- import requests
1
+ import httpx
2
2
 
3
3
  BASE_TIMEOUT = 10
4
4
 
@@ -11,7 +11,7 @@ log = get_module_logger(__name__)
11
11
 
12
12
  def authed_api():
13
13
  """
14
- Preconfigured requests for authenticated endpoints (supports all methods).
14
+ Preconfigured httpx client for authenticated endpoints (supports all methods).
15
15
  """
16
16
 
17
17
  api_url = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.api_url
@@ -27,12 +27,13 @@ def authed_api():
27
27
  headers = kwargs.pop("headers", {})
28
28
  headers.setdefault("Authorization", f"ApiKey {api_key}")
29
29
 
30
- return requests.request(
31
- method=method,
32
- url=url,
33
- headers=headers,
34
- timeout=BASE_TIMEOUT,
35
- **kwargs,
36
- )
30
+ with httpx.Client() as client:
31
+ return client.request(
32
+ method=method,
33
+ url=url,
34
+ headers=headers,
35
+ timeout=BASE_TIMEOUT,
36
+ **kwargs,
37
+ )
37
38
 
38
39
  return _request
@@ -0,0 +1,253 @@
1
+ from typing import Any, Callable, Optional, Protocol, Tuple, TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from daytona import (
5
+ CreateSandboxFromSnapshotParams,
6
+ Daytona,
7
+ DaytonaConfig,
8
+ Sandbox,
9
+ )
10
+ from fastapi import APIRouter, Body, FastAPI, HTTPException, Request
11
+ from jinja2 import Template, TemplateError
12
+ from openai import AsyncOpenAI, OpenAIError
13
+ from starlette.responses import Response as StarletteResponse, StreamingResponse
14
+ from jsonpath import JSONPointer
15
+ import jsonpath as jsonpath_module
16
+ import yaml as yaml_module
17
+
18
+
19
+ class _JsonpathModule(Protocol):
20
+ findall: Callable[..., Any]
21
+
22
+
23
+ class _LitellmModule(Protocol):
24
+ cost_calculator: Any
25
+ acompletion: Callable[..., Any]
26
+
27
+
28
+ class _FastAPIModule(Protocol):
29
+ FastAPI: type["FastAPI"]
30
+ APIRouter: type["APIRouter"]
31
+ Request: type["Request"]
32
+ HTTPException: type["HTTPException"]
33
+ Body: Any
34
+
35
+
36
+ class _YamlModule(Protocol):
37
+ def safe_load(self, *args: Any, **kwargs: Any) -> Any: ...
38
+
39
+
40
+ _litellm_module: Optional[_LitellmModule] = None
41
+ _litellm_checked = False
42
+
43
+ _jsonpath_module: Optional[_JsonpathModule] = None
44
+ _jsonpath_pointer: Optional[type["JSONPointer"]] = None
45
+ _jsonpath_checked = False
46
+
47
+ _openai_cached: Optional[Tuple[type["AsyncOpenAI"], type["OpenAIError"]]] = None
48
+ _openai_checked = False
49
+
50
+ _yaml_module: Optional[_YamlModule] = None
51
+ _yaml_checked = False
52
+
53
+ _jinja_cached: Optional[Tuple[type["Template"], type["TemplateError"]]] = None
54
+ _jinja_checked = False
55
+
56
+ _fastapi_module: Optional[_FastAPIModule] = None
57
+ _fastapi_checked = False
58
+
59
+ _starlette_responses_cached: Optional[
60
+ Tuple[type["StarletteResponse"], type["StreamingResponse"]]
61
+ ] = None
62
+ _starlette_responses_checked = False
63
+
64
+ _daytona_cached: Optional[
65
+ Tuple[
66
+ type["Daytona"],
67
+ type["DaytonaConfig"],
68
+ type["Sandbox"],
69
+ type["CreateSandboxFromSnapshotParams"],
70
+ ]
71
+ ] = None
72
+ _daytona_checked = False
73
+
74
+
75
+ def _load_litellm(
76
+ injected: Optional[_LitellmModule] = None,
77
+ ) -> Optional[_LitellmModule]:
78
+ global _litellm_module, _litellm_checked # pylint: disable=global-statement
79
+
80
+ if _litellm_checked:
81
+ return _litellm_module
82
+
83
+ if injected is not None:
84
+ _litellm_checked = True
85
+ _litellm_module = injected
86
+ return _litellm_module
87
+
88
+ _litellm_checked = True
89
+ try:
90
+ import litellm as _litellm
91
+ except Exception:
92
+ _litellm_module = None
93
+ else:
94
+ _litellm_module = _litellm
95
+
96
+ return _litellm_module
97
+
98
+
99
+ def _load_jsonpath() -> Tuple[Optional[_JsonpathModule], Optional[type["JSONPointer"]]]:
100
+ global _jsonpath_module, _jsonpath_pointer, _jsonpath_checked # pylint: disable=global-statement
101
+
102
+ if _jsonpath_checked:
103
+ return _jsonpath_module, _jsonpath_pointer
104
+
105
+ _jsonpath_checked = True
106
+ try:
107
+ import jsonpath as _jsonpath
108
+ from jsonpath import JSONPointer as _JSONPointer
109
+ except Exception:
110
+ _jsonpath_module = None
111
+ _jsonpath_pointer = None
112
+ else:
113
+ _jsonpath_module = _jsonpath
114
+ _jsonpath_pointer = _JSONPointer
115
+
116
+ return _jsonpath_module, _jsonpath_pointer
117
+
118
+
119
+ def _load_openai() -> Tuple[type["AsyncOpenAI"], type["OpenAIError"]]:
120
+ global _openai_cached, _openai_checked # pylint: disable=global-statement
121
+
122
+ if _openai_checked:
123
+ if _openai_cached is None:
124
+ raise ImportError(
125
+ "openai is required for semantic similarity evaluation. "
126
+ "Install it with `pip install openai`."
127
+ )
128
+ return _openai_cached
129
+
130
+ _openai_checked = True
131
+ try:
132
+ from openai import AsyncOpenAI, OpenAIError
133
+ except Exception as exc:
134
+ _openai_cached = None
135
+ raise ImportError(
136
+ "openai is required for semantic similarity evaluation. "
137
+ "Install it with `pip install openai`."
138
+ ) from exc
139
+
140
+ _openai_cached = (AsyncOpenAI, OpenAIError)
141
+ return _openai_cached
142
+
143
+
144
+ def _load_yaml() -> _YamlModule:
145
+ global _yaml_module, _yaml_checked # pylint: disable=global-statement
146
+
147
+ if _yaml_checked:
148
+ if _yaml_module is None:
149
+ raise ImportError("pyyaml is required to load YAML configs.")
150
+ return _yaml_module
151
+
152
+ _yaml_checked = True
153
+ try:
154
+ import yaml as _yaml
155
+ except Exception as exc:
156
+ _yaml_module = None
157
+ raise ImportError("pyyaml is required to load YAML configs.") from exc
158
+
159
+ _yaml_module = _yaml
160
+ return _yaml_module
161
+
162
+
163
+ def _load_jinja2() -> Tuple[type["Template"], type["TemplateError"]]:
164
+ global _jinja_cached, _jinja_checked # pylint: disable=global-statement
165
+
166
+ if _jinja_checked:
167
+ if _jinja_cached is None:
168
+ raise ImportError("jinja2 is required for jinja2 template rendering.")
169
+ return _jinja_cached
170
+
171
+ _jinja_checked = True
172
+ try:
173
+ from jinja2 import Template, TemplateError
174
+ except Exception as exc:
175
+ _jinja_cached = None
176
+ raise ImportError("jinja2 is required for jinja2 template rendering.") from exc
177
+
178
+ _jinja_cached = (Template, TemplateError)
179
+ return _jinja_cached
180
+
181
+
182
+ def _load_fastapi() -> _FastAPIModule:
183
+ global _fastapi_module, _fastapi_checked # pylint: disable=global-statement
184
+
185
+ if _fastapi_checked:
186
+ if _fastapi_module is None:
187
+ raise ImportError("fastapi is required for serving routes.")
188
+ return _fastapi_module
189
+
190
+ _fastapi_checked = True
191
+ try:
192
+ import fastapi as _fastapi
193
+ except Exception as exc:
194
+ _fastapi_module = None
195
+ raise ImportError("fastapi is required for serving routes.") from exc
196
+
197
+ _fastapi_module = _fastapi
198
+ return _fastapi_module
199
+
200
+
201
+ def _load_starlette_responses() -> Tuple[
202
+ type["StarletteResponse"], type["StreamingResponse"]
203
+ ]:
204
+ global _starlette_responses_cached, _starlette_responses_checked # pylint: disable=global-statement
205
+
206
+ if _starlette_responses_checked:
207
+ if _starlette_responses_cached is None:
208
+ raise ImportError("starlette is required for response handling.")
209
+ return _starlette_responses_cached
210
+
211
+ _starlette_responses_checked = True
212
+ try:
213
+ from starlette.responses import Response as StarletteResponse, StreamingResponse
214
+ except Exception as exc:
215
+ _starlette_responses_cached = None
216
+ raise ImportError("starlette is required for response handling.") from exc
217
+
218
+ _starlette_responses_cached = (StarletteResponse, StreamingResponse)
219
+ return _starlette_responses_cached
220
+
221
+
222
+ def _load_daytona() -> Tuple[
223
+ type["Daytona"],
224
+ type["DaytonaConfig"],
225
+ type["Sandbox"],
226
+ type["CreateSandboxFromSnapshotParams"],
227
+ ]:
228
+ global _daytona_cached, _daytona_checked # pylint: disable=global-statement
229
+
230
+ if _daytona_checked:
231
+ if _daytona_cached is None:
232
+ raise ImportError("daytona is required for Daytona sandbox execution.")
233
+ return _daytona_cached
234
+
235
+ _daytona_checked = True
236
+ try:
237
+ from daytona import (
238
+ Daytona,
239
+ DaytonaConfig,
240
+ Sandbox,
241
+ CreateSandboxFromSnapshotParams,
242
+ )
243
+ except Exception as exc:
244
+ _daytona_cached = None
245
+ raise ImportError("daytona is required for Daytona sandbox execution.") from exc
246
+
247
+ _daytona_cached = (
248
+ Daytona,
249
+ DaytonaConfig,
250
+ Sandbox,
251
+ CreateSandboxFromSnapshotParams,
252
+ )
253
+ return _daytona_cached
@@ -166,11 +166,13 @@ def auto_custom_code_run(
166
166
  #
167
167
  correct_answer_key: Optional[str] = "correct_answer",
168
168
  threshold: Optional[float] = 0.5,
169
+ runtime: Optional[str] = "python",
169
170
  ) -> Workflow:
170
171
  parameters = dict(
171
172
  code=code,
172
173
  correct_answer_key=correct_answer_key,
173
174
  threshold=threshold,
175
+ runtime=runtime,
174
176
  )
175
177
 
176
178
  return evaluator(
@@ -5,6 +5,7 @@ echo_v0_configuration = WorkflowServiceConfiguration()
5
5
  auto_exact_match_v0_configuration = WorkflowServiceConfiguration()
6
6
  auto_regex_test_v0_configuration = WorkflowServiceConfiguration()
7
7
  field_match_test_v0_configuration = WorkflowServiceConfiguration()
8
+ json_multi_field_match_v0_configuration = WorkflowServiceConfiguration()
8
9
  auto_webhook_test_v0_configuration = WorkflowServiceConfiguration()
9
10
  auto_custom_code_run_v0_configuration = WorkflowServiceConfiguration()
10
11
  auto_ai_critique_v0_configuration = WorkflowServiceConfiguration()