agenta 0.12.3__py3-none-any.whl → 0.32.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of agenta might be problematic. Click here for more details.
- agenta/__init__.py +64 -7
- agenta/cli/helper.py +7 -3
- agenta/cli/main.py +15 -50
- agenta/cli/variant_commands.py +50 -29
- agenta/client/Readme.md +72 -64
- agenta/client/api.py +2 -2
- agenta/client/backend/__init__.py +193 -22
- agenta/client/backend/access_control/__init__.py +1 -0
- agenta/client/backend/access_control/client.py +167 -0
- agenta/client/backend/apps/__init__.py +1 -0
- agenta/client/backend/apps/client.py +1691 -0
- agenta/client/backend/bases/__init__.py +1 -0
- agenta/client/backend/bases/client.py +190 -0
- agenta/client/backend/client.py +2508 -5712
- agenta/client/backend/configs/__init__.py +1 -0
- agenta/client/backend/configs/client.py +604 -0
- agenta/client/backend/containers/__init__.py +5 -0
- agenta/client/backend/containers/client.py +648 -0
- agenta/client/backend/containers/types/__init__.py +5 -0
- agenta/client/backend/{types → containers/types}/container_templates_response.py +1 -2
- agenta/client/backend/core/__init__.py +30 -0
- agenta/client/backend/core/client_wrapper.py +42 -9
- agenta/client/backend/core/file.py +70 -0
- agenta/client/backend/core/http_client.py +575 -0
- agenta/client/backend/core/jsonable_encoder.py +33 -39
- agenta/client/backend/core/pydantic_utilities.py +325 -0
- agenta/client/backend/core/query_encoder.py +60 -0
- agenta/client/backend/core/remove_none_from_dict.py +2 -2
- agenta/client/backend/core/request_options.py +35 -0
- agenta/client/backend/core/serialization.py +276 -0
- agenta/client/backend/environments/__init__.py +1 -0
- agenta/client/backend/environments/client.py +196 -0
- agenta/client/backend/evaluations/__init__.py +1 -0
- agenta/client/backend/evaluations/client.py +1469 -0
- agenta/client/backend/evaluators/__init__.py +1 -0
- agenta/client/backend/evaluators/client.py +1283 -0
- agenta/client/backend/observability/__init__.py +1 -0
- agenta/client/backend/observability/client.py +1286 -0
- agenta/client/backend/observability_v_1/__init__.py +5 -0
- agenta/client/backend/observability_v_1/client.py +763 -0
- agenta/client/backend/observability_v_1/types/__init__.py +7 -0
- agenta/client/backend/observability_v_1/types/format.py +5 -0
- agenta/client/backend/observability_v_1/types/query_analytics_response.py +7 -0
- agenta/client/backend/observability_v_1/types/query_traces_response.py +11 -0
- agenta/client/backend/scopes/__init__.py +1 -0
- agenta/client/backend/scopes/client.py +114 -0
- agenta/client/backend/testsets/__init__.py +1 -0
- agenta/client/backend/testsets/client.py +1284 -0
- agenta/client/backend/types/__init__.py +154 -26
- agenta/client/backend/types/agenta_node_dto.py +48 -0
- agenta/client/backend/types/agenta_node_dto_nodes_value.py +6 -0
- agenta/client/backend/types/agenta_nodes_response.py +30 -0
- agenta/client/backend/types/agenta_root_dto.py +30 -0
- agenta/client/backend/types/agenta_roots_response.py +30 -0
- agenta/client/backend/types/agenta_tree_dto.py +30 -0
- agenta/client/backend/types/agenta_trees_response.py +30 -0
- agenta/client/backend/types/aggregated_result.py +16 -31
- agenta/client/backend/types/aggregated_result_evaluator_config.py +8 -0
- agenta/client/backend/types/analytics_response.py +24 -0
- agenta/client/backend/types/app.py +17 -30
- agenta/client/backend/types/app_variant_response.py +36 -0
- agenta/client/backend/types/app_variant_revision.py +17 -32
- agenta/client/backend/types/base_output.py +13 -28
- agenta/client/backend/types/body_import_testset.py +15 -31
- agenta/client/backend/types/bucket_dto.py +26 -0
- agenta/client/backend/types/collect_status_response.py +22 -0
- agenta/client/backend/types/config_db.py +16 -31
- agenta/client/backend/types/config_dto.py +32 -0
- agenta/client/backend/types/config_response_model.py +32 -0
- agenta/client/backend/types/correct_answer.py +22 -0
- agenta/client/backend/types/create_app_output.py +13 -28
- agenta/client/backend/types/create_span.py +45 -0
- agenta/client/backend/types/create_trace_response.py +22 -0
- agenta/client/backend/types/docker_env_vars.py +13 -28
- agenta/client/backend/types/environment_output.py +22 -34
- agenta/client/backend/types/environment_output_extended.py +31 -0
- agenta/client/backend/types/environment_revision.py +26 -0
- agenta/client/backend/types/error.py +22 -0
- agenta/client/backend/types/evaluation.py +22 -33
- agenta/client/backend/types/evaluation_scenario.py +18 -33
- agenta/client/backend/types/evaluation_scenario_input.py +16 -31
- agenta/client/backend/types/evaluation_scenario_output.py +17 -30
- agenta/client/backend/types/evaluation_scenario_result.py +14 -29
- agenta/client/backend/types/evaluation_scenario_score_update.py +21 -0
- agenta/client/backend/types/evaluation_status_enum.py +11 -29
- agenta/client/backend/types/evaluation_type.py +3 -21
- agenta/client/backend/types/evaluator.py +20 -31
- agenta/client/backend/types/evaluator_config.py +21 -33
- agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
- agenta/client/backend/types/evaluator_output_interface.py +21 -0
- agenta/client/backend/types/exception_dto.py +26 -0
- agenta/client/backend/types/get_config_response.py +23 -0
- agenta/client/backend/types/header_dto.py +22 -0
- agenta/client/backend/types/http_validation_error.py +14 -29
- agenta/client/backend/types/human_evaluation.py +18 -34
- agenta/client/backend/types/human_evaluation_scenario.py +22 -38
- agenta/client/backend/types/human_evaluation_scenario_input.py +13 -28
- agenta/client/backend/types/human_evaluation_scenario_output.py +13 -28
- agenta/client/backend/types/human_evaluation_scenario_update.py +30 -0
- agenta/client/backend/types/human_evaluation_update.py +22 -0
- agenta/client/backend/types/image.py +18 -32
- agenta/client/backend/types/invite_request.py +16 -30
- agenta/client/backend/types/legacy_analytics_response.py +29 -0
- agenta/client/backend/types/legacy_data_point.py +27 -0
- agenta/client/backend/types/lifecycle_dto.py +24 -0
- agenta/client/backend/types/link_dto.py +24 -0
- agenta/client/backend/types/list_api_keys_response.py +24 -0
- agenta/client/backend/types/llm_run_rate_limit.py +13 -28
- agenta/client/backend/types/llm_tokens.py +23 -0
- agenta/client/backend/types/metrics_dto.py +24 -0
- agenta/client/backend/types/new_human_evaluation.py +27 -0
- agenta/client/backend/types/new_testset.py +16 -31
- agenta/client/backend/types/node_dto.py +24 -0
- agenta/client/backend/types/node_type.py +19 -0
- agenta/client/backend/types/o_tel_context_dto.py +22 -0
- agenta/client/backend/types/o_tel_event_dto.py +23 -0
- agenta/client/backend/types/o_tel_extra_dto.py +26 -0
- agenta/client/backend/types/o_tel_link_dto.py +23 -0
- agenta/client/backend/types/o_tel_span_dto.py +37 -0
- agenta/client/backend/types/o_tel_span_kind.py +15 -0
- agenta/client/backend/types/o_tel_spans_response.py +24 -0
- agenta/client/backend/types/o_tel_status_code.py +8 -0
- agenta/client/backend/types/organization.py +22 -35
- agenta/client/backend/types/organization_output.py +13 -28
- agenta/client/backend/types/outputs.py +5 -0
- agenta/client/backend/types/parent_dto.py +21 -0
- agenta/client/backend/types/permission.py +41 -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/reference_dto.py +23 -0
- agenta/client/backend/types/reference_request_model.py +23 -0
- agenta/client/backend/types/result.py +18 -31
- agenta/client/backend/types/root_dto.py +21 -0
- agenta/client/backend/types/{human_evaluation_scenario_score.py → score.py} +1 -1
- agenta/client/backend/types/secret_dto.py +24 -0
- agenta/client/backend/types/{human_evaluation_scenario_update_score.py → secret_kind.py} +1 -1
- agenta/client/backend/types/secret_response_dto.py +27 -0
- agenta/client/backend/types/simple_evaluation_output.py +13 -28
- agenta/client/backend/types/span.py +39 -49
- agenta/client/backend/types/span_detail.py +44 -0
- agenta/client/backend/types/span_dto.py +54 -0
- agenta/client/backend/types/span_dto_nodes_value.py +9 -0
- agenta/client/backend/types/span_status_code.py +5 -0
- agenta/client/backend/types/span_variant.py +23 -0
- agenta/client/backend/types/status_code.py +5 -0
- agenta/client/backend/types/status_dto.py +23 -0
- agenta/client/backend/types/template.py +14 -29
- agenta/client/backend/types/template_image_info.py +21 -35
- agenta/client/backend/types/test_set_output_response.py +20 -33
- agenta/client/backend/types/test_set_simple_response.py +13 -28
- agenta/client/backend/types/time_dto.py +23 -0
- agenta/client/backend/types/trace_detail.py +44 -0
- agenta/client/backend/types/tree_dto.py +23 -0
- agenta/client/backend/types/tree_type.py +5 -0
- agenta/client/backend/types/update_app_output.py +22 -0
- agenta/client/backend/types/uri.py +13 -28
- agenta/client/backend/types/validation_error.py +13 -28
- agenta/client/backend/types/variant_action.py +14 -29
- agenta/client/backend/types/variant_action_enum.py +1 -19
- agenta/client/backend/types/with_pagination.py +26 -0
- agenta/client/backend/types/workspace_member_response.py +23 -0
- agenta/client/backend/types/workspace_permission.py +25 -0
- agenta/client/backend/types/workspace_response.py +29 -0
- agenta/client/backend/types/workspace_role.py +15 -0
- agenta/client/backend/types/workspace_role_response.py +23 -0
- agenta/client/backend/variants/__init__.py +5 -0
- agenta/client/backend/variants/client.py +2814 -0
- agenta/client/backend/variants/types/__init__.py +7 -0
- agenta/client/backend/variants/types/add_variant_from_base_and_config_response.py +8 -0
- agenta/client/backend/vault/__init__.py +1 -0
- agenta/client/backend/vault/client.py +685 -0
- agenta/client/client.py +1 -1
- agenta/config.py +0 -2
- agenta/config.toml +0 -1
- agenta/docker/docker-assets/Dockerfile.cloud.template +2 -1
- agenta/docker/docker-assets/Dockerfile.template +2 -1
- agenta/docker/docker_utils.py +11 -12
- agenta/sdk/__init__.py +58 -7
- agenta/sdk/agenta_init.py +182 -164
- agenta/sdk/assets.py +95 -0
- agenta/sdk/client.py +56 -0
- agenta/sdk/context/__init__.py +0 -0
- agenta/sdk/context/exporting.py +25 -0
- agenta/sdk/context/routing.py +27 -0
- agenta/sdk/context/tracing.py +28 -0
- agenta/sdk/decorators/__init__.py +0 -0
- agenta/sdk/decorators/routing.py +576 -0
- agenta/sdk/decorators/tracing.py +296 -0
- agenta/sdk/litellm/__init__.py +1 -0
- agenta/sdk/litellm/litellm.py +314 -0
- agenta/sdk/litellm/mockllm.py +27 -0
- agenta/sdk/litellm/mocks/__init__.py +26 -0
- agenta/sdk/managers/__init__.py +6 -0
- agenta/sdk/managers/config.py +208 -0
- agenta/sdk/managers/deployment.py +45 -0
- agenta/sdk/managers/secrets.py +38 -0
- agenta/sdk/managers/shared.py +639 -0
- agenta/sdk/managers/variant.py +182 -0
- agenta/sdk/managers/vault.py +16 -0
- agenta/sdk/middleware/__init__.py +0 -0
- agenta/sdk/middleware/auth.py +180 -0
- agenta/sdk/middleware/cache.py +47 -0
- agenta/sdk/middleware/config.py +255 -0
- agenta/sdk/middleware/cors.py +29 -0
- agenta/sdk/middleware/inline.py +38 -0
- agenta/sdk/middleware/mock.py +33 -0
- agenta/sdk/middleware/otel.py +40 -0
- agenta/sdk/middleware/vault.py +145 -0
- agenta/sdk/router.py +0 -7
- agenta/sdk/tracing/__init__.py +1 -0
- agenta/sdk/tracing/attributes.py +141 -0
- agenta/sdk/tracing/conventions.py +49 -0
- agenta/sdk/tracing/exporters.py +103 -0
- agenta/sdk/tracing/inline.py +1146 -0
- agenta/sdk/tracing/processors.py +121 -0
- agenta/sdk/tracing/spans.py +136 -0
- agenta/sdk/tracing/tracing.py +237 -0
- agenta/sdk/types.py +478 -74
- agenta/sdk/utils/__init__.py +0 -0
- agenta/sdk/utils/constants.py +1 -0
- agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
- agenta/sdk/utils/exceptions.py +59 -0
- agenta/sdk/utils/globals.py +6 -10
- agenta/sdk/utils/helpers.py +8 -0
- agenta/sdk/utils/logging.py +21 -0
- agenta/sdk/utils/singleton.py +13 -0
- agenta/sdk/utils/timing.py +58 -0
- {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/METADATA +98 -151
- agenta-0.32.0a1.dist-info/RECORD +263 -0
- {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/WHEEL +1 -1
- agenta/client/backend/types/add_variant_from_base_and_config_response.py +0 -7
- agenta/client/backend/types/app_variant_output.py +0 -47
- agenta/client/backend/types/app_variant_output_extended.py +0 -50
- agenta/client/backend/types/delete_evaluation.py +0 -36
- agenta/client/backend/types/evaluation_webhook.py +0 -36
- agenta/client/backend/types/feedback.py +0 -40
- agenta/client/backend/types/get_config_reponse.py +0 -39
- agenta/client/backend/types/list_api_keys_output.py +0 -39
- agenta/client/backend/types/trace.py +0 -48
- agenta/sdk/agenta_decorator.py +0 -443
- agenta/sdk/context.py +0 -41
- agenta-0.12.3.dist-info/RECORD +0 -114
- {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
from sre_parse import NOT_LITERAL_UNI_IGNORE
|
|
2
|
+
from typing import Type, Any, Callable, Dict, Optional, Tuple, List
|
|
3
|
+
from inspect import signature, iscoroutinefunction, Signature, Parameter
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from traceback import format_exception
|
|
6
|
+
from asyncio import sleep
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
from pydantic import BaseModel, HttpUrl, ValidationError
|
|
9
|
+
|
|
10
|
+
from fastapi import Body, FastAPI, HTTPException, Request
|
|
11
|
+
|
|
12
|
+
from agenta.sdk.middleware.mock import MockMiddleware
|
|
13
|
+
from agenta.sdk.middleware.inline import InlineMiddleware
|
|
14
|
+
from agenta.sdk.middleware.vault import VaultMiddleware
|
|
15
|
+
from agenta.sdk.middleware.config import ConfigMiddleware
|
|
16
|
+
from agenta.sdk.middleware.otel import OTelMiddleware
|
|
17
|
+
from agenta.sdk.middleware.auth import AuthMiddleware
|
|
18
|
+
from agenta.sdk.middleware.cors import CORSMiddleware
|
|
19
|
+
|
|
20
|
+
from agenta.sdk.context.routing import (
|
|
21
|
+
routing_context_manager,
|
|
22
|
+
RoutingContext,
|
|
23
|
+
)
|
|
24
|
+
from agenta.sdk.context.tracing import (
|
|
25
|
+
tracing_context_manager,
|
|
26
|
+
tracing_context,
|
|
27
|
+
TracingContext,
|
|
28
|
+
)
|
|
29
|
+
from agenta.sdk.router import router
|
|
30
|
+
from agenta.sdk.utils.exceptions import suppress, display_exception
|
|
31
|
+
from agenta.sdk.utils.logging import log
|
|
32
|
+
from agenta.sdk.utils.helpers import get_current_version
|
|
33
|
+
from agenta.sdk.types import (
|
|
34
|
+
MultipleChoice,
|
|
35
|
+
BaseResponse,
|
|
36
|
+
MCField,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
import agenta as ag
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
app = FastAPI()
|
|
43
|
+
log.setLevel("DEBUG")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app.include_router(router, prefix="")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PathValidator(BaseModel):
|
|
50
|
+
url: HttpUrl
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class route: # pylint: disable=invalid-name
|
|
54
|
+
# This decorator is used to expose specific stages of a workflow (embedding, retrieval, summarization, etc.)
|
|
55
|
+
# as independent endpoints. It is designed for backward compatibility with existing code that uses
|
|
56
|
+
# the @entrypoint decorator, which has certain limitations. By using @route(), we can create new
|
|
57
|
+
# routes without altering the main workflow entrypoint. This helps in modularizing the services
|
|
58
|
+
# and provides flexibility in how we expose different functionalities as APIs.
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
path: Optional[str] = "/",
|
|
62
|
+
config_schema: Optional[BaseModel] = None,
|
|
63
|
+
):
|
|
64
|
+
self.config_schema: BaseModel = config_schema
|
|
65
|
+
path = "/" + path.strip("/").strip()
|
|
66
|
+
path = "" if path == "/" else path
|
|
67
|
+
PathValidator(url=f"http://example.com{path}")
|
|
68
|
+
|
|
69
|
+
self.route_path = path
|
|
70
|
+
|
|
71
|
+
self.e = None
|
|
72
|
+
|
|
73
|
+
def __call__(self, f):
|
|
74
|
+
self.e = entrypoint(
|
|
75
|
+
f,
|
|
76
|
+
route_path=self.route_path,
|
|
77
|
+
config_schema=self.config_schema,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return f
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class entrypoint:
|
|
84
|
+
"""
|
|
85
|
+
Decorator class to wrap a function for HTTP POST, terminal exposure and enable tracing.
|
|
86
|
+
|
|
87
|
+
This decorator generates the following endpoints:
|
|
88
|
+
|
|
89
|
+
Playground Endpoints
|
|
90
|
+
- /generate with @entrypoint, @route("/"), @route(path="") # LEGACY
|
|
91
|
+
- /playground/run with @entrypoint, @route("/"), @route(path="")
|
|
92
|
+
- /playground/run/{route} with @route({route}), @route(path={route})
|
|
93
|
+
|
|
94
|
+
Deployed Endpoints:
|
|
95
|
+
- /generate_deployed with @entrypoint, @route("/"), @route(path="") # LEGACY
|
|
96
|
+
- /run with @entrypoint, @route("/"), @route(path="")
|
|
97
|
+
- /run/{route} with @route({route}), @route(path={route})
|
|
98
|
+
|
|
99
|
+
The rationale is:
|
|
100
|
+
- There may be multiple endpoints, based on the different routes.
|
|
101
|
+
- It's better to make it explicit that an endpoint is for the playground.
|
|
102
|
+
- Prefixing the routes with /run is more futureproof in case we add more endpoints.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
```python
|
|
106
|
+
import agenta as ag
|
|
107
|
+
|
|
108
|
+
@ag.entrypoint
|
|
109
|
+
async def chain_of_prompts_llm(prompt: str):
|
|
110
|
+
return ...
|
|
111
|
+
```
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
routes = list()
|
|
115
|
+
|
|
116
|
+
_middleware = False
|
|
117
|
+
_run_path = "/run"
|
|
118
|
+
_test_path = "/test"
|
|
119
|
+
_config_key = "ag_config"
|
|
120
|
+
# LEGACY
|
|
121
|
+
_legacy_playground_run_path = "/playground/run"
|
|
122
|
+
_legacy_generate_path = "/generate"
|
|
123
|
+
_legacy_generate_deployed_path = "/generate_deployed"
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
func: Callable[..., Any],
|
|
128
|
+
route_path: str = "",
|
|
129
|
+
config_schema: Optional[BaseModel] = None,
|
|
130
|
+
):
|
|
131
|
+
self.func = func
|
|
132
|
+
self.route_path = route_path
|
|
133
|
+
self.config_schema = config_schema
|
|
134
|
+
|
|
135
|
+
signature_parameters = signature(func).parameters
|
|
136
|
+
config, default_parameters = self.parse_config()
|
|
137
|
+
|
|
138
|
+
### --- Middleware --- #
|
|
139
|
+
if not entrypoint._middleware:
|
|
140
|
+
entrypoint._middleware = True
|
|
141
|
+
app.add_middleware(MockMiddleware)
|
|
142
|
+
app.add_middleware(InlineMiddleware)
|
|
143
|
+
app.add_middleware(VaultMiddleware)
|
|
144
|
+
app.add_middleware(ConfigMiddleware)
|
|
145
|
+
app.add_middleware(OTelMiddleware)
|
|
146
|
+
app.add_middleware(AuthMiddleware)
|
|
147
|
+
app.add_middleware(CORSMiddleware)
|
|
148
|
+
### ------------------ #
|
|
149
|
+
|
|
150
|
+
### --- Run --- #
|
|
151
|
+
@wraps(func)
|
|
152
|
+
async def run_wrapper(request: Request, *args, **kwargs) -> Any:
|
|
153
|
+
# LEGACY
|
|
154
|
+
# TODO: Removing this implies breaking changes in :
|
|
155
|
+
# - calls to /generate_deployed
|
|
156
|
+
kwargs = {
|
|
157
|
+
k: v
|
|
158
|
+
for k, v in kwargs.items()
|
|
159
|
+
if k not in ["config", "environment", "app"]
|
|
160
|
+
}
|
|
161
|
+
# LEGACY
|
|
162
|
+
|
|
163
|
+
kwargs, _ = self.process_kwargs(kwargs, default_parameters)
|
|
164
|
+
if (
|
|
165
|
+
request.state.config["parameters"] is None
|
|
166
|
+
or request.state.config["references"] is None
|
|
167
|
+
):
|
|
168
|
+
raise HTTPException(
|
|
169
|
+
status_code=400,
|
|
170
|
+
detail="Config not found based on provided references.",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return await self.execute_wrapper(request, *args, **kwargs)
|
|
174
|
+
|
|
175
|
+
self.update_run_wrapper_signature(wrapper=run_wrapper)
|
|
176
|
+
|
|
177
|
+
run_route = f"{entrypoint._run_path}{route_path}"
|
|
178
|
+
app.post(run_route, response_model=BaseResponse)(run_wrapper)
|
|
179
|
+
|
|
180
|
+
# LEGACY
|
|
181
|
+
# TODO: Removing this implies breaking changes in :
|
|
182
|
+
# - calls to /generate_deployed must be replaced with calls to /run
|
|
183
|
+
if route_path == "":
|
|
184
|
+
run_route = entrypoint._legacy_generate_deployed_path
|
|
185
|
+
app.post(run_route, response_model=BaseResponse)(run_wrapper)
|
|
186
|
+
# LEGACY
|
|
187
|
+
### ----------- #
|
|
188
|
+
|
|
189
|
+
### --- Test --- #
|
|
190
|
+
@wraps(func)
|
|
191
|
+
async def test_wrapper(request: Request, *args, **kwargs) -> Any:
|
|
192
|
+
kwargs, config = self.process_kwargs(kwargs, default_parameters)
|
|
193
|
+
request.state.inline = True
|
|
194
|
+
request.state.config["parameters"] = config
|
|
195
|
+
if request.state.config["references"]:
|
|
196
|
+
request.state.config["references"] = {
|
|
197
|
+
k: v
|
|
198
|
+
for k, v in request.state.config["references"].items()
|
|
199
|
+
if k.startswith("application")
|
|
200
|
+
} or None
|
|
201
|
+
return await self.execute_wrapper(request, *args, **kwargs)
|
|
202
|
+
|
|
203
|
+
self.update_test_wrapper_signature(wrapper=test_wrapper, config_instance=config)
|
|
204
|
+
|
|
205
|
+
test_route = f"{entrypoint._test_path}{route_path}"
|
|
206
|
+
app.post(test_route, response_model=BaseResponse)(test_wrapper)
|
|
207
|
+
|
|
208
|
+
# LEGACY
|
|
209
|
+
# TODO: Removing this implies breaking changes in :
|
|
210
|
+
# - calls to /generate must be replaced with calls to /test
|
|
211
|
+
if route_path == "":
|
|
212
|
+
test_route = entrypoint._legacy_generate_path
|
|
213
|
+
app.post(test_route, response_model=BaseResponse)(test_wrapper)
|
|
214
|
+
# LEGACY
|
|
215
|
+
|
|
216
|
+
# LEGACY
|
|
217
|
+
# TODO: Removing this implies no breaking changes
|
|
218
|
+
if route_path == "":
|
|
219
|
+
test_route = entrypoint._legacy_playground_run_path
|
|
220
|
+
app.post(test_route, response_model=BaseResponse)(test_wrapper)
|
|
221
|
+
# LEGACY
|
|
222
|
+
### ------------ #
|
|
223
|
+
|
|
224
|
+
### --- OpenAPI --- #
|
|
225
|
+
test_route = f"{entrypoint._test_path}{route_path}"
|
|
226
|
+
entrypoint.routes.append(
|
|
227
|
+
{
|
|
228
|
+
"func": func.__name__,
|
|
229
|
+
"endpoint": test_route,
|
|
230
|
+
"params": signature_parameters,
|
|
231
|
+
"config": config,
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# LEGACY
|
|
236
|
+
if route_path == "":
|
|
237
|
+
test_route = entrypoint._legacy_generate_path
|
|
238
|
+
entrypoint.routes.append(
|
|
239
|
+
{
|
|
240
|
+
"func": func.__name__,
|
|
241
|
+
"endpoint": test_route,
|
|
242
|
+
"params": (
|
|
243
|
+
{**default_parameters, **signature_parameters}
|
|
244
|
+
if not config
|
|
245
|
+
else signature_parameters
|
|
246
|
+
),
|
|
247
|
+
"config": config,
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
# LEGACY
|
|
251
|
+
|
|
252
|
+
app.openapi_schema = None # Forces FastAPI to re-generate the schema
|
|
253
|
+
openapi_schema = app.openapi()
|
|
254
|
+
openapi_schema["agenta_sdk"] = {"version": get_current_version()}
|
|
255
|
+
for _route in entrypoint.routes:
|
|
256
|
+
if _route["config"] is not None:
|
|
257
|
+
self.override_config_in_schema(
|
|
258
|
+
openapi_schema=openapi_schema,
|
|
259
|
+
func_name=_route["func"],
|
|
260
|
+
endpoint=_route["endpoint"],
|
|
261
|
+
config=_route["config"],
|
|
262
|
+
)
|
|
263
|
+
### --------------- #
|
|
264
|
+
|
|
265
|
+
def parse_config(self) -> Tuple[Optional[Type[BaseModel]], Dict[str, Any]]:
|
|
266
|
+
"""Parse the config schema and return the config class and default parameters."""
|
|
267
|
+
config = None
|
|
268
|
+
default_parameters = {}
|
|
269
|
+
|
|
270
|
+
if self.config_schema:
|
|
271
|
+
try:
|
|
272
|
+
config = self.config_schema() if self.config_schema else None
|
|
273
|
+
default_parameters = config.dict() if config else {}
|
|
274
|
+
except ValidationError as e:
|
|
275
|
+
raise ValueError(
|
|
276
|
+
f"Error initializing config_schema. Please ensure all required fields have default values: {str(e)}"
|
|
277
|
+
) from e
|
|
278
|
+
except Exception as e:
|
|
279
|
+
raise ValueError(
|
|
280
|
+
f"Unexpected error initializing config_schema: {str(e)}"
|
|
281
|
+
) from e
|
|
282
|
+
|
|
283
|
+
return config, default_parameters
|
|
284
|
+
|
|
285
|
+
def process_kwargs(
|
|
286
|
+
self, kwargs: Dict[str, Any], default_parameters: Dict[str, Any]
|
|
287
|
+
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
288
|
+
"""Remove the config parameters from the kwargs."""
|
|
289
|
+
# Extract agenta_config if present
|
|
290
|
+
config_params = kwargs.pop(self._config_key, {})
|
|
291
|
+
if isinstance(config_params, BaseModel):
|
|
292
|
+
config_params = config_params.dict()
|
|
293
|
+
# Merge with default parameters
|
|
294
|
+
config = {**default_parameters, **config_params}
|
|
295
|
+
|
|
296
|
+
return kwargs, config
|
|
297
|
+
|
|
298
|
+
async def execute_wrapper(
|
|
299
|
+
self,
|
|
300
|
+
request: Request,
|
|
301
|
+
*args,
|
|
302
|
+
**kwargs,
|
|
303
|
+
):
|
|
304
|
+
if not request:
|
|
305
|
+
raise HTTPException(status_code=500, detail="Missing 'request'.")
|
|
306
|
+
|
|
307
|
+
state = request.state
|
|
308
|
+
credentials = state.auth.get("credentials")
|
|
309
|
+
parameters = state.config.get("parameters")
|
|
310
|
+
references = state.config.get("references")
|
|
311
|
+
secrets = state.vault.get("secrets")
|
|
312
|
+
inline = state.inline
|
|
313
|
+
mock = state.mock
|
|
314
|
+
|
|
315
|
+
with routing_context_manager(
|
|
316
|
+
context=RoutingContext(
|
|
317
|
+
parameters=parameters,
|
|
318
|
+
secrets=secrets,
|
|
319
|
+
mock=mock,
|
|
320
|
+
)
|
|
321
|
+
):
|
|
322
|
+
with tracing_context_manager(
|
|
323
|
+
context=TracingContext(
|
|
324
|
+
credentials=credentials,
|
|
325
|
+
parameters=parameters,
|
|
326
|
+
references=references,
|
|
327
|
+
)
|
|
328
|
+
):
|
|
329
|
+
try:
|
|
330
|
+
result = (
|
|
331
|
+
await self.func(*args, **kwargs)
|
|
332
|
+
if iscoroutinefunction(self.func)
|
|
333
|
+
else self.func(*args, **kwargs)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return await self.handle_success(result, inline)
|
|
337
|
+
|
|
338
|
+
except Exception as error: # pylint: disable=broad-except
|
|
339
|
+
self.handle_failure(error)
|
|
340
|
+
|
|
341
|
+
async def handle_success(
|
|
342
|
+
self,
|
|
343
|
+
result: Any,
|
|
344
|
+
inline: bool,
|
|
345
|
+
):
|
|
346
|
+
data = None
|
|
347
|
+
tree = None
|
|
348
|
+
content_type = "text/plain"
|
|
349
|
+
tree_id = None
|
|
350
|
+
|
|
351
|
+
with suppress():
|
|
352
|
+
if isinstance(result, (dict, list)):
|
|
353
|
+
content_type = "application/json"
|
|
354
|
+
data = self.patch_result(result)
|
|
355
|
+
|
|
356
|
+
if inline:
|
|
357
|
+
tree, tree_id = await self.fetch_inline_trace(inline)
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
return BaseResponse(
|
|
361
|
+
data=data, tree=tree, content_type=content_type, tree_id=tree_id
|
|
362
|
+
)
|
|
363
|
+
except:
|
|
364
|
+
return BaseResponse(data=data, content_type=content_type)
|
|
365
|
+
|
|
366
|
+
def handle_failure(
|
|
367
|
+
self,
|
|
368
|
+
error: Exception,
|
|
369
|
+
):
|
|
370
|
+
display_exception("Application Exception")
|
|
371
|
+
|
|
372
|
+
status_code = 500
|
|
373
|
+
stacktrace = format_exception(error, value=error, tb=error.__traceback__) # type: ignore
|
|
374
|
+
|
|
375
|
+
raise HTTPException(
|
|
376
|
+
status_code=status_code,
|
|
377
|
+
detail={"message": str(error), "stacktrace": stacktrace},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def patch_result(
|
|
381
|
+
self,
|
|
382
|
+
result: Any,
|
|
383
|
+
):
|
|
384
|
+
"""
|
|
385
|
+
Patch the result to only include the message if the result is a FuncResponse-style dictionary with message, cost, and usage keys.
|
|
386
|
+
|
|
387
|
+
Example:
|
|
388
|
+
```python
|
|
389
|
+
result = {
|
|
390
|
+
"message": "Hello, world!",
|
|
391
|
+
"cost": 0.5,
|
|
392
|
+
"usage": {
|
|
393
|
+
"prompt_tokens": 10,
|
|
394
|
+
"completion_tokens": 20,
|
|
395
|
+
"total_tokens": 30
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
result = patch_result(result)
|
|
399
|
+
print(result)
|
|
400
|
+
# Output: "Hello, world!"
|
|
401
|
+
```
|
|
402
|
+
"""
|
|
403
|
+
data = (
|
|
404
|
+
result["message"]
|
|
405
|
+
if isinstance(result, dict)
|
|
406
|
+
and all(key in result for key in ["message", "cost", "usage"])
|
|
407
|
+
else result
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if data is None:
|
|
411
|
+
data = (
|
|
412
|
+
"Function executed successfully, but did return None. \n Are you sure you did not forget to return a value?",
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if not isinstance(result, dict):
|
|
416
|
+
data = str(data)
|
|
417
|
+
|
|
418
|
+
return data
|
|
419
|
+
|
|
420
|
+
async def fetch_inline_trace(
|
|
421
|
+
self,
|
|
422
|
+
inline: bool,
|
|
423
|
+
):
|
|
424
|
+
TIMEOUT = 1
|
|
425
|
+
TIMESTEP = 0.1
|
|
426
|
+
NOFSTEPS = TIMEOUT / TIMESTEP
|
|
427
|
+
|
|
428
|
+
context = tracing_context.get()
|
|
429
|
+
|
|
430
|
+
link = context.link
|
|
431
|
+
|
|
432
|
+
tree = None
|
|
433
|
+
_tree_id = link.get("tree_id") if link else None # in int format
|
|
434
|
+
tree_id = str(UUID(int=_tree_id)) if _tree_id else None # in uuid_as_str format
|
|
435
|
+
|
|
436
|
+
if _tree_id is not None:
|
|
437
|
+
if inline:
|
|
438
|
+
remaining_steps = NOFSTEPS
|
|
439
|
+
while (
|
|
440
|
+
not ag.tracing.is_inline_trace_ready(_tree_id)
|
|
441
|
+
and remaining_steps > 0
|
|
442
|
+
):
|
|
443
|
+
await sleep(TIMESTEP)
|
|
444
|
+
|
|
445
|
+
remaining_steps -= 1
|
|
446
|
+
|
|
447
|
+
tree = ag.tracing.get_inline_trace(_tree_id)
|
|
448
|
+
return tree, tree_id
|
|
449
|
+
|
|
450
|
+
# --- OpenAPI --- #
|
|
451
|
+
|
|
452
|
+
def add_request_to_signature(
|
|
453
|
+
self,
|
|
454
|
+
wrapper: Callable[..., Any],
|
|
455
|
+
):
|
|
456
|
+
original_sig = signature(wrapper)
|
|
457
|
+
parameters = [
|
|
458
|
+
Parameter(
|
|
459
|
+
"request",
|
|
460
|
+
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
|
461
|
+
annotation=Request,
|
|
462
|
+
),
|
|
463
|
+
*original_sig.parameters.values(),
|
|
464
|
+
]
|
|
465
|
+
new_sig = Signature(
|
|
466
|
+
parameters,
|
|
467
|
+
return_annotation=original_sig.return_annotation,
|
|
468
|
+
)
|
|
469
|
+
wrapper.__signature__ = new_sig
|
|
470
|
+
|
|
471
|
+
def update_wrapper_signature(
|
|
472
|
+
self, wrapper: Callable[..., Any], updated_params: List
|
|
473
|
+
):
|
|
474
|
+
"""
|
|
475
|
+
Updates the signature of a wrapper function with a new list of parameters.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
wrapper (callable): A callable object, such as a function or a method, that requires a signature update.
|
|
479
|
+
updated_params (List[Parameter]): A list of `Parameter` objects representing the updated parameters
|
|
480
|
+
for the wrapper function.
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
wrapper_signature = signature(wrapper)
|
|
484
|
+
wrapper_signature = wrapper_signature.replace(parameters=updated_params)
|
|
485
|
+
wrapper.__signature__ = wrapper_signature # type: ignore
|
|
486
|
+
|
|
487
|
+
def update_test_wrapper_signature(
|
|
488
|
+
self,
|
|
489
|
+
wrapper: Callable[..., Any],
|
|
490
|
+
config_instance: Type[BaseModel], # TODO: change to our type
|
|
491
|
+
) -> None:
|
|
492
|
+
"""Update the function signature to include new parameters."""
|
|
493
|
+
|
|
494
|
+
updated_params: List[Parameter] = []
|
|
495
|
+
self.add_config_params_to_parser(updated_params, config_instance)
|
|
496
|
+
self.add_func_params_to_parser(updated_params)
|
|
497
|
+
self.update_wrapper_signature(wrapper, updated_params)
|
|
498
|
+
self.add_request_to_signature(wrapper)
|
|
499
|
+
|
|
500
|
+
def update_run_wrapper_signature(
|
|
501
|
+
self,
|
|
502
|
+
wrapper: Callable[..., Any],
|
|
503
|
+
) -> None:
|
|
504
|
+
"""Update the function signature to include new parameters."""
|
|
505
|
+
|
|
506
|
+
updated_params: List[Parameter] = []
|
|
507
|
+
self.add_func_params_to_parser(updated_params)
|
|
508
|
+
self.update_wrapper_signature(wrapper, updated_params)
|
|
509
|
+
self.add_request_to_signature(wrapper)
|
|
510
|
+
|
|
511
|
+
def add_config_params_to_parser(
|
|
512
|
+
self, updated_params: list, config_instance: Type[BaseModel]
|
|
513
|
+
) -> None:
|
|
514
|
+
"""Add configuration parameters to function signature."""
|
|
515
|
+
|
|
516
|
+
for name, field in config_instance.model_fields.items():
|
|
517
|
+
assert field.default is not None, f"Field {name} has no default value"
|
|
518
|
+
|
|
519
|
+
updated_params.append(
|
|
520
|
+
Parameter(
|
|
521
|
+
name=self._config_key,
|
|
522
|
+
kind=Parameter.KEYWORD_ONLY,
|
|
523
|
+
annotation=type(config_instance), # Get the actual class type
|
|
524
|
+
default=Body(config_instance), # Use the instance directly
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def add_func_params_to_parser(self, updated_params: list) -> None:
|
|
529
|
+
"""Add function parameters to function signature."""
|
|
530
|
+
for name, param in signature(self.func).parameters.items():
|
|
531
|
+
assert (
|
|
532
|
+
len(param.default.__class__.__bases__) == 1
|
|
533
|
+
), f"Inherited standard type of {param.default.__class__} needs to be one."
|
|
534
|
+
updated_params.append(
|
|
535
|
+
Parameter(
|
|
536
|
+
name,
|
|
537
|
+
Parameter.KEYWORD_ONLY,
|
|
538
|
+
default=Body(..., embed=True),
|
|
539
|
+
annotation=param.default.__class__.__bases__[
|
|
540
|
+
0
|
|
541
|
+
], # determines and get the base (parent/inheritance) type of the sdk-type at run-time. \
|
|
542
|
+
# E.g __class__ is ag.MessagesInput() and accessing it parent type will return (<class 'list'>,), \
|
|
543
|
+
# thus, why we are accessing the first item.
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
def override_config_in_schema(
|
|
548
|
+
self,
|
|
549
|
+
openapi_schema: dict,
|
|
550
|
+
func_name: str,
|
|
551
|
+
endpoint: str,
|
|
552
|
+
config: Type[BaseModel],
|
|
553
|
+
):
|
|
554
|
+
"""Override config in OpenAPI schema to add agenta-specific metadata."""
|
|
555
|
+
endpoint = endpoint[1:].replace("/", "_")
|
|
556
|
+
schema_key = f"Body_{func_name}_{endpoint}_post"
|
|
557
|
+
schema_to_override = openapi_schema["components"]["schemas"][schema_key]
|
|
558
|
+
|
|
559
|
+
# Get the config class name to find its schema
|
|
560
|
+
config_class_name = type(config).__name__
|
|
561
|
+
config_schema = openapi_schema["components"]["schemas"][config_class_name]
|
|
562
|
+
# Process each field in the config class
|
|
563
|
+
for field_name, field in config.__class__.__fields__.items():
|
|
564
|
+
# Check if field has Annotated metadata for MultipleChoice
|
|
565
|
+
if hasattr(field, "metadata") and field.metadata:
|
|
566
|
+
for meta in field.metadata:
|
|
567
|
+
if isinstance(meta, MultipleChoice):
|
|
568
|
+
choices = meta.choices
|
|
569
|
+
if isinstance(choices, dict):
|
|
570
|
+
config_schema["properties"][field_name].update(
|
|
571
|
+
{"x-parameter": "grouped_choice", "choices": choices}
|
|
572
|
+
)
|
|
573
|
+
elif isinstance(choices, list):
|
|
574
|
+
config_schema["properties"][field_name].update(
|
|
575
|
+
{"x-parameter": "choice", "enum": choices}
|
|
576
|
+
)
|