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