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
@@ -1,36 +1,27 @@
1
1
  # /agenta/sdk/decorators/tracing.py
2
2
 
3
- from typing import Callable, Optional, Any, Dict, List, Union
4
-
5
- from opentelemetry import context as otel_context
6
- from opentelemetry.context import attach, detach
7
-
8
-
9
3
  from functools import wraps
10
- from itertools import chain
11
4
  from inspect import (
12
5
  getfullargspec,
6
+ isasyncgenfunction,
13
7
  iscoroutinefunction,
14
8
  isgeneratorfunction,
15
- isasyncgenfunction,
16
9
  )
10
+ from itertools import chain
11
+ from typing import Any, Callable, Dict, List, Optional, Union
17
12
 
18
- from pydantic import BaseModel
19
-
20
- from opentelemetry import baggage
21
- from opentelemetry.context import attach, detach, get_current
22
- from opentelemetry.baggage import set_baggage, get_all
23
-
24
- from agenta.sdk.utils.logging import get_module_logger
25
- from agenta.sdk.utils.exceptions import suppress
13
+ import agenta as ag
26
14
  from agenta.sdk.contexts.tracing import (
27
15
  TracingContext,
28
16
  tracing_context_manager,
29
17
  )
30
18
  from agenta.sdk.tracing.conventions import parse_span_kind
31
-
32
- import agenta as ag
33
-
19
+ from agenta.sdk.utils.exceptions import suppress
20
+ from agenta.sdk.utils.logging import get_module_logger
21
+ from opentelemetry import context as otel_context
22
+ from opentelemetry.baggage import get_all, set_baggage
23
+ from opentelemetry.context import attach, detach, get_current
24
+ from pydantic import BaseModel
34
25
 
35
26
  log = get_module_logger(__name__)
36
27
 
@@ -88,11 +79,12 @@ class instrument: # pylint: disable=invalid-name
88
79
  with tracing_context_manager(context=TracingContext.get()):
89
80
  # debug_otel_context("[BEFORE STREAM] [BEFORE SETUP]")
90
81
 
91
- captured_ctx = otel_context.get_current()
92
-
93
82
  self._parse_type_and_kind()
94
83
 
95
- self._attach_baggage()
84
+ baggage_token = self._attach_baggage()
85
+
86
+ # Capture AFTER baggage attach so we do not wipe it later.
87
+ captured_ctx = otel_context.get_current()
96
88
 
97
89
  ctx = self._get_traceparent()
98
90
 
@@ -141,6 +133,7 @@ class instrument: # pylint: disable=invalid-name
141
133
  otel_context.detach(otel_token)
142
134
 
143
135
  # debug_otel_context("[WITHIN STREAM] [AFTER DETACH]")
136
+ self._detach_baggage(baggage_token)
144
137
 
145
138
  return wrapped_generator()
146
139
 
@@ -311,15 +304,43 @@ class instrument: # pylint: disable=invalid-name
311
304
 
312
305
  def _attach_baggage(self):
313
306
  context = TracingContext.get()
307
+ otel_ctx = get_current()
308
+
309
+ # 1. Propagate any incoming `ag.*` baggage as-is (for example
310
+ # `ag.meta.session_id`) so all nested spans inherit it.
311
+ if context.baggage:
312
+ for k, v in context.baggage.items():
313
+ if not isinstance(k, str) or not k.startswith("ag."):
314
+ continue
315
+ if v is None:
316
+ continue
317
+ otel_ctx = set_baggage(name=k, value=str(v), context=otel_ctx)
318
+
319
+ # 2. Propagate Agenta references in baggage (used for linking traces to
320
+ # application/variant/environment).
321
+ if context.references:
322
+ for k, v in context.references.items():
323
+ if v is None:
324
+ continue
325
+ if isinstance(v, BaseModel):
326
+ try:
327
+ v = v.model_dump(mode="json", exclude_none=True)
328
+ except Exception: # pylint: disable=bare-except
329
+ pass
330
+ if isinstance(v, dict):
331
+ for field, value in v.items():
332
+ otel_ctx = set_baggage(
333
+ name=f"ag.refs.{k}.{field}",
334
+ value=str(value),
335
+ context=otel_ctx,
336
+ )
337
+ continue
338
+ otel_ctx = set_baggage(
339
+ name=f"ag.refs.{k}", value=str(v), context=otel_ctx
340
+ )
314
341
 
315
- references = context.references
316
-
317
- token = None
318
- if references:
319
- for k, v in references.items():
320
- token = attach(baggage.set_baggage(f"ag.refs.{k}", v))
321
-
322
- return token
342
+ # Attach once so we can reliably detach later.
343
+ return attach(otel_ctx)
323
344
 
324
345
  def _detach_baggage(
325
346
  self,
@@ -957,9 +957,10 @@ def parse_to_agenta_span_dto(
957
957
  ########################################
958
958
 
959
959
 
960
- from litellm import cost_calculator
961
960
  from opentelemetry.sdk.trace import ReadableSpan
962
961
 
962
+ from agenta.sdk.utils.lazy import _load_litellm
963
+
963
964
  from agenta.sdk.types import AgentaNodeDto, AgentaNodesResponse
964
965
 
965
966
 
@@ -1120,6 +1121,12 @@ TYPES_WITH_COSTS = [
1120
1121
 
1121
1122
 
1122
1123
  def calculate_costs(span_idx: Dict[str, SpanDTO]):
1124
+ litellm = _load_litellm()
1125
+ if not litellm:
1126
+ return
1127
+
1128
+ cost_calculator = litellm.cost_calculator
1129
+
1123
1130
  for span in span_idx.values():
1124
1131
  if (
1125
1132
  span.node.type
@@ -1,18 +1,17 @@
1
- from typing import Optional, Dict, List
2
1
  from threading import Lock
2
+ from typing import Dict, List, Optional
3
3
 
4
+ from agenta.sdk.models.tracing import BaseModel
5
+ from agenta.sdk.utils.logging import get_module_logger
4
6
  from opentelemetry.baggage import get_all as get_baggage
5
7
  from opentelemetry.context import Context
6
8
  from opentelemetry.sdk.trace import Span, SpanProcessor
7
9
  from opentelemetry.sdk.trace.export import (
8
- SpanExporter,
9
- ReadableSpan,
10
10
  BatchSpanProcessor,
11
+ ReadableSpan,
12
+ SpanExporter,
11
13
  )
12
14
 
13
- from agenta.sdk.utils.logging import get_module_logger
14
- from agenta.sdk.engines.tracing.conventions import Reference
15
-
16
15
  log = get_module_logger(__name__)
17
16
 
18
17
 
@@ -51,15 +50,27 @@ class TraceProcessor(SpanProcessor):
51
50
  parent_context: Optional[Context] = None,
52
51
  ) -> None:
53
52
  for key in self.references.keys():
54
- span.set_attribute(f"ag.refs.{key}", self.references[key])
53
+ ref = self.references[key]
54
+ if ref is None:
55
+ continue
56
+ if isinstance(ref, BaseModel):
57
+ try:
58
+ ref = ref.model_dump(mode="json", exclude_none=True)
59
+ except Exception: # pylint: disable=bare-except
60
+ pass
61
+ if isinstance(ref, dict):
62
+ for field, value in ref.items():
63
+ span.set_attribute(f"ag.refs.{key}.{field}", str(value))
64
+ else:
65
+ span.set_attribute(f"ag.refs.{key}", str(ref))
55
66
 
56
67
  baggage = get_baggage(parent_context)
57
68
 
58
- for key in baggage.keys():
59
- if key.startswith("ag.refs."):
60
- _key = key.replace("ag.refs.", "")
61
- if _key in [_.value for _ in Reference.__members__.values()]:
62
- span.set_attribute(key, baggage[key])
69
+ # Copy any `ag.*` baggage entries onto the span attributes so they can be
70
+ # used for filtering and grouping (for example `ag.meta.session_id`).
71
+ for key, value in baggage.items():
72
+ if key.startswith("ag."):
73
+ span.set_attribute(key, value)
63
74
 
64
75
  trace_id = span.context.trace_id
65
76
  span_id = span.context.span_id
@@ -126,10 +126,11 @@ async def _upsert_entities(
126
126
  for testcases_data in simple_evaluation_data.testset_steps:
127
127
  if isinstance(testcases_data, List):
128
128
  if all(isinstance(step, Dict) for step in testcases_data):
129
- testset_revision_id = await acreate_testset(
129
+ created_revision = await acreate_testset(
130
130
  data=testcases_data,
131
131
  )
132
- testset_steps[str(testset_revision_id)] = "custom"
132
+ if created_revision and created_revision.id:
133
+ testset_steps[str(created_revision.id)] = "custom"
133
134
 
134
135
  simple_evaluation_data.testset_steps = testset_steps
135
136
 
@@ -215,15 +216,16 @@ async def _retrieve_entities(
215
216
  Dict[UUID, EvaluatorRevision],
216
217
  ]:
217
218
  testset_revisions: Dict[UUID, TestsetRevision] = {}
218
- # for testset_revision_id, origin in simple_evaluation_data.testset_steps.items():
219
- # testset_revision = await retrieve_testset(
220
- # testset_revision_id=testset_revision_id,
221
- # )
222
- for testset_id, origin in simple_evaluation_data.testset_steps.items():
219
+ for testset_ref, origin in simple_evaluation_data.testset_steps.items():
223
220
  testset_revision = await aretrieve_testset(
224
- testset_id=testset_id,
221
+ testset_revision_id=testset_ref,
225
222
  )
226
223
 
224
+ if not testset_revision or not testset_revision.id:
225
+ testset_revision = await aretrieve_testset(
226
+ testset_id=testset_ref,
227
+ )
228
+
227
229
  if not testset_revision or not testset_revision.id:
228
230
  continue
229
231
 
@@ -308,6 +310,32 @@ async def aevaluate(
308
310
  "────────────────────────────────────────────────────────────────────────────"
309
311
  )
310
312
 
313
+ # Normalize testset_steps to revision ids (no JIT transfers in backend)
314
+ if simple_evaluation_data.testset_steps and isinstance(
315
+ simple_evaluation_data.testset_steps, dict
316
+ ):
317
+ normalized_testset_steps: Dict[str, Origin] = {}
318
+ for testset_id_str, origin in simple_evaluation_data.testset_steps.items():
319
+ try:
320
+ testset_uuid = UUID(str(testset_id_str))
321
+ except Exception:
322
+ continue
323
+
324
+ testset_revision = await aretrieve_testset(
325
+ testset_revision_id=testset_uuid,
326
+ )
327
+
328
+ if not testset_revision or not testset_revision.id:
329
+ # Fallback: treat as testset_id (latest revision)
330
+ testset_revision = await aretrieve_testset(
331
+ testset_id=testset_uuid,
332
+ )
333
+
334
+ if testset_revision and testset_revision.id:
335
+ normalized_testset_steps[str(testset_revision.id)] = origin
336
+
337
+ simple_evaluation_data.testset_steps = normalized_testset_steps
338
+
311
339
  suffix = _timestamp_suffix()
312
340
  name = f"{name}{suffix}"
313
341
 
@@ -68,7 +68,8 @@ async def acreate(
68
68
  repeats=repeats,
69
69
  ),
70
70
  #
71
- jit={"testsets": True, "evaluators": False},
71
+ # Default: expect callers to pass testset revision ids; no JIT migration
72
+ jit={"testsets": False, "evaluators": False},
72
73
  )
73
74
  )
74
75
 
@@ -2,9 +2,8 @@ from typing import Optional, Protocol, Any
2
2
  from os import environ
3
3
  from contextlib import contextmanager
4
4
 
5
- import litellm
6
-
7
5
  from agenta.sdk.utils.logging import get_module_logger
6
+ from agenta.sdk.utils.lazy import _load_litellm
8
7
 
9
8
  from agenta.sdk.litellm.mocks import MOCKS
10
9
  from agenta.sdk.contexts.routing import RoutingContext
@@ -81,6 +80,7 @@ async def acompletion(*args, **kwargs):
81
80
 
82
81
  return MOCKS[mock](*args, **kwargs)
83
82
 
83
+ litellm = _load_litellm(injected=globals().get("litellm"))
84
84
  if not litellm:
85
85
  raise ValueError("litellm not found")
86
86
 
@@ -2,10 +2,10 @@ import json
2
2
  from pathlib import Path
3
3
  from typing import Optional, Type, TypeVar, Dict, Any, Union
4
4
 
5
- import yaml
6
5
  from pydantic import BaseModel
7
6
 
8
7
  from agenta.sdk.utils.logging import get_module_logger
8
+ from agenta.sdk.utils.lazy import _load_yaml
9
9
  from agenta.sdk.managers.shared import SharedManager
10
10
  from agenta.sdk.contexts.routing import RoutingContext
11
11
 
@@ -174,6 +174,8 @@ class ConfigManager:
174
174
  """
175
175
  file_path = Path(filename)
176
176
 
177
+ yaml = _load_yaml()
178
+
177
179
  with open(file_path, "r", encoding="utf-8") as file:
178
180
  parameters = yaml.safe_load(file)
179
181
 
@@ -15,10 +15,15 @@ log = get_module_logger(__name__)
15
15
 
16
16
  class SecretsManager:
17
17
  @staticmethod
18
- def get_from_route() -> Optional[List[Dict[str, Any]]]:
18
+ def get_from_route(scope: str = "all") -> Optional[List[Dict[str, Any]]]:
19
19
  context = RoutingContext.get()
20
20
 
21
- secrets = context.secrets
21
+ if scope == "local":
22
+ secrets = context.local_secrets
23
+ elif scope == "vault":
24
+ secrets = context.vault_secrets
25
+ else:
26
+ secrets = context.secrets
22
27
 
23
28
  if not secrets:
24
29
  return []
@@ -140,7 +145,7 @@ class SecretsManager:
140
145
  return modified_model
141
146
 
142
147
  @staticmethod
143
- def get_provider_settings(model: str) -> Optional[Dict]:
148
+ def get_provider_settings(model: str, scope: str = "all") -> Optional[Dict]:
144
149
  """
145
150
  Builds the LLM request with appropriate kwargs based on the custom provider/model
146
151
 
@@ -154,7 +159,7 @@ class SecretsManager:
154
159
  request_provider_model = model
155
160
 
156
161
  # STEP 1: get vault secrets from route context and transform it
157
- secrets = SecretsManager.get_from_route()
162
+ secrets = SecretsManager.get_from_route(scope=scope)
158
163
  if not secrets:
159
164
  return None
160
165
 
@@ -231,7 +236,7 @@ class SecretsManager:
231
236
  return provider_settings
232
237
 
233
238
  @staticmethod
234
- async def retrieve_secrets():
239
+ async def retrieve_secrets() -> tuple[list, list, list]:
235
240
  return await get_secrets(
236
241
  f"{ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host}/api",
237
242
  RunningContext.get().credentials,
@@ -241,14 +246,20 @@ class SecretsManager:
241
246
  async def ensure_secrets_in_workflow():
242
247
  ctx = RunningContext.get()
243
248
 
244
- ctx.secrets = await SecretsManager.retrieve_secrets()
249
+ secrets, vault_secrets, local_secrets = await SecretsManager.retrieve_secrets()
250
+
251
+ ctx.secrets = secrets
252
+ ctx.vault_secrets = vault_secrets
253
+ ctx.local_secrets = local_secrets
245
254
 
246
255
  RunningContext.set(ctx)
247
256
 
248
257
  return ctx.secrets
249
258
 
250
259
  @staticmethod
251
- def get_provider_settings_from_workflow(model: str) -> Optional[Dict]:
260
+ def get_provider_settings_from_workflow(
261
+ model: str, scope: str = "all"
262
+ ) -> Optional[Dict]:
252
263
  """
253
264
  Builds the LLM request with appropriate kwargs based on the custom provider/model
254
265
 
@@ -262,7 +273,13 @@ class SecretsManager:
262
273
  request_provider_model = model
263
274
 
264
275
  # STEP 1: get vault secrets from route context and transform it
265
- secrets = RunningContext.get().secrets
276
+ ctx = RunningContext.get()
277
+ if scope == "local":
278
+ secrets = ctx.local_secrets
279
+ elif scope == "vault":
280
+ secrets = ctx.vault_secrets
281
+ else:
282
+ secrets = ctx.secrets
266
283
  if not secrets:
267
284
  return None
268
285