agenta 0.12.2__py3-none-any.whl → 0.32.0a1__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.

Potentially problematic release.


This version of agenta might be problematic. Click here for more details.

Files changed (244) hide show
  1. agenta/__init__.py +64 -7
  2. agenta/cli/helper.py +7 -3
  3. agenta/cli/main.py +15 -50
  4. agenta/cli/variant_commands.py +50 -29
  5. agenta/client/Readme.md +72 -64
  6. agenta/client/api.py +2 -2
  7. agenta/client/backend/__init__.py +193 -22
  8. agenta/client/backend/access_control/__init__.py +1 -0
  9. agenta/client/backend/access_control/client.py +167 -0
  10. agenta/client/backend/apps/__init__.py +1 -0
  11. agenta/client/backend/apps/client.py +1691 -0
  12. agenta/client/backend/bases/__init__.py +1 -0
  13. agenta/client/backend/bases/client.py +190 -0
  14. agenta/client/backend/client.py +2508 -5712
  15. agenta/client/backend/configs/__init__.py +1 -0
  16. agenta/client/backend/configs/client.py +604 -0
  17. agenta/client/backend/containers/__init__.py +5 -0
  18. agenta/client/backend/containers/client.py +648 -0
  19. agenta/client/backend/containers/types/__init__.py +5 -0
  20. agenta/client/backend/{types → containers/types}/container_templates_response.py +1 -2
  21. agenta/client/backend/core/__init__.py +30 -0
  22. agenta/client/backend/core/client_wrapper.py +42 -9
  23. agenta/client/backend/core/file.py +70 -0
  24. agenta/client/backend/core/http_client.py +575 -0
  25. agenta/client/backend/core/jsonable_encoder.py +33 -39
  26. agenta/client/backend/core/pydantic_utilities.py +325 -0
  27. agenta/client/backend/core/query_encoder.py +60 -0
  28. agenta/client/backend/core/remove_none_from_dict.py +2 -2
  29. agenta/client/backend/core/request_options.py +35 -0
  30. agenta/client/backend/core/serialization.py +276 -0
  31. agenta/client/backend/environments/__init__.py +1 -0
  32. agenta/client/backend/environments/client.py +196 -0
  33. agenta/client/backend/evaluations/__init__.py +1 -0
  34. agenta/client/backend/evaluations/client.py +1469 -0
  35. agenta/client/backend/evaluators/__init__.py +1 -0
  36. agenta/client/backend/evaluators/client.py +1283 -0
  37. agenta/client/backend/observability/__init__.py +1 -0
  38. agenta/client/backend/observability/client.py +1286 -0
  39. agenta/client/backend/observability_v_1/__init__.py +5 -0
  40. agenta/client/backend/observability_v_1/client.py +763 -0
  41. agenta/client/backend/observability_v_1/types/__init__.py +7 -0
  42. agenta/client/backend/observability_v_1/types/format.py +5 -0
  43. agenta/client/backend/observability_v_1/types/query_analytics_response.py +7 -0
  44. agenta/client/backend/observability_v_1/types/query_traces_response.py +11 -0
  45. agenta/client/backend/scopes/__init__.py +1 -0
  46. agenta/client/backend/scopes/client.py +114 -0
  47. agenta/client/backend/testsets/__init__.py +1 -0
  48. agenta/client/backend/testsets/client.py +1284 -0
  49. agenta/client/backend/types/__init__.py +154 -26
  50. agenta/client/backend/types/agenta_node_dto.py +48 -0
  51. agenta/client/backend/types/agenta_node_dto_nodes_value.py +6 -0
  52. agenta/client/backend/types/agenta_nodes_response.py +30 -0
  53. agenta/client/backend/types/agenta_root_dto.py +30 -0
  54. agenta/client/backend/types/agenta_roots_response.py +30 -0
  55. agenta/client/backend/types/agenta_tree_dto.py +30 -0
  56. agenta/client/backend/types/agenta_trees_response.py +30 -0
  57. agenta/client/backend/types/aggregated_result.py +16 -31
  58. agenta/client/backend/types/aggregated_result_evaluator_config.py +8 -0
  59. agenta/client/backend/types/analytics_response.py +24 -0
  60. agenta/client/backend/types/app.py +17 -30
  61. agenta/client/backend/types/app_variant_response.py +36 -0
  62. agenta/client/backend/types/app_variant_revision.py +17 -32
  63. agenta/client/backend/types/base_output.py +13 -28
  64. agenta/client/backend/types/body_import_testset.py +15 -31
  65. agenta/client/backend/types/bucket_dto.py +26 -0
  66. agenta/client/backend/types/collect_status_response.py +22 -0
  67. agenta/client/backend/types/config_db.py +16 -31
  68. agenta/client/backend/types/config_dto.py +32 -0
  69. agenta/client/backend/types/config_response_model.py +32 -0
  70. agenta/client/backend/types/correct_answer.py +22 -0
  71. agenta/client/backend/types/create_app_output.py +13 -28
  72. agenta/client/backend/types/create_span.py +45 -0
  73. agenta/client/backend/types/create_trace_response.py +22 -0
  74. agenta/client/backend/types/docker_env_vars.py +13 -28
  75. agenta/client/backend/types/environment_output.py +22 -34
  76. agenta/client/backend/types/environment_output_extended.py +31 -0
  77. agenta/client/backend/types/environment_revision.py +26 -0
  78. agenta/client/backend/types/error.py +22 -0
  79. agenta/client/backend/types/evaluation.py +22 -33
  80. agenta/client/backend/types/evaluation_scenario.py +18 -33
  81. agenta/client/backend/types/evaluation_scenario_input.py +16 -31
  82. agenta/client/backend/types/evaluation_scenario_output.py +17 -30
  83. agenta/client/backend/types/evaluation_scenario_result.py +14 -29
  84. agenta/client/backend/types/evaluation_scenario_score_update.py +21 -0
  85. agenta/client/backend/types/evaluation_status_enum.py +11 -29
  86. agenta/client/backend/types/evaluation_type.py +3 -21
  87. agenta/client/backend/types/evaluator.py +20 -31
  88. agenta/client/backend/types/evaluator_config.py +21 -33
  89. agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
  90. agenta/client/backend/types/evaluator_output_interface.py +21 -0
  91. agenta/client/backend/types/exception_dto.py +26 -0
  92. agenta/client/backend/types/get_config_response.py +23 -0
  93. agenta/client/backend/types/header_dto.py +22 -0
  94. agenta/client/backend/types/http_validation_error.py +14 -29
  95. agenta/client/backend/types/human_evaluation.py +18 -34
  96. agenta/client/backend/types/human_evaluation_scenario.py +22 -38
  97. agenta/client/backend/types/human_evaluation_scenario_input.py +13 -28
  98. agenta/client/backend/types/human_evaluation_scenario_output.py +13 -28
  99. agenta/client/backend/types/human_evaluation_scenario_update.py +30 -0
  100. agenta/client/backend/types/human_evaluation_update.py +22 -0
  101. agenta/client/backend/types/image.py +18 -32
  102. agenta/client/backend/types/invite_request.py +16 -30
  103. agenta/client/backend/types/legacy_analytics_response.py +29 -0
  104. agenta/client/backend/types/legacy_data_point.py +27 -0
  105. agenta/client/backend/types/lifecycle_dto.py +24 -0
  106. agenta/client/backend/types/link_dto.py +24 -0
  107. agenta/client/backend/types/list_api_keys_response.py +24 -0
  108. agenta/client/backend/types/llm_run_rate_limit.py +13 -28
  109. agenta/client/backend/types/llm_tokens.py +23 -0
  110. agenta/client/backend/types/metrics_dto.py +24 -0
  111. agenta/client/backend/types/new_human_evaluation.py +27 -0
  112. agenta/client/backend/types/new_testset.py +16 -31
  113. agenta/client/backend/types/node_dto.py +24 -0
  114. agenta/client/backend/types/node_type.py +19 -0
  115. agenta/client/backend/types/o_tel_context_dto.py +22 -0
  116. agenta/client/backend/types/o_tel_event_dto.py +23 -0
  117. agenta/client/backend/types/o_tel_extra_dto.py +26 -0
  118. agenta/client/backend/types/o_tel_link_dto.py +23 -0
  119. agenta/client/backend/types/o_tel_span_dto.py +37 -0
  120. agenta/client/backend/types/o_tel_span_kind.py +15 -0
  121. agenta/client/backend/types/o_tel_spans_response.py +24 -0
  122. agenta/client/backend/types/o_tel_status_code.py +8 -0
  123. agenta/client/backend/types/organization.py +22 -35
  124. agenta/client/backend/types/organization_output.py +13 -28
  125. agenta/client/backend/types/outputs.py +5 -0
  126. agenta/client/backend/types/parent_dto.py +21 -0
  127. agenta/client/backend/types/permission.py +41 -0
  128. agenta/client/backend/types/projects_response.py +28 -0
  129. agenta/client/backend/types/provider_key_dto.py +23 -0
  130. agenta/client/backend/types/provider_kind.py +21 -0
  131. agenta/client/backend/types/reference_dto.py +23 -0
  132. agenta/client/backend/types/reference_request_model.py +23 -0
  133. agenta/client/backend/types/result.py +18 -31
  134. agenta/client/backend/types/root_dto.py +21 -0
  135. agenta/client/backend/types/{human_evaluation_scenario_score.py → score.py} +1 -1
  136. agenta/client/backend/types/secret_dto.py +24 -0
  137. agenta/client/backend/types/{human_evaluation_scenario_update_score.py → secret_kind.py} +1 -1
  138. agenta/client/backend/types/secret_response_dto.py +27 -0
  139. agenta/client/backend/types/simple_evaluation_output.py +13 -28
  140. agenta/client/backend/types/span.py +39 -49
  141. agenta/client/backend/types/span_detail.py +44 -0
  142. agenta/client/backend/types/span_dto.py +54 -0
  143. agenta/client/backend/types/span_dto_nodes_value.py +9 -0
  144. agenta/client/backend/types/span_status_code.py +5 -0
  145. agenta/client/backend/types/span_variant.py +23 -0
  146. agenta/client/backend/types/status_code.py +5 -0
  147. agenta/client/backend/types/status_dto.py +23 -0
  148. agenta/client/backend/types/template.py +14 -29
  149. agenta/client/backend/types/template_image_info.py +21 -35
  150. agenta/client/backend/types/test_set_output_response.py +20 -33
  151. agenta/client/backend/types/test_set_simple_response.py +13 -28
  152. agenta/client/backend/types/time_dto.py +23 -0
  153. agenta/client/backend/types/trace_detail.py +44 -0
  154. agenta/client/backend/types/tree_dto.py +23 -0
  155. agenta/client/backend/types/tree_type.py +5 -0
  156. agenta/client/backend/types/update_app_output.py +22 -0
  157. agenta/client/backend/types/uri.py +13 -28
  158. agenta/client/backend/types/validation_error.py +13 -28
  159. agenta/client/backend/types/variant_action.py +14 -29
  160. agenta/client/backend/types/variant_action_enum.py +1 -19
  161. agenta/client/backend/types/with_pagination.py +26 -0
  162. agenta/client/backend/types/workspace_member_response.py +23 -0
  163. agenta/client/backend/types/workspace_permission.py +25 -0
  164. agenta/client/backend/types/workspace_response.py +29 -0
  165. agenta/client/backend/types/workspace_role.py +15 -0
  166. agenta/client/backend/types/workspace_role_response.py +23 -0
  167. agenta/client/backend/variants/__init__.py +5 -0
  168. agenta/client/backend/variants/client.py +2814 -0
  169. agenta/client/backend/variants/types/__init__.py +7 -0
  170. agenta/client/backend/variants/types/add_variant_from_base_and_config_response.py +8 -0
  171. agenta/client/backend/vault/__init__.py +1 -0
  172. agenta/client/backend/vault/client.py +685 -0
  173. agenta/client/client.py +1 -1
  174. agenta/config.py +0 -2
  175. agenta/config.toml +0 -1
  176. agenta/docker/docker-assets/Dockerfile.cloud.template +2 -1
  177. agenta/docker/docker-assets/Dockerfile.template +2 -1
  178. agenta/docker/docker_utils.py +11 -12
  179. agenta/sdk/__init__.py +58 -7
  180. agenta/sdk/agenta_init.py +182 -164
  181. agenta/sdk/assets.py +95 -0
  182. agenta/sdk/client.py +56 -0
  183. agenta/sdk/context/__init__.py +0 -0
  184. agenta/sdk/context/exporting.py +25 -0
  185. agenta/sdk/context/routing.py +27 -0
  186. agenta/sdk/context/tracing.py +28 -0
  187. agenta/sdk/decorators/__init__.py +0 -0
  188. agenta/sdk/decorators/routing.py +576 -0
  189. agenta/sdk/decorators/tracing.py +296 -0
  190. agenta/sdk/litellm/__init__.py +1 -0
  191. agenta/sdk/litellm/litellm.py +314 -0
  192. agenta/sdk/litellm/mockllm.py +27 -0
  193. agenta/sdk/litellm/mocks/__init__.py +26 -0
  194. agenta/sdk/managers/__init__.py +6 -0
  195. agenta/sdk/managers/config.py +208 -0
  196. agenta/sdk/managers/deployment.py +45 -0
  197. agenta/sdk/managers/secrets.py +38 -0
  198. agenta/sdk/managers/shared.py +639 -0
  199. agenta/sdk/managers/variant.py +182 -0
  200. agenta/sdk/managers/vault.py +16 -0
  201. agenta/sdk/middleware/__init__.py +0 -0
  202. agenta/sdk/middleware/auth.py +180 -0
  203. agenta/sdk/middleware/cache.py +47 -0
  204. agenta/sdk/middleware/config.py +255 -0
  205. agenta/sdk/middleware/cors.py +29 -0
  206. agenta/sdk/middleware/inline.py +38 -0
  207. agenta/sdk/middleware/mock.py +33 -0
  208. agenta/sdk/middleware/otel.py +40 -0
  209. agenta/sdk/middleware/vault.py +145 -0
  210. agenta/sdk/router.py +0 -7
  211. agenta/sdk/tracing/__init__.py +1 -0
  212. agenta/sdk/tracing/attributes.py +141 -0
  213. agenta/sdk/tracing/conventions.py +49 -0
  214. agenta/sdk/tracing/exporters.py +103 -0
  215. agenta/sdk/tracing/inline.py +1146 -0
  216. agenta/sdk/tracing/processors.py +121 -0
  217. agenta/sdk/tracing/spans.py +136 -0
  218. agenta/sdk/tracing/tracing.py +237 -0
  219. agenta/sdk/types.py +478 -74
  220. agenta/sdk/utils/__init__.py +0 -0
  221. agenta/sdk/utils/constants.py +1 -0
  222. agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
  223. agenta/sdk/utils/exceptions.py +59 -0
  224. agenta/sdk/utils/globals.py +6 -10
  225. agenta/sdk/utils/helpers.py +8 -0
  226. agenta/sdk/utils/logging.py +21 -0
  227. agenta/sdk/utils/singleton.py +13 -0
  228. agenta/sdk/utils/timing.py +58 -0
  229. {agenta-0.12.2.dist-info → agenta-0.32.0a1.dist-info}/METADATA +98 -151
  230. agenta-0.32.0a1.dist-info/RECORD +263 -0
  231. {agenta-0.12.2.dist-info → agenta-0.32.0a1.dist-info}/WHEEL +1 -1
  232. agenta/client/backend/types/add_variant_from_base_and_config_response.py +0 -7
  233. agenta/client/backend/types/app_variant_output.py +0 -47
  234. agenta/client/backend/types/app_variant_output_extended.py +0 -50
  235. agenta/client/backend/types/delete_evaluation.py +0 -36
  236. agenta/client/backend/types/evaluation_webhook.py +0 -36
  237. agenta/client/backend/types/feedback.py +0 -40
  238. agenta/client/backend/types/get_config_reponse.py +0 -39
  239. agenta/client/backend/types/list_api_keys_output.py +0 -39
  240. agenta/client/backend/types/trace.py +0 -48
  241. agenta/sdk/agenta_decorator.py +0 -443
  242. agenta/sdk/context.py +0 -41
  243. agenta-0.12.2.dist-info/RECORD +0 -114
  244. {agenta-0.12.2.dist-info → agenta-0.32.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,182 @@
1
+ from typing import Optional
2
+
3
+ from agenta.sdk.managers.shared import SharedManager
4
+
5
+
6
+ class VariantManager(SharedManager):
7
+ @classmethod
8
+ def create(
9
+ cls,
10
+ *,
11
+ parameters: dict,
12
+ variant_slug: str,
13
+ #
14
+ app_id: Optional[str] = None,
15
+ app_slug: Optional[str] = None,
16
+ ):
17
+ variant = SharedManager.add(
18
+ app_id=app_id,
19
+ app_slug=app_slug,
20
+ variant_slug=variant_slug,
21
+ )
22
+
23
+ if variant:
24
+ variant = SharedManager.commit(
25
+ parameters=parameters,
26
+ app_id=app_id,
27
+ app_slug=app_slug,
28
+ variant_slug=variant_slug,
29
+ )
30
+
31
+ return variant
32
+
33
+ @classmethod
34
+ async def acreate(
35
+ cls,
36
+ *,
37
+ parameters: dict,
38
+ variant_slug: str,
39
+ #
40
+ app_id: Optional[str] = None,
41
+ app_slug: Optional[str] = None,
42
+ ):
43
+ variant = await SharedManager.aadd(
44
+ app_id=app_id,
45
+ app_slug=app_slug,
46
+ variant_slug=variant_slug,
47
+ )
48
+ if variant:
49
+ variant = await SharedManager.acommit(
50
+ parameters=parameters,
51
+ app_id=app_id,
52
+ app_slug=app_slug,
53
+ variant_slug=variant_slug,
54
+ )
55
+
56
+ return variant
57
+
58
+ @classmethod
59
+ def commit(
60
+ cls,
61
+ *,
62
+ parameters: dict,
63
+ variant_slug: str,
64
+ #
65
+ app_id: Optional[str] = None,
66
+ app_slug: Optional[str] = None,
67
+ ):
68
+ variant = SharedManager.commit(
69
+ parameters=parameters,
70
+ app_id=app_id,
71
+ app_slug=app_slug,
72
+ variant_slug=variant_slug,
73
+ )
74
+ return variant
75
+
76
+ @classmethod
77
+ async def acommit(
78
+ cls,
79
+ *,
80
+ parameters: dict,
81
+ variant_slug: str,
82
+ #
83
+ app_id: Optional[str] = None,
84
+ app_slug: Optional[str] = None,
85
+ ):
86
+ variant = await SharedManager.acommit(
87
+ parameters=parameters,
88
+ app_id=app_id,
89
+ app_slug=app_slug,
90
+ variant_slug=variant_slug,
91
+ )
92
+ return variant
93
+
94
+ @classmethod
95
+ def delete(
96
+ cls,
97
+ *,
98
+ variant_slug: str,
99
+ #
100
+ app_id: Optional[str] = None,
101
+ app_slug: Optional[str] = None,
102
+ ):
103
+ message = SharedManager.delete(
104
+ app_id=app_id,
105
+ app_slug=app_slug,
106
+ variant_slug=variant_slug,
107
+ )
108
+ return message
109
+
110
+ @classmethod
111
+ async def adelete(
112
+ cls,
113
+ *,
114
+ variant_slug: str,
115
+ #
116
+ app_id: Optional[str] = None,
117
+ app_slug: Optional[str] = None,
118
+ ):
119
+ message = await SharedManager.adelete(
120
+ app_id=app_id,
121
+ app_slug=app_slug,
122
+ variant_slug=variant_slug,
123
+ )
124
+ return message
125
+
126
+ @classmethod
127
+ def list(
128
+ cls,
129
+ *,
130
+ app_id: Optional[str] = None,
131
+ app_slug: Optional[str] = None,
132
+ ):
133
+ variants = SharedManager.list(
134
+ app_id=app_id,
135
+ app_slug=app_slug,
136
+ )
137
+ return variants
138
+
139
+ @classmethod
140
+ async def alist(
141
+ cls,
142
+ *,
143
+ app_id: Optional[str] = None,
144
+ app_slug: Optional[str] = None,
145
+ ):
146
+ variants = await SharedManager.alist(
147
+ app_id=app_id,
148
+ app_slug=app_slug,
149
+ )
150
+ return variants
151
+
152
+ @classmethod
153
+ def history(
154
+ cls,
155
+ *,
156
+ variant_slug: str,
157
+ #
158
+ app_id: Optional[str] = None,
159
+ app_slug: Optional[str] = None,
160
+ ):
161
+ variants = SharedManager.history(
162
+ app_id=app_id,
163
+ app_slug=app_slug,
164
+ variant_slug=variant_slug,
165
+ )
166
+ return variants
167
+
168
+ @classmethod
169
+ async def ahistory(
170
+ cls,
171
+ *,
172
+ variant_slug: str,
173
+ #
174
+ app_id: Optional[str] = None,
175
+ app_slug: Optional[str] = None,
176
+ ):
177
+ variants = await SharedManager.ahistory(
178
+ app_id=app_id,
179
+ app_slug=app_slug,
180
+ variant_slug=variant_slug,
181
+ )
182
+ return variants
@@ -0,0 +1,16 @@
1
+ from typing import Optional, Dict, Any
2
+
3
+ from agenta.sdk.context.routing import routing_context
4
+
5
+
6
+ class VaultManager:
7
+ @staticmethod
8
+ def get_from_route() -> Optional[Dict[str, Any]]:
9
+ context = routing_context.get()
10
+
11
+ secrets = context.secrets
12
+
13
+ if not secrets:
14
+ return None
15
+
16
+ return secrets
File without changes
@@ -0,0 +1,180 @@
1
+ from typing import Callable, Optional
2
+
3
+ from os import getenv
4
+ from json import dumps
5
+
6
+ import httpx
7
+ from starlette.middleware.base import BaseHTTPMiddleware
8
+ from fastapi import FastAPI, Request
9
+ from fastapi.responses import JSONResponse
10
+
11
+ from agenta.sdk.middleware.cache import TTLLRUCache, CACHE_CAPACITY, CACHE_TTL
12
+ from agenta.sdk.utils.constants import TRUTHY
13
+ from agenta.sdk.utils.exceptions import display_exception
14
+
15
+ import agenta as ag
16
+
17
+
18
+ _SHARED_SERVICE = getenv("AGENTA_SHARED_SERVICE", "false").lower() in TRUTHY
19
+ _CACHE_ENABLED = getenv("AGENTA_MIDDLEWARE_CACHE_ENABLED", "true").lower() in TRUTHY
20
+ _UNAUTHORIZED_ALLOWED = (
21
+ getenv("AGENTA_UNAUTHORIZED_EXECUTION_ALLOWED", "false").lower() in TRUTHY
22
+ )
23
+ _ALWAYS_ALLOW_LIST = ["/health"]
24
+
25
+ _cache = TTLLRUCache(capacity=CACHE_CAPACITY, ttl=CACHE_TTL)
26
+
27
+
28
+ class DenyResponse(JSONResponse):
29
+ def __init__(
30
+ self,
31
+ status_code: int = 401,
32
+ detail: str = "Unauthorized",
33
+ ) -> None:
34
+ super().__init__(
35
+ status_code=status_code,
36
+ content={"detail": detail},
37
+ )
38
+
39
+
40
+ class DenyException(Exception):
41
+ def __init__(
42
+ self,
43
+ status_code: int = 401,
44
+ content: str = "Unauthorized",
45
+ ) -> None:
46
+ super().__init__()
47
+
48
+ self.status_code = status_code
49
+ self.content = content
50
+
51
+
52
+ class AuthMiddleware(BaseHTTPMiddleware):
53
+ def __init__(self, app: FastAPI):
54
+ super().__init__(app)
55
+
56
+ self.host = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host
57
+ self.resource_id = (
58
+ ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.service_id
59
+ if not _SHARED_SERVICE
60
+ else None
61
+ )
62
+
63
+ async def dispatch(self, request: Request, call_next: Callable):
64
+ try:
65
+ if _UNAUTHORIZED_ALLOWED or request.url.path in _ALWAYS_ALLOW_LIST:
66
+ request.state.auth = {}
67
+
68
+ else:
69
+ credentials = await self._get_credentials(request)
70
+
71
+ request.state.auth = {"credentials": credentials}
72
+
73
+ return await call_next(request)
74
+
75
+ except DenyException as deny:
76
+ display_exception("Auth Middleware Exception")
77
+
78
+ return DenyResponse(
79
+ status_code=deny.status_code,
80
+ detail=deny.content,
81
+ )
82
+
83
+ except: # pylint: disable=bare-except
84
+ display_exception("Auth Middleware Exception")
85
+
86
+ return DenyResponse(
87
+ status_code=500,
88
+ detail="Auth: Unexpected Error.",
89
+ )
90
+
91
+ async def _get_credentials(self, request: Request) -> Optional[str]:
92
+ try:
93
+ authorization = request.headers.get("authorization", None)
94
+
95
+ headers = {"Authorization": authorization} if authorization else None
96
+
97
+ access_token = request.cookies.get("sAccessToken", None)
98
+
99
+ cookies = {"sAccessToken": access_token} if access_token else None
100
+
101
+ baggage = request.state.otel.get("baggage") if request.state.otel else {}
102
+
103
+ project_id = (
104
+ # CLEANEST
105
+ baggage.get("project_id")
106
+ # ALTERNATIVE
107
+ or request.query_params.get("project_id")
108
+ )
109
+
110
+ params = {"action": "run_service", "resource_type": "service"}
111
+
112
+ if self.resource_id:
113
+ params["resource_id"] = self.resource_id
114
+
115
+ if project_id:
116
+ params["project_id"] = project_id
117
+
118
+ _hash = dumps(
119
+ {
120
+ "headers": headers,
121
+ "cookies": cookies,
122
+ "params": params,
123
+ },
124
+ sort_keys=True,
125
+ )
126
+
127
+ if _CACHE_ENABLED:
128
+ credentials = _cache.get(_hash)
129
+
130
+ if credentials:
131
+ return credentials
132
+
133
+ async with httpx.AsyncClient() as client:
134
+ response = await client.get(
135
+ f"{self.host}/api/permissions/verify",
136
+ headers=headers,
137
+ cookies=cookies,
138
+ params=params,
139
+ )
140
+
141
+ if response.status_code == 401:
142
+ raise DenyException(
143
+ status_code=401,
144
+ content="Invalid credentials",
145
+ )
146
+ elif response.status_code == 403:
147
+ raise DenyException(
148
+ status_code=403,
149
+ content="Service execution not allowed.",
150
+ )
151
+ elif response.status_code != 200:
152
+ raise DenyException(
153
+ status_code=400,
154
+ content="Auth: Unexpected Error.",
155
+ )
156
+
157
+ auth = response.json()
158
+
159
+ if auth.get("effect") != "allow":
160
+ raise DenyException(
161
+ status_code=403,
162
+ content="Service execution not allowed.",
163
+ )
164
+
165
+ credentials = auth.get("credentials")
166
+
167
+ _cache.put(_hash, credentials)
168
+
169
+ return credentials
170
+
171
+ except DenyException as deny:
172
+ raise deny
173
+
174
+ except Exception as exc: # pylint: disable=bare-except
175
+ display_exception("Auth Middleware Exception (suppressed)")
176
+
177
+ raise DenyException(
178
+ status_code=500,
179
+ content="Auth: Unexpected Error.",
180
+ ) from exc
@@ -0,0 +1,47 @@
1
+ from os import getenv
2
+ from time import time
3
+ from collections import OrderedDict
4
+
5
+ CACHE_CAPACITY = int(getenv("AGENTA_MIDDLEWARE_CACHE_CAPACITY", "512"))
6
+ CACHE_TTL = int(getenv("AGENTA_MIDDLEWARE_CACHE_TTL", str(5 * 60))) # 5 minutes
7
+
8
+
9
+ class TTLLRUCache:
10
+ def __init__(self, capacity: int, ttl: int):
11
+ self.cache = OrderedDict()
12
+ self.capacity = capacity
13
+ self.ttl = ttl
14
+
15
+ def get(self, key):
16
+ # CACHE
17
+ if key not in self.cache:
18
+ return None
19
+
20
+ value, expiry = self.cache[key]
21
+ # -----
22
+
23
+ # TTL
24
+ if time() > expiry:
25
+ del self.cache[key]
26
+
27
+ return None
28
+ # ---
29
+
30
+ # LRU
31
+ self.cache.move_to_end(key)
32
+ # ---
33
+
34
+ return value
35
+
36
+ def put(self, key, value):
37
+ # CACHE
38
+ if key in self.cache:
39
+ del self.cache[key]
40
+ # CACHE & LRU
41
+ elif len(self.cache) >= self.capacity:
42
+ self.cache.popitem(last=False)
43
+ # -----------
44
+
45
+ # TTL
46
+ self.cache[key] = (value, time() + self.ttl)
47
+ # ---
@@ -0,0 +1,255 @@
1
+ from typing import Callable, Optional, Tuple, Dict
2
+
3
+ from os import getenv
4
+ from json import dumps
5
+
6
+ from pydantic import BaseModel
7
+
8
+ from starlette.middleware.base import BaseHTTPMiddleware
9
+ from fastapi import Request, FastAPI
10
+
11
+ import httpx
12
+
13
+ from agenta.sdk.middleware.cache import TTLLRUCache, CACHE_CAPACITY, CACHE_TTL
14
+ from agenta.sdk.utils.constants import TRUTHY
15
+ from agenta.sdk.utils.exceptions import suppress
16
+
17
+ import agenta as ag
18
+
19
+
20
+ _CACHE_ENABLED = getenv("AGENTA_MIDDLEWARE_CACHE_ENABLED", "true").lower() in TRUTHY
21
+
22
+ _cache = TTLLRUCache(capacity=CACHE_CAPACITY, ttl=CACHE_TTL)
23
+
24
+
25
+ class Reference(BaseModel):
26
+ id: Optional[str] = None
27
+ slug: Optional[str] = None
28
+ version: Optional[str] = None
29
+
30
+
31
+ class ConfigMiddleware(BaseHTTPMiddleware):
32
+ def __init__(self, app: FastAPI):
33
+ super().__init__(app)
34
+
35
+ self.host = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host
36
+ self.application_id = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.app_id
37
+
38
+ async def dispatch(
39
+ self,
40
+ request: Request,
41
+ call_next: Callable,
42
+ ):
43
+ request.state.config = {"parameters": None, "references": None}
44
+
45
+ with suppress():
46
+ parameters, references = await self._get_config(request)
47
+
48
+ request.state.config = {
49
+ "parameters": parameters,
50
+ "references": references,
51
+ }
52
+
53
+ return await call_next(request)
54
+
55
+ # @atimeit
56
+ async def _get_config(self, request: Request) -> Optional[Tuple[Dict, Dict]]:
57
+ credentials = request.state.auth.get("credentials")
58
+
59
+ headers = None
60
+ if credentials:
61
+ headers = {"Authorization": credentials}
62
+
63
+ application_ref = await self._parse_application_ref(request)
64
+ variant_ref = await self._parse_variant_ref(request)
65
+ environment_ref = await self._parse_environment_ref(request)
66
+
67
+ refs = {}
68
+ if application_ref:
69
+ refs["application_ref"] = application_ref.model_dump()
70
+ if variant_ref:
71
+ refs["variant_ref"] = variant_ref.model_dump()
72
+ if environment_ref:
73
+ refs["environment_ref"] = environment_ref.model_dump()
74
+
75
+ if not refs:
76
+ return None, None
77
+
78
+ _hash = dumps(
79
+ {
80
+ "headers": headers,
81
+ "refs": refs,
82
+ },
83
+ sort_keys=True,
84
+ )
85
+
86
+ if _CACHE_ENABLED:
87
+ config_cache = _cache.get(_hash)
88
+
89
+ if config_cache:
90
+ parameters = config_cache.get("parameters")
91
+ references = config_cache.get("references")
92
+
93
+ return parameters, references
94
+
95
+ config = None
96
+ async with httpx.AsyncClient() as client:
97
+ response = await client.post(
98
+ f"{self.host}/api/variants/configs/fetch",
99
+ headers=headers,
100
+ json=refs,
101
+ )
102
+
103
+ if response.status_code != 200:
104
+ return None, None
105
+
106
+ config = response.json()
107
+
108
+ if not config:
109
+ _cache.put(_hash, {"parameters": None, "references": None})
110
+
111
+ return None, None
112
+
113
+ parameters = config.get("params")
114
+
115
+ references = {}
116
+
117
+ for ref_key in ["application_ref", "variant_ref", "environment_ref"]:
118
+ refs = config.get(ref_key)
119
+ if refs:
120
+ ref_prefix = ref_key.split("_", maxsplit=1)[0]
121
+
122
+ for ref_part_key in ["id", "slug", "version"]:
123
+ ref_part = refs.get(ref_part_key)
124
+
125
+ if ref_part:
126
+ references[ref_prefix + "." + ref_part_key] = ref_part
127
+
128
+ _cache.put(_hash, {"parameters": parameters, "references": references})
129
+
130
+ return parameters, references
131
+
132
+ async def _parse_application_ref(
133
+ self,
134
+ request: Request,
135
+ ) -> Optional[Reference]:
136
+ baggage = request.state.otel.get("baggage") if request.state.otel else {}
137
+
138
+ body = {}
139
+ try:
140
+ body = await request.json()
141
+ except: # pylint: disable=bare-except
142
+ pass
143
+
144
+ application_id = (
145
+ # CLEANEST
146
+ baggage.get("application_id")
147
+ # ALTERNATIVE
148
+ or request.query_params.get("application_id")
149
+ # LEGACY
150
+ or request.query_params.get("app_id")
151
+ or self.application_id
152
+ )
153
+ application_slug = (
154
+ # CLEANEST
155
+ baggage.get("application_slug")
156
+ # ALTERNATIVE
157
+ or request.query_params.get("application_slug")
158
+ # LEGACY
159
+ or request.query_params.get("app_slug")
160
+ or body.get("app")
161
+ )
162
+
163
+ if not any([application_id, application_slug]):
164
+ return None
165
+
166
+ return Reference(
167
+ id=application_id,
168
+ slug=application_slug,
169
+ )
170
+
171
+ async def _parse_variant_ref(
172
+ self,
173
+ request: Request,
174
+ ) -> Optional[Reference]:
175
+ baggage = request.state.otel.get("baggage") if request.state.otel else {}
176
+
177
+ body = {}
178
+ try:
179
+ body = await request.json()
180
+ except: # pylint: disable=bare-except
181
+ pass
182
+
183
+ variant_id = (
184
+ # CLEANEST
185
+ baggage.get("variant_id")
186
+ # ALTERNATIVE
187
+ or request.query_params.get("variant_id")
188
+ )
189
+ variant_slug = (
190
+ # CLEANEST
191
+ baggage.get("variant_slug")
192
+ # ALTERNATIVE
193
+ or request.query_params.get("variant_slug")
194
+ # LEGACY
195
+ or request.query_params.get("config")
196
+ or body.get("config")
197
+ )
198
+ variant_version = (
199
+ # CLEANEST
200
+ baggage.get("variant_version")
201
+ # ALTERNATIVE
202
+ or request.query_params.get("variant_version")
203
+ )
204
+
205
+ if not any([variant_id, variant_slug, variant_version]):
206
+ return None
207
+
208
+ return Reference(
209
+ id=variant_id,
210
+ slug=variant_slug,
211
+ version=variant_version,
212
+ )
213
+
214
+ async def _parse_environment_ref(
215
+ self,
216
+ request: Request,
217
+ ) -> Optional[Reference]:
218
+ baggage = request.state.otel.get("baggage") if request.state.otel else {}
219
+
220
+ body = {}
221
+ try:
222
+ body = await request.json()
223
+ except: # pylint: disable=bare-except
224
+ pass
225
+
226
+ environment_id = (
227
+ # CLEANEST
228
+ baggage.get("environment_id")
229
+ # ALTERNATIVE
230
+ or request.query_params.get("environment_id")
231
+ )
232
+ environment_slug = (
233
+ # CLEANEST
234
+ baggage.get("environment_slug")
235
+ # ALTERNATIVE
236
+ or request.query_params.get("environment_slug")
237
+ # LEGACY
238
+ or request.query_params.get("environment")
239
+ or body.get("environment")
240
+ )
241
+ environment_version = (
242
+ # CLEANEST
243
+ baggage.get("environment_version")
244
+ # ALTERNATIVE
245
+ or request.query_params.get("environment_version")
246
+ )
247
+
248
+ if not any([environment_id, environment_slug, environment_version]):
249
+ return None
250
+
251
+ return Reference(
252
+ id=environment_id,
253
+ slug=environment_slug,
254
+ version=environment_version,
255
+ )
@@ -0,0 +1,29 @@
1
+ from os import getenv
2
+
3
+ from starlette.types import ASGIApp, Receive, Scope, Send
4
+ from fastapi.middleware.cors import CORSMiddleware as _CORSMiddleware
5
+
6
+ _TRUTHY = {"true", "1", "t", "y", "yes", "on", "enable", "enabled"}
7
+ _USE_CORS = getenv("AGENTA_USE_CORS", "enable").lower() in _TRUTHY
8
+
9
+
10
+ class CORSMiddleware(_CORSMiddleware):
11
+ def __init__(self, app: ASGIApp):
12
+ self.app = app
13
+
14
+ if _USE_CORS:
15
+ super().__init__(
16
+ app=app,
17
+ allow_origins=["*"],
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ allow_credentials=True,
21
+ expose_headers=None,
22
+ max_age=None,
23
+ )
24
+
25
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
26
+ if _USE_CORS:
27
+ return await super().__call__(scope, receive, send)
28
+
29
+ return await self.app(scope, receive, send)