agenta 0.57.0__py3-none-any.whl → 0.63.2__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 (267) hide show
  1. agenta/__init__.py +12 -3
  2. agenta/client/__init__.py +4 -4
  3. agenta/client/backend/__init__.py +4 -4
  4. agenta/client/backend/api_keys/client.py +2 -2
  5. agenta/client/backend/billing/client.py +2 -2
  6. agenta/client/backend/billing/raw_client.py +2 -2
  7. agenta/client/backend/client.py +56 -48
  8. agenta/client/backend/core/client_wrapper.py +2 -2
  9. agenta/client/backend/core/file.py +3 -1
  10. agenta/client/backend/core/http_client.py +3 -3
  11. agenta/client/backend/core/pydantic_utilities.py +13 -3
  12. agenta/client/backend/human_evaluations/client.py +2 -2
  13. agenta/client/backend/human_evaluations/raw_client.py +2 -2
  14. agenta/client/backend/organization/client.py +46 -34
  15. agenta/client/backend/organization/raw_client.py +32 -26
  16. agenta/client/backend/raw_client.py +26 -26
  17. agenta/client/backend/testsets/client.py +18 -18
  18. agenta/client/backend/testsets/raw_client.py +30 -30
  19. agenta/client/backend/types/__init__.py +4 -4
  20. agenta/client/backend/types/account_request.py +3 -1
  21. agenta/client/backend/types/account_response.py +3 -1
  22. agenta/client/backend/types/agenta_node_dto.py +3 -1
  23. agenta/client/backend/types/agenta_nodes_response.py +3 -1
  24. agenta/client/backend/types/agenta_root_dto.py +3 -1
  25. agenta/client/backend/types/agenta_roots_response.py +3 -1
  26. agenta/client/backend/types/agenta_tree_dto.py +3 -1
  27. agenta/client/backend/types/agenta_trees_response.py +3 -1
  28. agenta/client/backend/types/aggregated_result.py +3 -1
  29. agenta/client/backend/types/analytics_response.py +3 -1
  30. agenta/client/backend/types/annotation.py +6 -4
  31. agenta/client/backend/types/annotation_create.py +3 -1
  32. agenta/client/backend/types/annotation_edit.py +3 -1
  33. agenta/client/backend/types/annotation_link.py +3 -1
  34. agenta/client/backend/types/annotation_link_response.py +3 -1
  35. agenta/client/backend/types/annotation_query.py +3 -1
  36. agenta/client/backend/types/annotation_query_request.py +3 -1
  37. agenta/client/backend/types/annotation_reference.py +3 -1
  38. agenta/client/backend/types/annotation_references.py +3 -1
  39. agenta/client/backend/types/annotation_response.py +3 -1
  40. agenta/client/backend/types/annotations_response.py +3 -1
  41. agenta/client/backend/types/app.py +3 -1
  42. agenta/client/backend/types/app_variant_response.py +3 -1
  43. agenta/client/backend/types/app_variant_revision.py +3 -1
  44. agenta/client/backend/types/artifact.py +6 -4
  45. agenta/client/backend/types/base_output.py +3 -1
  46. agenta/client/backend/types/body_fetch_workflow_revision.py +3 -1
  47. agenta/client/backend/types/body_import_testset.py +3 -1
  48. agenta/client/backend/types/bucket_dto.py +3 -1
  49. agenta/client/backend/types/collect_status_response.py +3 -1
  50. agenta/client/backend/types/config_db.py +3 -1
  51. agenta/client/backend/types/config_dto.py +3 -1
  52. agenta/client/backend/types/config_response_model.py +3 -1
  53. agenta/client/backend/types/correct_answer.py +3 -1
  54. agenta/client/backend/types/create_app_output.py +3 -1
  55. agenta/client/backend/types/custom_model_settings_dto.py +3 -1
  56. agenta/client/backend/types/custom_provider_dto.py +3 -1
  57. agenta/client/backend/types/custom_provider_kind.py +1 -1
  58. agenta/client/backend/types/custom_provider_settings_dto.py +3 -1
  59. agenta/client/backend/types/delete_evaluation.py +3 -1
  60. agenta/client/backend/types/environment_output.py +3 -1
  61. agenta/client/backend/types/environment_output_extended.py +3 -1
  62. agenta/client/backend/types/environment_revision.py +3 -1
  63. agenta/client/backend/types/error.py +3 -1
  64. agenta/client/backend/types/evaluation.py +3 -1
  65. agenta/client/backend/types/evaluation_scenario.py +3 -1
  66. agenta/client/backend/types/evaluation_scenario_input.py +3 -1
  67. agenta/client/backend/types/evaluation_scenario_output.py +3 -1
  68. agenta/client/backend/types/evaluation_scenario_result.py +3 -1
  69. agenta/client/backend/types/evaluator.py +6 -4
  70. agenta/client/backend/types/evaluator_config.py +6 -4
  71. agenta/client/backend/types/evaluator_flags.py +3 -1
  72. agenta/client/backend/types/evaluator_mapping_output_interface.py +3 -1
  73. agenta/client/backend/types/evaluator_output_interface.py +3 -1
  74. agenta/client/backend/types/evaluator_query.py +3 -1
  75. agenta/client/backend/types/evaluator_query_request.py +3 -1
  76. agenta/client/backend/types/evaluator_request.py +3 -1
  77. agenta/client/backend/types/evaluator_response.py +3 -1
  78. agenta/client/backend/types/evaluators_response.py +3 -1
  79. agenta/client/backend/types/exception_dto.py +3 -1
  80. agenta/client/backend/types/extended_o_tel_tracing_response.py +3 -1
  81. agenta/client/backend/types/get_config_response.py +3 -1
  82. agenta/client/backend/types/header.py +3 -1
  83. agenta/client/backend/types/http_validation_error.py +3 -1
  84. agenta/client/backend/types/human_evaluation.py +3 -1
  85. agenta/client/backend/types/human_evaluation_scenario.py +3 -1
  86. agenta/client/backend/types/human_evaluation_scenario_input.py +3 -1
  87. agenta/client/backend/types/human_evaluation_scenario_output.py +3 -1
  88. agenta/client/backend/types/invite_request.py +3 -1
  89. agenta/client/backend/types/legacy_analytics_response.py +3 -1
  90. agenta/client/backend/types/legacy_data_point.py +3 -1
  91. agenta/client/backend/types/legacy_evaluator.py +3 -1
  92. agenta/client/backend/types/legacy_scope_request.py +3 -1
  93. agenta/client/backend/types/legacy_scopes_response.py +3 -1
  94. agenta/client/backend/types/legacy_subscription_request.py +3 -1
  95. agenta/client/backend/types/legacy_user_request.py +3 -1
  96. agenta/client/backend/types/legacy_user_response.py +3 -1
  97. agenta/client/backend/types/lifecycle_dto.py +3 -1
  98. agenta/client/backend/types/link_dto.py +3 -1
  99. agenta/client/backend/types/list_api_keys_response.py +3 -1
  100. agenta/client/backend/types/llm_run_rate_limit.py +3 -1
  101. agenta/client/backend/types/meta_request.py +3 -1
  102. agenta/client/backend/types/metrics_dto.py +3 -1
  103. agenta/client/backend/types/new_testset.py +3 -1
  104. agenta/client/backend/types/node_dto.py +3 -1
  105. agenta/client/backend/types/o_tel_context_dto.py +3 -1
  106. agenta/client/backend/types/o_tel_event.py +6 -4
  107. agenta/client/backend/types/o_tel_event_dto.py +3 -1
  108. agenta/client/backend/types/o_tel_extra_dto.py +3 -1
  109. agenta/client/backend/types/o_tel_flat_span.py +6 -4
  110. agenta/client/backend/types/o_tel_link.py +6 -4
  111. agenta/client/backend/types/o_tel_link_dto.py +3 -1
  112. agenta/client/backend/types/o_tel_links_response.py +3 -1
  113. agenta/client/backend/types/o_tel_span.py +1 -1
  114. agenta/client/backend/types/o_tel_span_dto.py +3 -1
  115. agenta/client/backend/types/o_tel_spans_tree.py +3 -1
  116. agenta/client/backend/types/o_tel_tracing_data_response.py +3 -1
  117. agenta/client/backend/types/o_tel_tracing_request.py +3 -1
  118. agenta/client/backend/types/o_tel_tracing_response.py +3 -1
  119. agenta/client/backend/types/organization.py +3 -1
  120. agenta/client/backend/types/organization_details.py +3 -1
  121. agenta/client/backend/types/organization_membership_request.py +3 -1
  122. agenta/client/backend/types/organization_output.py +3 -1
  123. agenta/client/backend/types/organization_request.py +3 -1
  124. agenta/client/backend/types/parent_dto.py +3 -1
  125. agenta/client/backend/types/project_membership_request.py +3 -1
  126. agenta/client/backend/types/project_request.py +3 -1
  127. agenta/client/backend/types/project_scope.py +3 -1
  128. agenta/client/backend/types/projects_response.py +3 -1
  129. agenta/client/backend/types/reference.py +6 -4
  130. agenta/client/backend/types/reference_dto.py +3 -1
  131. agenta/client/backend/types/reference_request_model.py +3 -1
  132. agenta/client/backend/types/result.py +3 -1
  133. agenta/client/backend/types/root_dto.py +3 -1
  134. agenta/client/backend/types/scopes_response_model.py +3 -1
  135. agenta/client/backend/types/secret_dto.py +3 -1
  136. agenta/client/backend/types/secret_response_dto.py +3 -1
  137. agenta/client/backend/types/simple_evaluation_output.py +3 -1
  138. agenta/client/backend/types/span_dto.py +6 -4
  139. agenta/client/backend/types/standard_provider_dto.py +3 -1
  140. agenta/client/backend/types/standard_provider_settings_dto.py +3 -1
  141. agenta/client/backend/types/status_dto.py +3 -1
  142. agenta/client/backend/types/tags_request.py +3 -1
  143. agenta/client/backend/types/testcase_response.py +6 -4
  144. agenta/client/backend/types/testset.py +6 -4
  145. agenta/client/backend/types/{test_set_output_response.py → testset_output_response.py} +4 -2
  146. agenta/client/backend/types/testset_request.py +3 -1
  147. agenta/client/backend/types/testset_response.py +3 -1
  148. agenta/client/backend/types/{test_set_simple_response.py → testset_simple_response.py} +4 -2
  149. agenta/client/backend/types/testsets_response.py +3 -1
  150. agenta/client/backend/types/time_dto.py +3 -1
  151. agenta/client/backend/types/tree_dto.py +3 -1
  152. agenta/client/backend/types/update_app_output.py +3 -1
  153. agenta/client/backend/types/user_request.py +3 -1
  154. agenta/client/backend/types/validation_error.py +3 -1
  155. agenta/client/backend/types/workflow_artifact.py +6 -4
  156. agenta/client/backend/types/workflow_data.py +3 -1
  157. agenta/client/backend/types/workflow_flags.py +3 -1
  158. agenta/client/backend/types/workflow_request.py +3 -1
  159. agenta/client/backend/types/workflow_response.py +3 -1
  160. agenta/client/backend/types/workflow_revision.py +6 -4
  161. agenta/client/backend/types/workflow_revision_request.py +3 -1
  162. agenta/client/backend/types/workflow_revision_response.py +3 -1
  163. agenta/client/backend/types/workflow_revisions_response.py +3 -1
  164. agenta/client/backend/types/workflow_variant.py +6 -4
  165. agenta/client/backend/types/workflow_variant_request.py +3 -1
  166. agenta/client/backend/types/workflow_variant_response.py +3 -1
  167. agenta/client/backend/types/workflow_variants_response.py +3 -1
  168. agenta/client/backend/types/workflows_response.py +3 -1
  169. agenta/client/backend/types/workspace.py +3 -1
  170. agenta/client/backend/types/workspace_member_response.py +3 -1
  171. agenta/client/backend/types/workspace_membership_request.py +3 -1
  172. agenta/client/backend/types/workspace_permission.py +3 -1
  173. agenta/client/backend/types/workspace_request.py +3 -1
  174. agenta/client/backend/types/workspace_response.py +3 -1
  175. agenta/client/backend/workspace/client.py +2 -2
  176. agenta/client/client.py +102 -88
  177. agenta/sdk/__init__.py +52 -3
  178. agenta/sdk/agenta_init.py +43 -16
  179. agenta/sdk/assets.py +22 -15
  180. agenta/sdk/context/serving.py +20 -8
  181. agenta/sdk/context/tracing.py +40 -22
  182. agenta/sdk/contexts/__init__.py +0 -0
  183. agenta/sdk/contexts/routing.py +38 -0
  184. agenta/sdk/contexts/running.py +57 -0
  185. agenta/sdk/contexts/tracing.py +86 -0
  186. agenta/sdk/decorators/__init__.py +1 -0
  187. agenta/sdk/decorators/routing.py +284 -0
  188. agenta/sdk/decorators/running.py +692 -98
  189. agenta/sdk/decorators/serving.py +20 -21
  190. agenta/sdk/decorators/tracing.py +176 -131
  191. agenta/sdk/engines/__init__.py +0 -0
  192. agenta/sdk/engines/running/__init__.py +0 -0
  193. agenta/sdk/engines/running/utils.py +17 -0
  194. agenta/sdk/engines/tracing/__init__.py +1 -0
  195. agenta/sdk/engines/tracing/attributes.py +185 -0
  196. agenta/sdk/engines/tracing/conventions.py +49 -0
  197. agenta/sdk/engines/tracing/exporters.py +130 -0
  198. agenta/sdk/engines/tracing/inline.py +1154 -0
  199. agenta/sdk/engines/tracing/processors.py +190 -0
  200. agenta/sdk/engines/tracing/propagation.py +102 -0
  201. agenta/sdk/engines/tracing/spans.py +136 -0
  202. agenta/sdk/engines/tracing/tracing.py +324 -0
  203. agenta/sdk/evaluations/__init__.py +2 -0
  204. agenta/sdk/evaluations/metrics.py +37 -0
  205. agenta/sdk/evaluations/preview/__init__.py +0 -0
  206. agenta/sdk/evaluations/preview/evaluate.py +765 -0
  207. agenta/sdk/evaluations/preview/utils.py +861 -0
  208. agenta/sdk/evaluations/results.py +66 -0
  209. agenta/sdk/evaluations/runs.py +153 -0
  210. agenta/sdk/evaluations/scenarios.py +48 -0
  211. agenta/sdk/litellm/litellm.py +12 -0
  212. agenta/sdk/litellm/mockllm.py +6 -8
  213. agenta/sdk/litellm/mocks/__init__.py +5 -5
  214. agenta/sdk/managers/applications.py +304 -0
  215. agenta/sdk/managers/config.py +2 -2
  216. agenta/sdk/managers/evaluations.py +0 -0
  217. agenta/sdk/managers/evaluators.py +303 -0
  218. agenta/sdk/managers/secrets.py +161 -24
  219. agenta/sdk/managers/shared.py +3 -1
  220. agenta/sdk/managers/testsets.py +441 -0
  221. agenta/sdk/managers/vault.py +3 -3
  222. agenta/sdk/middleware/auth.py +0 -176
  223. agenta/sdk/middleware/vault.py +203 -8
  224. agenta/sdk/middlewares/__init__.py +0 -0
  225. agenta/sdk/middlewares/routing/__init__.py +0 -0
  226. agenta/sdk/middlewares/routing/auth.py +263 -0
  227. agenta/sdk/middlewares/routing/cors.py +30 -0
  228. agenta/sdk/middlewares/routing/otel.py +29 -0
  229. agenta/sdk/middlewares/running/__init__.py +0 -0
  230. agenta/sdk/middlewares/running/normalizer.py +321 -0
  231. agenta/sdk/middlewares/running/resolver.py +161 -0
  232. agenta/sdk/middlewares/running/vault.py +140 -0
  233. agenta/sdk/models/__init__.py +0 -0
  234. agenta/sdk/models/blobs.py +33 -0
  235. agenta/sdk/models/evaluations.py +119 -0
  236. agenta/sdk/models/git.py +126 -0
  237. agenta/sdk/models/shared.py +167 -0
  238. agenta/sdk/models/testsets.py +163 -0
  239. agenta/sdk/models/tracing.py +202 -0
  240. agenta/sdk/models/workflows.py +753 -0
  241. agenta/sdk/tracing/exporters.py +67 -17
  242. agenta/sdk/tracing/processors.py +97 -0
  243. agenta/sdk/tracing/propagation.py +3 -1
  244. agenta/sdk/tracing/spans.py +4 -0
  245. agenta/sdk/tracing/tracing.py +13 -13
  246. agenta/sdk/types.py +211 -17
  247. agenta/sdk/utils/cache.py +1 -1
  248. agenta/sdk/utils/client.py +38 -0
  249. agenta/sdk/utils/helpers.py +13 -12
  250. agenta/sdk/utils/logging.py +18 -78
  251. agenta/sdk/utils/references.py +23 -0
  252. agenta/sdk/workflows/builtin.py +600 -0
  253. agenta/sdk/workflows/configurations.py +22 -0
  254. agenta/sdk/workflows/errors.py +292 -0
  255. agenta/sdk/workflows/handlers.py +1791 -0
  256. agenta/sdk/workflows/interfaces.py +948 -0
  257. agenta/sdk/workflows/sandbox.py +118 -0
  258. agenta/sdk/workflows/utils.py +303 -6
  259. {agenta-0.57.0.dist-info → agenta-0.63.2.dist-info}/METADATA +33 -30
  260. agenta-0.63.2.dist-info/RECORD +421 -0
  261. agenta/sdk/middleware/adapt.py +0 -253
  262. agenta/sdk/middleware/base.py +0 -40
  263. agenta/sdk/middleware/flags.py +0 -40
  264. agenta/sdk/workflows/types.py +0 -472
  265. agenta-0.57.0.dist-info/RECORD +0 -371
  266. /agenta/sdk/{workflows → engines/running}/registry.py +0 -0
  267. {agenta-0.57.0.dist-info → agenta-0.63.2.dist-info}/WHEEL +0 -0
@@ -1,4 +1,7 @@
1
- from typing import Sequence, Dict, List, Optional
1
+ from typing import Sequence, Dict, List, Optional, Any
2
+ from threading import Thread
3
+ from os import environ
4
+ from uuid import UUID
2
5
 
3
6
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
4
7
  from opentelemetry.sdk.trace.export import (
@@ -8,18 +11,21 @@ from opentelemetry.sdk.trace.export import (
8
11
  ReadableSpan,
9
12
  )
10
13
 
14
+ from agenta.sdk.utils.constants import TRUTHY
11
15
  from agenta.sdk.utils.logging import get_module_logger
12
16
  from agenta.sdk.utils.exceptions import suppress
13
17
  from agenta.sdk.utils.cache import TTLLRUCache
14
- from agenta.sdk.context.tracing import (
15
- tracing_exporter_context_manager,
16
- tracing_exporter_context,
17
- TracingExporterContext,
18
+ from agenta.sdk.contexts.tracing import (
19
+ otlp_context_manager,
20
+ otlp_context,
21
+ OTLPContext,
18
22
  )
19
23
 
20
24
 
21
25
  log = get_module_logger(__name__)
22
26
 
27
+ _ASYNC_EXPORT = environ.get("AGENTA_OTLP_ASYNC_EXPORT", "true").lower() in TRUTHY
28
+
23
29
 
24
30
  class InlineTraceExporter(SpanExporter):
25
31
  def __init__(
@@ -45,6 +51,8 @@ class InlineTraceExporter(SpanExporter):
45
51
 
46
52
  self._registry[trace_id].append(span)
47
53
 
54
+ return
55
+
48
56
  def shutdown(self) -> None:
49
57
  self._shutdown = True
50
58
 
@@ -84,7 +92,7 @@ class OTLPExporter(OTLPSpanExporter):
84
92
  self.credentials = credentials
85
93
 
86
94
  def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
87
- grouped_spans: Dict[str, List[str]] = {}
95
+ grouped_spans: Dict[Optional[str], List[ReadableSpan]] = dict()
88
96
 
89
97
  for span in spans:
90
98
  trace_id = span.get_span_context().trace_id
@@ -94,18 +102,28 @@ class OTLPExporter(OTLPSpanExporter):
94
102
  credentials = self.credentials.get(trace_id)
95
103
 
96
104
  if credentials not in grouped_spans:
97
- grouped_spans[credentials] = []
105
+ grouped_spans[credentials] = list()
98
106
 
99
107
  grouped_spans[credentials].append(span)
100
108
 
101
109
  serialized_spans = []
102
110
 
103
111
  for credentials, _spans in grouped_spans.items():
104
- with tracing_exporter_context_manager(
105
- context=TracingExporterContext(
112
+ with otlp_context_manager(
113
+ context=OTLPContext(
106
114
  credentials=credentials,
107
115
  )
108
116
  ):
117
+ for _span in _spans:
118
+ trace_id = _span.get_span_context().trace_id
119
+ span_id = _span.get_span_context().span_id
120
+
121
+ # log.debug(
122
+ # "[SPAN] [EXPORT]",
123
+ # trace_id=UUID(int=trace_id).hex,
124
+ # span_id=UUID(int=span_id).hex[-16:],
125
+ # )
126
+
109
127
  serialized_spans.append(super().export(_spans))
110
128
 
111
129
  if all(serialized_spans):
@@ -114,16 +132,48 @@ class OTLPExporter(OTLPSpanExporter):
114
132
  return SpanExportResult.FAILURE
115
133
 
116
134
  def _export(self, serialized_data: bytes, timeout_sec: Optional[float] = None):
117
- credentials = tracing_exporter_context.get().credentials
135
+ try:
136
+ credentials = otlp_context.get().credentials
137
+
138
+ if credentials:
139
+ self._session.headers.update({"Authorization": credentials})
140
+
141
+ def __export():
142
+ with suppress():
143
+ resp = None
144
+ if timeout_sec is not None:
145
+ resp = super(OTLPExporter, self)._export(
146
+ serialized_data,
147
+ timeout_sec,
148
+ )
149
+ else:
150
+ resp = super(OTLPExporter, self)._export(
151
+ serialized_data,
152
+ )
153
+
154
+ # log.debug(
155
+ # "[SPAN] [_EXPORT]",
156
+ # data=serialized_data,
157
+ # resp=resp,
158
+ # )
159
+
160
+ if _ASYNC_EXPORT is True:
161
+ # log.debug("[SPAN] [ASYNC.X]", credentials=(credentials is not None))
162
+ thread = Thread(target=__export, daemon=False)
163
+ thread.start()
164
+ else:
165
+ # log.debug("[SPAN] [ SYNC.X]", credentials=(credentials is not None))
166
+ return __export()
118
167
 
119
- if credentials:
120
- self._session.headers.update({"Authorization": credentials})
168
+ except Exception as e:
169
+ log.error(f"Export failed with error: {e}", exc_info=True)
121
170
 
122
- with suppress():
123
- if timeout_sec is not None:
124
- return super()._export(serialized_data, timeout_sec)
125
- else:
126
- return super()._export(serialized_data)
171
+ finally:
172
+
173
+ class Response:
174
+ ok = True
175
+
176
+ return Response()
127
177
 
128
178
 
129
179
  ConsoleExporter = ConsoleSpanExporter
@@ -1,5 +1,7 @@
1
1
  from typing import Optional, Dict, List
2
2
  from threading import Lock
3
+ from json import dumps
4
+ from uuid import UUID
3
5
 
4
6
  from opentelemetry.baggage import get_all as get_baggage
5
7
  from opentelemetry.context import Context
@@ -9,10 +11,13 @@ from opentelemetry.sdk.trace.export import (
9
11
  ReadableSpan,
10
12
  BatchSpanProcessor,
11
13
  )
14
+ from opentelemetry.trace import SpanContext
12
15
 
13
16
  from agenta.sdk.utils.logging import get_module_logger
14
17
  from agenta.sdk.tracing.conventions import Reference
15
18
 
19
+ from agenta.sdk.contexts.tracing import TracingContext
20
+
16
21
  log = get_module_logger(__name__)
17
22
 
18
23
 
@@ -50,6 +55,15 @@ class TraceProcessor(SpanProcessor):
50
55
  span: Span,
51
56
  parent_context: Optional[Context] = None,
52
57
  ) -> None:
58
+ trace_id = span.context.trace_id
59
+ span_id = span.context.span_id
60
+
61
+ # log.debug(
62
+ # "[SPAN] [START] ",
63
+ # trace_id=UUID(int=trace_id).hex,
64
+ # span_id=UUID(int=span_id).hex[-16:],
65
+ # )
66
+
53
67
  for key in self.references.keys():
54
68
  span.set_attribute(f"ag.refs.{key}", self.references[key])
55
69
 
@@ -61,6 +75,83 @@ class TraceProcessor(SpanProcessor):
61
75
  if _key in [_.value for _ in Reference.__members__.values()]:
62
76
  span.set_attribute(key, baggage[key])
63
77
 
78
+ context = TracingContext.get()
79
+
80
+ trace_type = span.attributes.get("trace_type") if span.attributes else None
81
+
82
+ context.annotate = (
83
+ context.annotate
84
+ or (context.type == "annotation")
85
+ or (trace_type == "annotation")
86
+ )
87
+ context.type = (
88
+ (str(trace_type) if trace_type else None)
89
+ or context.type
90
+ or ("annotation" if context.annotate else "invocation")
91
+ )
92
+
93
+ span.set_attribute("ag.type.tree", context.type)
94
+
95
+ if context.flags:
96
+ for key in context.flags.keys():
97
+ span.set_attribute(f"ag.flags.{key}", context.flags[key])
98
+ # if context.tags:
99
+ # for key in context.tags.keys():
100
+ # span.set_attribute(f"ag.tags.{key}", context.tags[key])
101
+ # if context.meta:
102
+ # span.set_attribute(f"ag.meta.", dumps(context.meta))
103
+
104
+ # --- DISTRIBUTED
105
+ if not self.inline:
106
+ if context.links:
107
+ for key, link in context.links.items():
108
+ try:
109
+ link = link.model_dump(mode="json", exclude_none=True)
110
+ except: # pylint: disable=bare-except
111
+ pass
112
+ if not isinstance(link, dict):
113
+ continue
114
+ if not link.get("trace_id") or not link.get("span_id"):
115
+ continue
116
+
117
+ span.add_link(
118
+ context=SpanContext(
119
+ trace_id=int(str(link.get("trace_id")), 16),
120
+ span_id=int(str(link.get("span_id")), 16),
121
+ is_remote=True,
122
+ ),
123
+ attributes=dict(
124
+ key=str(key),
125
+ ),
126
+ )
127
+
128
+ if context.references:
129
+ for key, ref in context.references.items():
130
+ try:
131
+ ref = ref.model_dump(mode="json", exclude_none=True)
132
+ except: # pylint: disable=bare-except
133
+ pass
134
+ if not isinstance(ref, dict):
135
+ continue
136
+ if not ref.get("id") and not ref.get("slug") and not ref.get("version"):
137
+ continue
138
+
139
+ if ref.get("id"):
140
+ span.set_attribute(
141
+ f"ag.refs.{key}.id",
142
+ str(ref.get("id")),
143
+ )
144
+ if ref.get("slug"):
145
+ span.set_attribute(
146
+ f"ag.refs.{key}.slug",
147
+ str(ref.get("slug")),
148
+ )
149
+ if ref.get("version"):
150
+ span.set_attribute(
151
+ f"ag.refs.{key}.version",
152
+ str(ref.get("version")),
153
+ )
154
+
64
155
  trace_id = span.context.trace_id
65
156
  span_id = span.context.span_id
66
157
 
@@ -74,6 +165,12 @@ class TraceProcessor(SpanProcessor):
74
165
  trace_id = span.context.trace_id
75
166
  span_id = span.context.span_id
76
167
 
168
+ # log.debug(
169
+ # "[SPAN] [END] ",
170
+ # trace_id=UUID(int=trace_id).hex,
171
+ # span_id=UUID(int=span_id).hex[-16:],
172
+ # )
173
+
77
174
  self._spans.setdefault(trace_id, []).append(span)
78
175
  self._registry.setdefault(trace_id, {})
79
176
  self._registry[trace_id].pop(span_id, None)
@@ -6,6 +6,8 @@ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapProp
6
6
  from opentelemetry.baggage import set_baggage
7
7
  from opentelemetry.context import get_current
8
8
 
9
+ from agenta.sdk.contexts.tracing import TracingContext
10
+
9
11
  import agenta as ag
10
12
 
11
13
 
@@ -72,7 +74,7 @@ def inject(
72
74
 
73
75
  _context = get_current()
74
76
 
75
- ctx = ag.sdk.context.tracing.tracing_context.get()
77
+ ctx = TracingContext.get()
76
78
 
77
79
  # --- Inject traceparent --- #
78
80
  try:
@@ -35,6 +35,10 @@ class CustomSpan(Span): # INHERITANCE FOR TYPING ONLY
35
35
 
36
36
  ## --- PROXY METHODS --- ##
37
37
 
38
+ @property
39
+ def name(self) -> str:
40
+ return self._span.name
41
+
38
42
  def get_span_context(self):
39
43
  return self._span.get_span_context()
40
44
 
@@ -99,19 +99,6 @@ class Tracing(metaclass=Singleton):
99
99
  resource=Resource(attributes={"service.name": "agenta-sdk"})
100
100
  )
101
101
 
102
- # --- INLINE
103
- if inline:
104
- # TRACE PROCESSORS -- INLINE
105
- self.inline = TraceProcessor(
106
- InlineExporter(
107
- registry=self.inline_spans,
108
- ),
109
- references=self.references,
110
- inline=inline,
111
- )
112
- self.tracer_provider.add_span_processor(self.inline)
113
- # --- INLINE
114
-
115
102
  # TRACE PROCESSORS -- OTLP
116
103
  try:
117
104
  log.info("Agenta - OLTP URL: %s", self.otlp_url)
@@ -129,6 +116,19 @@ class Tracing(metaclass=Singleton):
129
116
  except: # pylint: disable=bare-except
130
117
  log.warning("Agenta - OLTP unreachable, skipping exports.")
131
118
 
119
+ # --- INLINE
120
+ if inline:
121
+ # TRACE PROCESSORS -- INLINE
122
+ self.inline = TraceProcessor(
123
+ InlineExporter(
124
+ registry=self.inline_spans,
125
+ ),
126
+ references=self.references,
127
+ inline=inline,
128
+ )
129
+ self.tracer_provider.add_span_processor(self.inline)
130
+ # --- INLINE
131
+
132
132
  # GLOBAL TRACER PROVIDER -- INSTRUMENTATION LIBRARIES
133
133
  set_tracer_provider(self.tracer_provider)
134
134
  # TRACER
agenta/sdk/types.py CHANGED
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import List, Union, Optional, Dict, Literal, Any
4
4
 
5
5
  from pydantic import ConfigDict, BaseModel, HttpUrl
6
- from pydantic import BaseModel, Field, model_validator
6
+ from pydantic import BaseModel, Field, model_validator, AliasChoices
7
7
 
8
8
  from starlette.responses import StreamingResponse
9
9
 
@@ -71,15 +71,15 @@ class StreamResponse(StreamingResponse):
71
71
  ):
72
72
  headers = dict(extra_headers or {})
73
73
  if version is not None:
74
- headers["X-ag-version"] = version
74
+ headers["x-ag-version"] = version
75
75
  if content_type:
76
- headers["X-ag-content-type"] = content_type
76
+ headers["x-ag-content-type"] = content_type
77
77
  if tree_id:
78
- headers["X-ag-tree-id"] = tree_id
78
+ headers["x-ag-tree-id"] = tree_id
79
79
  if trace_id:
80
- headers["X-ag-trace-id"] = trace_id
80
+ headers["x-ag-trace-id"] = trace_id
81
81
  if span_id:
82
- headers["X-ag-span-id"] = span_id
82
+ headers["x-ag-span-id"] = span_id
83
83
 
84
84
  super().__init__(
85
85
  content=content,
@@ -335,7 +335,29 @@ class ContentPartImage(BaseModel):
335
335
  image_url: ImageURL
336
336
 
337
337
 
338
- ContentPart = Union[ContentPartText, ContentPartImage]
338
+ class FileInput(BaseModel):
339
+ file_id: Optional[str] = Field(
340
+ default=None,
341
+ alias="file_id",
342
+ validation_alias=AliasChoices("file_id", "fileId"),
343
+ )
344
+ file_data: Optional[str] = Field(
345
+ default=None,
346
+ alias="file_data",
347
+ validation_alias=AliasChoices("file_data", "fileData"),
348
+ )
349
+ filename: Optional[str] = None
350
+ format: Optional[str] = None
351
+
352
+ model_config = {"populate_by_name": True}
353
+
354
+
355
+ class ContentPartFile(BaseModel):
356
+ type: Literal["file"] = "file"
357
+ file: FileInput
358
+
359
+
360
+ ContentPart = Union[ContentPartText, ContentPartImage, ContentPartFile]
339
361
 
340
362
 
341
363
  class Message(BaseModel):
@@ -387,7 +409,7 @@ class ModelConfig(BaseModel):
387
409
  """Configuration for model parameters"""
388
410
 
389
411
  model: str = MCField(
390
- default="gpt-3.5-turbo",
412
+ default="gpt-4o-mini",
391
413
  choices=supported_llm_models,
392
414
  )
393
415
 
@@ -421,6 +443,14 @@ class ModelConfig(BaseModel):
421
443
  le=2.0,
422
444
  description="Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far",
423
445
  )
446
+ reasoning_effort: Optional[Literal["none", "low", "medium", "high"]] = Field(
447
+ default=None,
448
+ description="Controls the reasoning effort for thinking models. Options: 'none' (cost-optimized, 0 tokens), 'low' (1024 tokens), 'medium' (2048 tokens), 'high' (4096 tokens)",
449
+ json_schema_extra={
450
+ "x-parameter": "choice",
451
+ "enum": ["none", "low", "medium", "high"],
452
+ },
453
+ )
424
454
  response_format: Optional[ResponseFormat] = Field(
425
455
  default=None,
426
456
  description="An object specifying the format that the model must output",
@@ -462,6 +492,154 @@ class TemplateFormatError(PromptTemplateError):
462
492
  super().__init__(message)
463
493
 
464
494
 
495
+ import json
496
+ import re
497
+ from typing import Any, Dict, Iterable, Tuple, Optional
498
+
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
506
+
507
+ # ========= Scheme detection =========
508
+
509
+
510
+ def detect_scheme(expr: str) -> str:
511
+ """Return 'json-path', 'json-pointer', or 'dot-notation' based on the placeholder prefix."""
512
+ if expr.startswith("$"):
513
+ return "json-path"
514
+ if expr.startswith("/"):
515
+ return "json-pointer"
516
+ return "dot-notation"
517
+
518
+
519
+ # ========= Resolvers =========
520
+
521
+
522
+ def resolve_dot_notation(expr: str, data: dict) -> object:
523
+ if "[" in expr or "]" in expr:
524
+ raise KeyError(f"Bracket syntax is not supported in dot-notation: {expr!r}")
525
+
526
+ # First, check if the expression exists as a literal key (e.g., "topic.story" as a single key)
527
+ # This allows users to use dots in their variable names without nested access
528
+ if expr in data:
529
+ return data[expr]
530
+
531
+ # If not found as a literal key, try to parse as dot-notation path
532
+ cur = data
533
+ for token in (p for p in expr.split(".") if p):
534
+ if isinstance(cur, list) and token.isdigit():
535
+ cur = cur[int(token)]
536
+ else:
537
+ if not isinstance(cur, dict):
538
+ raise KeyError(
539
+ f"Cannot access key {token!r} on non-dict while resolving {expr!r}"
540
+ )
541
+ if token not in cur:
542
+ raise KeyError(f"Missing key {token!r} while resolving {expr!r}")
543
+ cur = cur[token]
544
+ return cur
545
+
546
+
547
+ def resolve_json_path(expr: str, data: dict) -> object:
548
+ if jsonpath is None:
549
+ raise ImportError("python-jsonpath is required for json-path ($...)")
550
+
551
+ if not (expr == "$" or expr.startswith("$.") or expr.startswith("$[")):
552
+ raise ValueError(
553
+ f"Invalid json-path expression {expr!r}. "
554
+ "Must start with '$', '$.' or '$[' (no implicit normalization)."
555
+ )
556
+
557
+ # Use package-level APIf
558
+ results = jsonpath.findall(expr, data) # always returns a list
559
+ return results[0] if len(results) == 1 else results
560
+
561
+
562
+ def resolve_json_pointer(expr: str, data: Dict[str, Any]) -> Any:
563
+ """Resolve a JSON Pointer; returns a single value."""
564
+ if JSONPointer is None:
565
+ raise ImportError("python-jsonpath is required for json-pointer (/...)")
566
+ return JSONPointer(expr).resolve(data)
567
+
568
+
569
+ def resolve_any(expr: str, data: Dict[str, Any]) -> Any:
570
+ """Dispatch to the right resolver based on detected scheme."""
571
+ scheme = detect_scheme(expr)
572
+ if scheme == "json-path":
573
+ return resolve_json_path(expr, data)
574
+ if scheme == "json-pointer":
575
+ return resolve_json_pointer(expr, data)
576
+ return resolve_dot_notation(expr, data)
577
+
578
+
579
+ # ========= Placeholder & coercion helpers =========
580
+
581
+ _PLACEHOLDER_RE = re.compile(r"\{\{\s*(.*?)\s*\}\}")
582
+
583
+
584
+ def extract_placeholders(template: str) -> Iterable[str]:
585
+ """Yield the inner text of all {{ ... }} occurrences (trimmed)."""
586
+ for m in _PLACEHOLDER_RE.finditer(template):
587
+ yield m.group(1).strip()
588
+
589
+
590
+ def coerce_to_str(value: Any) -> str:
591
+ """Pretty stringify values for embedding into templates."""
592
+ if isinstance(value, (dict, list)):
593
+ return json.dumps(value, ensure_ascii=False)
594
+ return str(value)
595
+
596
+
597
+ def build_replacements(
598
+ placeholders: Iterable[str], data: Dict[str, Any]
599
+ ) -> Tuple[Dict[str, str], set]:
600
+ """
601
+ Resolve all placeholders against data.
602
+ Returns (replacements, unresolved_placeholders).
603
+ """
604
+ replacements: Dict[str, str] = {}
605
+ unresolved: set = set()
606
+ for expr in set(placeholders):
607
+ try:
608
+ val = resolve_any(expr, data)
609
+ # Escape backslashes to avoid regex replacement surprises
610
+ replacements[expr] = coerce_to_str(val).replace("\\", "\\\\")
611
+ except Exception:
612
+ unresolved.add(expr)
613
+ return replacements, unresolved
614
+
615
+
616
+ def apply_replacements(template: str, replacements: Dict[str, str]) -> str:
617
+ """Replace {{ expr }} using a callback to avoid regex-injection issues."""
618
+
619
+ def _repl(m: re.Match) -> str:
620
+ expr = m.group(1).strip()
621
+ return replacements.get(expr, m.group(0))
622
+
623
+ return _PLACEHOLDER_RE.sub(_repl, template)
624
+
625
+
626
+ def compute_truly_unreplaced(original: set, rendered: str) -> set:
627
+ """Only count placeholders that were in the original template and remain."""
628
+ now = set(extract_placeholders(rendered))
629
+ return original & now
630
+
631
+
632
+ def missing_lib_hints(unreplaced: set) -> Optional[str]:
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
+ )
640
+ return None
641
+
642
+
465
643
  class PromptTemplate(BaseModel):
466
644
  """A template for generating prompts with formatting capabilities"""
467
645
 
@@ -508,6 +686,7 @@ class PromptTemplate(BaseModel):
508
686
  try:
509
687
  if self.template_format == "fstring":
510
688
  return content.format(**kwargs)
689
+
511
690
  elif self.template_format == "jinja2":
512
691
  from jinja2 import Template, TemplateError
513
692
 
@@ -518,22 +697,33 @@ class PromptTemplate(BaseModel):
518
697
  f"Jinja2 template error in content: '{content}'. Error: {str(e)}",
519
698
  original_error=e,
520
699
  )
700
+
521
701
  elif self.template_format == "curly":
522
- import re
702
+ original_placeholders = set(extract_placeholders(content))
703
+
704
+ replacements, _unresolved = build_replacements(
705
+ original_placeholders, kwargs
706
+ )
707
+
708
+ result = apply_replacements(content, replacements)
523
709
 
524
- result = content
525
- for key, value in kwargs.items():
526
- result = re.sub(r"\{\{" + key + r"\}\}", str(value), result)
527
- if re.search(r"\{\{.*?\}\}", result):
528
- unreplaced = re.findall(r"\{\{(.*?)\}\}", result)
710
+ truly_unreplaced = compute_truly_unreplaced(
711
+ original_placeholders, result
712
+ )
713
+ if truly_unreplaced:
714
+ hint = missing_lib_hints(truly_unreplaced)
715
+ suffix = f" Hint: {hint}" if hint else ""
529
716
  raise TemplateFormatError(
530
- f"Unreplaced variables in curly template: {unreplaced}"
717
+ f"Unreplaced variables in curly template: {sorted(truly_unreplaced)}.{suffix}"
531
718
  )
719
+
532
720
  return result
721
+
533
722
  else:
534
723
  raise TemplateFormatError(
535
724
  f"Unknown template format: {self.template_format}"
536
725
  )
726
+
537
727
  except KeyError as e:
538
728
  key = str(e).strip("'")
539
729
  raise TemplateFormatError(
@@ -541,7 +731,8 @@ class PromptTemplate(BaseModel):
541
731
  )
542
732
  except Exception as e:
543
733
  raise TemplateFormatError(
544
- f"Error formatting template '{content}': {str(e)}", original_error=e
734
+ f"Error formatting template '{content}': {str(e)}",
735
+ original_error=e,
545
736
  )
546
737
 
547
738
  def _substitute_variables(self, obj: Any, kwargs: Dict[str, Any]) -> Any:
@@ -616,7 +807,7 @@ class PromptTemplate(BaseModel):
616
807
  )
617
808
  )
618
809
 
619
- new_llm_config = self.llm_config.copy(deep=True)
810
+ new_llm_config = self.llm_config.model_copy(deep=True)
620
811
  if new_llm_config.response_format is not None:
621
812
  rf_dict = new_llm_config.response_format.model_dump(by_alias=True)
622
813
  substituted = self._substitute_variables(rf_dict, kwargs)
@@ -658,6 +849,9 @@ class PromptTemplate(BaseModel):
658
849
  if self.llm_config.presence_penalty is not None:
659
850
  kwargs["presence_penalty"] = self.llm_config.presence_penalty
660
851
 
852
+ if self.llm_config.reasoning_effort is not None:
853
+ kwargs["reasoning_effort"] = self.llm_config.reasoning_effort
854
+
661
855
  if self.llm_config.response_format:
662
856
  kwargs["response_format"] = self.llm_config.response_format.dict(
663
857
  by_alias=True
agenta/sdk/utils/cache.py CHANGED
@@ -5,7 +5,7 @@ from collections import OrderedDict
5
5
  from threading import Lock
6
6
 
7
7
  CACHE_CAPACITY = int(getenv("AGENTA_MIDDLEWARE_CACHE_CAPACITY", "512"))
8
- CACHE_TTL = int(getenv("AGENTA_MIDDLEWARE_CACHE_TTL", str(5 * 60))) # 5 minutes
8
+ CACHE_TTL = int(getenv("AGENTA_MIDDLEWARE_CACHE_TTL", str(1 * 60))) # 1 minutes
9
9
 
10
10
 
11
11
  class TTLLRUCache: