agenta 0.30.0a1__py3-none-any.whl → 0.30.0a3__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.
- agenta/__init__.py +1 -0
- agenta/client/backend/__init__.py +32 -3
- agenta/client/backend/access_control/__init__.py +1 -0
- agenta/client/backend/access_control/client.py +167 -0
- agenta/client/backend/apps/client.py +70 -10
- agenta/client/backend/client.py +61 -45
- agenta/client/backend/configs/client.py +6 -0
- agenta/client/backend/containers/client.py +6 -0
- agenta/client/backend/core/file.py +13 -8
- agenta/client/backend/environments/client.py +6 -0
- agenta/client/backend/evaluations/client.py +14 -1
- agenta/client/backend/evaluators/client.py +24 -0
- agenta/client/backend/observability/client.py +22 -16
- agenta/client/backend/observability_v_1/__init__.py +2 -2
- agenta/client/backend/observability_v_1/client.py +203 -0
- agenta/client/backend/observability_v_1/types/__init__.py +2 -1
- agenta/client/backend/observability_v_1/types/format.py +1 -1
- agenta/client/backend/observability_v_1/types/query_analytics_response.py +7 -0
- agenta/client/backend/scopes/__init__.py +1 -0
- agenta/client/backend/scopes/client.py +114 -0
- agenta/client/backend/testsets/client.py +305 -121
- agenta/client/backend/types/__init__.py +24 -2
- agenta/client/backend/types/analytics_response.py +24 -0
- agenta/client/backend/types/app.py +2 -1
- agenta/client/backend/types/body_import_testset.py +0 -1
- agenta/client/backend/types/bucket_dto.py +26 -0
- agenta/client/backend/types/header_dto.py +22 -0
- agenta/client/backend/types/legacy_analytics_response.py +29 -0
- agenta/client/backend/types/legacy_data_point.py +27 -0
- agenta/client/backend/types/metrics_dto.py +24 -0
- agenta/client/backend/types/permission.py +1 -0
- agenta/client/backend/types/projects_response.py +28 -0
- agenta/client/backend/types/provider_key_dto.py +23 -0
- agenta/client/backend/types/provider_kind.py +21 -0
- agenta/client/backend/types/secret_dto.py +24 -0
- agenta/client/backend/types/secret_kind.py +5 -0
- agenta/client/backend/types/secret_response_dto.py +27 -0
- agenta/client/backend/variants/client.py +66 -0
- agenta/client/backend/vault/__init__.py +1 -0
- agenta/client/backend/vault/client.py +685 -0
- agenta/client/client.py +1 -1
- agenta/sdk/__init__.py +1 -0
- agenta/sdk/agenta_init.py +47 -118
- agenta/sdk/assets.py +57 -46
- agenta/sdk/context/exporting.py +25 -0
- agenta/sdk/context/routing.py +12 -12
- agenta/sdk/context/tracing.py +26 -1
- agenta/sdk/decorators/routing.py +272 -267
- agenta/sdk/decorators/tracing.py +53 -31
- agenta/sdk/managers/config.py +8 -118
- agenta/sdk/managers/secrets.py +38 -0
- agenta/sdk/middleware/auth.py +128 -93
- agenta/sdk/middleware/cache.py +4 -0
- agenta/sdk/middleware/config.py +254 -0
- agenta/sdk/middleware/cors.py +27 -0
- agenta/sdk/middleware/otel.py +40 -0
- agenta/sdk/middleware/vault.py +158 -0
- agenta/sdk/tracing/exporters.py +40 -2
- agenta/sdk/tracing/inline.py +2 -2
- agenta/sdk/tracing/processors.py +11 -3
- agenta/sdk/tracing/tracing.py +14 -12
- agenta/sdk/utils/constants.py +1 -0
- agenta/sdk/utils/exceptions.py +20 -19
- agenta/sdk/utils/globals.py +4 -8
- agenta/sdk/utils/timing.py +58 -0
- {agenta-0.30.0a1.dist-info → agenta-0.30.0a3.dist-info}/METADATA +3 -2
- {agenta-0.30.0a1.dist-info → agenta-0.30.0a3.dist-info}/RECORD +69 -44
- {agenta-0.30.0a1.dist-info → agenta-0.30.0a3.dist-info}/WHEEL +1 -1
- agenta/client/backend/types/lm_providers_enum.py +0 -21
- agenta/sdk/tracing/context.py +0 -24
- {agenta-0.30.0a1.dist-info → agenta-0.30.0a3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
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 = {}
|
|
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
|
+
ref_prefix = ref_key.split("_", maxsplit=1)[0]
|
|
120
|
+
|
|
121
|
+
for ref_part_key in ["id", "slug", "version"]:
|
|
122
|
+
ref_part = refs.get(ref_part_key)
|
|
123
|
+
|
|
124
|
+
if ref_part:
|
|
125
|
+
references[ref_prefix + "." + ref_part_key] = ref_part
|
|
126
|
+
|
|
127
|
+
_cache.put(_hash, {"parameters": parameters, "references": references})
|
|
128
|
+
|
|
129
|
+
return parameters, references
|
|
130
|
+
|
|
131
|
+
async def _parse_application_ref(
|
|
132
|
+
self,
|
|
133
|
+
request: Request,
|
|
134
|
+
) -> Optional[Reference]:
|
|
135
|
+
baggage = request.state.otel.get("baggage") if request.state.otel else {}
|
|
136
|
+
|
|
137
|
+
body = {}
|
|
138
|
+
try:
|
|
139
|
+
body = await request.json()
|
|
140
|
+
except: # pylint: disable=bare-except
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
application_id = (
|
|
144
|
+
# CLEANEST
|
|
145
|
+
baggage.get("application_id")
|
|
146
|
+
# ALTERNATIVE
|
|
147
|
+
or request.query_params.get("application_id")
|
|
148
|
+
# LEGACY
|
|
149
|
+
or request.query_params.get("app_id")
|
|
150
|
+
or self.application_id
|
|
151
|
+
)
|
|
152
|
+
application_slug = (
|
|
153
|
+
# CLEANEST
|
|
154
|
+
baggage.get("application_slug")
|
|
155
|
+
# ALTERNATIVE
|
|
156
|
+
or request.query_params.get("application_slug")
|
|
157
|
+
# LEGACY
|
|
158
|
+
or request.query_params.get("app_slug")
|
|
159
|
+
or body.get("app")
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if not any([application_id, application_slug]):
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
return Reference(
|
|
166
|
+
id=application_id,
|
|
167
|
+
slug=application_slug,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
async def _parse_variant_ref(
|
|
171
|
+
self,
|
|
172
|
+
request: Request,
|
|
173
|
+
) -> Optional[Reference]:
|
|
174
|
+
baggage = request.state.otel.get("baggage") if request.state.otel else {}
|
|
175
|
+
|
|
176
|
+
body = {}
|
|
177
|
+
try:
|
|
178
|
+
body = await request.json()
|
|
179
|
+
except: # pylint: disable=bare-except
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
variant_id = (
|
|
183
|
+
# CLEANEST
|
|
184
|
+
baggage.get("variant_id")
|
|
185
|
+
# ALTERNATIVE
|
|
186
|
+
or request.query_params.get("variant_id")
|
|
187
|
+
)
|
|
188
|
+
variant_slug = (
|
|
189
|
+
# CLEANEST
|
|
190
|
+
baggage.get("variant_slug")
|
|
191
|
+
# ALTERNATIVE
|
|
192
|
+
or request.query_params.get("variant_slug")
|
|
193
|
+
# LEGACY
|
|
194
|
+
or request.query_params.get("config")
|
|
195
|
+
or body.get("config")
|
|
196
|
+
)
|
|
197
|
+
variant_version = (
|
|
198
|
+
# CLEANEST
|
|
199
|
+
baggage.get("variant_version")
|
|
200
|
+
# ALTERNATIVE
|
|
201
|
+
or request.query_params.get("variant_version")
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not any([variant_id, variant_slug, variant_version]):
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
return Reference(
|
|
208
|
+
id=variant_id,
|
|
209
|
+
slug=variant_slug,
|
|
210
|
+
version=variant_version,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
async def _parse_environment_ref(
|
|
214
|
+
self,
|
|
215
|
+
request: Request,
|
|
216
|
+
) -> Optional[Reference]:
|
|
217
|
+
baggage = request.state.otel.get("baggage") if request.state.otel else {}
|
|
218
|
+
|
|
219
|
+
body = {}
|
|
220
|
+
try:
|
|
221
|
+
body = await request.json()
|
|
222
|
+
except: # pylint: disable=bare-except
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
environment_id = (
|
|
226
|
+
# CLEANEST
|
|
227
|
+
baggage.get("environment_id")
|
|
228
|
+
# ALTERNATIVE
|
|
229
|
+
or request.query_params.get("environment_id")
|
|
230
|
+
)
|
|
231
|
+
environment_slug = (
|
|
232
|
+
# CLEANEST
|
|
233
|
+
baggage.get("environment_slug")
|
|
234
|
+
# ALTERNATIVE
|
|
235
|
+
or request.query_params.get("environment_slug")
|
|
236
|
+
# LEGACY
|
|
237
|
+
or request.query_params.get("environment")
|
|
238
|
+
or body.get("environment")
|
|
239
|
+
)
|
|
240
|
+
environment_version = (
|
|
241
|
+
# CLEANEST
|
|
242
|
+
baggage.get("environment_version")
|
|
243
|
+
# ALTERNATIVE
|
|
244
|
+
or request.query_params.get("environment_version")
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if not any([environment_id, environment_slug, environment_version]):
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
return Reference(
|
|
251
|
+
id=environment_id,
|
|
252
|
+
slug=environment_slug,
|
|
253
|
+
version=environment_version,
|
|
254
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
if _USE_CORS:
|
|
13
|
+
super().__init__(
|
|
14
|
+
app=app,
|
|
15
|
+
allow_origins=["*"],
|
|
16
|
+
allow_methods=["*"],
|
|
17
|
+
allow_headers=["*"],
|
|
18
|
+
allow_credentials=True,
|
|
19
|
+
expose_headers=None,
|
|
20
|
+
max_age=None,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
24
|
+
if _USE_CORS:
|
|
25
|
+
return await super().__call__(scope, receive, send)
|
|
26
|
+
|
|
27
|
+
return await self.app(scope, receive, send)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
4
|
+
from fastapi import Request, FastAPI
|
|
5
|
+
|
|
6
|
+
from opentelemetry.baggage.propagation import W3CBaggagePropagator
|
|
7
|
+
|
|
8
|
+
from agenta.sdk.utils.exceptions import suppress
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OTelMiddleware(BaseHTTPMiddleware):
|
|
12
|
+
def __init__(self, app: FastAPI):
|
|
13
|
+
super().__init__(app)
|
|
14
|
+
|
|
15
|
+
async def dispatch(self, request: Request, call_next: Callable):
|
|
16
|
+
request.state.otel = {}
|
|
17
|
+
|
|
18
|
+
with suppress():
|
|
19
|
+
baggage = await self._get_baggage(request)
|
|
20
|
+
|
|
21
|
+
request.state.otel = {"baggage": baggage}
|
|
22
|
+
|
|
23
|
+
return await call_next(request)
|
|
24
|
+
|
|
25
|
+
async def _get_baggage(
|
|
26
|
+
self,
|
|
27
|
+
request,
|
|
28
|
+
):
|
|
29
|
+
_baggage = {"baggage": request.headers.get("Baggage", "")}
|
|
30
|
+
|
|
31
|
+
context = W3CBaggagePropagator().extract(_baggage)
|
|
32
|
+
|
|
33
|
+
baggage = {}
|
|
34
|
+
|
|
35
|
+
if context:
|
|
36
|
+
for partial in context.values():
|
|
37
|
+
for key, value in partial.items():
|
|
38
|
+
baggage[key] = value
|
|
39
|
+
|
|
40
|
+
return baggage
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from os import getenv
|
|
2
|
+
from json import dumps
|
|
3
|
+
from typing import Callable, Dict, Optional, List, Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
7
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
8
|
+
|
|
9
|
+
from agenta.sdk.utils.constants import TRUTHY
|
|
10
|
+
from agenta.client.backend.types.provider_kind import ProviderKind
|
|
11
|
+
from agenta.sdk.utils.exceptions import suppress, display_exception
|
|
12
|
+
from agenta.client.backend.types.secret_dto import SecretDto as SecretDTO
|
|
13
|
+
from agenta.client.backend.types.provider_key_dto import (
|
|
14
|
+
ProviderKeyDto as ProviderKeyDTO,
|
|
15
|
+
)
|
|
16
|
+
from agenta.sdk.middleware.cache import TTLLRUCache, CACHE_CAPACITY, CACHE_TTL
|
|
17
|
+
|
|
18
|
+
import agenta as ag
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ProviderKind (agenta.client.backend.types.provider_kind import ProviderKind) defines a type hint that allows \
|
|
22
|
+
# for a fixed set of string literals representing various provider names, alongside `typing.Any`.
|
|
23
|
+
PROVIDER_KINDS = []
|
|
24
|
+
|
|
25
|
+
# Rationale behind the following:
|
|
26
|
+
# -------------------------------
|
|
27
|
+
# You cannot loop directly over the values in `typing.Literal` because:
|
|
28
|
+
# - `Literal` is not iterable.
|
|
29
|
+
# - `ProviderKind.__args__` includes `Literal` and `Any`, but the actual string values
|
|
30
|
+
# are nested within the `Literal`'s own `__args__` attribute.
|
|
31
|
+
|
|
32
|
+
# To solve this, we programmatically extract the values from `Literal` while retaining
|
|
33
|
+
# the structure of ProviderKind. This ensures:
|
|
34
|
+
# 1. We don't modify the original `ProviderKind` type definition.
|
|
35
|
+
# 2. We dynamically access the literal values for use at runtime when necessary.
|
|
36
|
+
for arg in ProviderKind.__args__: # type: ignore
|
|
37
|
+
if hasattr(arg, "__args__"):
|
|
38
|
+
PROVIDER_KINDS.extend(arg.__args__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_CACHE_ENABLED = getenv("AGENTA_MIDDLEWARE_CACHE_ENABLED", "true").lower() in TRUTHY
|
|
42
|
+
|
|
43
|
+
_cache = TTLLRUCache(capacity=CACHE_CAPACITY, ttl=CACHE_TTL)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class VaultMiddleware(BaseHTTPMiddleware):
|
|
47
|
+
def __init__(self, app: FastAPI):
|
|
48
|
+
super().__init__(app)
|
|
49
|
+
|
|
50
|
+
self.host = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.host
|
|
51
|
+
|
|
52
|
+
def _transform_secrets_response_to_secret_dto(
|
|
53
|
+
self, secrets_list: List[Dict[str, Any]]
|
|
54
|
+
) -> List[Dict[str, Any]]:
|
|
55
|
+
secrets_dto_dict = [
|
|
56
|
+
{
|
|
57
|
+
"kind": secret.get("secret", {}).get("kind"),
|
|
58
|
+
"data": secret.get("secret", {}).get("data", {}),
|
|
59
|
+
}
|
|
60
|
+
for secret in secrets_list
|
|
61
|
+
]
|
|
62
|
+
return secrets_dto_dict
|
|
63
|
+
|
|
64
|
+
async def dispatch(
|
|
65
|
+
self,
|
|
66
|
+
request: Request,
|
|
67
|
+
call_next: Callable,
|
|
68
|
+
):
|
|
69
|
+
request.state.vault = {}
|
|
70
|
+
|
|
71
|
+
with suppress():
|
|
72
|
+
secrets = await self._get_secrets(request)
|
|
73
|
+
|
|
74
|
+
request.state.vault = {"secrets": secrets}
|
|
75
|
+
|
|
76
|
+
return await call_next(request)
|
|
77
|
+
|
|
78
|
+
async def _get_secrets(self, request: Request) -> Optional[Dict]:
|
|
79
|
+
credentials = request.state.auth.get("credentials")
|
|
80
|
+
|
|
81
|
+
headers = None
|
|
82
|
+
if credentials:
|
|
83
|
+
headers = {"Authorization": credentials}
|
|
84
|
+
|
|
85
|
+
_hash = dumps(
|
|
86
|
+
{
|
|
87
|
+
"headers": headers,
|
|
88
|
+
},
|
|
89
|
+
sort_keys=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if _CACHE_ENABLED:
|
|
93
|
+
secrets_cache = _cache.get(_hash)
|
|
94
|
+
|
|
95
|
+
if secrets_cache:
|
|
96
|
+
secrets = secrets_cache.get("secrets")
|
|
97
|
+
|
|
98
|
+
return secrets
|
|
99
|
+
|
|
100
|
+
local_secrets: List[SecretDTO] = []
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
for provider_kind in PROVIDER_KINDS:
|
|
104
|
+
provider = provider_kind
|
|
105
|
+
key_name = f"{provider.upper()}_API_KEY"
|
|
106
|
+
key = getenv(key_name)
|
|
107
|
+
|
|
108
|
+
if not key:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
secret = SecretDTO( # 'kind' attribute in SecretDTO defaults to 'provider_kind'
|
|
112
|
+
data=ProviderKeyDTO(
|
|
113
|
+
provider=provider,
|
|
114
|
+
key=key,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
local_secrets.append(secret.model_dump())
|
|
119
|
+
except: # pylint: disable=bare-except
|
|
120
|
+
display_exception("Vault: Local Secrets Exception")
|
|
121
|
+
|
|
122
|
+
vault_secrets: List[SecretDTO] = []
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
async with httpx.AsyncClient() as client:
|
|
126
|
+
response = await client.get(
|
|
127
|
+
f"{self.host}/api/vault/v1/secrets",
|
|
128
|
+
headers=headers,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if response.status_code != 200:
|
|
132
|
+
vault_secrets = []
|
|
133
|
+
|
|
134
|
+
else:
|
|
135
|
+
secrets = response.json()
|
|
136
|
+
vault_secrets = self._transform_secrets_response_to_secret_dto(
|
|
137
|
+
secrets
|
|
138
|
+
)
|
|
139
|
+
except: # pylint: disable=bare-except
|
|
140
|
+
display_exception("Vault: Vault Secrets Exception")
|
|
141
|
+
|
|
142
|
+
merged_secrets = {}
|
|
143
|
+
|
|
144
|
+
if local_secrets:
|
|
145
|
+
for secret in local_secrets:
|
|
146
|
+
provider = secret["data"]["provider"]
|
|
147
|
+
merged_secrets[provider] = secret
|
|
148
|
+
|
|
149
|
+
if vault_secrets:
|
|
150
|
+
for secret in vault_secrets:
|
|
151
|
+
provider = secret["data"]["provider"]
|
|
152
|
+
merged_secrets[provider] = secret
|
|
153
|
+
|
|
154
|
+
secrets = list(merged_secrets.values())
|
|
155
|
+
|
|
156
|
+
_cache.put(_hash, {"secrets": secrets})
|
|
157
|
+
|
|
158
|
+
return secrets
|
agenta/sdk/tracing/exporters.py
CHANGED
|
@@ -9,6 +9,11 @@ from opentelemetry.sdk.trace.export import (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
from agenta.sdk.utils.exceptions import suppress
|
|
12
|
+
from agenta.sdk.context.exporting import (
|
|
13
|
+
exporting_context_manager,
|
|
14
|
+
exporting_context,
|
|
15
|
+
ExportingContext,
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
class InlineTraceExporter(SpanExporter):
|
|
@@ -58,8 +63,41 @@ class InlineTraceExporter(SpanExporter):
|
|
|
58
63
|
return trace
|
|
59
64
|
|
|
60
65
|
|
|
61
|
-
OTLPSpanExporter
|
|
66
|
+
class OTLPExporter(OTLPSpanExporter):
|
|
67
|
+
_MAX_RETRY_TIMEOUT = 2
|
|
68
|
+
|
|
69
|
+
def __init__(self, *args, credentials: Dict[int, str] = None, **kwargs):
|
|
70
|
+
super().__init__(*args, **kwargs)
|
|
71
|
+
|
|
72
|
+
self.credentials = credentials
|
|
73
|
+
|
|
74
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
|
75
|
+
credentials = None
|
|
76
|
+
|
|
77
|
+
if self.credentials:
|
|
78
|
+
trace_ids = set(span.get_span_context().trace_id for span in spans)
|
|
79
|
+
|
|
80
|
+
if len(trace_ids) == 1:
|
|
81
|
+
trace_id = trace_ids.pop()
|
|
82
|
+
|
|
83
|
+
if trace_id in self.credentials:
|
|
84
|
+
credentials = self.credentials.pop(trace_id)
|
|
85
|
+
|
|
86
|
+
with exporting_context_manager(
|
|
87
|
+
context=ExportingContext(
|
|
88
|
+
credentials=credentials,
|
|
89
|
+
)
|
|
90
|
+
):
|
|
91
|
+
return super().export(spans)
|
|
92
|
+
|
|
93
|
+
def _export(self, serialized_data: bytes):
|
|
94
|
+
credentials = exporting_context.get().credentials
|
|
95
|
+
|
|
96
|
+
if credentials:
|
|
97
|
+
self._session.headers.update({"Authorization": credentials})
|
|
98
|
+
|
|
99
|
+
return super()._export(serialized_data)
|
|
100
|
+
|
|
62
101
|
|
|
63
102
|
ConsoleExporter = ConsoleSpanExporter
|
|
64
103
|
InlineExporter = InlineTraceExporter
|
|
65
|
-
OTLPExporter = OTLPSpanExporter
|
agenta/sdk/tracing/inline.py
CHANGED
|
@@ -101,8 +101,8 @@ class NodeDTO(BaseModel):
|
|
|
101
101
|
Data = Dict[str, Any]
|
|
102
102
|
Metrics = Dict[str, Any]
|
|
103
103
|
Metadata = Dict[str, Any]
|
|
104
|
-
Tags = Dict[str,
|
|
105
|
-
Refs = Dict[str,
|
|
104
|
+
Tags = Dict[str, Any]
|
|
105
|
+
Refs = Dict[str, Any]
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
class LinkDTO(BaseModel):
|
agenta/sdk/tracing/processors.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Optional, Dict, List
|
|
2
2
|
|
|
3
|
+
from opentelemetry.baggage import get_all as get_baggage
|
|
3
4
|
from opentelemetry.context import Context
|
|
4
5
|
from opentelemetry.sdk.trace import Span
|
|
5
6
|
from opentelemetry.sdk.trace.export import (
|
|
@@ -11,8 +12,7 @@ from opentelemetry.sdk.trace.export import (
|
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
from agenta.sdk.utils.logging import log
|
|
14
|
-
|
|
15
|
-
# LOAD CONTEXT, HERE !
|
|
15
|
+
from agenta.sdk.tracing.conventions import Reference
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TraceProcessor(BatchSpanProcessor):
|
|
@@ -43,9 +43,17 @@ class TraceProcessor(BatchSpanProcessor):
|
|
|
43
43
|
span: Span,
|
|
44
44
|
parent_context: Optional[Context] = None,
|
|
45
45
|
) -> None:
|
|
46
|
+
baggage = get_baggage(parent_context)
|
|
47
|
+
|
|
46
48
|
for key in self.references.keys():
|
|
47
49
|
span.set_attribute(f"ag.refs.{key}", self.references[key])
|
|
48
50
|
|
|
51
|
+
for key in baggage.keys():
|
|
52
|
+
if key.startswith("ag.refs."):
|
|
53
|
+
_key = key.replace("ag.refs.", "")
|
|
54
|
+
if _key in [_.value for _ in Reference.__members__.values()]:
|
|
55
|
+
span.set_attribute(key, baggage[key])
|
|
56
|
+
|
|
49
57
|
if span.context.trace_id not in self._registry:
|
|
50
58
|
self._registry[span.context.trace_id] = dict()
|
|
51
59
|
|
|
@@ -89,7 +97,7 @@ class TraceProcessor(BatchSpanProcessor):
|
|
|
89
97
|
ret = super().force_flush(timeout_millis)
|
|
90
98
|
|
|
91
99
|
if not ret:
|
|
92
|
-
log.warning("Agenta
|
|
100
|
+
log.warning("Agenta - Skipping export due to timeout.")
|
|
93
101
|
|
|
94
102
|
def is_ready(
|
|
95
103
|
self,
|
agenta/sdk/tracing/tracing.py
CHANGED
|
@@ -41,6 +41,8 @@ class Tracing(metaclass=Singleton):
|
|
|
41
41
|
self.headers: Dict[str, str] = dict()
|
|
42
42
|
# REFERENCES
|
|
43
43
|
self.references: Dict[str, str] = dict()
|
|
44
|
+
# CREDENTIALS
|
|
45
|
+
self.credentials: Dict[int, str] = dict()
|
|
44
46
|
|
|
45
47
|
# TRACER PROVIDER
|
|
46
48
|
self.tracer_provider: Optional[TracerProvider] = None
|
|
@@ -60,13 +62,16 @@ class Tracing(metaclass=Singleton):
|
|
|
60
62
|
def configure(
|
|
61
63
|
self,
|
|
62
64
|
api_key: Optional[str] = None,
|
|
65
|
+
service_id: Optional[str] = None,
|
|
63
66
|
# DEPRECATING
|
|
64
67
|
app_id: Optional[str] = None,
|
|
65
68
|
):
|
|
66
69
|
# HEADERS (OTLP)
|
|
67
70
|
if api_key:
|
|
68
|
-
self.headers["Authorization"] = api_key
|
|
71
|
+
self.headers["Authorization"] = f"ApiKey {api_key}"
|
|
69
72
|
# REFERENCES
|
|
73
|
+
if service_id:
|
|
74
|
+
self.references["service.id"] = service_id
|
|
70
75
|
if app_id:
|
|
71
76
|
self.references["application.id"] = app_id
|
|
72
77
|
|
|
@@ -84,31 +89,28 @@ class Tracing(metaclass=Singleton):
|
|
|
84
89
|
self.tracer_provider.add_span_processor(self.inline)
|
|
85
90
|
# TRACE PROCESSORS -- OTLP
|
|
86
91
|
try:
|
|
87
|
-
log.info("--------------------------------------------")
|
|
88
92
|
log.info(
|
|
89
|
-
"Agenta
|
|
93
|
+
"Agenta - OLTP URL: %s",
|
|
90
94
|
self.otlp_url,
|
|
91
95
|
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
96
|
+
# check(
|
|
97
|
+
# self.otlp_url,
|
|
98
|
+
# headers=self.headers,
|
|
99
|
+
# timeout=1,
|
|
100
|
+
# )
|
|
98
101
|
|
|
99
102
|
_otlp = TraceProcessor(
|
|
100
103
|
OTLPExporter(
|
|
101
104
|
endpoint=self.otlp_url,
|
|
102
105
|
headers=self.headers,
|
|
106
|
+
credentials=self.credentials,
|
|
103
107
|
),
|
|
104
108
|
references=self.references,
|
|
105
109
|
)
|
|
106
110
|
|
|
107
111
|
self.tracer_provider.add_span_processor(_otlp)
|
|
108
|
-
log.info("Success: traces will be exported.")
|
|
109
|
-
log.info("--------------------------------------------")
|
|
110
112
|
except: # pylint: disable=bare-except
|
|
111
|
-
log.warning("Agenta
|
|
113
|
+
log.warning("Agenta - OLTP unreachable, skipping exports.")
|
|
112
114
|
|
|
113
115
|
# GLOBAL TRACER PROVIDER -- INSTRUMENTATION LIBRARIES
|
|
114
116
|
set_tracer_provider(self.tracer_provider)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TRUTHY = {"true", "1", "t", "y", "yes", "on", "enable", "enabled"}
|