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.
- agenta/__init__.py +12 -3
- agenta/client/__init__.py +4 -4
- agenta/client/backend/__init__.py +4 -4
- agenta/client/backend/api_keys/client.py +2 -2
- agenta/client/backend/billing/client.py +2 -2
- agenta/client/backend/billing/raw_client.py +2 -2
- agenta/client/backend/client.py +56 -48
- agenta/client/backend/core/client_wrapper.py +2 -2
- agenta/client/backend/core/file.py +3 -1
- agenta/client/backend/core/http_client.py +3 -3
- agenta/client/backend/core/pydantic_utilities.py +13 -3
- agenta/client/backend/human_evaluations/client.py +2 -2
- agenta/client/backend/human_evaluations/raw_client.py +2 -2
- agenta/client/backend/organization/client.py +46 -34
- agenta/client/backend/organization/raw_client.py +32 -26
- agenta/client/backend/raw_client.py +26 -26
- agenta/client/backend/testsets/client.py +18 -18
- agenta/client/backend/testsets/raw_client.py +30 -30
- agenta/client/backend/types/__init__.py +4 -4
- agenta/client/backend/types/account_request.py +3 -1
- agenta/client/backend/types/account_response.py +3 -1
- agenta/client/backend/types/agenta_node_dto.py +3 -1
- agenta/client/backend/types/agenta_nodes_response.py +3 -1
- agenta/client/backend/types/agenta_root_dto.py +3 -1
- agenta/client/backend/types/agenta_roots_response.py +3 -1
- agenta/client/backend/types/agenta_tree_dto.py +3 -1
- agenta/client/backend/types/agenta_trees_response.py +3 -1
- agenta/client/backend/types/aggregated_result.py +3 -1
- agenta/client/backend/types/analytics_response.py +3 -1
- agenta/client/backend/types/annotation.py +6 -4
- agenta/client/backend/types/annotation_create.py +3 -1
- agenta/client/backend/types/annotation_edit.py +3 -1
- agenta/client/backend/types/annotation_link.py +3 -1
- agenta/client/backend/types/annotation_link_response.py +3 -1
- agenta/client/backend/types/annotation_query.py +3 -1
- agenta/client/backend/types/annotation_query_request.py +3 -1
- agenta/client/backend/types/annotation_reference.py +3 -1
- agenta/client/backend/types/annotation_references.py +3 -1
- agenta/client/backend/types/annotation_response.py +3 -1
- agenta/client/backend/types/annotations_response.py +3 -1
- agenta/client/backend/types/app.py +3 -1
- agenta/client/backend/types/app_variant_response.py +3 -1
- agenta/client/backend/types/app_variant_revision.py +3 -1
- agenta/client/backend/types/artifact.py +6 -4
- agenta/client/backend/types/base_output.py +3 -1
- agenta/client/backend/types/body_fetch_workflow_revision.py +3 -1
- agenta/client/backend/types/body_import_testset.py +3 -1
- agenta/client/backend/types/bucket_dto.py +3 -1
- agenta/client/backend/types/collect_status_response.py +3 -1
- agenta/client/backend/types/config_db.py +3 -1
- agenta/client/backend/types/config_dto.py +3 -1
- agenta/client/backend/types/config_response_model.py +3 -1
- agenta/client/backend/types/correct_answer.py +3 -1
- agenta/client/backend/types/create_app_output.py +3 -1
- agenta/client/backend/types/custom_model_settings_dto.py +3 -1
- agenta/client/backend/types/custom_provider_dto.py +3 -1
- agenta/client/backend/types/custom_provider_kind.py +1 -1
- agenta/client/backend/types/custom_provider_settings_dto.py +3 -1
- agenta/client/backend/types/delete_evaluation.py +3 -1
- agenta/client/backend/types/environment_output.py +3 -1
- agenta/client/backend/types/environment_output_extended.py +3 -1
- agenta/client/backend/types/environment_revision.py +3 -1
- agenta/client/backend/types/error.py +3 -1
- agenta/client/backend/types/evaluation.py +3 -1
- agenta/client/backend/types/evaluation_scenario.py +3 -1
- agenta/client/backend/types/evaluation_scenario_input.py +3 -1
- agenta/client/backend/types/evaluation_scenario_output.py +3 -1
- agenta/client/backend/types/evaluation_scenario_result.py +3 -1
- agenta/client/backend/types/evaluator.py +6 -4
- agenta/client/backend/types/evaluator_config.py +6 -4
- agenta/client/backend/types/evaluator_flags.py +3 -1
- agenta/client/backend/types/evaluator_mapping_output_interface.py +3 -1
- agenta/client/backend/types/evaluator_output_interface.py +3 -1
- agenta/client/backend/types/evaluator_query.py +3 -1
- agenta/client/backend/types/evaluator_query_request.py +3 -1
- agenta/client/backend/types/evaluator_request.py +3 -1
- agenta/client/backend/types/evaluator_response.py +3 -1
- agenta/client/backend/types/evaluators_response.py +3 -1
- agenta/client/backend/types/exception_dto.py +3 -1
- agenta/client/backend/types/extended_o_tel_tracing_response.py +3 -1
- agenta/client/backend/types/get_config_response.py +3 -1
- agenta/client/backend/types/header.py +3 -1
- agenta/client/backend/types/http_validation_error.py +3 -1
- agenta/client/backend/types/human_evaluation.py +3 -1
- agenta/client/backend/types/human_evaluation_scenario.py +3 -1
- agenta/client/backend/types/human_evaluation_scenario_input.py +3 -1
- agenta/client/backend/types/human_evaluation_scenario_output.py +3 -1
- agenta/client/backend/types/invite_request.py +3 -1
- agenta/client/backend/types/legacy_analytics_response.py +3 -1
- agenta/client/backend/types/legacy_data_point.py +3 -1
- agenta/client/backend/types/legacy_evaluator.py +3 -1
- agenta/client/backend/types/legacy_scope_request.py +3 -1
- agenta/client/backend/types/legacy_scopes_response.py +3 -1
- agenta/client/backend/types/legacy_subscription_request.py +3 -1
- agenta/client/backend/types/legacy_user_request.py +3 -1
- agenta/client/backend/types/legacy_user_response.py +3 -1
- agenta/client/backend/types/lifecycle_dto.py +3 -1
- agenta/client/backend/types/link_dto.py +3 -1
- agenta/client/backend/types/list_api_keys_response.py +3 -1
- agenta/client/backend/types/llm_run_rate_limit.py +3 -1
- agenta/client/backend/types/meta_request.py +3 -1
- agenta/client/backend/types/metrics_dto.py +3 -1
- agenta/client/backend/types/new_testset.py +3 -1
- agenta/client/backend/types/node_dto.py +3 -1
- agenta/client/backend/types/o_tel_context_dto.py +3 -1
- agenta/client/backend/types/o_tel_event.py +6 -4
- agenta/client/backend/types/o_tel_event_dto.py +3 -1
- agenta/client/backend/types/o_tel_extra_dto.py +3 -1
- agenta/client/backend/types/o_tel_flat_span.py +6 -4
- agenta/client/backend/types/o_tel_link.py +6 -4
- agenta/client/backend/types/o_tel_link_dto.py +3 -1
- agenta/client/backend/types/o_tel_links_response.py +3 -1
- agenta/client/backend/types/o_tel_span.py +1 -1
- agenta/client/backend/types/o_tel_span_dto.py +3 -1
- agenta/client/backend/types/o_tel_spans_tree.py +3 -1
- agenta/client/backend/types/o_tel_tracing_data_response.py +3 -1
- agenta/client/backend/types/o_tel_tracing_request.py +3 -1
- agenta/client/backend/types/o_tel_tracing_response.py +3 -1
- agenta/client/backend/types/organization.py +3 -1
- agenta/client/backend/types/organization_details.py +3 -1
- agenta/client/backend/types/organization_membership_request.py +3 -1
- agenta/client/backend/types/organization_output.py +3 -1
- agenta/client/backend/types/organization_request.py +3 -1
- agenta/client/backend/types/parent_dto.py +3 -1
- agenta/client/backend/types/project_membership_request.py +3 -1
- agenta/client/backend/types/project_request.py +3 -1
- agenta/client/backend/types/project_scope.py +3 -1
- agenta/client/backend/types/projects_response.py +3 -1
- agenta/client/backend/types/reference.py +6 -4
- agenta/client/backend/types/reference_dto.py +3 -1
- agenta/client/backend/types/reference_request_model.py +3 -1
- agenta/client/backend/types/result.py +3 -1
- agenta/client/backend/types/root_dto.py +3 -1
- agenta/client/backend/types/scopes_response_model.py +3 -1
- agenta/client/backend/types/secret_dto.py +3 -1
- agenta/client/backend/types/secret_response_dto.py +3 -1
- agenta/client/backend/types/simple_evaluation_output.py +3 -1
- agenta/client/backend/types/span_dto.py +6 -4
- agenta/client/backend/types/standard_provider_dto.py +3 -1
- agenta/client/backend/types/standard_provider_settings_dto.py +3 -1
- agenta/client/backend/types/status_dto.py +3 -1
- agenta/client/backend/types/tags_request.py +3 -1
- agenta/client/backend/types/testcase_response.py +6 -4
- agenta/client/backend/types/testset.py +6 -4
- agenta/client/backend/types/{test_set_output_response.py → testset_output_response.py} +4 -2
- agenta/client/backend/types/testset_request.py +3 -1
- agenta/client/backend/types/testset_response.py +3 -1
- agenta/client/backend/types/{test_set_simple_response.py → testset_simple_response.py} +4 -2
- agenta/client/backend/types/testsets_response.py +3 -1
- agenta/client/backend/types/time_dto.py +3 -1
- agenta/client/backend/types/tree_dto.py +3 -1
- agenta/client/backend/types/update_app_output.py +3 -1
- agenta/client/backend/types/user_request.py +3 -1
- agenta/client/backend/types/validation_error.py +3 -1
- agenta/client/backend/types/workflow_artifact.py +6 -4
- agenta/client/backend/types/workflow_data.py +3 -1
- agenta/client/backend/types/workflow_flags.py +3 -1
- agenta/client/backend/types/workflow_request.py +3 -1
- agenta/client/backend/types/workflow_response.py +3 -1
- agenta/client/backend/types/workflow_revision.py +6 -4
- agenta/client/backend/types/workflow_revision_request.py +3 -1
- agenta/client/backend/types/workflow_revision_response.py +3 -1
- agenta/client/backend/types/workflow_revisions_response.py +3 -1
- agenta/client/backend/types/workflow_variant.py +6 -4
- agenta/client/backend/types/workflow_variant_request.py +3 -1
- agenta/client/backend/types/workflow_variant_response.py +3 -1
- agenta/client/backend/types/workflow_variants_response.py +3 -1
- agenta/client/backend/types/workflows_response.py +3 -1
- agenta/client/backend/types/workspace.py +3 -1
- agenta/client/backend/types/workspace_member_response.py +3 -1
- agenta/client/backend/types/workspace_membership_request.py +3 -1
- agenta/client/backend/types/workspace_permission.py +3 -1
- agenta/client/backend/types/workspace_request.py +3 -1
- agenta/client/backend/types/workspace_response.py +3 -1
- agenta/client/backend/vault/raw_client.py +4 -4
- agenta/client/backend/workspace/client.py +2 -2
- agenta/client/client.py +102 -88
- agenta/sdk/__init__.py +52 -3
- agenta/sdk/agenta_init.py +43 -16
- agenta/sdk/assets.py +23 -15
- agenta/sdk/context/serving.py +20 -8
- agenta/sdk/context/tracing.py +40 -22
- agenta/sdk/contexts/__init__.py +0 -0
- agenta/sdk/contexts/routing.py +38 -0
- agenta/sdk/contexts/running.py +57 -0
- agenta/sdk/contexts/tracing.py +86 -0
- agenta/sdk/decorators/__init__.py +1 -0
- agenta/sdk/decorators/routing.py +284 -0
- agenta/sdk/decorators/running.py +692 -98
- agenta/sdk/decorators/serving.py +20 -21
- agenta/sdk/decorators/tracing.py +176 -131
- agenta/sdk/engines/__init__.py +0 -0
- agenta/sdk/engines/running/__init__.py +0 -0
- agenta/sdk/engines/running/utils.py +17 -0
- agenta/sdk/engines/tracing/__init__.py +1 -0
- agenta/sdk/engines/tracing/attributes.py +185 -0
- agenta/sdk/engines/tracing/conventions.py +49 -0
- agenta/sdk/engines/tracing/exporters.py +130 -0
- agenta/sdk/engines/tracing/inline.py +1154 -0
- agenta/sdk/engines/tracing/processors.py +190 -0
- agenta/sdk/engines/tracing/propagation.py +102 -0
- agenta/sdk/engines/tracing/spans.py +136 -0
- agenta/sdk/engines/tracing/tracing.py +324 -0
- agenta/sdk/evaluations/__init__.py +2 -0
- agenta/sdk/evaluations/metrics.py +37 -0
- agenta/sdk/evaluations/preview/__init__.py +0 -0
- agenta/sdk/evaluations/preview/evaluate.py +765 -0
- agenta/sdk/evaluations/preview/utils.py +861 -0
- agenta/sdk/evaluations/results.py +66 -0
- agenta/sdk/evaluations/runs.py +153 -0
- agenta/sdk/evaluations/scenarios.py +48 -0
- agenta/sdk/litellm/litellm.py +12 -0
- agenta/sdk/litellm/mockllm.py +6 -8
- agenta/sdk/litellm/mocks/__init__.py +5 -5
- agenta/sdk/managers/applications.py +304 -0
- agenta/sdk/managers/config.py +2 -2
- agenta/sdk/managers/evaluations.py +0 -0
- agenta/sdk/managers/evaluators.py +303 -0
- agenta/sdk/managers/secrets.py +161 -24
- agenta/sdk/managers/shared.py +3 -1
- agenta/sdk/managers/testsets.py +441 -0
- agenta/sdk/managers/vault.py +3 -3
- agenta/sdk/middleware/auth.py +0 -176
- agenta/sdk/middleware/config.py +27 -9
- agenta/sdk/middleware/vault.py +204 -9
- agenta/sdk/middlewares/__init__.py +0 -0
- agenta/sdk/middlewares/routing/__init__.py +0 -0
- agenta/sdk/middlewares/routing/auth.py +263 -0
- agenta/sdk/middlewares/routing/cors.py +30 -0
- agenta/sdk/middlewares/routing/otel.py +29 -0
- agenta/sdk/middlewares/running/__init__.py +0 -0
- agenta/sdk/middlewares/running/normalizer.py +321 -0
- agenta/sdk/middlewares/running/resolver.py +161 -0
- agenta/sdk/middlewares/running/vault.py +140 -0
- agenta/sdk/models/__init__.py +0 -0
- agenta/sdk/models/blobs.py +33 -0
- agenta/sdk/models/evaluations.py +119 -0
- agenta/sdk/models/git.py +126 -0
- agenta/sdk/models/shared.py +167 -0
- agenta/sdk/models/testsets.py +163 -0
- agenta/sdk/models/tracing.py +202 -0
- agenta/sdk/models/workflows.py +753 -0
- agenta/sdk/tracing/attributes.py +4 -4
- agenta/sdk/tracing/exporters.py +67 -17
- agenta/sdk/tracing/inline.py +37 -45
- agenta/sdk/tracing/processors.py +97 -0
- agenta/sdk/tracing/propagation.py +3 -1
- agenta/sdk/tracing/spans.py +4 -0
- agenta/sdk/tracing/tracing.py +13 -15
- agenta/sdk/types.py +222 -22
- agenta/sdk/utils/cache.py +1 -1
- agenta/sdk/utils/client.py +38 -0
- agenta/sdk/utils/helpers.py +13 -12
- agenta/sdk/utils/logging.py +18 -78
- agenta/sdk/utils/references.py +23 -0
- agenta/sdk/workflows/builtin.py +600 -0
- agenta/sdk/workflows/configurations.py +22 -0
- agenta/sdk/workflows/errors.py +292 -0
- agenta/sdk/workflows/handlers.py +1791 -0
- agenta/sdk/workflows/interfaces.py +948 -0
- agenta/sdk/workflows/sandbox.py +118 -0
- agenta/sdk/workflows/utils.py +303 -6
- {agenta-0.52.6.dist-info → agenta-0.63.2.dist-info}/METADATA +37 -33
- agenta-0.63.2.dist-info/RECORD +421 -0
- {agenta-0.52.6.dist-info → agenta-0.63.2.dist-info}/WHEEL +1 -1
- agenta/sdk/middleware/adapt.py +0 -253
- agenta/sdk/middleware/base.py +0 -40
- agenta/sdk/middleware/flags.py +0 -40
- agenta/sdk/workflows/types.py +0 -472
- agenta-0.52.6.dist-info/RECORD +0 -371
- /agenta/sdk/{workflows → engines/running}/registry.py +0 -0
agenta/sdk/middleware/vault.py
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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="
|
|
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
|
-
|
|
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
|