agenta 0.57.0__py3-none-any.whl → 0.65.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 (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 +4 -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 +152 -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.65.0.dist-info}/METADATA +44 -47
  260. agenta-0.65.0.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.65.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,321 @@
1
+ # /agenta/sdk/middlewares/running/normalizer.py
2
+ import inspect
3
+ from typing import Any, Dict, Callable, Union
4
+ from inspect import isawaitable, isasyncgen, isgenerator
5
+ from traceback import format_exception
6
+ from uuid import UUID
7
+
8
+
9
+ from agenta.sdk.utils.exceptions import suppress
10
+ from agenta.sdk.models.workflows import (
11
+ WorkflowServiceStatus,
12
+ WorkflowServiceRequestData,
13
+ WorkflowServiceResponseData,
14
+ WorkflowServiceRequest,
15
+ WorkflowServiceBatchResponse,
16
+ WorkflowServiceStreamResponse,
17
+ )
18
+ from agenta.sdk.workflows.errors import ErrorStatus
19
+ from agenta.sdk.contexts.running import RunningContext
20
+ from agenta.sdk.contexts.tracing import TracingContext
21
+
22
+
23
+ class NormalizerMiddleware:
24
+ """Middleware that normalizes workflow service requests and responses.
25
+
26
+ This middleware performs two key normalization operations:
27
+
28
+ 1. **Request Normalization**: Transforms a WorkflowServiceRequest into the appropriate
29
+ keyword arguments for the workflow handler function by:
30
+ - Mapping request data fields to handler function parameters
31
+ - Extracting inputs from request.data.inputs and mapping them to function parameters
32
+ - Handling special parameters like 'request' and WorkflowServiceRequestData fields
33
+ - Supporting **kwargs expansion for additional fields
34
+
35
+ 2. **Response Normalization**: Transforms handler function results into standardized
36
+ WorkflowServiceBatchResponse or WorkflowServiceStreamResponse objects by:
37
+ - Handling various return types (plain values, awaitables, generators, async generators)
38
+ - Aggregating streaming results into batches when aggregate flag is set
39
+ - Extracting trace_id and span_id from TracingContext for observability
40
+ - Wrapping raw outputs in proper response structures
41
+
42
+ The middleware ensures consistent interfaces between the workflow service layer and
43
+ the actual handler functions, allowing handlers to use simple function signatures
44
+ while maintaining structured request/response formats at the service boundary.
45
+ """
46
+
47
+ DATA_FIELDS = set(("request",)) | set(
48
+ WorkflowServiceRequestData.model_fields.keys()
49
+ )
50
+
51
+ async def _normalize_request(
52
+ self,
53
+ request: WorkflowServiceRequest,
54
+ handler: Callable,
55
+ ) -> Dict[str, Any]:
56
+ """Transform a WorkflowServiceRequest into kwargs for the handler function.
57
+
58
+ Inspects the handler's function signature and maps the request data to the
59
+ appropriate parameter names and values. The mapping follows this priority order:
60
+
61
+ 1. If parameter name is 'request': passes the entire WorkflowServiceRequest
62
+ 2. If parameter name matches DATA_FIELDS (like 'inputs', 'outputs', 'parameters'):
63
+ extracts that field from request.data
64
+ 3. If parameter is **kwargs: includes all unconsumed DATA_FIELDS
65
+ 4. Otherwise: looks up the parameter name in request.data.inputs dict
66
+
67
+ Args:
68
+ request: The workflow service request containing inputs and data
69
+ handler: The callable workflow handler whose signature to inspect
70
+
71
+ Returns:
72
+ Dictionary mapping parameter names to values for calling the handler
73
+ """
74
+ sig = inspect.signature(handler)
75
+ params = sig.parameters
76
+ normalized: Dict[str, Any] = {}
77
+ consumed = set()
78
+
79
+ for name, param in params.items():
80
+ if name == "request":
81
+ normalized[name] = request
82
+ consumed.add(name)
83
+
84
+ elif name in self.DATA_FIELDS:
85
+ normalized[name] = (
86
+ getattr(request.data, name, None) if request.data else None
87
+ )
88
+ consumed.add(name)
89
+
90
+ elif param.kind == inspect.Parameter.VAR_KEYWORD:
91
+ if request.data:
92
+ for f in self.DATA_FIELDS - consumed:
93
+ normalized[f] = getattr(request.data, f, None)
94
+ consumed |= self.DATA_FIELDS
95
+
96
+ else:
97
+ if request.data and isinstance(request.data.inputs, dict):
98
+ if name in request.data.inputs:
99
+ normalized[name] = request.data.inputs[name]
100
+ consumed.add(name)
101
+ continue
102
+ normalized[name] = None
103
+
104
+ return normalized
105
+
106
+ async def _normalize_response(
107
+ self,
108
+ result: Any,
109
+ ) -> Union[
110
+ WorkflowServiceBatchResponse,
111
+ WorkflowServiceStreamResponse,
112
+ ]:
113
+ if isawaitable(result):
114
+ result = await result
115
+
116
+ if isinstance(
117
+ result, (WorkflowServiceBatchResponse, WorkflowServiceStreamResponse)
118
+ ):
119
+ trace_id = None
120
+ span_id = None
121
+
122
+ with suppress():
123
+ link = (TracingContext.get().link) or {}
124
+
125
+ _trace_id = link.get("trace_id") if link else None # in int format
126
+ _span_id = link.get("span_id") if link else None # in int format
127
+
128
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
129
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
130
+
131
+ result.trace_id = trace_id
132
+ result.span_id = span_id
133
+
134
+ return result
135
+
136
+ if isasyncgen(result):
137
+ if RunningContext.get().aggregate:
138
+ collected = [item async for item in result]
139
+
140
+ trace_id = None
141
+ span_id = None
142
+
143
+ with suppress():
144
+ link = (TracingContext.get().link) or {}
145
+
146
+ _trace_id = link.get("trace_id") if link else None # in int format
147
+ _span_id = link.get("span_id") if link else None # in int format
148
+
149
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
150
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
151
+
152
+ return WorkflowServiceBatchResponse(
153
+ data=WorkflowServiceResponseData(outputs=collected),
154
+ trace_id=trace_id,
155
+ span_id=span_id,
156
+ )
157
+
158
+ async def iterator():
159
+ async for item in result:
160
+ yield item
161
+
162
+ trace_id = None
163
+ span_id = None
164
+
165
+ with suppress():
166
+ link = (TracingContext.get().link) or {}
167
+
168
+ _trace_id = link.get("trace_id") if link else None # in int format
169
+ _span_id = link.get("span_id") if link else None # in int format
170
+
171
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
172
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
173
+
174
+ return WorkflowServiceStreamResponse(
175
+ generator=iterator,
176
+ trace_id=trace_id,
177
+ span_id=span_id,
178
+ )
179
+
180
+ if isgenerator(result):
181
+ if RunningContext.get().aggregate:
182
+ collected = list(result)
183
+
184
+ trace_id = None
185
+ span_id = None
186
+
187
+ with suppress():
188
+ link = (TracingContext.get().link) or {}
189
+
190
+ _trace_id = link.get("trace_id") if link else None # in int format
191
+ _span_id = link.get("span_id") if link else None # in int format
192
+
193
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
194
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
195
+
196
+ return WorkflowServiceBatchResponse(
197
+ data=WorkflowServiceResponseData(outputs=collected),
198
+ trace_id=trace_id,
199
+ span_id=span_id,
200
+ )
201
+
202
+ async def iterator():
203
+ for item in result:
204
+ yield item
205
+
206
+ trace_id = None
207
+ span_id = None
208
+
209
+ with suppress():
210
+ link = (TracingContext.get().link) or {}
211
+
212
+ _trace_id = link.get("trace_id") if link else None # in int format
213
+ _span_id = link.get("span_id") if link else None # in int format
214
+
215
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
216
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
217
+
218
+ return WorkflowServiceStreamResponse(
219
+ generator=iterator,
220
+ trace_id=trace_id,
221
+ span_id=span_id,
222
+ )
223
+
224
+ trace_id = None
225
+ span_id = None
226
+
227
+ with suppress():
228
+ link = (TracingContext.get().link) or {}
229
+
230
+ _trace_id = link.get("trace_id") if link else None # in int format
231
+ _span_id = link.get("span_id") if link else None # in int format
232
+
233
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
234
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
235
+
236
+ return WorkflowServiceBatchResponse(
237
+ data=WorkflowServiceResponseData(outputs=result),
238
+ trace_id=trace_id,
239
+ span_id=span_id,
240
+ )
241
+
242
+ async def _normalize_exception(
243
+ self,
244
+ exc: Exception,
245
+ ) -> WorkflowServiceBatchResponse:
246
+ error_status = None
247
+
248
+ if isinstance(exc, ErrorStatus):
249
+ error_status = WorkflowServiceStatus(
250
+ type=exc.type,
251
+ code=exc.code,
252
+ message=exc.message,
253
+ stacktrace=exc.stacktrace,
254
+ )
255
+ else:
256
+ type = "https://agenta.ai/docs/errors#v1:sdk:unknown-workflow-invoke-error"
257
+
258
+ code = getattr(exc, "status_code") if hasattr(exc, "status_code") else 500
259
+
260
+ if code in [401, 403]:
261
+ code = 424
262
+
263
+ message = str(exc) or "Internal Server Error"
264
+
265
+ stacktrace = format_exception(
266
+ exc, # type: ignore
267
+ value=exc,
268
+ tb=exc.__traceback__,
269
+ )
270
+
271
+ error_status = WorkflowServiceStatus(
272
+ type=type,
273
+ code=code,
274
+ message=message,
275
+ stacktrace=stacktrace,
276
+ )
277
+
278
+ trace_id = None
279
+ span_id = None
280
+
281
+ with suppress():
282
+ link = (TracingContext.get().link) or {}
283
+
284
+ _trace_id = link.get("trace_id") if link else None # in int format
285
+ _span_id = link.get("span_id") if link else None # in int format
286
+
287
+ trace_id = UUID(int=_trace_id).hex if _trace_id else None
288
+ span_id = UUID(int=_span_id).hex[16:] if _span_id else None
289
+
290
+ error_response = WorkflowServiceBatchResponse(
291
+ status=error_status,
292
+ trace_id=trace_id,
293
+ span_id=span_id,
294
+ )
295
+
296
+ return error_response
297
+
298
+ async def __call__(
299
+ self,
300
+ request: WorkflowServiceRequest,
301
+ call_next: Callable[[WorkflowServiceRequest], Any],
302
+ ):
303
+ ctx = RunningContext.get()
304
+ handler = ctx.handler
305
+
306
+ if not handler:
307
+ raise RuntimeError("NormalizerMiddleware: no handler set in context")
308
+
309
+ kwargs = await self._normalize_request(request, handler)
310
+
311
+ try:
312
+ response = handler(**kwargs)
313
+
314
+ normalized = await self._normalize_response(response)
315
+
316
+ except Exception as exception:
317
+ normalized = await self._normalize_exception(exception)
318
+
319
+ return normalized
320
+
321
+ return normalized
@@ -0,0 +1,161 @@
1
+ # /agenta/sdk/middlewares/running/resolver.py
2
+ from typing import Callable, Any, Optional
3
+
4
+ from agenta.sdk.utils.logging import get_module_logger
5
+ from agenta.sdk.models.workflows import (
6
+ WorkflowServiceRequestData,
7
+ WorkflowServiceResponseData,
8
+ WorkflowServiceRequest,
9
+ WorkflowServiceInterface,
10
+ WorkflowServiceConfiguration,
11
+ )
12
+ from agenta.sdk.contexts.running import RunningContext
13
+ from agenta.sdk.workflows.utils import (
14
+ retrieve_handler,
15
+ retrieve_interface,
16
+ retrieve_configuration,
17
+ )
18
+ from agenta.sdk.workflows.errors import InvalidInterfaceURIV0Error
19
+
20
+
21
+ log = get_module_logger(__name__)
22
+
23
+
24
+ async def resolve_interface(
25
+ *,
26
+ request: Optional[WorkflowServiceRequest] = None,
27
+ interface: Optional[WorkflowServiceInterface] = None,
28
+ ) -> Optional[WorkflowServiceInterface]:
29
+ """Resolve the workflow service interface from multiple sources.
30
+
31
+ Checks for interface in this priority order:
32
+ 1. Provided interface parameter
33
+ 2. Interface from the request
34
+ 3. Interface from the RunningContext
35
+
36
+ Args:
37
+ request: Optional workflow service request that may contain an interface
38
+ interface: Optional interface to use directly
39
+
40
+ Returns:
41
+ The resolved WorkflowServiceInterface or None if not found
42
+ """
43
+ if interface is not None:
44
+ return interface
45
+
46
+ if request and request.interface:
47
+ return request.interface
48
+
49
+ ctx = RunningContext.get()
50
+ return ctx.interface
51
+
52
+
53
+ async def resolve_configuration(
54
+ *,
55
+ request: Optional[WorkflowServiceRequest] = None,
56
+ configuration: Optional[WorkflowServiceConfiguration] = None,
57
+ ) -> Optional[WorkflowServiceConfiguration]:
58
+ """Resolve workflow parameters from multiple sources.
59
+
60
+ Checks for parameters in this priority order:
61
+ 1. Provided parameters parameter
62
+ 2. Parameters from request.data.parameters
63
+ 3. Parameters from the RunningContext
64
+
65
+ Args:
66
+ request: Optional workflow service request that may contain parameters
67
+ parameters: Optional parameters dict to use directly
68
+
69
+ Returns:
70
+ The resolved parameters dict or None if not found
71
+ """
72
+ if configuration is not None:
73
+ return configuration
74
+
75
+ if request and request.configuration:
76
+ return request.configuration
77
+
78
+ ctx = RunningContext.get()
79
+ return ctx.configuration
80
+
81
+
82
+ async def resolve_handler(
83
+ *,
84
+ uri: Optional[str] = None,
85
+ ):
86
+ """Retrieve and validate a workflow handler by its URI.
87
+
88
+ Looks up a registered handler function using the provided URI.
89
+ Raises an exception if the URI is None or if no handler is found.
90
+
91
+ Args:
92
+ uri: The service URI identifying the handler to retrieve
93
+
94
+ Returns:
95
+ The resolved handler callable
96
+
97
+ Raises:
98
+ InvalidInterfaceURIV0Error: If uri is None or if no handler found for the URI
99
+ """
100
+ if uri is None:
101
+ raise InvalidInterfaceURIV0Error(got="None")
102
+
103
+ handler = retrieve_handler(uri)
104
+
105
+ if handler is None:
106
+ raise InvalidInterfaceURIV0Error(got=uri)
107
+
108
+ return handler
109
+
110
+
111
+ class ResolverMiddleware:
112
+ """Middleware that resolves workflow components before execution.
113
+
114
+ This middleware is responsible for resolving three critical components needed
115
+ to execute a workflow:
116
+
117
+ 1. **Interface**: The WorkflowServiceInterface containing the service URI and schemas
118
+ 2. **Parameters**: Configuration parameters for the workflow
119
+ 3. **Handler**: The actual callable function that implements the workflow logic
120
+
121
+ The middleware resolves these components from various sources (request, context, registry)
122
+ and stores them in the RunningContext for downstream middleware and the handler to use.
123
+ It also ensures the request.data.parameters is populated for the workflow execution.
124
+ """
125
+
126
+ async def __call__(
127
+ self,
128
+ request: WorkflowServiceRequest,
129
+ call_next: Callable[[WorkflowServiceRequest], Any],
130
+ ):
131
+ """Resolve workflow components and populate the running context.
132
+
133
+ Args:
134
+ request: The workflow service request being processed
135
+ call_next: The next middleware or handler in the chain
136
+
137
+ Returns:
138
+ The result from calling the next middleware/handler in the chain
139
+
140
+ Raises:
141
+ InvalidInterfaceURIV0Error: If the handler cannot be resolved from the interface URI
142
+ """
143
+ interface = await resolve_interface(request=request)
144
+ configuration = await resolve_configuration(request=request)
145
+ handler = await resolve_handler(uri=(interface.uri if interface else None))
146
+
147
+ ctx = RunningContext.get()
148
+ ctx.interface = interface
149
+ ctx.configuration = configuration
150
+ ctx.handler = handler
151
+
152
+ if not request.data:
153
+ request.data = WorkflowServiceRequestData()
154
+
155
+ request.data.parameters = (
156
+ request.data.parameters or configuration.parameters
157
+ if configuration
158
+ else None
159
+ )
160
+
161
+ return await call_next(request)
@@ -0,0 +1,140 @@
1
+ from os import getenv
2
+ from json import dumps
3
+ from typing import Callable, Dict, Optional, List, Any
4
+
5
+ import httpx
6
+
7
+ from agenta.sdk.utils.logging import get_module_logger
8
+ from agenta.sdk.utils.constants import TRUTHY
9
+ from agenta.sdk.utils.cache import TTLLRUCache
10
+ from agenta.sdk.utils.exceptions import suppress, display_exception
11
+
12
+ from agenta.sdk.models.workflows import WorkflowServiceRequest
13
+ from agenta.sdk.contexts.running import RunningContext
14
+
15
+ from agenta.client.backend.types import SecretDto as SecretDTO
16
+ from agenta.client.backend.types import (
17
+ StandardProviderKind,
18
+ StandardProviderDto as StandardProviderDTO,
19
+ StandardProviderSettingsDto as StandardProviderSettingsDTO,
20
+ )
21
+
22
+ import agenta as ag
23
+
24
+ log = get_module_logger(__name__)
25
+
26
+
27
+ _PROVIDER_KINDS = []
28
+
29
+ for provider_kind in StandardProviderKind.__args__[0].__args__: # type: ignore
30
+ _PROVIDER_KINDS.append(provider_kind)
31
+
32
+ _CACHE_ENABLED = (
33
+ getenv("AGENTA_SERVICE_MIDDLEWARE_CACHE_ENABLED", "true").lower() in TRUTHY
34
+ )
35
+
36
+ _cache = TTLLRUCache()
37
+
38
+
39
+ async def get_secrets(api_url, credentials) -> list:
40
+ headers = None
41
+ if credentials:
42
+ headers = {"Authorization": credentials}
43
+
44
+ _hash = dumps(
45
+ {
46
+ "headers": headers,
47
+ },
48
+ sort_keys=True,
49
+ )
50
+
51
+ if _CACHE_ENABLED:
52
+ secrets_cache = _cache.get(_hash)
53
+
54
+ if secrets_cache:
55
+ secrets = secrets_cache.get("secrets")
56
+
57
+ return secrets
58
+
59
+ local_secrets: List[Dict[str, Any]] = []
60
+
61
+ try:
62
+ for provider_kind in _PROVIDER_KINDS:
63
+ provider = provider_kind
64
+ key_name = f"{provider.upper()}_API_KEY"
65
+ key = getenv(key_name)
66
+
67
+ if not key:
68
+ continue
69
+
70
+ secret = SecretDTO(
71
+ kind="provider_key", # type: ignore
72
+ data=StandardProviderDTO(
73
+ kind=provider,
74
+ provider=StandardProviderSettingsDTO(key=key),
75
+ ),
76
+ )
77
+
78
+ local_secrets.append(secret.model_dump())
79
+ except: # pylint: disable=bare-except
80
+ display_exception("Vault: Local Secrets Exception")
81
+
82
+ vault_secrets: List[Dict[str, Any]] = []
83
+
84
+ try:
85
+ async with httpx.AsyncClient() as client:
86
+ response = await client.get(
87
+ f"{api_url}/vault/v1/secrets/",
88
+ headers=headers,
89
+ )
90
+
91
+ if response.status_code != 200:
92
+ vault_secrets = []
93
+
94
+ else:
95
+ vault_secrets = response.json()
96
+ except: # pylint: disable=bare-except
97
+ display_exception("Vault: Vault Secrets Exception")
98
+
99
+ secrets = local_secrets + vault_secrets
100
+
101
+ standard_secrets = {}
102
+ custom_secrets = []
103
+
104
+ if local_secrets:
105
+ for secret in local_secrets:
106
+ standard_secrets[secret["data"]["kind"]] = secret # type: ignore
107
+
108
+ if vault_secrets:
109
+ for secret in vault_secrets:
110
+ if secret["kind"] == "provider_key": # type: ignore
111
+ standard_secrets[secret["data"]["kind"]] = secret # type: ignore
112
+ elif secret["kind"] == "custom_provider": # type: ignore
113
+ custom_secrets.append(secret)
114
+
115
+ standard_secrets = list(standard_secrets.values())
116
+
117
+ secrets = standard_secrets + custom_secrets
118
+
119
+ _cache.put(_hash, {"secrets": secrets})
120
+
121
+ return secrets
122
+
123
+
124
+ class VaultMiddleware:
125
+ async def __call__(
126
+ self,
127
+ request: WorkflowServiceRequest,
128
+ call_next: Callable[[WorkflowServiceRequest], Any],
129
+ ):
130
+ api_url = f"{ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host}/api"
131
+
132
+ with suppress():
133
+ ctx = RunningContext.get()
134
+ credentials = ctx.credentials
135
+
136
+ secrets = await get_secrets(api_url, credentials)
137
+
138
+ ctx.secrets = secrets
139
+
140
+ return await call_next(request)
File without changes
@@ -0,0 +1,33 @@
1
+ from typing import Optional
2
+ from uuid import UUID
3
+
4
+
5
+ from agenta.sdk.models.shared import (
6
+ TraceID,
7
+ SpanID,
8
+ Link,
9
+ Identifier,
10
+ Slug,
11
+ Version,
12
+ Reference,
13
+ Lifecycle,
14
+ Header,
15
+ Flags,
16
+ Tags,
17
+ Meta,
18
+ Metadata,
19
+ Data,
20
+ Commit,
21
+ AliasConfig,
22
+ sync_alias,
23
+ )
24
+
25
+
26
+ class Blob(Identifier, Lifecycle):
27
+ flags: Optional[Flags] = None # type: ignore
28
+ tags: Optional[Tags] = None # type: ignore
29
+ meta: Optional[Meta] = None # type: ignore
30
+
31
+ data: Optional[Data] = None # type: ignore
32
+
33
+ set_id: Optional[UUID] = None