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,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
|
+
}
|