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