agenta 0.27.0__py3-none-any.whl → 0.27.0a0__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 +3 -23
- agenta/cli/helper.py +1 -5
- agenta/client/backend/__init__.py +0 -14
- agenta/client/backend/apps/client.py +20 -28
- agenta/client/backend/client.py +2 -25
- agenta/client/backend/containers/client.py +1 -5
- agenta/client/backend/core/__init__.py +1 -2
- agenta/client/backend/core/client_wrapper.py +6 -6
- agenta/client/backend/core/file.py +11 -33
- agenta/client/backend/core/http_client.py +18 -24
- agenta/client/backend/core/pydantic_utilities.py +29 -144
- agenta/client/backend/core/request_options.py +0 -3
- agenta/client/backend/core/serialization.py +42 -139
- agenta/client/backend/evaluations/client.py +2 -7
- agenta/client/backend/evaluators/client.py +1 -349
- agenta/client/backend/observability/client.py +2 -11
- agenta/client/backend/testsets/client.py +10 -10
- agenta/client/backend/types/__init__.py +0 -14
- agenta/client/backend/types/app.py +0 -1
- agenta/client/backend/types/app_variant_response.py +1 -3
- agenta/client/backend/types/create_span.py +2 -3
- agenta/client/backend/types/environment_output.py +0 -1
- agenta/client/backend/types/environment_output_extended.py +0 -1
- agenta/client/backend/types/evaluation.py +2 -1
- agenta/client/backend/types/evaluator.py +0 -2
- agenta/client/backend/types/evaluator_config.py +0 -1
- agenta/client/backend/types/human_evaluation.py +2 -1
- agenta/client/backend/types/llm_tokens.py +2 -2
- agenta/client/backend/types/span.py +0 -1
- agenta/client/backend/types/span_detail.py +1 -7
- agenta/client/backend/types/test_set_output_response.py +2 -5
- agenta/client/backend/types/trace_detail.py +1 -7
- agenta/client/backend/types/with_pagination.py +2 -4
- agenta/client/backend/variants/client.py +273 -1566
- agenta/docker/docker-assets/Dockerfile.cloud.template +1 -1
- agenta/sdk/__init__.py +5 -21
- agenta/sdk/agenta_init.py +29 -34
- agenta/sdk/config_manager.py +205 -0
- agenta/sdk/context/routing.py +5 -6
- agenta/sdk/decorators/routing.py +146 -158
- agenta/sdk/decorators/tracing.py +239 -206
- agenta/sdk/litellm/litellm.py +36 -47
- agenta/sdk/tracing/attributes.py +47 -7
- agenta/sdk/tracing/context.py +2 -5
- agenta/sdk/tracing/conventions.py +19 -25
- agenta/sdk/tracing/exporters.py +5 -17
- agenta/sdk/tracing/inline.py +146 -92
- agenta/sdk/tracing/processors.py +13 -65
- agenta/sdk/tracing/spans.py +4 -16
- agenta/sdk/tracing/tracing.py +65 -124
- agenta/sdk/types.py +2 -61
- agenta/sdk/utils/exceptions.py +5 -38
- {agenta-0.27.0.dist-info → agenta-0.27.0a0.dist-info}/METADATA +1 -1
- {agenta-0.27.0.dist-info → agenta-0.27.0a0.dist-info}/RECORD +56 -67
- agenta/client/backend/types/config_dto.py +0 -32
- agenta/client/backend/types/config_response_model.py +0 -32
- agenta/client/backend/types/evaluator_mapping_output_interface.py +0 -21
- agenta/client/backend/types/evaluator_output_interface.py +0 -21
- agenta/client/backend/types/lifecycle_dto.py +0 -24
- agenta/client/backend/types/reference_dto.py +0 -23
- agenta/client/backend/types/reference_request_model.py +0 -23
- agenta/sdk/managers/__init__.py +0 -6
- agenta/sdk/managers/config.py +0 -318
- agenta/sdk/managers/deployment.py +0 -45
- agenta/sdk/managers/shared.py +0 -639
- agenta/sdk/managers/variant.py +0 -182
- {agenta-0.27.0.dist-info → agenta-0.27.0a0.dist-info}/WHEEL +0 -0
- {agenta-0.27.0.dist-info → agenta-0.27.0a0.dist-info}/entry_points.txt +0 -0
agenta/sdk/decorators/tracing.py
CHANGED
|
@@ -1,250 +1,283 @@
|
|
|
1
|
-
|
|
1
|
+
import inspect
|
|
2
|
+
import traceback
|
|
2
3
|
from functools import wraps
|
|
3
4
|
from itertools import chain
|
|
4
|
-
from
|
|
5
|
+
from typing import Callable, Optional, Any, Dict, List
|
|
6
|
+
|
|
7
|
+
import agenta as ag
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
from agenta.sdk.utils.exceptions import suppress
|
|
11
|
+
|
|
7
12
|
from agenta.sdk.context.tracing import tracing_context
|
|
8
13
|
from agenta.sdk.tracing.conventions import parse_span_kind
|
|
9
14
|
|
|
10
|
-
import agenta as ag
|
|
11
|
-
|
|
12
15
|
|
|
13
|
-
class instrument:
|
|
16
|
+
class instrument:
|
|
14
17
|
DEFAULT_KEY = "__default__"
|
|
15
18
|
|
|
16
19
|
def __init__(
|
|
17
20
|
self,
|
|
18
|
-
|
|
21
|
+
kind: str = "task",
|
|
19
22
|
config: Optional[Dict[str, Any]] = None,
|
|
20
23
|
ignore_inputs: Optional[bool] = None,
|
|
21
24
|
ignore_outputs: Optional[bool] = None,
|
|
22
25
|
max_depth: Optional[int] = 2,
|
|
23
|
-
#
|
|
24
|
-
kind: str = "task",
|
|
26
|
+
# DEPRECATED
|
|
25
27
|
spankind: Optional[str] = "TASK",
|
|
26
28
|
) -> None:
|
|
27
|
-
self.
|
|
28
|
-
self.kind = None
|
|
29
|
+
self.kind = spankind if spankind is not None else kind
|
|
29
30
|
self.config = config
|
|
30
31
|
self.ignore_inputs = ignore_inputs
|
|
31
32
|
self.ignore_outputs = ignore_outputs
|
|
32
33
|
self.max_depth = max_depth
|
|
33
34
|
|
|
34
35
|
def __call__(self, func: Callable[..., Any]):
|
|
35
|
-
is_coroutine_function = iscoroutinefunction(func)
|
|
36
|
+
is_coroutine_function = inspect.iscoroutinefunction(func)
|
|
37
|
+
|
|
38
|
+
def parse(*args, **kwargs) -> Dict[str, Any]:
|
|
39
|
+
inputs = {
|
|
40
|
+
key: value
|
|
41
|
+
for key, value in chain(
|
|
42
|
+
zip(inspect.getfullargspec(func).args, args),
|
|
43
|
+
kwargs.items(),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return inputs
|
|
48
|
+
|
|
49
|
+
def redact(
|
|
50
|
+
io: Dict[str, Any], ignore: List[str] | bool = False
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
"""
|
|
53
|
+
Redact user-defined sensitive information from inputs and outputs as defined by the ignore list or boolean flag.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
- ignore = ["password"] -> {"username": "admin", "password": "********"} -> {"username": "admin"}
|
|
57
|
+
- ignore = True -> {"username": "admin", "password": "********"} -> {}
|
|
58
|
+
- ignore = False -> {"username": "admin", "password": "********"} -> {"username": "admin", "password": "********"}
|
|
59
|
+
"""
|
|
60
|
+
io = {
|
|
61
|
+
key: value
|
|
62
|
+
for key, value in io.items()
|
|
63
|
+
if key
|
|
64
|
+
not in (
|
|
65
|
+
ignore
|
|
66
|
+
if isinstance(ignore, list)
|
|
67
|
+
else io.keys()
|
|
68
|
+
if ignore is True
|
|
69
|
+
else []
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return io
|
|
74
|
+
|
|
75
|
+
def patch(result: Any) -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Patch the result to ensure that it is a dictionary, with a default key when necessary.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
- result = "Hello, World!" -> {"__default__": "Hello, World!"}
|
|
81
|
+
- result = {"message": "Hello, World!", "cost": 0.0, "usage": {}} -> {"__default__": "Hello, World!"}
|
|
82
|
+
- result = {"message": "Hello, World!"} -> {"message": "Hello, World!"}
|
|
83
|
+
"""
|
|
84
|
+
outputs = (
|
|
85
|
+
{instrument.DEFAULT_KEY: result}
|
|
86
|
+
if not isinstance(result, dict)
|
|
87
|
+
else (
|
|
88
|
+
{instrument.DEFAULT_KEY: result["message"]}
|
|
89
|
+
if all(key in result for key in ["message", "cost", "usage"])
|
|
90
|
+
else result
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return outputs
|
|
36
95
|
|
|
37
96
|
@wraps(func)
|
|
38
97
|
async def async_wrapper(*args, **kwargs):
|
|
39
|
-
async def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
98
|
+
async def wrapped_func(*args, **kwargs):
|
|
99
|
+
if not ag.tracing.get_current_span().is_recording():
|
|
100
|
+
self.kind = "workflow"
|
|
101
|
+
|
|
102
|
+
kind = parse_span_kind(self.kind)
|
|
103
|
+
|
|
104
|
+
with ag.tracer.start_as_current_span(func.__name__, kind=kind):
|
|
105
|
+
span = ag.tracing.get_current_span()
|
|
106
|
+
|
|
107
|
+
with suppress():
|
|
108
|
+
span.set_attributes(
|
|
109
|
+
attributes={"node": self.kind},
|
|
110
|
+
namespace="type",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if span.parent is None:
|
|
114
|
+
rctx = tracing_context.get()
|
|
115
|
+
|
|
116
|
+
span.set_attributes(
|
|
117
|
+
attributes={"configuration": rctx.get("config", {})},
|
|
118
|
+
namespace="meta",
|
|
119
|
+
)
|
|
120
|
+
span.set_attributes(
|
|
121
|
+
attributes={"environment": rctx.get("environment", {})},
|
|
122
|
+
namespace="meta",
|
|
123
|
+
)
|
|
124
|
+
span.set_attributes(
|
|
125
|
+
attributes={"version": rctx.get("version", {})},
|
|
126
|
+
namespace="meta",
|
|
127
|
+
)
|
|
128
|
+
span.set_attributes(
|
|
129
|
+
attributes={"variant": rctx.get("variant", {})},
|
|
130
|
+
namespace="meta",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
_inputs = redact(parse(*args, **kwargs), self.ignore_inputs)
|
|
134
|
+
span.set_attributes(
|
|
135
|
+
attributes={"inputs": _inputs},
|
|
136
|
+
namespace="data",
|
|
137
|
+
max_depth=self.max_depth,
|
|
138
|
+
)
|
|
44
139
|
|
|
45
140
|
result = await func(*args, **kwargs)
|
|
46
141
|
|
|
47
|
-
|
|
142
|
+
with suppress():
|
|
143
|
+
cost = None
|
|
144
|
+
usage = {}
|
|
145
|
+
|
|
146
|
+
if isinstance(result, dict):
|
|
147
|
+
cost = result.get("cost", None)
|
|
148
|
+
usage = result.get("usage", {})
|
|
149
|
+
|
|
150
|
+
span.set_attributes(
|
|
151
|
+
attributes={"total": cost},
|
|
152
|
+
namespace="metrics.unit.costs",
|
|
153
|
+
)
|
|
154
|
+
span.set_attributes(
|
|
155
|
+
attributes=(
|
|
156
|
+
{
|
|
157
|
+
"prompt": usage.get("prompt_tokens", None),
|
|
158
|
+
"completion": usage.get("completion_tokens", None),
|
|
159
|
+
"total": usage.get("total_tokens", None),
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
namespace="metrics.unit.tokens",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
_outputs = redact(patch(result), self.ignore_outputs)
|
|
166
|
+
span.set_attributes(
|
|
167
|
+
attributes={"outputs": _outputs},
|
|
168
|
+
namespace="data",
|
|
169
|
+
max_depth=self.max_depth,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
span.set_status("OK")
|
|
173
|
+
|
|
174
|
+
with suppress():
|
|
175
|
+
if hasattr(span, "parent") and span.parent is None:
|
|
176
|
+
tracing_context.set(
|
|
177
|
+
tracing_context.get()
|
|
178
|
+
| {
|
|
179
|
+
"root": {
|
|
180
|
+
"trace_id": span.get_span_context().trace_id,
|
|
181
|
+
"span_id": span.get_span_context().span_id,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
)
|
|
48
185
|
|
|
49
186
|
return result
|
|
50
187
|
|
|
51
|
-
return await
|
|
188
|
+
return await wrapped_func(*args, **kwargs)
|
|
52
189
|
|
|
53
190
|
@wraps(func)
|
|
54
191
|
def sync_wrapper(*args, **kwargs):
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
192
|
+
def wrapped_func(*args, **kwargs):
|
|
193
|
+
if not ag.tracing.get_current_span().is_recording():
|
|
194
|
+
self.kind = "workflow"
|
|
195
|
+
|
|
196
|
+
kind = parse_span_kind(self.kind)
|
|
197
|
+
|
|
198
|
+
with ag.tracer.start_as_current_span(func.__name__, kind=kind):
|
|
199
|
+
span = ag.tracing.get_current_span()
|
|
200
|
+
|
|
201
|
+
with suppress():
|
|
202
|
+
span.set_attributes(
|
|
203
|
+
attributes={"node": self.kind},
|
|
204
|
+
namespace="type",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if span.parent is None:
|
|
208
|
+
rctx = tracing_context.get()
|
|
209
|
+
|
|
210
|
+
span.set_attributes(
|
|
211
|
+
attributes={"configuration": rctx.get("config", {})},
|
|
212
|
+
namespace="meta",
|
|
213
|
+
)
|
|
214
|
+
span.set_attributes(
|
|
215
|
+
attributes={"environment": rctx.get("environment", {})},
|
|
216
|
+
namespace="meta",
|
|
217
|
+
)
|
|
218
|
+
span.set_attributes(
|
|
219
|
+
attributes={"version": rctx.get("version", {})},
|
|
220
|
+
namespace="meta",
|
|
221
|
+
)
|
|
222
|
+
span.set_attributes(
|
|
223
|
+
attributes={"variant": rctx.get("variant", {})},
|
|
224
|
+
namespace="meta",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
_inputs = redact(parse(*args, **kwargs), self.ignore_inputs)
|
|
228
|
+
span.set_attributes(
|
|
229
|
+
attributes={"inputs": _inputs},
|
|
230
|
+
namespace="data",
|
|
231
|
+
max_depth=self.max_depth,
|
|
232
|
+
)
|
|
60
233
|
|
|
61
234
|
result = func(*args, **kwargs)
|
|
62
235
|
|
|
63
|
-
|
|
236
|
+
with suppress():
|
|
237
|
+
cost = None
|
|
238
|
+
usage = {}
|
|
239
|
+
if isinstance(result, dict):
|
|
240
|
+
cost = result.get("cost", None)
|
|
241
|
+
usage = result.get("usage", {})
|
|
242
|
+
|
|
243
|
+
span.set_attributes(
|
|
244
|
+
attributes={"total": cost},
|
|
245
|
+
namespace="metrics.unit.costs",
|
|
246
|
+
)
|
|
247
|
+
span.set_attributes(
|
|
248
|
+
attributes=(
|
|
249
|
+
{
|
|
250
|
+
"prompt": usage.get("prompt_tokens", None),
|
|
251
|
+
"completion": usage.get("completion_tokens", None),
|
|
252
|
+
"total": usage.get("total_tokens", None),
|
|
253
|
+
}
|
|
254
|
+
),
|
|
255
|
+
namespace="metrics.unit.tokens",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
_outputs = redact(patch(result), self.ignore_outputs)
|
|
259
|
+
span.set_attributes(
|
|
260
|
+
attributes={"outputs": _outputs},
|
|
261
|
+
namespace="data",
|
|
262
|
+
max_depth=self.max_depth,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
span.set_status("OK")
|
|
266
|
+
|
|
267
|
+
with suppress():
|
|
268
|
+
if hasattr(span, "parent") and span.parent is None:
|
|
269
|
+
tracing_context.set(
|
|
270
|
+
tracing_context.get()
|
|
271
|
+
| {
|
|
272
|
+
"root": {
|
|
273
|
+
"trace_id": span.get_span_context().trace_id,
|
|
274
|
+
"span_id": span.get_span_context().span_id,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
)
|
|
64
278
|
|
|
65
279
|
return result
|
|
66
280
|
|
|
67
|
-
return
|
|
281
|
+
return wrapped_func(*args, **kwargs)
|
|
68
282
|
|
|
69
283
|
return async_wrapper if is_coroutine_function else sync_wrapper
|
|
70
|
-
|
|
71
|
-
def _parse_type_and_kind(self):
|
|
72
|
-
if not ag.tracing.get_current_span().is_recording():
|
|
73
|
-
self.type = "workflow"
|
|
74
|
-
|
|
75
|
-
self.kind = parse_span_kind(self.type)
|
|
76
|
-
|
|
77
|
-
def _pre_instrument(
|
|
78
|
-
self,
|
|
79
|
-
func,
|
|
80
|
-
*args,
|
|
81
|
-
**kwargs,
|
|
82
|
-
):
|
|
83
|
-
span = ag.tracing.get_current_span()
|
|
84
|
-
|
|
85
|
-
with suppress():
|
|
86
|
-
span.set_attributes(
|
|
87
|
-
attributes={"node": self.type},
|
|
88
|
-
namespace="type",
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
if span.parent is None:
|
|
92
|
-
rctx = tracing_context.get()
|
|
93
|
-
|
|
94
|
-
span.set_attributes(
|
|
95
|
-
attributes={"configuration": rctx.get("config", {})},
|
|
96
|
-
namespace="meta",
|
|
97
|
-
)
|
|
98
|
-
span.set_attributes(
|
|
99
|
-
attributes={"environment": rctx.get("environment", {})},
|
|
100
|
-
namespace="meta",
|
|
101
|
-
)
|
|
102
|
-
span.set_attributes(
|
|
103
|
-
attributes={"version": rctx.get("version", {})},
|
|
104
|
-
namespace="meta",
|
|
105
|
-
)
|
|
106
|
-
span.set_attributes(
|
|
107
|
-
attributes={"variant": rctx.get("variant", {})},
|
|
108
|
-
namespace="meta",
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
_inputs = self._redact(
|
|
112
|
-
self._parse(
|
|
113
|
-
func,
|
|
114
|
-
*args,
|
|
115
|
-
**kwargs,
|
|
116
|
-
),
|
|
117
|
-
self.ignore_inputs,
|
|
118
|
-
)
|
|
119
|
-
span.set_attributes(
|
|
120
|
-
attributes={"inputs": _inputs},
|
|
121
|
-
namespace="data",
|
|
122
|
-
max_depth=self.max_depth,
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
def _post_instrument(
|
|
126
|
-
self,
|
|
127
|
-
result,
|
|
128
|
-
):
|
|
129
|
-
span = ag.tracing.get_current_span()
|
|
130
|
-
with suppress():
|
|
131
|
-
cost = None
|
|
132
|
-
usage = {}
|
|
133
|
-
|
|
134
|
-
if isinstance(result, dict):
|
|
135
|
-
cost = result.get("cost", None)
|
|
136
|
-
usage = result.get("usage", {})
|
|
137
|
-
|
|
138
|
-
if isinstance(usage, (int, float)):
|
|
139
|
-
usage = {"total_tokens": usage}
|
|
140
|
-
|
|
141
|
-
span.set_attributes(
|
|
142
|
-
attributes={"total": cost},
|
|
143
|
-
namespace="metrics.unit.costs",
|
|
144
|
-
)
|
|
145
|
-
span.set_attributes(
|
|
146
|
-
attributes=(
|
|
147
|
-
{
|
|
148
|
-
"prompt": usage.get("prompt_tokens", None),
|
|
149
|
-
"completion": usage.get("completion_tokens", None),
|
|
150
|
-
"total": usage.get("total_tokens", None),
|
|
151
|
-
}
|
|
152
|
-
),
|
|
153
|
-
namespace="metrics.unit.tokens",
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
_outputs = self._redact(self._patch(result), self.ignore_outputs)
|
|
157
|
-
span.set_attributes(
|
|
158
|
-
attributes={"outputs": _outputs},
|
|
159
|
-
namespace="data",
|
|
160
|
-
max_depth=self.max_depth,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
span.set_status("OK")
|
|
164
|
-
|
|
165
|
-
with suppress():
|
|
166
|
-
if hasattr(span, "parent") and span.parent is None:
|
|
167
|
-
tracing_context.set(
|
|
168
|
-
tracing_context.get()
|
|
169
|
-
| {
|
|
170
|
-
"root": {
|
|
171
|
-
"trace_id": span.get_span_context().trace_id,
|
|
172
|
-
"span_id": span.get_span_context().span_id,
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
def _parse(
|
|
178
|
-
self,
|
|
179
|
-
func,
|
|
180
|
-
*args,
|
|
181
|
-
**kwargs,
|
|
182
|
-
) -> Dict[str, Any]:
|
|
183
|
-
inputs = {
|
|
184
|
-
key: value
|
|
185
|
-
for key, value in chain(
|
|
186
|
-
zip(getfullargspec(func).args, args),
|
|
187
|
-
kwargs.items(),
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return inputs
|
|
192
|
-
|
|
193
|
-
def _redact(
|
|
194
|
-
self,
|
|
195
|
-
io: Dict[str, Any],
|
|
196
|
-
ignore: Union[List[str], bool] = False,
|
|
197
|
-
) -> Dict[str, Any]:
|
|
198
|
-
"""
|
|
199
|
-
Redact user-defined sensitive information
|
|
200
|
-
from inputs and outputs as defined by the ignore list or boolean flag.
|
|
201
|
-
|
|
202
|
-
Example:
|
|
203
|
-
- ignore = ["password"] -> {"username": "admin", "password": "********"}
|
|
204
|
-
-> {"username": "admin"}
|
|
205
|
-
- ignore = True -> {"username": "admin", "password": "********"}
|
|
206
|
-
-> {}
|
|
207
|
-
- ignore = False -> {"username": "admin", "password": "********"}
|
|
208
|
-
-> {"username": "admin", "password": "********"}
|
|
209
|
-
"""
|
|
210
|
-
io = {
|
|
211
|
-
key: value
|
|
212
|
-
for key, value in io.items()
|
|
213
|
-
if key
|
|
214
|
-
not in (
|
|
215
|
-
ignore
|
|
216
|
-
if isinstance(ignore, list)
|
|
217
|
-
else io.keys()
|
|
218
|
-
if ignore is True
|
|
219
|
-
else []
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return io
|
|
224
|
-
|
|
225
|
-
def _patch(
|
|
226
|
-
self,
|
|
227
|
-
result: Any,
|
|
228
|
-
) -> Dict[str, Any]:
|
|
229
|
-
"""
|
|
230
|
-
Patch the result to ensure that it is a dictionary, with a default key when necessary.
|
|
231
|
-
|
|
232
|
-
Example:
|
|
233
|
-
- result = "Hello, World!"
|
|
234
|
-
-> {"__default__": "Hello, World!"}
|
|
235
|
-
- result = {"message": "Hello, World!", "cost": 0.0, "usage": {}}
|
|
236
|
-
-> {"__default__": "Hello, World!"}
|
|
237
|
-
- result = {"message": "Hello, World!"}
|
|
238
|
-
-> {"message": "Hello, World!"}
|
|
239
|
-
"""
|
|
240
|
-
outputs = (
|
|
241
|
-
{instrument.DEFAULT_KEY: result}
|
|
242
|
-
if not isinstance(result, dict)
|
|
243
|
-
else (
|
|
244
|
-
{instrument.DEFAULT_KEY: result["message"]}
|
|
245
|
-
if all(key in result for key in ["message", "cost", "usage"])
|
|
246
|
-
else result
|
|
247
|
-
)
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
return outputs
|
agenta/sdk/litellm/litellm.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
from opentelemetry.trace import SpanKind
|
|
2
|
-
|
|
3
1
|
import agenta as ag
|
|
4
2
|
|
|
3
|
+
from opentelemetry.trace import SpanKind
|
|
4
|
+
|
|
5
5
|
from agenta.sdk.tracing.spans import CustomSpan
|
|
6
|
-
from agenta.sdk.utils.exceptions import suppress
|
|
6
|
+
from agenta.sdk.utils.exceptions import suppress
|
|
7
7
|
from agenta.sdk.utils.logging import log
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def litellm_handler():
|
|
11
11
|
try:
|
|
12
|
-
from litellm.
|
|
12
|
+
from litellm.utils import ModelResponse
|
|
13
|
+
from litellm.integrations.custom_logger import (
|
|
13
14
|
CustomLogger as LitellmCustomLogger,
|
|
14
15
|
)
|
|
15
16
|
except ImportError as exc:
|
|
@@ -17,23 +18,18 @@ def litellm_handler():
|
|
|
17
18
|
"The litellm SDK is not installed. Please install it using `pip install litellm`."
|
|
18
19
|
) from exc
|
|
19
20
|
except Exception as exc:
|
|
20
|
-
raise Exception(
|
|
21
|
-
|
|
21
|
+
raise Exception(
|
|
22
|
+
"Unexpected error occurred when importing litellm: {}".format(exc)
|
|
22
23
|
) from exc
|
|
23
24
|
|
|
24
25
|
class LitellmHandler(LitellmCustomLogger):
|
|
25
|
-
"""
|
|
26
|
-
This handler is responsible for instrumenting certain events,
|
|
27
|
-
when using litellm to call LLMs.
|
|
26
|
+
"""This handler is responsible for instrumenting certain events when using litellm to call LLMs.
|
|
28
27
|
|
|
29
28
|
Args:
|
|
30
|
-
LitellmCustomLogger (object): custom logger that allows us
|
|
31
|
-
to override the events to capture.
|
|
29
|
+
LitellmCustomLogger (object): custom logger that allows us to override the events to capture.
|
|
32
30
|
"""
|
|
33
31
|
|
|
34
32
|
def __init__(self):
|
|
35
|
-
super().__init__()
|
|
36
|
-
|
|
37
33
|
self.span = None
|
|
38
34
|
|
|
39
35
|
def log_pre_api_call(
|
|
@@ -42,7 +38,7 @@ def litellm_handler():
|
|
|
42
38
|
messages,
|
|
43
39
|
kwargs,
|
|
44
40
|
):
|
|
45
|
-
type = (
|
|
41
|
+
type = (
|
|
46
42
|
"chat"
|
|
47
43
|
if kwargs.get("call_type") in ["completion", "acompletion"]
|
|
48
44
|
else "embedding"
|
|
@@ -63,8 +59,10 @@ def litellm_handler():
|
|
|
63
59
|
log.error("LiteLLM callback error: span not found.")
|
|
64
60
|
return
|
|
65
61
|
|
|
62
|
+
log.info(f"log_pre_api_call({hex(self.span.context.span_id)[2:]})")
|
|
63
|
+
|
|
66
64
|
self.span.set_attributes(
|
|
67
|
-
attributes={"inputs": {"
|
|
65
|
+
attributes={"inputs": {"messages": kwargs["messages"]}},
|
|
68
66
|
namespace="data",
|
|
69
67
|
)
|
|
70
68
|
|
|
@@ -89,14 +87,12 @@ def litellm_handler():
|
|
|
89
87
|
log.error("LiteLLM callback error: span not found.")
|
|
90
88
|
return
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
outputs = (
|
|
95
|
-
{"__default__": result} if not isinstance(result, dict) else result
|
|
96
|
-
)
|
|
90
|
+
# log.info(f"log_stream({hex(self.span.context.span_id)[2:]})")
|
|
97
91
|
|
|
98
92
|
self.span.set_attributes(
|
|
99
|
-
attributes={
|
|
93
|
+
attributes={
|
|
94
|
+
"output": {"__default__": kwargs.get("complete_streaming_response")}
|
|
95
|
+
},
|
|
100
96
|
namespace="data",
|
|
101
97
|
)
|
|
102
98
|
|
|
@@ -131,20 +127,14 @@ def litellm_handler():
|
|
|
131
127
|
log.error("LiteLLM callback error: span not found.")
|
|
132
128
|
return
|
|
133
129
|
|
|
134
|
-
|
|
135
|
-
result = []
|
|
136
|
-
for choice in response_obj.choices:
|
|
137
|
-
message = choice.message.__dict__
|
|
138
|
-
result.append(message)
|
|
130
|
+
# log.info(f"log_success({hex(self.span.context.span_id)[2:]})")
|
|
139
131
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
except Exception as e:
|
|
147
|
-
pass
|
|
132
|
+
self.span.set_attributes(
|
|
133
|
+
attributes={
|
|
134
|
+
"output": {"__default__": response_obj.choices[0].message.content}
|
|
135
|
+
},
|
|
136
|
+
namespace="data",
|
|
137
|
+
)
|
|
148
138
|
|
|
149
139
|
self.span.set_attributes(
|
|
150
140
|
attributes={"total": kwargs.get("response_cost")},
|
|
@@ -177,6 +167,8 @@ def litellm_handler():
|
|
|
177
167
|
log.error("LiteLLM callback error: span not found.")
|
|
178
168
|
return
|
|
179
169
|
|
|
170
|
+
# log.info(f"log_failure({hex(self.span.context.span_id)[2:]})")
|
|
171
|
+
|
|
180
172
|
self.span.record_exception(kwargs["exception"])
|
|
181
173
|
|
|
182
174
|
self.span.set_status(status="ERROR")
|
|
@@ -194,14 +186,12 @@ def litellm_handler():
|
|
|
194
186
|
log.error("LiteLLM callback error: span not found.")
|
|
195
187
|
return
|
|
196
188
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
outputs = (
|
|
200
|
-
{"__default__": result} if not isinstance(result, dict) else result
|
|
201
|
-
)
|
|
189
|
+
# log.info(f"async_log_stream({hex(self.span.context.span_id)[2:]})")
|
|
202
190
|
|
|
203
191
|
self.span.set_attributes(
|
|
204
|
-
attributes={
|
|
192
|
+
attributes={
|
|
193
|
+
"output": {"__default__": kwargs.get("complete_streaming_response")}
|
|
194
|
+
},
|
|
205
195
|
namespace="data",
|
|
206
196
|
)
|
|
207
197
|
|
|
@@ -236,15 +226,12 @@ def litellm_handler():
|
|
|
236
226
|
log.error("LiteLLM callback error: span not found.")
|
|
237
227
|
return
|
|
238
228
|
|
|
239
|
-
|
|
240
|
-
result = response_obj.choices[0].message.content
|
|
241
|
-
|
|
242
|
-
outputs = (
|
|
243
|
-
{"__default__": result} if not isinstance(result, dict) else result
|
|
244
|
-
)
|
|
229
|
+
log.info(f"async_log_success({hex(self.span.context.span_id)[2:]})")
|
|
245
230
|
|
|
246
231
|
self.span.set_attributes(
|
|
247
|
-
attributes={
|
|
232
|
+
attributes={
|
|
233
|
+
"output": {"__default__": kwargs.get("complete_streaming_response")}
|
|
234
|
+
},
|
|
248
235
|
namespace="data",
|
|
249
236
|
)
|
|
250
237
|
|
|
@@ -279,6 +266,8 @@ def litellm_handler():
|
|
|
279
266
|
log.error("LiteLLM callback error: span not found.")
|
|
280
267
|
return
|
|
281
268
|
|
|
269
|
+
# log.info(f"async_log_failure({hex(self.span.context.span_id)[2:]})")
|
|
270
|
+
|
|
282
271
|
self.span.record_exception(kwargs["exception"])
|
|
283
272
|
|
|
284
273
|
self.span.set_status(status="ERROR")
|