agenta 0.72.4__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 (38) 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/context/serving.py +2 -0
  5. agenta/sdk/contexts/routing.py +2 -0
  6. agenta/sdk/contexts/running.py +3 -2
  7. agenta/sdk/decorators/running.py +8 -4
  8. agenta/sdk/decorators/serving.py +82 -41
  9. agenta/sdk/engines/tracing/inline.py +8 -1
  10. agenta/sdk/evaluations/preview/evaluate.py +36 -8
  11. agenta/sdk/evaluations/runs.py +2 -1
  12. agenta/sdk/litellm/mockllm.py +2 -2
  13. agenta/sdk/managers/config.py +3 -1
  14. agenta/sdk/managers/secrets.py +25 -8
  15. agenta/sdk/managers/testsets.py +143 -227
  16. agenta/sdk/middleware/vault.py +33 -18
  17. agenta/sdk/middlewares/running/vault.py +33 -17
  18. agenta/sdk/router.py +30 -5
  19. agenta/sdk/tracing/inline.py +8 -1
  20. agenta/sdk/types.py +13 -19
  21. agenta/sdk/utils/client.py +10 -9
  22. agenta/sdk/utils/lazy.py +253 -0
  23. agenta/sdk/workflows/builtin.py +2 -0
  24. agenta/sdk/workflows/configurations.py +1 -0
  25. agenta/sdk/workflows/handlers.py +236 -81
  26. agenta/sdk/workflows/interfaces.py +47 -0
  27. agenta/sdk/workflows/runners/base.py +6 -2
  28. agenta/sdk/workflows/runners/daytona.py +250 -131
  29. agenta/sdk/workflows/runners/local.py +22 -56
  30. agenta/sdk/workflows/runners/registry.py +1 -1
  31. agenta/sdk/workflows/sandbox.py +17 -5
  32. agenta/sdk/workflows/templates.py +81 -0
  33. agenta/sdk/workflows/utils.py +6 -0
  34. {agenta-0.72.4.dist-info → agenta-0.75.0.dist-info}/METADATA +4 -8
  35. {agenta-0.72.4.dist-info → agenta-0.75.0.dist-info}/RECORD +36 -36
  36. agenta/config.py +0 -25
  37. agenta/config.toml +0 -4
  38. {agenta-0.72.4.dist-info → agenta-0.75.0.dist-info}/WHEEL +0 -0
@@ -82,13 +82,17 @@ class VaultMiddleware(BaseHTTPMiddleware):
82
82
  request.state.vault = {}
83
83
 
84
84
  with suppress():
85
- secrets = await self._get_secrets(request)
85
+ secrets, vault_secrets, local_secrets = await self._get_secrets(request)
86
86
 
87
- request.state.vault = {"secrets": secrets}
87
+ request.state.vault = {
88
+ "secrets": secrets,
89
+ "vault_secrets": vault_secrets,
90
+ "local_secrets": local_secrets,
91
+ }
88
92
 
89
93
  return await call_next(request)
90
94
 
91
- async def _get_secrets(self, request: Request) -> Optional[Dict]:
95
+ async def _get_secrets(self, request: Request) -> tuple[list, list, list]:
92
96
  credentials = request.state.auth.get("credentials")
93
97
 
94
98
  headers = None
@@ -107,8 +111,13 @@ class VaultMiddleware(BaseHTTPMiddleware):
107
111
 
108
112
  if secrets_cache:
109
113
  secrets = secrets_cache.get("secrets")
114
+ vault_secrets = secrets_cache.get("vault_secrets")
115
+ local_secrets = secrets_cache.get("local_secrets")
110
116
 
111
- return secrets
117
+ if vault_secrets is None or local_secrets is None:
118
+ return secrets, [], []
119
+
120
+ return secrets, vault_secrets, local_secrets
112
121
 
113
122
  local_secrets: List[Dict[str, Any]] = []
114
123
  allow_secrets = True
@@ -137,7 +146,7 @@ class VaultMiddleware(BaseHTTPMiddleware):
137
146
  except DenyException as e: # pylint: disable=bare-except
138
147
  log.warning(f"Agenta [secrets] {e.status_code}: {e.content}")
139
148
  allow_secrets = False
140
- except: # pylint: disable=bare-except
149
+ except Exception: # pylint: disable=bare-except
141
150
  display_exception("Vault: Local Secrets Exception")
142
151
 
143
152
  vault_secrets: List[Dict[str, Any]] = []
@@ -154,33 +163,39 @@ class VaultMiddleware(BaseHTTPMiddleware):
154
163
 
155
164
  else:
156
165
  vault_secrets = response.json()
157
- except: # pylint: disable=bare-except
166
+ except Exception: # pylint: disable=bare-except
158
167
  display_exception("Vault: Vault Secrets Exception")
159
168
 
160
- secrets = local_secrets + vault_secrets
161
-
162
- standard_secrets = {}
163
- custom_secrets = []
169
+ local_standard = {}
170
+ vault_standard = {}
171
+ vault_custom = []
164
172
 
165
173
  if local_secrets:
166
174
  for secret in local_secrets:
167
- standard_secrets[secret["data"]["kind"]] = secret # type: ignore
175
+ local_standard[secret["data"]["kind"]] = secret # type: ignore
168
176
 
169
177
  if vault_secrets:
170
178
  for secret in vault_secrets:
171
179
  if secret["kind"] == "provider_key": # type: ignore
172
- standard_secrets[secret["data"]["kind"]] = secret # type: ignore
180
+ vault_standard[secret["data"]["kind"]] = secret # type: ignore
173
181
  elif secret["kind"] == "custom_provider": # type: ignore
174
- custom_secrets.append(secret)
175
-
176
- standard_secrets = list(standard_secrets.values())
182
+ vault_custom.append(secret)
177
183
 
178
- secrets = standard_secrets + custom_secrets
184
+ combined_standard = {**local_standard, **vault_standard}
185
+ combined_vault = list(vault_standard.values()) + vault_custom
186
+ secrets = list(combined_standard.values()) + vault_custom
179
187
 
180
188
  if not allow_secrets:
181
- _cache.put(_hash, {"secrets": secrets})
189
+ _cache.put(
190
+ _hash,
191
+ {
192
+ "secrets": secrets,
193
+ "vault_secrets": combined_vault,
194
+ "local_secrets": local_secrets,
195
+ },
196
+ )
182
197
 
183
- return secrets
198
+ return secrets, combined_vault, local_secrets
184
199
 
185
200
  async def _allow_local_secrets(self, credentials):
186
201
  try:
@@ -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
agenta/sdk/types.py CHANGED
@@ -496,17 +496,11 @@ class TemplateFormatError(PromptTemplateError):
496
496
  super().__init__(message)
497
497
 
498
498
 
499
- import json
500
499
  import re
501
500
  from typing import Any, Dict, Iterable, Tuple, Optional
502
501
 
503
- # --- Optional dependency: python-jsonpath (provides JSONPath + JSON Pointer) ---
504
- try:
505
- import jsonpath # ✅ use module API
506
- from jsonpath import JSONPointer # pointer class is fine to use
507
- except Exception:
508
- jsonpath = None
509
- JSONPointer = None
502
+ from agenta.sdk.utils.lazy import _load_jinja2, _load_jsonpath
503
+
510
504
 
511
505
  # ========= Scheme detection =========
512
506
 
@@ -549,7 +543,8 @@ def resolve_dot_notation(expr: str, data: dict) -> object:
549
543
 
550
544
 
551
545
  def resolve_json_path(expr: str, data: dict) -> object:
552
- if jsonpath is None:
546
+ json_path, _ = _load_jsonpath()
547
+ if json_path is None:
553
548
  raise ImportError("python-jsonpath is required for json-path ($...)")
554
549
 
555
550
  if not (expr == "$" or expr.startswith("$.") or expr.startswith("$[")):
@@ -559,15 +554,16 @@ def resolve_json_path(expr: str, data: dict) -> object:
559
554
  )
560
555
 
561
556
  # Use package-level APIf
562
- results = jsonpath.findall(expr, data) # always returns a list
557
+ results = json_path.findall(expr, data) # always returns a list
563
558
  return results[0] if len(results) == 1 else results
564
559
 
565
560
 
566
561
  def resolve_json_pointer(expr: str, data: Dict[str, Any]) -> Any:
567
562
  """Resolve a JSON Pointer; returns a single value."""
568
- if JSONPointer is None:
563
+ _, json_pointer = _load_jsonpath()
564
+ if json_pointer is None:
569
565
  raise ImportError("python-jsonpath is required for json-pointer (/...)")
570
- return JSONPointer(expr).resolve(data)
566
+ return json_pointer(expr).resolve(data)
571
567
 
572
568
 
573
569
  def resolve_any(expr: str, data: Dict[str, Any]) -> Any:
@@ -635,12 +631,10 @@ def compute_truly_unreplaced(original: set, rendered: str) -> set:
635
631
 
636
632
  def missing_lib_hints(unreplaced: set) -> Optional[str]:
637
633
  """Suggest installing python-jsonpath if placeholders indicate json-path or json-pointer usage."""
638
- if any(expr.startswith("$") or expr.startswith("/") for expr in unreplaced) and (
639
- jsonpath is None or JSONPointer is None
640
- ):
641
- return (
642
- "Install python-jsonpath to enable json-path ($...) and json-pointer (/...)"
643
- )
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 (/...)"
644
638
  return None
645
639
 
646
640
 
@@ -692,7 +686,7 @@ class PromptTemplate(BaseModel):
692
686
  return content.format(**kwargs)
693
687
 
694
688
  elif self.template_format == "jinja2":
695
- from jinja2 import Template, TemplateError
689
+ Template, TemplateError = _load_jinja2()
696
690
 
697
691
  try:
698
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()