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.

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.3.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.3.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.3.dist-info/RECORD +0 -114
  244. {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,296 @@
1
+ from typing import Callable, Optional, Any, Dict, List, Union
2
+
3
+ from functools import wraps
4
+ from itertools import chain
5
+ from inspect import iscoroutinefunction, getfullargspec
6
+
7
+ from opentelemetry import baggage as baggage
8
+ from opentelemetry.context import attach, detach
9
+
10
+ from agenta.sdk.utils.exceptions import suppress
11
+ from agenta.sdk.context.tracing import tracing_context
12
+ from agenta.sdk.tracing.conventions import parse_span_kind
13
+
14
+ import agenta as ag
15
+
16
+
17
+ class instrument: # pylint: disable=invalid-name
18
+ DEFAULT_KEY = "__default__"
19
+
20
+ def __init__(
21
+ self,
22
+ type: str = "task", # pylint: disable=redefined-builtin
23
+ config: Optional[Dict[str, Any]] = None,
24
+ ignore_inputs: Optional[bool] = None,
25
+ ignore_outputs: Optional[bool] = None,
26
+ redact: Optional[Callable[..., Any]] = None,
27
+ redact_on_error: Optional[bool] = True,
28
+ max_depth: Optional[int] = 2,
29
+ # DEPRECATING
30
+ kind: str = "task",
31
+ spankind: Optional[str] = "TASK",
32
+ ) -> None:
33
+ self.type = spankind or kind or type
34
+ self.kind = None
35
+ self.config = config
36
+ self.ignore_inputs = ignore_inputs
37
+ self.ignore_outputs = ignore_outputs
38
+ self.redact = redact
39
+ self.redact_on_error = redact_on_error
40
+ self.max_depth = max_depth
41
+
42
+ def __call__(self, func: Callable[..., Any]):
43
+ is_coroutine_function = iscoroutinefunction(func)
44
+
45
+ @wraps(func)
46
+ async def awrapper(*args, **kwargs):
47
+ async def aauto_instrumented(*args, **kwargs):
48
+ self._parse_type_and_kind()
49
+
50
+ token = self._attach_baggage()
51
+
52
+ with ag.tracer.start_as_current_span(func.__name__, kind=self.kind):
53
+ self._pre_instrument(func, *args, **kwargs)
54
+
55
+ result = await func(*args, **kwargs)
56
+
57
+ self._post_instrument(result)
58
+
59
+ return result
60
+
61
+ self._detach_baggage(token)
62
+
63
+ return await aauto_instrumented(*args, **kwargs)
64
+
65
+ @wraps(func)
66
+ def wrapper(*args, **kwargs):
67
+ def auto_instrumented(*args, **kwargs):
68
+ self._parse_type_and_kind()
69
+
70
+ token = self._attach_baggage()
71
+
72
+ with ag.tracer.start_as_current_span(func.__name__, kind=self.kind):
73
+ self._pre_instrument(func, *args, **kwargs)
74
+
75
+ result = func(*args, **kwargs)
76
+
77
+ self._post_instrument(result)
78
+
79
+ return result
80
+
81
+ self._detach_baggage(token)
82
+
83
+ return auto_instrumented(*args, **kwargs)
84
+
85
+ return awrapper if is_coroutine_function else wrapper
86
+
87
+ def _parse_type_and_kind(self):
88
+ if not ag.tracing.get_current_span().is_recording():
89
+ self.type = "workflow"
90
+
91
+ self.kind = parse_span_kind(self.type)
92
+
93
+ def _attach_baggage(self):
94
+ context = tracing_context.get()
95
+
96
+ references = context.references
97
+
98
+ token = None
99
+ if references:
100
+ for k, v in references.items():
101
+ token = attach(baggage.set_baggage(f"ag.refs.{k}", v))
102
+
103
+ return token
104
+
105
+ def _detach_baggage(
106
+ self,
107
+ token,
108
+ ):
109
+ if token:
110
+ detach(token)
111
+
112
+ def _pre_instrument(
113
+ self,
114
+ func,
115
+ *args,
116
+ **kwargs,
117
+ ):
118
+ span = ag.tracing.get_current_span()
119
+
120
+ context = tracing_context.get()
121
+
122
+ with suppress():
123
+ trace_id = span.context.trace_id
124
+
125
+ ag.tracing.credentials[trace_id] = context.credentials
126
+
127
+ span.set_attributes(
128
+ attributes={"node": self.type},
129
+ namespace="type",
130
+ )
131
+
132
+ if span.parent is None:
133
+ span.set_attributes(
134
+ attributes={"configuration": context.parameters or {}},
135
+ namespace="meta",
136
+ )
137
+
138
+ _inputs = self._redact(
139
+ name=span.name,
140
+ field="inputs",
141
+ io=self._parse(func, *args, **kwargs),
142
+ ignore=self.ignore_inputs,
143
+ )
144
+
145
+ span.set_attributes(
146
+ attributes={"inputs": _inputs},
147
+ namespace="data",
148
+ max_depth=self.max_depth,
149
+ )
150
+
151
+ def _post_instrument(
152
+ self,
153
+ result,
154
+ ):
155
+ span = ag.tracing.get_current_span()
156
+ with suppress():
157
+ cost = None
158
+ usage = {}
159
+
160
+ if isinstance(result, dict):
161
+ cost = result.get("cost", None)
162
+ usage = result.get("usage", {})
163
+
164
+ if isinstance(usage, (int, float)):
165
+ usage = {"total_tokens": usage}
166
+
167
+ span.set_attributes(
168
+ attributes={"total": cost},
169
+ namespace="metrics.unit.costs",
170
+ )
171
+ span.set_attributes(
172
+ attributes=(
173
+ {
174
+ "prompt": usage.get("prompt_tokens", None),
175
+ "completion": usage.get("completion_tokens", None),
176
+ "total": usage.get("total_tokens", None),
177
+ }
178
+ ),
179
+ namespace="metrics.unit.tokens",
180
+ )
181
+
182
+ _outputs = self._redact(
183
+ name=span.name,
184
+ field="outputs",
185
+ io=self._patch(result),
186
+ ignore=self.ignore_outputs,
187
+ )
188
+
189
+ span.set_attributes(
190
+ attributes={"outputs": _outputs},
191
+ namespace="data",
192
+ max_depth=self.max_depth,
193
+ )
194
+
195
+ span.set_status("OK")
196
+
197
+ with suppress():
198
+ if hasattr(span, "parent") and span.parent is None:
199
+ context = tracing_context.get()
200
+ context.link = {
201
+ "tree_id": span.get_span_context().trace_id,
202
+ "node_id": span.get_span_context().span_id,
203
+ }
204
+ tracing_context.set(context)
205
+
206
+ def _parse(
207
+ self,
208
+ func,
209
+ *args,
210
+ **kwargs,
211
+ ) -> Dict[str, Any]:
212
+ inputs = {
213
+ key: value
214
+ for key, value in chain(
215
+ zip(getfullargspec(func).args, args),
216
+ kwargs.items(),
217
+ )
218
+ }
219
+
220
+ return inputs
221
+
222
+ def _redact(
223
+ self,
224
+ *,
225
+ name: str,
226
+ field: str,
227
+ io: Dict[str, Any],
228
+ ignore: Union[List[str], bool] = False,
229
+ ) -> Dict[str, Any]:
230
+ """
231
+ Redact user-defined sensitive information
232
+ from inputs and outputs as defined by the ignore list or boolean flag.
233
+
234
+ Example:
235
+ - ignore = ["password"] -> {"username": "admin", "password": "********"}
236
+ -> {"username": "admin"}
237
+ - ignore = True -> {"username": "admin", "password": "********"}
238
+ -> {}
239
+ - ignore = False -> {"username": "admin", "password": "********"}
240
+ -> {"username": "admin", "password": "********"}
241
+ """
242
+ io = {
243
+ key: value
244
+ for key, value in io.items()
245
+ if key
246
+ not in (
247
+ ignore
248
+ if isinstance(ignore, list)
249
+ else io.keys()
250
+ if ignore is True
251
+ else []
252
+ )
253
+ }
254
+
255
+ if self.redact is not None:
256
+ try:
257
+ io = self.redact(name, field, io)
258
+ except: # pylint: disable=bare-except
259
+ if self.redact_on_error:
260
+ io = {}
261
+
262
+ if ag.tracing.redact is not None:
263
+ try:
264
+ io = ag.tracing.redact(name, field, io)
265
+ except: # pylint: disable=bare-except
266
+ if ag.tracing.redact_on_error:
267
+ io = {}
268
+
269
+ return io
270
+
271
+ def _patch(
272
+ self,
273
+ result: Any,
274
+ ) -> Dict[str, Any]:
275
+ """
276
+ Patch the result to ensure that it is a dictionary, with a default key when necessary.
277
+
278
+ Example:
279
+ - result = "Hello, World!"
280
+ -> {"__default__": "Hello, World!"}
281
+ - result = {"message": "Hello, World!", "cost": 0.0, "usage": {}}
282
+ -> {"__default__": "Hello, World!"}
283
+ - result = {"message": "Hello, World!"}
284
+ -> {"message": "Hello, World!"}
285
+ """
286
+ outputs = (
287
+ {instrument.DEFAULT_KEY: result}
288
+ if not isinstance(result, dict)
289
+ else (
290
+ {instrument.DEFAULT_KEY: result["message"]}
291
+ if all(key in result for key in ["message", "cost", "usage"])
292
+ else result
293
+ )
294
+ )
295
+
296
+ return outputs
@@ -0,0 +1 @@
1
+ from .litellm import litellm_handler
@@ -0,0 +1,314 @@
1
+ from typing import Dict
2
+ from opentelemetry.trace import SpanKind
3
+
4
+ import agenta as ag
5
+
6
+ from agenta.sdk.tracing.spans import CustomSpan
7
+ from agenta.sdk.utils.exceptions import suppress # TODO: use it !
8
+ from agenta.sdk.utils.logging import log
9
+
10
+
11
+ def litellm_handler():
12
+ try:
13
+ from litellm.integrations.custom_logger import ( # pylint: disable=import-outside-toplevel
14
+ CustomLogger as LitellmCustomLogger,
15
+ )
16
+ except ImportError as exc:
17
+ raise ImportError(
18
+ "The litellm SDK is not installed. Please install it using `pip install litellm`."
19
+ ) from exc
20
+ except Exception as exc:
21
+ raise Exception( # pylint: disable=broad-exception-raised
22
+ f"Unexpected error occurred when importing litellm: {exc}"
23
+ ) from exc
24
+
25
+ class LitellmHandler(LitellmCustomLogger):
26
+ """
27
+ This handler is responsible for instrumenting certain events,
28
+ when using litellm to call LLMs.
29
+
30
+ Args:
31
+ LitellmCustomLogger (object): custom logger that allows us
32
+ to override the events to capture.
33
+ """
34
+
35
+ def __init__(self):
36
+ super().__init__()
37
+
38
+ self.span: Dict[str, CustomSpan] = dict()
39
+
40
+ def log_pre_api_call(
41
+ self,
42
+ model,
43
+ messages,
44
+ kwargs,
45
+ ):
46
+ litellm_call_id = kwargs.get("litellm_call_id")
47
+
48
+ if not litellm_call_id:
49
+ log.warning("Agenta SDK - litellm tracing failed")
50
+ return
51
+
52
+ type = ( # pylint: disable=redefined-builtin
53
+ "chat"
54
+ if kwargs.get("call_type") in ["completion", "acompletion"]
55
+ else "embedding"
56
+ )
57
+
58
+ kind = SpanKind.CLIENT
59
+
60
+ self.span[litellm_call_id] = CustomSpan(
61
+ ag.tracer.start_span(name=f"litellm_{kind.name.lower()}", kind=kind)
62
+ )
63
+
64
+ span = self.span[litellm_call_id]
65
+
66
+ if not span:
67
+ log.warning("Agenta SDK - litellm tracing failed")
68
+ return
69
+
70
+ if not span.is_recording():
71
+ log.error("Agenta SDK - litellm span not recording.")
72
+ return
73
+
74
+ span.set_attributes(
75
+ attributes={"node": type},
76
+ namespace="type",
77
+ )
78
+
79
+ span.set_attributes(
80
+ attributes={"inputs": {"prompt": kwargs["messages"]}},
81
+ namespace="data",
82
+ )
83
+
84
+ span.set_attributes(
85
+ attributes={
86
+ "configuration": {
87
+ "model": kwargs.get("model"),
88
+ **kwargs.get("optional_params"),
89
+ }
90
+ },
91
+ namespace="meta",
92
+ )
93
+
94
+ def log_stream_event(
95
+ self,
96
+ kwargs,
97
+ response_obj,
98
+ start_time,
99
+ end_time,
100
+ ):
101
+ litellm_call_id = kwargs.get("litellm_call_id")
102
+
103
+ if not litellm_call_id:
104
+ log.warning("Agenta SDK - litellm tracing failed")
105
+ return
106
+
107
+ span = self.span[litellm_call_id]
108
+
109
+ if not span:
110
+ log.warning("Agenta SDK - litellm tracing failed")
111
+ return
112
+
113
+ if not span.is_recording():
114
+ return
115
+
116
+ def log_success_event(
117
+ self,
118
+ kwargs,
119
+ response_obj,
120
+ start_time,
121
+ end_time,
122
+ ):
123
+ if kwargs.get("stream"):
124
+ return
125
+
126
+ litellm_call_id = kwargs.get("litellm_call_id")
127
+
128
+ if not litellm_call_id:
129
+ log.warning("Agenta SDK - litellm tracing failed")
130
+ return
131
+
132
+ span = self.span[litellm_call_id]
133
+
134
+ if not span:
135
+ log.warning("Agenta SDK - litellm tracing failed")
136
+ return
137
+
138
+ if not span.is_recording():
139
+ return
140
+
141
+ try:
142
+ result = []
143
+ for choice in response_obj.choices:
144
+ message = choice.message.__dict__
145
+ result.append(message)
146
+
147
+ outputs = {"completion": result}
148
+ span.set_attributes(
149
+ attributes={"outputs": outputs},
150
+ namespace="data",
151
+ )
152
+
153
+ except Exception as e:
154
+ pass
155
+
156
+ span.set_attributes(
157
+ attributes={"total": kwargs.get("response_cost")},
158
+ namespace="metrics.unit.costs",
159
+ )
160
+
161
+ span.set_attributes(
162
+ attributes=(
163
+ {
164
+ "prompt": response_obj.usage.prompt_tokens,
165
+ "completion": response_obj.usage.completion_tokens,
166
+ "total": response_obj.usage.total_tokens,
167
+ }
168
+ ),
169
+ namespace="metrics.unit.tokens",
170
+ )
171
+
172
+ span.set_status(status="OK")
173
+
174
+ span.end()
175
+
176
+ def log_failure_event(
177
+ self,
178
+ kwargs,
179
+ response_obj,
180
+ start_time,
181
+ end_time,
182
+ ):
183
+ litellm_call_id = kwargs.get("litellm_call_id")
184
+
185
+ if not litellm_call_id:
186
+ log.warning("Agenta SDK - litellm tracing failed")
187
+ return
188
+
189
+ span = self.span[litellm_call_id]
190
+
191
+ if not span:
192
+ log.warning("Agenta SDK - litellm tracing failed")
193
+ return
194
+
195
+ if not span.is_recording():
196
+ return
197
+
198
+ span.record_exception(kwargs["exception"])
199
+
200
+ span.set_status(status="ERROR")
201
+
202
+ span.end()
203
+
204
+ async def async_log_stream_event(
205
+ self,
206
+ kwargs,
207
+ response_obj,
208
+ start_time,
209
+ end_time,
210
+ ):
211
+ if kwargs.get("stream"):
212
+ return
213
+
214
+ litellm_call_id = kwargs.get("litellm_call_id")
215
+
216
+ if not litellm_call_id:
217
+ log.warning("Agenta SDK - litellm tracing failed")
218
+ return
219
+
220
+ span = self.span[litellm_call_id]
221
+
222
+ if not span:
223
+ log.warning("Agenta SDK - litellm tracing failed")
224
+ return
225
+
226
+ if not span.is_recording():
227
+ return
228
+
229
+ async def async_log_success_event(
230
+ self,
231
+ kwargs,
232
+ response_obj,
233
+ start_time,
234
+ end_time,
235
+ ):
236
+ litellm_call_id = kwargs.get("litellm_call_id")
237
+
238
+ if not litellm_call_id:
239
+ log.warning("Agenta SDK - litellm tracing failed")
240
+ return
241
+
242
+ span = self.span[litellm_call_id]
243
+
244
+ if not span:
245
+ log.warning("Agenta SDK - litellm tracing failed")
246
+ return
247
+
248
+ if not span.is_recording():
249
+ return
250
+
251
+ try:
252
+ result = []
253
+ for choice in response_obj.choices:
254
+ message = choice.message.__dict__
255
+ result.append(message)
256
+
257
+ outputs = {"completion": result}
258
+ span.set_attributes(
259
+ attributes={"outputs": outputs},
260
+ namespace="data",
261
+ )
262
+
263
+ except Exception as e:
264
+ pass
265
+
266
+ span.set_attributes(
267
+ attributes={"total": kwargs.get("response_cost")},
268
+ namespace="metrics.unit.costs",
269
+ )
270
+
271
+ span.set_attributes(
272
+ attributes=(
273
+ {
274
+ "prompt": response_obj.usage.prompt_tokens,
275
+ "completion": response_obj.usage.completion_tokens,
276
+ "total": response_obj.usage.total_tokens,
277
+ }
278
+ ),
279
+ namespace="metrics.unit.tokens",
280
+ )
281
+
282
+ span.set_status(status="OK")
283
+
284
+ span.end()
285
+
286
+ async def async_log_failure_event(
287
+ self,
288
+ kwargs,
289
+ response_obj,
290
+ start_time,
291
+ end_time,
292
+ ):
293
+ litellm_call_id = kwargs.get("litellm_call_id")
294
+
295
+ if not litellm_call_id:
296
+ log.warning("Agenta SDK - litellm tracing failed")
297
+ return
298
+
299
+ span = self.span[litellm_call_id]
300
+
301
+ if not span:
302
+ log.warning("Agenta SDK - litellm tracing failed")
303
+ return
304
+
305
+ if not span.is_recording():
306
+ return
307
+
308
+ span.record_exception(kwargs["exception"])
309
+
310
+ span.set_status(status="ERROR")
311
+
312
+ span.end()
313
+
314
+ return LitellmHandler()
@@ -0,0 +1,27 @@
1
+ from typing import Optional, Protocol, Any
2
+
3
+ from agenta.sdk.context.routing import routing_context
4
+ from agenta.sdk.litellm.mocks import MOCKS
5
+
6
+
7
+ class LitellmProtocol(Protocol):
8
+ async def acompletion(self, *args: Any, **kwargs: Any) -> Any:
9
+ ...
10
+
11
+
12
+ litellm: Optional[LitellmProtocol] = None # pylint: disable=invalid-name
13
+
14
+
15
+ async def acompletion(*args, **kwargs):
16
+ mock = routing_context.get().mock
17
+
18
+ if mock:
19
+ if mock not in MOCKS:
20
+ raise ValueError(f"Mock {mock} not found")
21
+
22
+ return MOCKS[mock]
23
+
24
+ if not litellm:
25
+ raise ValueError("litellm not found")
26
+
27
+ return await litellm.acompletion(*args, **kwargs)
@@ -0,0 +1,26 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class MockMessageModel(BaseModel):
5
+ content: str
6
+
7
+
8
+ class MockChoiceModel(BaseModel):
9
+ message: MockMessageModel
10
+
11
+
12
+ class MockResponseModel(BaseModel):
13
+ choices: list[MockChoiceModel]
14
+
15
+
16
+ MOCKS = {
17
+ "hello": MockResponseModel(
18
+ choices=[
19
+ MockChoiceModel(
20
+ message=MockMessageModel(
21
+ content="world",
22
+ )
23
+ )
24
+ ],
25
+ ),
26
+ }
@@ -0,0 +1,6 @@
1
+ from agenta.sdk.managers.config import ConfigManager
2
+ from agenta.sdk.managers.variant import VariantManager
3
+ from agenta.sdk.managers.deployment import DeploymentManager
4
+
5
+
6
+ __all__ = ["ConfigManager", "VariantManager", "DeploymentManager"]