agenta 0.52.6__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 (271) 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/vault/raw_client.py +4 -4
  176. agenta/client/backend/workspace/client.py +2 -2
  177. agenta/client/client.py +102 -88
  178. agenta/sdk/__init__.py +52 -3
  179. agenta/sdk/agenta_init.py +43 -16
  180. agenta/sdk/assets.py +23 -15
  181. agenta/sdk/context/serving.py +20 -8
  182. agenta/sdk/context/tracing.py +40 -22
  183. agenta/sdk/contexts/__init__.py +0 -0
  184. agenta/sdk/contexts/routing.py +38 -0
  185. agenta/sdk/contexts/running.py +57 -0
  186. agenta/sdk/contexts/tracing.py +86 -0
  187. agenta/sdk/decorators/__init__.py +1 -0
  188. agenta/sdk/decorators/routing.py +284 -0
  189. agenta/sdk/decorators/running.py +692 -98
  190. agenta/sdk/decorators/serving.py +20 -21
  191. agenta/sdk/decorators/tracing.py +176 -131
  192. agenta/sdk/engines/__init__.py +0 -0
  193. agenta/sdk/engines/running/__init__.py +0 -0
  194. agenta/sdk/engines/running/utils.py +17 -0
  195. agenta/sdk/engines/tracing/__init__.py +1 -0
  196. agenta/sdk/engines/tracing/attributes.py +185 -0
  197. agenta/sdk/engines/tracing/conventions.py +49 -0
  198. agenta/sdk/engines/tracing/exporters.py +130 -0
  199. agenta/sdk/engines/tracing/inline.py +1154 -0
  200. agenta/sdk/engines/tracing/processors.py +190 -0
  201. agenta/sdk/engines/tracing/propagation.py +102 -0
  202. agenta/sdk/engines/tracing/spans.py +136 -0
  203. agenta/sdk/engines/tracing/tracing.py +324 -0
  204. agenta/sdk/evaluations/__init__.py +2 -0
  205. agenta/sdk/evaluations/metrics.py +37 -0
  206. agenta/sdk/evaluations/preview/__init__.py +0 -0
  207. agenta/sdk/evaluations/preview/evaluate.py +765 -0
  208. agenta/sdk/evaluations/preview/utils.py +861 -0
  209. agenta/sdk/evaluations/results.py +66 -0
  210. agenta/sdk/evaluations/runs.py +153 -0
  211. agenta/sdk/evaluations/scenarios.py +48 -0
  212. agenta/sdk/litellm/litellm.py +12 -0
  213. agenta/sdk/litellm/mockllm.py +6 -8
  214. agenta/sdk/litellm/mocks/__init__.py +5 -5
  215. agenta/sdk/managers/applications.py +304 -0
  216. agenta/sdk/managers/config.py +2 -2
  217. agenta/sdk/managers/evaluations.py +0 -0
  218. agenta/sdk/managers/evaluators.py +303 -0
  219. agenta/sdk/managers/secrets.py +161 -24
  220. agenta/sdk/managers/shared.py +3 -1
  221. agenta/sdk/managers/testsets.py +441 -0
  222. agenta/sdk/managers/vault.py +3 -3
  223. agenta/sdk/middleware/auth.py +0 -176
  224. agenta/sdk/middleware/config.py +27 -9
  225. agenta/sdk/middleware/vault.py +204 -9
  226. agenta/sdk/middlewares/__init__.py +0 -0
  227. agenta/sdk/middlewares/routing/__init__.py +0 -0
  228. agenta/sdk/middlewares/routing/auth.py +263 -0
  229. agenta/sdk/middlewares/routing/cors.py +30 -0
  230. agenta/sdk/middlewares/routing/otel.py +29 -0
  231. agenta/sdk/middlewares/running/__init__.py +0 -0
  232. agenta/sdk/middlewares/running/normalizer.py +321 -0
  233. agenta/sdk/middlewares/running/resolver.py +161 -0
  234. agenta/sdk/middlewares/running/vault.py +140 -0
  235. agenta/sdk/models/__init__.py +0 -0
  236. agenta/sdk/models/blobs.py +33 -0
  237. agenta/sdk/models/evaluations.py +119 -0
  238. agenta/sdk/models/git.py +126 -0
  239. agenta/sdk/models/shared.py +167 -0
  240. agenta/sdk/models/testsets.py +163 -0
  241. agenta/sdk/models/tracing.py +202 -0
  242. agenta/sdk/models/workflows.py +753 -0
  243. agenta/sdk/tracing/attributes.py +4 -4
  244. agenta/sdk/tracing/exporters.py +67 -17
  245. agenta/sdk/tracing/inline.py +37 -45
  246. agenta/sdk/tracing/processors.py +97 -0
  247. agenta/sdk/tracing/propagation.py +3 -1
  248. agenta/sdk/tracing/spans.py +4 -0
  249. agenta/sdk/tracing/tracing.py +13 -15
  250. agenta/sdk/types.py +222 -22
  251. agenta/sdk/utils/cache.py +1 -1
  252. agenta/sdk/utils/client.py +38 -0
  253. agenta/sdk/utils/helpers.py +13 -12
  254. agenta/sdk/utils/logging.py +18 -78
  255. agenta/sdk/utils/references.py +23 -0
  256. agenta/sdk/workflows/builtin.py +600 -0
  257. agenta/sdk/workflows/configurations.py +22 -0
  258. agenta/sdk/workflows/errors.py +292 -0
  259. agenta/sdk/workflows/handlers.py +1791 -0
  260. agenta/sdk/workflows/interfaces.py +948 -0
  261. agenta/sdk/workflows/sandbox.py +118 -0
  262. agenta/sdk/workflows/utils.py +303 -6
  263. {agenta-0.52.6.dist-info → agenta-0.63.2.dist-info}/METADATA +37 -33
  264. agenta-0.63.2.dist-info/RECORD +421 -0
  265. {agenta-0.52.6.dist-info → agenta-0.63.2.dist-info}/WHEEL +1 -1
  266. agenta/sdk/middleware/adapt.py +0 -253
  267. agenta/sdk/middleware/base.py +0 -40
  268. agenta/sdk/middleware/flags.py +0 -40
  269. agenta/sdk/workflows/types.py +0 -472
  270. agenta-0.52.6.dist-info/RECORD +0 -371
  271. /agenta/sdk/{workflows → engines/running}/registry.py +0 -0
@@ -6,23 +6,47 @@ import httpx
6
6
  from fastapi import FastAPI, Request
7
7
  from starlette.middleware.base import BaseHTTPMiddleware
8
8
 
9
+ from agenta.sdk.utils.logging import get_module_logger
9
10
  from agenta.sdk.utils.constants import TRUTHY
10
11
  from agenta.sdk.utils.cache import TTLLRUCache
11
12
  from agenta.sdk.utils.exceptions import suppress, display_exception
12
13
  from agenta.client.backend.types import SecretDto as SecretDTO
13
14
  from agenta.client.backend.types import (
14
- StandardProviderKind,
15
15
  StandardProviderDto as StandardProviderDTO,
16
16
  StandardProviderSettingsDto as StandardProviderSettingsDTO,
17
17
  )
18
18
 
19
19
  import agenta as ag
20
20
 
21
-
22
- _PROVIDER_KINDS = []
23
-
24
- for provider_kind in StandardProviderKind.__args__[0].__args__: # type: ignore
25
- _PROVIDER_KINDS.append(provider_kind)
21
+ log = get_module_logger(__name__)
22
+
23
+
24
+ AGENTA_RUNTIME_PREFIX = getenv("AGENTA_RUNTIME_PREFIX", "")
25
+
26
+ _ALWAYS_ALLOW_LIST = [
27
+ f"{AGENTA_RUNTIME_PREFIX}/health",
28
+ f"{AGENTA_RUNTIME_PREFIX}/openapi.json",
29
+ ]
30
+
31
+ _PROVIDER_KINDS = [
32
+ "openai",
33
+ "cohere",
34
+ "anyscale",
35
+ "deepinfra",
36
+ "alephalpha",
37
+ "groq",
38
+ "mistral",
39
+ "mistralai",
40
+ "anthropic",
41
+ "perplexityai",
42
+ "togetherai",
43
+ "openrouter",
44
+ "gemini",
45
+ ]
46
+
47
+ _AUTH_ENABLED = (
48
+ getenv("AGENTA_SERVICE_MIDDLEWARE_AUTH_ENABLED", "true").lower() in TRUTHY
49
+ )
26
50
 
27
51
  _CACHE_ENABLED = (
28
52
  getenv("AGENTA_SERVICE_MIDDLEWARE_CACHE_ENABLED", "true").lower() in TRUTHY
@@ -31,12 +55,27 @@ _CACHE_ENABLED = (
31
55
  _cache = TTLLRUCache()
32
56
 
33
57
 
58
+ class DenyException(Exception):
59
+ def __init__(
60
+ self,
61
+ status_code: int = 403,
62
+ content: str = "Forbidden",
63
+ ) -> None:
64
+ super().__init__()
65
+
66
+ self.status_code = status_code
67
+ self.content = content
68
+
69
+
34
70
  class VaultMiddleware(BaseHTTPMiddleware):
35
71
  def __init__(self, app: FastAPI):
36
72
  super().__init__(app)
37
73
 
38
74
  self.host = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host
39
75
 
76
+ self.scope_type = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.scope_type
77
+ self.scope_id = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.scope_id
78
+
40
79
  async def dispatch(
41
80
  self,
42
81
  request: Request,
@@ -74,8 +113,12 @@ class VaultMiddleware(BaseHTTPMiddleware):
74
113
  return secrets
75
114
 
76
115
  local_secrets: List[Dict[str, Any]] = []
116
+ allow_secrets = True
77
117
 
78
118
  try:
119
+ if not request.url.path in _ALWAYS_ALLOW_LIST:
120
+ await self._allow_local_secrets(credentials)
121
+
79
122
  for provider_kind in _PROVIDER_KINDS:
80
123
  provider = provider_kind
81
124
  key_name = f"{provider.upper()}_API_KEY"
@@ -85,7 +128,7 @@ class VaultMiddleware(BaseHTTPMiddleware):
85
128
  continue
86
129
 
87
130
  secret = SecretDTO(
88
- kind="provider_kind", # type: ignore
131
+ kind="provider_key", # type: ignore
89
132
  data=StandardProviderDTO(
90
133
  kind=provider,
91
134
  provider=StandardProviderSettingsDTO(key=key),
@@ -93,6 +136,9 @@ class VaultMiddleware(BaseHTTPMiddleware):
93
136
  )
94
137
 
95
138
  local_secrets.append(secret.model_dump())
139
+ except DenyException as e: # pylint: disable=bare-except
140
+ log.warning(f"Agenta [secrets] {e.status_code}: {e.content}")
141
+ allow_secrets = False
96
142
  except: # pylint: disable=bare-except
97
143
  display_exception("Vault: Local Secrets Exception")
98
144
 
@@ -101,7 +147,7 @@ class VaultMiddleware(BaseHTTPMiddleware):
101
147
  try:
102
148
  async with httpx.AsyncClient() as client:
103
149
  response = await client.get(
104
- f"{self.host}/api/vault/v1/secrets",
150
+ f"{self.host}/api/vault/v1/secrets/",
105
151
  headers=headers,
106
152
  )
107
153
 
@@ -133,6 +179,155 @@ class VaultMiddleware(BaseHTTPMiddleware):
133
179
 
134
180
  secrets = standard_secrets + custom_secrets
135
181
 
136
- _cache.put(_hash, {"secrets": secrets})
182
+ if not allow_secrets:
183
+ _cache.put(_hash, {"secrets": secrets})
137
184
 
138
185
  return secrets
186
+
187
+ async def _allow_local_secrets(self, credentials):
188
+ try:
189
+ if not _AUTH_ENABLED:
190
+ return
191
+
192
+ if not credentials:
193
+ raise DenyException(
194
+ status_code=401,
195
+ content="Invalid credentials. Please check your credentials or login again.",
196
+ )
197
+
198
+ # HEADERS
199
+ headers = {"Authorization": credentials}
200
+ # PARAMS
201
+ params = {}
202
+ ## SCOPE
203
+ if self.scope_type and self.scope_id:
204
+ params["scope_type"] = self.scope_type
205
+ params["scope_id"] = self.scope_id
206
+ ## ACTION
207
+ params["action"] = "view_secret"
208
+ ## RESOURCE
209
+ params["resource_type"] = "local_secrets"
210
+
211
+ _hash = dumps(
212
+ {
213
+ "headers": headers,
214
+ "params": params,
215
+ },
216
+ sort_keys=True,
217
+ )
218
+
219
+ access = None
220
+
221
+ if _CACHE_ENABLED:
222
+ access = _cache.get(_hash)
223
+
224
+ if isinstance(access, Exception):
225
+ raise access
226
+
227
+ try:
228
+ async with httpx.AsyncClient() as client:
229
+ try:
230
+ response = await client.get(
231
+ f"{self.host}/api/permissions/verify",
232
+ headers=headers,
233
+ params=params,
234
+ timeout=30.0,
235
+ )
236
+ except httpx.TimeoutException as exc:
237
+ # log.debug(f"Timeout error while verify secrets access: {exc}")
238
+ raise DenyException(
239
+ status_code=504,
240
+ content=f"Could not verify secrets access: connection to {self.host} timed out. Please check your network connection.",
241
+ ) from exc
242
+ except httpx.ConnectError as exc:
243
+ # log.debug(f"Connection error while verify secrets access: {exc}")
244
+ raise DenyException(
245
+ status_code=503,
246
+ content=f"Could not verify secrets access: connection to {self.host} failed. Please check if agenta is available.",
247
+ ) from exc
248
+ except httpx.NetworkError as exc:
249
+ # log.debug(f"Network error while verify secrets access: {exc}")
250
+ raise DenyException(
251
+ status_code=503,
252
+ content=f"Could not verify secrets access: connection to {self.host} failed. Please check your network connection.",
253
+ ) from exc
254
+ except httpx.HTTPError as exc:
255
+ # log.debug(f"HTTP error while verify secrets access: {exc}")
256
+ raise DenyException(
257
+ status_code=502,
258
+ content=f"Could not verify secrets access: connection to {self.host} failed. Please check if agenta is available.",
259
+ ) from exc
260
+
261
+ if response.status_code == 401:
262
+ # log.debug("Agenta returned 401 - Invalid credentials")
263
+ raise DenyException(
264
+ status_code=401,
265
+ content="Invalid credentials. Please check your credentials or login again.",
266
+ )
267
+ elif response.status_code == 403:
268
+ # log.debug("Agenta returned 403 - Permission denied")
269
+ raise DenyException(
270
+ status_code=403,
271
+ content="Out of credits. Please set your LLM provider API keys or contact support.",
272
+ )
273
+ elif response.status_code != 200:
274
+ # log.debug(
275
+ # f"Agenta returned {response.status_code} - Unexpected status code"
276
+ # )
277
+ raise DenyException(
278
+ status_code=500,
279
+ content=f"Could not verify secrets access: {self.host} returned unexpected status code {response.status_code}. Please try again later or contact support if the issue persists.",
280
+ )
281
+
282
+ try:
283
+ auth = response.json()
284
+ except ValueError as exc:
285
+ # log.debug(f"Agenta returned invalid JSON response: {exc}")
286
+ raise DenyException(
287
+ status_code=500,
288
+ content=f"Could not verify secrets access: {self.host} returned unexpected invalid JSON response. Please try again later or contact support if the issue persists.",
289
+ ) from exc
290
+
291
+ if not isinstance(auth, dict):
292
+ # log.debug(
293
+ # f"Agenta returned invalid response format: {type(auth)}"
294
+ # )
295
+ raise DenyException(
296
+ status_code=500,
297
+ content=f"Could not verify secrets access: {self.host} returned unexpected invalid response format. Please try again later or contact support if the issue persists.",
298
+ )
299
+
300
+ effect = auth.get("effect")
301
+
302
+ access = effect == "allow"
303
+
304
+ if effect != "allow":
305
+ # log.debug("Access denied by Agenta - effect: {effect}")
306
+ raise DenyException(
307
+ status_code=403,
308
+ content="Out of credits. Please set your LLM provider API keys or contact support.",
309
+ )
310
+
311
+ return
312
+
313
+ except DenyException as deny:
314
+ _cache.put(_hash, deny)
315
+
316
+ raise deny
317
+ except Exception as exc: # pylint: disable=bare-except
318
+ # log.debug(
319
+ # f"Unexpected error while verifying credentials (remote): {exc}"
320
+ # )
321
+ raise DenyException(
322
+ status_code=500,
323
+ content=f"Could not verify credentials: unexpected error - {str(exc)}. Please try again later or contact support if the issue persists.",
324
+ ) from exc
325
+
326
+ except DenyException as deny:
327
+ raise deny
328
+ except Exception as exc:
329
+ # log.debug(f"Unexpected error while verifying credentials (local): {exc}")
330
+ raise DenyException(
331
+ status_code=500,
332
+ content=f"Could not verify credentials: unexpected error - {str(exc)}. Please try again later or contact support if the issue persists.",
333
+ ) from exc
File without changes
File without changes
@@ -0,0 +1,263 @@
1
+ from typing import Callable, Optional
2
+ from os import getenv
3
+ from json import dumps
4
+
5
+ import httpx
6
+
7
+ from starlette.types import ASGIApp
8
+ from starlette.middleware.base import BaseHTTPMiddleware
9
+ from fastapi import Request
10
+ from fastapi.responses import JSONResponse
11
+
12
+ from agenta.sdk.utils.logging import get_module_logger
13
+ from agenta.sdk.utils.exceptions import display_exception
14
+ from agenta.sdk.utils.cache import TTLLRUCache
15
+ from agenta.sdk.utils.constants import TRUTHY
16
+
17
+ # import agenta as ag
18
+
19
+
20
+ log = get_module_logger(__name__)
21
+
22
+ AGENTA_RUNTIME_PREFIX = getenv("AGENTA_RUNTIME_PREFIX", "")
23
+
24
+ _AUTH_ENABLED = (
25
+ getenv("AGENTA_SERVICE_MIDDLEWARE_AUTH_ENABLED", "true").lower() in TRUTHY
26
+ )
27
+
28
+ _CACHE_ENABLED = (
29
+ getenv("AGENTA_SERVICE_MIDDLEWARE_CACHE_ENABLED", "true").lower() in TRUTHY
30
+ )
31
+
32
+ _ALWAYS_ALLOW_LIST = [f"{AGENTA_RUNTIME_PREFIX}/health"]
33
+
34
+ _cache = TTLLRUCache()
35
+
36
+
37
+ class DenyResponse(JSONResponse):
38
+ def __init__(
39
+ self,
40
+ status_code: int = 401,
41
+ detail: str = "Unauthorized",
42
+ ) -> None:
43
+ super().__init__(
44
+ status_code=status_code,
45
+ content={"detail": detail},
46
+ )
47
+
48
+
49
+ class DenyException(Exception):
50
+ def __init__(
51
+ self,
52
+ status_code: int = 401,
53
+ content: str = "Unauthorized",
54
+ ) -> None:
55
+ super().__init__()
56
+
57
+ self.status_code = status_code
58
+ self.content = content
59
+
60
+
61
+ class AuthMiddleware(BaseHTTPMiddleware):
62
+ def __init__(self, app: ASGIApp, **options):
63
+ super().__init__(app)
64
+
65
+ # self.host = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host
66
+
67
+ # self.scope_type = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.scope_type
68
+ # self.scope_id = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.scope_id
69
+
70
+ async def dispatch(self, request: Request, call_next: Callable):
71
+ try:
72
+ if request.url.path in _ALWAYS_ALLOW_LIST:
73
+ request.state.auth = {}
74
+
75
+ else:
76
+ credentials = await self._get_credentials(request)
77
+
78
+ request.state.auth = {"credentials": credentials}
79
+
80
+ return await call_next(request)
81
+
82
+ except DenyException as deny:
83
+ display_exception("Auth Middleware Exception")
84
+
85
+ return DenyResponse(
86
+ status_code=deny.status_code,
87
+ detail=deny.content,
88
+ )
89
+
90
+ except: # pylint: disable=bare-except
91
+ display_exception("Auth Middleware Exception")
92
+
93
+ return DenyResponse(
94
+ status_code=500,
95
+ detail="Auth: Unexpected Error.",
96
+ )
97
+
98
+ async def _get_credentials(self, request: Request) -> Optional[str]:
99
+ try:
100
+ if not _AUTH_ENABLED:
101
+ return request.headers.get("authorization", None)
102
+
103
+ # HEADERS
104
+ authorization = request.headers.get("authorization", None)
105
+ headers = {"Authorization": authorization} if authorization else None
106
+
107
+ # COOKIES
108
+ access_token = request.cookies.get("sAccessToken", None)
109
+ cookies = {"sAccessToken": access_token} if access_token else None
110
+
111
+ # if not headers and not cookies:
112
+ # log.debug("No auth header nor auth cookie found in the request")
113
+
114
+ # PARAMS
115
+ params = {}
116
+ ## PROJECT_ID
117
+ project_id = (
118
+ # CLEANEST
119
+ request.state.otel["baggage"].get("project_id")
120
+ # ALTERNATIVE
121
+ or request.query_params.get("project_id")
122
+ )
123
+ # if not project_id:
124
+ # log.debug("No project ID found in request")
125
+
126
+ if project_id:
127
+ params["project_id"] = project_id
128
+ ## SCOPE
129
+ if self.scope_type and self.scope_id:
130
+ params["scope_type"] = self.scope_type
131
+ params["scope_id"] = self.scope_id
132
+ ## ACTION
133
+ params["action"] = "run_service"
134
+ ## RESOURCE
135
+ params["resource_type"] = "service"
136
+ # params["resource_id"] = None
137
+
138
+ _hash = dumps(
139
+ {
140
+ "headers": headers,
141
+ "cookies": cookies,
142
+ "params": params,
143
+ },
144
+ sort_keys=True,
145
+ )
146
+
147
+ if _CACHE_ENABLED:
148
+ credentials = _cache.get(_hash)
149
+
150
+ if credentials:
151
+ # log.debug("Using cached credentials")
152
+ return credentials
153
+
154
+ try:
155
+ async with httpx.AsyncClient() as client:
156
+ try:
157
+ response = await client.get(
158
+ f"{self.host}/api/permissions/verify",
159
+ headers=headers,
160
+ cookies=cookies,
161
+ params=params,
162
+ timeout=30.0,
163
+ )
164
+ except httpx.TimeoutException as exc:
165
+ # log.debug(f"Timeout error while verify credentials: {exc}")
166
+ raise DenyException(
167
+ status_code=504,
168
+ content=f"Could not verify credentials: connection to {self.host} timed out. Please check your network connection.",
169
+ ) from exc
170
+ except httpx.ConnectError as exc:
171
+ # log.debug(f"Connection error while verify credentials: {exc}")
172
+ raise DenyException(
173
+ status_code=503,
174
+ content=f"Could not verify credentials: connection to {self.host} failed. Please check if agenta is available.",
175
+ ) from exc
176
+ except httpx.NetworkError as exc:
177
+ # log.debug(f"Network error while verify credentials: {exc}")
178
+ raise DenyException(
179
+ status_code=503,
180
+ content=f"Could not verify credentials: connection to {self.host} failed. Please check your network connection.",
181
+ ) from exc
182
+ except httpx.HTTPError as exc:
183
+ # log.debug(f"HTTP error while verify credentials: {exc}")
184
+ raise DenyException(
185
+ status_code=502,
186
+ content=f"Could not verify credentials: connection to {self.host} failed. Please check if agenta is available.",
187
+ ) from exc
188
+
189
+ if response.status_code == 401:
190
+ # log.debug("Agenta returned 401 - Invalid credentials")
191
+ raise DenyException(
192
+ status_code=401,
193
+ content="Invalid credentials. Please check your credentials or login again.",
194
+ )
195
+ elif response.status_code == 403:
196
+ # log.debug("Agenta returned 403 - Permission denied")
197
+ raise DenyException(
198
+ status_code=403,
199
+ content="Permission denied. Please check your permissions or contact your administrator.",
200
+ )
201
+ elif response.status_code != 200:
202
+ # log.debug(
203
+ # f"Agenta returned {response.status_code} - Unexpected status code"
204
+ # )
205
+ raise DenyException(
206
+ status_code=500,
207
+ content=f"Could not verify credentials: {self.host} returned unexpected status code {response.status_code}. Please try again later or contact support if the issue persists.",
208
+ )
209
+
210
+ try:
211
+ auth = response.json()
212
+ except ValueError as exc:
213
+ # log.debug(f"Agenta returned invalid JSON response: {exc}")
214
+ raise DenyException(
215
+ status_code=500,
216
+ content=f"Could not verify credentials: {self.host} returned unexpected invalid JSON response. Please try again later or contact support if the issue persists.",
217
+ ) from exc
218
+
219
+ if not isinstance(auth, dict):
220
+ # log.debug(
221
+ # f"Agenta returned invalid response format: {type(auth)}"
222
+ # )
223
+ raise DenyException(
224
+ status_code=500,
225
+ content=f"Could not verify credentials: {self.host} returned unexpected invalid response format. Please try again later or contact support if the issue persists.",
226
+ )
227
+
228
+ effect = auth.get("effect")
229
+ if effect != "allow":
230
+ # log.debug("Access denied by Agenta - effect: {effect}")
231
+ raise DenyException(
232
+ status_code=403,
233
+ content="Permission denied. Please check your permissions or contact your administrator.",
234
+ )
235
+
236
+ credentials = auth.get("credentials")
237
+
238
+ # if not credentials:
239
+ # log.debug("No credentials found in the response")
240
+
241
+ _cache.put(_hash, credentials)
242
+
243
+ return credentials
244
+
245
+ except DenyException as deny:
246
+ raise deny
247
+ except Exception as exc: # pylint: disable=bare-except
248
+ # log.debug(
249
+ # f"Unexpected error while verifying credentials (remote): {exc}"
250
+ # )
251
+ raise DenyException(
252
+ status_code=500,
253
+ content=f"Could not verify credentials: unexpected error - {str(exc)}. Please try again later or contact support if the issue persists.",
254
+ ) from exc
255
+
256
+ except DenyException as deny:
257
+ raise deny
258
+ except Exception as exc:
259
+ # log.debug(f"Unexpected error while verifying credentials (local): {exc}")
260
+ raise DenyException(
261
+ status_code=500,
262
+ content=f"Could not verify credentials: unexpected error - {str(exc)}. Please try again later or contact support if the issue persists.",
263
+ ) from exc
@@ -0,0 +1,30 @@
1
+ from os import getenv
2
+
3
+ from starlette.types import ASGIApp, Receive, Scope, Send
4
+ from fastapi.middleware.cors import CORSMiddleware as BaseCORSMiddleware
5
+
6
+ from agenta.sdk.utils.constants import TRUTHY
7
+
8
+ _USE_CORS = getenv("AGENTA_USE_CORS", "enable").lower() in TRUTHY
9
+
10
+
11
+ class CORSMiddleware(BaseCORSMiddleware):
12
+ def __init__(self, app: ASGIApp, **options):
13
+ self.app = app
14
+
15
+ if _USE_CORS:
16
+ super().__init__(
17
+ app=app,
18
+ allow_origins=["*"],
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ allow_credentials=True,
22
+ expose_headers=None,
23
+ max_age=None,
24
+ )
25
+
26
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
27
+ if _USE_CORS:
28
+ return await super().__call__(scope, receive, send)
29
+
30
+ return await self.app(scope, receive, send)
@@ -0,0 +1,29 @@
1
+ from typing import Callable
2
+
3
+ from starlette.types import ASGIApp
4
+ from starlette.middleware.base import BaseHTTPMiddleware
5
+ from fastapi import Request
6
+
7
+ from agenta.sdk.utils.logging import get_module_logger
8
+ from agenta.sdk.utils.exceptions import suppress
9
+ from agenta.sdk.engines.tracing.propagation import extract
10
+
11
+
12
+ log = get_module_logger(__name__)
13
+
14
+
15
+ class OTelMiddleware(BaseHTTPMiddleware):
16
+ async def dispatch(self, request: Request, call_next: Callable):
17
+ request.state.otel = {"baggage": {}, "traceparent": None}
18
+
19
+ headers: dict = dict(request.headers)
20
+
21
+ if "newrelic" in headers:
22
+ headers["traceparent"] = None
23
+
24
+ with suppress():
25
+ _, traceparent, baggage = extract(headers)
26
+
27
+ request.state.otel = {"baggage": baggage, "traceparent": traceparent}
28
+
29
+ return await call_next(request)
File without changes