agenta 0.12.2__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.

Files changed (244) hide show
  1. agenta/__init__.py +64 -7
  2. agenta/cli/helper.py +7 -3
  3. agenta/cli/main.py +15 -50
  4. agenta/cli/variant_commands.py +50 -29
  5. agenta/client/Readme.md +72 -64
  6. agenta/client/api.py +2 -2
  7. agenta/client/backend/__init__.py +193 -22
  8. agenta/client/backend/access_control/__init__.py +1 -0
  9. agenta/client/backend/access_control/client.py +167 -0
  10. agenta/client/backend/apps/__init__.py +1 -0
  11. agenta/client/backend/apps/client.py +1691 -0
  12. agenta/client/backend/bases/__init__.py +1 -0
  13. agenta/client/backend/bases/client.py +190 -0
  14. agenta/client/backend/client.py +2508 -5712
  15. agenta/client/backend/configs/__init__.py +1 -0
  16. agenta/client/backend/configs/client.py +604 -0
  17. agenta/client/backend/containers/__init__.py +5 -0
  18. agenta/client/backend/containers/client.py +648 -0
  19. agenta/client/backend/containers/types/__init__.py +5 -0
  20. agenta/client/backend/{types → containers/types}/container_templates_response.py +1 -2
  21. agenta/client/backend/core/__init__.py +30 -0
  22. agenta/client/backend/core/client_wrapper.py +42 -9
  23. agenta/client/backend/core/file.py +70 -0
  24. agenta/client/backend/core/http_client.py +575 -0
  25. agenta/client/backend/core/jsonable_encoder.py +33 -39
  26. agenta/client/backend/core/pydantic_utilities.py +325 -0
  27. agenta/client/backend/core/query_encoder.py +60 -0
  28. agenta/client/backend/core/remove_none_from_dict.py +2 -2
  29. agenta/client/backend/core/request_options.py +35 -0
  30. agenta/client/backend/core/serialization.py +276 -0
  31. agenta/client/backend/environments/__init__.py +1 -0
  32. agenta/client/backend/environments/client.py +196 -0
  33. agenta/client/backend/evaluations/__init__.py +1 -0
  34. agenta/client/backend/evaluations/client.py +1469 -0
  35. agenta/client/backend/evaluators/__init__.py +1 -0
  36. agenta/client/backend/evaluators/client.py +1283 -0
  37. agenta/client/backend/observability/__init__.py +1 -0
  38. agenta/client/backend/observability/client.py +1286 -0
  39. agenta/client/backend/observability_v_1/__init__.py +5 -0
  40. agenta/client/backend/observability_v_1/client.py +763 -0
  41. agenta/client/backend/observability_v_1/types/__init__.py +7 -0
  42. agenta/client/backend/observability_v_1/types/format.py +5 -0
  43. agenta/client/backend/observability_v_1/types/query_analytics_response.py +7 -0
  44. agenta/client/backend/observability_v_1/types/query_traces_response.py +11 -0
  45. agenta/client/backend/scopes/__init__.py +1 -0
  46. agenta/client/backend/scopes/client.py +114 -0
  47. agenta/client/backend/testsets/__init__.py +1 -0
  48. agenta/client/backend/testsets/client.py +1284 -0
  49. agenta/client/backend/types/__init__.py +154 -26
  50. agenta/client/backend/types/agenta_node_dto.py +48 -0
  51. agenta/client/backend/types/agenta_node_dto_nodes_value.py +6 -0
  52. agenta/client/backend/types/agenta_nodes_response.py +30 -0
  53. agenta/client/backend/types/agenta_root_dto.py +30 -0
  54. agenta/client/backend/types/agenta_roots_response.py +30 -0
  55. agenta/client/backend/types/agenta_tree_dto.py +30 -0
  56. agenta/client/backend/types/agenta_trees_response.py +30 -0
  57. agenta/client/backend/types/aggregated_result.py +16 -31
  58. agenta/client/backend/types/aggregated_result_evaluator_config.py +8 -0
  59. agenta/client/backend/types/analytics_response.py +24 -0
  60. agenta/client/backend/types/app.py +17 -30
  61. agenta/client/backend/types/app_variant_response.py +36 -0
  62. agenta/client/backend/types/app_variant_revision.py +17 -32
  63. agenta/client/backend/types/base_output.py +13 -28
  64. agenta/client/backend/types/body_import_testset.py +15 -31
  65. agenta/client/backend/types/bucket_dto.py +26 -0
  66. agenta/client/backend/types/collect_status_response.py +22 -0
  67. agenta/client/backend/types/config_db.py +16 -31
  68. agenta/client/backend/types/config_dto.py +32 -0
  69. agenta/client/backend/types/config_response_model.py +32 -0
  70. agenta/client/backend/types/correct_answer.py +22 -0
  71. agenta/client/backend/types/create_app_output.py +13 -28
  72. agenta/client/backend/types/create_span.py +45 -0
  73. agenta/client/backend/types/create_trace_response.py +22 -0
  74. agenta/client/backend/types/docker_env_vars.py +13 -28
  75. agenta/client/backend/types/environment_output.py +22 -34
  76. agenta/client/backend/types/environment_output_extended.py +31 -0
  77. agenta/client/backend/types/environment_revision.py +26 -0
  78. agenta/client/backend/types/error.py +22 -0
  79. agenta/client/backend/types/evaluation.py +22 -33
  80. agenta/client/backend/types/evaluation_scenario.py +18 -33
  81. agenta/client/backend/types/evaluation_scenario_input.py +16 -31
  82. agenta/client/backend/types/evaluation_scenario_output.py +17 -30
  83. agenta/client/backend/types/evaluation_scenario_result.py +14 -29
  84. agenta/client/backend/types/evaluation_scenario_score_update.py +21 -0
  85. agenta/client/backend/types/evaluation_status_enum.py +11 -29
  86. agenta/client/backend/types/evaluation_type.py +3 -21
  87. agenta/client/backend/types/evaluator.py +20 -31
  88. agenta/client/backend/types/evaluator_config.py +21 -33
  89. agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
  90. agenta/client/backend/types/evaluator_output_interface.py +21 -0
  91. agenta/client/backend/types/exception_dto.py +26 -0
  92. agenta/client/backend/types/get_config_response.py +23 -0
  93. agenta/client/backend/types/header_dto.py +22 -0
  94. agenta/client/backend/types/http_validation_error.py +14 -29
  95. agenta/client/backend/types/human_evaluation.py +18 -34
  96. agenta/client/backend/types/human_evaluation_scenario.py +22 -38
  97. agenta/client/backend/types/human_evaluation_scenario_input.py +13 -28
  98. agenta/client/backend/types/human_evaluation_scenario_output.py +13 -28
  99. agenta/client/backend/types/human_evaluation_scenario_update.py +30 -0
  100. agenta/client/backend/types/human_evaluation_update.py +22 -0
  101. agenta/client/backend/types/image.py +18 -32
  102. agenta/client/backend/types/invite_request.py +16 -30
  103. agenta/client/backend/types/legacy_analytics_response.py +29 -0
  104. agenta/client/backend/types/legacy_data_point.py +27 -0
  105. agenta/client/backend/types/lifecycle_dto.py +24 -0
  106. agenta/client/backend/types/link_dto.py +24 -0
  107. agenta/client/backend/types/list_api_keys_response.py +24 -0
  108. agenta/client/backend/types/llm_run_rate_limit.py +13 -28
  109. agenta/client/backend/types/llm_tokens.py +23 -0
  110. agenta/client/backend/types/metrics_dto.py +24 -0
  111. agenta/client/backend/types/new_human_evaluation.py +27 -0
  112. agenta/client/backend/types/new_testset.py +16 -31
  113. agenta/client/backend/types/node_dto.py +24 -0
  114. agenta/client/backend/types/node_type.py +19 -0
  115. agenta/client/backend/types/o_tel_context_dto.py +22 -0
  116. agenta/client/backend/types/o_tel_event_dto.py +23 -0
  117. agenta/client/backend/types/o_tel_extra_dto.py +26 -0
  118. agenta/client/backend/types/o_tel_link_dto.py +23 -0
  119. agenta/client/backend/types/o_tel_span_dto.py +37 -0
  120. agenta/client/backend/types/o_tel_span_kind.py +15 -0
  121. agenta/client/backend/types/o_tel_spans_response.py +24 -0
  122. agenta/client/backend/types/o_tel_status_code.py +8 -0
  123. agenta/client/backend/types/organization.py +22 -35
  124. agenta/client/backend/types/organization_output.py +13 -28
  125. agenta/client/backend/types/outputs.py +5 -0
  126. agenta/client/backend/types/parent_dto.py +21 -0
  127. agenta/client/backend/types/permission.py +41 -0
  128. agenta/client/backend/types/projects_response.py +28 -0
  129. agenta/client/backend/types/provider_key_dto.py +23 -0
  130. agenta/client/backend/types/provider_kind.py +21 -0
  131. agenta/client/backend/types/reference_dto.py +23 -0
  132. agenta/client/backend/types/reference_request_model.py +23 -0
  133. agenta/client/backend/types/result.py +18 -31
  134. agenta/client/backend/types/root_dto.py +21 -0
  135. agenta/client/backend/types/{human_evaluation_scenario_score.py → score.py} +1 -1
  136. agenta/client/backend/types/secret_dto.py +24 -0
  137. agenta/client/backend/types/{human_evaluation_scenario_update_score.py → secret_kind.py} +1 -1
  138. agenta/client/backend/types/secret_response_dto.py +27 -0
  139. agenta/client/backend/types/simple_evaluation_output.py +13 -28
  140. agenta/client/backend/types/span.py +39 -49
  141. agenta/client/backend/types/span_detail.py +44 -0
  142. agenta/client/backend/types/span_dto.py +54 -0
  143. agenta/client/backend/types/span_dto_nodes_value.py +9 -0
  144. agenta/client/backend/types/span_status_code.py +5 -0
  145. agenta/client/backend/types/span_variant.py +23 -0
  146. agenta/client/backend/types/status_code.py +5 -0
  147. agenta/client/backend/types/status_dto.py +23 -0
  148. agenta/client/backend/types/template.py +14 -29
  149. agenta/client/backend/types/template_image_info.py +21 -35
  150. agenta/client/backend/types/test_set_output_response.py +20 -33
  151. agenta/client/backend/types/test_set_simple_response.py +13 -28
  152. agenta/client/backend/types/time_dto.py +23 -0
  153. agenta/client/backend/types/trace_detail.py +44 -0
  154. agenta/client/backend/types/tree_dto.py +23 -0
  155. agenta/client/backend/types/tree_type.py +5 -0
  156. agenta/client/backend/types/update_app_output.py +22 -0
  157. agenta/client/backend/types/uri.py +13 -28
  158. agenta/client/backend/types/validation_error.py +13 -28
  159. agenta/client/backend/types/variant_action.py +14 -29
  160. agenta/client/backend/types/variant_action_enum.py +1 -19
  161. agenta/client/backend/types/with_pagination.py +26 -0
  162. agenta/client/backend/types/workspace_member_response.py +23 -0
  163. agenta/client/backend/types/workspace_permission.py +25 -0
  164. agenta/client/backend/types/workspace_response.py +29 -0
  165. agenta/client/backend/types/workspace_role.py +15 -0
  166. agenta/client/backend/types/workspace_role_response.py +23 -0
  167. agenta/client/backend/variants/__init__.py +5 -0
  168. agenta/client/backend/variants/client.py +2814 -0
  169. agenta/client/backend/variants/types/__init__.py +7 -0
  170. agenta/client/backend/variants/types/add_variant_from_base_and_config_response.py +8 -0
  171. agenta/client/backend/vault/__init__.py +1 -0
  172. agenta/client/backend/vault/client.py +685 -0
  173. agenta/client/client.py +1 -1
  174. agenta/config.py +0 -2
  175. agenta/config.toml +0 -1
  176. agenta/docker/docker-assets/Dockerfile.cloud.template +2 -1
  177. agenta/docker/docker-assets/Dockerfile.template +2 -1
  178. agenta/docker/docker_utils.py +11 -12
  179. agenta/sdk/__init__.py +58 -7
  180. agenta/sdk/agenta_init.py +182 -164
  181. agenta/sdk/assets.py +95 -0
  182. agenta/sdk/client.py +56 -0
  183. agenta/sdk/context/__init__.py +0 -0
  184. agenta/sdk/context/exporting.py +25 -0
  185. agenta/sdk/context/routing.py +27 -0
  186. agenta/sdk/context/tracing.py +28 -0
  187. agenta/sdk/decorators/__init__.py +0 -0
  188. agenta/sdk/decorators/routing.py +576 -0
  189. agenta/sdk/decorators/tracing.py +296 -0
  190. agenta/sdk/litellm/__init__.py +1 -0
  191. agenta/sdk/litellm/litellm.py +314 -0
  192. agenta/sdk/litellm/mockllm.py +27 -0
  193. agenta/sdk/litellm/mocks/__init__.py +26 -0
  194. agenta/sdk/managers/__init__.py +6 -0
  195. agenta/sdk/managers/config.py +208 -0
  196. agenta/sdk/managers/deployment.py +45 -0
  197. agenta/sdk/managers/secrets.py +38 -0
  198. agenta/sdk/managers/shared.py +639 -0
  199. agenta/sdk/managers/variant.py +182 -0
  200. agenta/sdk/managers/vault.py +16 -0
  201. agenta/sdk/middleware/__init__.py +0 -0
  202. agenta/sdk/middleware/auth.py +180 -0
  203. agenta/sdk/middleware/cache.py +47 -0
  204. agenta/sdk/middleware/config.py +255 -0
  205. agenta/sdk/middleware/cors.py +29 -0
  206. agenta/sdk/middleware/inline.py +38 -0
  207. agenta/sdk/middleware/mock.py +33 -0
  208. agenta/sdk/middleware/otel.py +40 -0
  209. agenta/sdk/middleware/vault.py +145 -0
  210. agenta/sdk/router.py +0 -7
  211. agenta/sdk/tracing/__init__.py +1 -0
  212. agenta/sdk/tracing/attributes.py +141 -0
  213. agenta/sdk/tracing/conventions.py +49 -0
  214. agenta/sdk/tracing/exporters.py +103 -0
  215. agenta/sdk/tracing/inline.py +1146 -0
  216. agenta/sdk/tracing/processors.py +121 -0
  217. agenta/sdk/tracing/spans.py +136 -0
  218. agenta/sdk/tracing/tracing.py +237 -0
  219. agenta/sdk/types.py +478 -74
  220. agenta/sdk/utils/__init__.py +0 -0
  221. agenta/sdk/utils/constants.py +1 -0
  222. agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
  223. agenta/sdk/utils/exceptions.py +59 -0
  224. agenta/sdk/utils/globals.py +6 -10
  225. agenta/sdk/utils/helpers.py +8 -0
  226. agenta/sdk/utils/logging.py +21 -0
  227. agenta/sdk/utils/singleton.py +13 -0
  228. agenta/sdk/utils/timing.py +58 -0
  229. {agenta-0.12.2.dist-info → agenta-0.32.0a1.dist-info}/METADATA +98 -151
  230. agenta-0.32.0a1.dist-info/RECORD +263 -0
  231. {agenta-0.12.2.dist-info → agenta-0.32.0a1.dist-info}/WHEEL +1 -1
  232. agenta/client/backend/types/add_variant_from_base_and_config_response.py +0 -7
  233. agenta/client/backend/types/app_variant_output.py +0 -47
  234. agenta/client/backend/types/app_variant_output_extended.py +0 -50
  235. agenta/client/backend/types/delete_evaluation.py +0 -36
  236. agenta/client/backend/types/evaluation_webhook.py +0 -36
  237. agenta/client/backend/types/feedback.py +0 -40
  238. agenta/client/backend/types/get_config_reponse.py +0 -39
  239. agenta/client/backend/types/list_api_keys_output.py +0 -39
  240. agenta/client/backend/types/trace.py +0 -48
  241. agenta/sdk/agenta_decorator.py +0 -443
  242. agenta/sdk/context.py +0 -41
  243. agenta-0.12.2.dist-info/RECORD +0 -114
  244. {agenta-0.12.2.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
+ )