agenta 0.26.0a0__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 +6 -7
- agenta/client/backend/client.py +22 -14
- agenta/client/backend/core/http_client.py +23 -15
- agenta/sdk/__init__.py +27 -6
- agenta/sdk/agenta_init.py +73 -26
- agenta/sdk/config_manager.py +2 -2
- agenta/sdk/context/__init__.py +0 -0
- agenta/sdk/context/routing.py +25 -0
- agenta/sdk/context/tracing.py +3 -0
- agenta/sdk/decorators/__init__.py +0 -0
- agenta/sdk/decorators/{llm_entrypoint.py → routing.py} +137 -124
- agenta/sdk/decorators/tracing.py +228 -76
- agenta/sdk/litellm/__init__.py +1 -0
- agenta/sdk/litellm/litellm.py +277 -0
- agenta/sdk/router.py +0 -7
- agenta/sdk/tracing/__init__.py +1 -0
- agenta/sdk/tracing/attributes.py +181 -0
- agenta/sdk/tracing/context.py +21 -0
- agenta/sdk/tracing/conventions.py +43 -0
- agenta/sdk/tracing/exporters.py +53 -0
- agenta/sdk/tracing/inline.py +1306 -0
- agenta/sdk/tracing/processors.py +65 -0
- agenta/sdk/tracing/spans.py +124 -0
- agenta/sdk/tracing/tracing.py +174 -0
- agenta/sdk/types.py +0 -12
- agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
- agenta/sdk/utils/debug.py +5 -5
- agenta/sdk/utils/exceptions.py +19 -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.0a0.dist-info → agenta-0.27.0a0.dist-info}/METADATA +5 -1
- {agenta-0.26.0a0.dist-info → agenta-0.27.0a0.dist-info}/RECORD +35 -25
- 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.0a0.dist-info → agenta-0.27.0a0.dist-info}/WHEEL +0 -0
- {agenta-0.26.0a0.dist-info → agenta-0.27.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import copy
|
|
3
|
-
import json
|
|
4
|
-
from uuid import uuid4
|
|
5
|
-
|
|
6
|
-
import traceback
|
|
7
|
-
from threading import Lock
|
|
8
|
-
from datetime import datetime, timezone
|
|
9
|
-
from typing import Optional, Dict, Any, List
|
|
10
|
-
|
|
11
|
-
from contextlib import contextmanager
|
|
12
|
-
|
|
13
|
-
from agenta.sdk.tracing.tracing_context import tracing_context, TracingContext
|
|
14
|
-
from agenta.sdk.tracing.logger import llm_logger as logging
|
|
15
|
-
from agenta.sdk.tracing.tasks_manager import TaskQueue
|
|
16
|
-
from agenta.client.backend.client import AsyncAgentaApi
|
|
17
|
-
from agenta.client.backend.client import AsyncObservabilityClient
|
|
18
|
-
from agenta.client.backend.types.create_span import (
|
|
19
|
-
CreateSpan,
|
|
20
|
-
LlmTokens,
|
|
21
|
-
)
|
|
22
|
-
from agenta.client.backend.types.span_status_code import SpanStatusCode
|
|
23
|
-
|
|
24
|
-
from bson.objectid import ObjectId
|
|
25
|
-
|
|
26
|
-
VARIANT_TRACKING_FEATURE_FLAG = False
|
|
27
|
-
|
|
28
|
-
from agenta.sdk.utils.debug import debug
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
logging.setLevel("DEBUG")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class SingletonMeta(type):
|
|
35
|
-
"""
|
|
36
|
-
Thread-safe implementation of Singleton.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
_instances = {} # type: ignore
|
|
40
|
-
|
|
41
|
-
# We need the lock mechanism to synchronize threads \
|
|
42
|
-
# during the initial access to the Singleton object.
|
|
43
|
-
_lock: Lock = Lock()
|
|
44
|
-
|
|
45
|
-
def __call__(cls, *args, **kwargs):
|
|
46
|
-
"""
|
|
47
|
-
Ensures that changes to the `__init__` arguments do not affect the
|
|
48
|
-
returned instance.
|
|
49
|
-
|
|
50
|
-
Uses a lock to make this method thread-safe. If an instance of the class
|
|
51
|
-
does not already exist, it creates one. Otherwise, it returns the
|
|
52
|
-
existing instance.
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
with cls._lock:
|
|
56
|
-
if cls not in cls._instances:
|
|
57
|
-
instance = super().__call__(*args, **kwargs)
|
|
58
|
-
cls._instances[cls] = instance
|
|
59
|
-
return cls._instances[cls]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class Tracing(metaclass=SingletonMeta):
|
|
63
|
-
"""The `Tracing` class is an agent for LLM tracing with specific initialization arguments.
|
|
64
|
-
|
|
65
|
-
__init__ args:
|
|
66
|
-
host (str): The URL of the backend host
|
|
67
|
-
api_key (str): The API Key of the backend host
|
|
68
|
-
tasks_manager (TaskQueue): The tasks manager dedicated to handling asynchronous tasks
|
|
69
|
-
max_workers (int): The maximum number of workers to run tracing
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
def __init__(
|
|
73
|
-
self,
|
|
74
|
-
host: str,
|
|
75
|
-
app_id: str,
|
|
76
|
-
api_key: Optional[str] = None,
|
|
77
|
-
max_workers: Optional[int] = None,
|
|
78
|
-
):
|
|
79
|
-
self.host = host + "/api"
|
|
80
|
-
self.api_key = api_key if api_key is not None else ""
|
|
81
|
-
self.app_id = app_id
|
|
82
|
-
self.tasks_manager = TaskQueue(
|
|
83
|
-
max_workers if max_workers else 4, logger=logging
|
|
84
|
-
)
|
|
85
|
-
self.baggage = None
|
|
86
|
-
|
|
87
|
-
@property
|
|
88
|
-
def client(self) -> AsyncObservabilityClient:
|
|
89
|
-
"""Initialize observability async client
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
AsyncObservabilityClient: async client
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
return AsyncAgentaApi(
|
|
96
|
-
base_url=self.host, api_key=self.api_key, timeout=120 # type: ignore
|
|
97
|
-
).observability
|
|
98
|
-
|
|
99
|
-
### --- Context Manager --- ###
|
|
100
|
-
|
|
101
|
-
@contextmanager
|
|
102
|
-
def Context(self, **kwargs):
|
|
103
|
-
# This will evolve as be work towards OTel compliance
|
|
104
|
-
|
|
105
|
-
token = None
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
if tracing_context.get() is None:
|
|
109
|
-
token = tracing_context.set(TracingContext())
|
|
110
|
-
|
|
111
|
-
self.open_span(**kwargs)
|
|
112
|
-
|
|
113
|
-
yield
|
|
114
|
-
|
|
115
|
-
self.set_status(status="OK")
|
|
116
|
-
|
|
117
|
-
except Exception as e:
|
|
118
|
-
logging.error(e)
|
|
119
|
-
|
|
120
|
-
result = {
|
|
121
|
-
"message": str(e),
|
|
122
|
-
"stacktrace": traceback.format_exc(),
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
self.set_status(status="ERROR")
|
|
126
|
-
self.set_attributes({"traceback_exception": traceback.format_exc()})
|
|
127
|
-
self.store_outputs(result)
|
|
128
|
-
|
|
129
|
-
raise
|
|
130
|
-
|
|
131
|
-
finally:
|
|
132
|
-
self.close_span()
|
|
133
|
-
|
|
134
|
-
if token is not None:
|
|
135
|
-
self.flush_spans()
|
|
136
|
-
|
|
137
|
-
tracing_context.reset(token)
|
|
138
|
-
|
|
139
|
-
### --- API --- ###
|
|
140
|
-
|
|
141
|
-
@debug()
|
|
142
|
-
def get_context(self):
|
|
143
|
-
tracing = tracing_context.get()
|
|
144
|
-
|
|
145
|
-
return tracing
|
|
146
|
-
|
|
147
|
-
@debug()
|
|
148
|
-
def update_baggage(
|
|
149
|
-
self,
|
|
150
|
-
attributes: Dict[str, Any] = {},
|
|
151
|
-
):
|
|
152
|
-
if self.baggage is None:
|
|
153
|
-
self.baggage = {}
|
|
154
|
-
|
|
155
|
-
for key, value in attributes.items():
|
|
156
|
-
self.baggage[key] = value
|
|
157
|
-
|
|
158
|
-
@debug()
|
|
159
|
-
def open_trace(
|
|
160
|
-
self,
|
|
161
|
-
span: Optional[CreateSpan],
|
|
162
|
-
config: Optional[Dict[str, Any]] = None,
|
|
163
|
-
**kwargs,
|
|
164
|
-
) -> None:
|
|
165
|
-
tracing = tracing_context.get()
|
|
166
|
-
|
|
167
|
-
tracing.trace_id = self._create_trace_id()
|
|
168
|
-
|
|
169
|
-
logging.info(f"Opening trace {tracing.trace_id}")
|
|
170
|
-
|
|
171
|
-
if span is not None:
|
|
172
|
-
### --- TO BE CLEANED --- >>>
|
|
173
|
-
span.environment = (
|
|
174
|
-
self.baggage.get("environment")
|
|
175
|
-
if self.baggage is not None
|
|
176
|
-
else os.environ.get("environment", "unset")
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
span.config = (
|
|
180
|
-
self.baggage.get("config")
|
|
181
|
-
if not config and self.baggage is not None
|
|
182
|
-
else None
|
|
183
|
-
)
|
|
184
|
-
if VARIANT_TRACKING_FEATURE_FLAG:
|
|
185
|
-
# TODO: we should get the variant_id and variant_name (and environment) from the config object
|
|
186
|
-
span.variant_id = config.variant_id # type: ignore
|
|
187
|
-
span.variant_name = (config.variant_name,) # type: ignore
|
|
188
|
-
### --- TO BE CLEANED --- <<<
|
|
189
|
-
|
|
190
|
-
logging.info(f"Opened trace {tracing.trace_id}")
|
|
191
|
-
|
|
192
|
-
@debug()
|
|
193
|
-
def set_trace_tags(self, tags: List[str]) -> None:
|
|
194
|
-
tracing = tracing_context.get()
|
|
195
|
-
|
|
196
|
-
tracing.trace_tags.extend(tags)
|
|
197
|
-
|
|
198
|
-
@debug()
|
|
199
|
-
def is_trace_ready(self):
|
|
200
|
-
tracing = tracing_context.get()
|
|
201
|
-
|
|
202
|
-
are_spans_ready = [
|
|
203
|
-
(span.end_time == span.start_time) for span in tracing.spans.values()
|
|
204
|
-
]
|
|
205
|
-
|
|
206
|
-
return all(are_spans_ready)
|
|
207
|
-
|
|
208
|
-
@debug()
|
|
209
|
-
def close_trace(self) -> None:
|
|
210
|
-
"""
|
|
211
|
-
Ends the active trace and sends the recorded spans for processing.
|
|
212
|
-
|
|
213
|
-
Args:
|
|
214
|
-
parent_span (CreateSpan): The parent span of the trace.
|
|
215
|
-
|
|
216
|
-
Raises:
|
|
217
|
-
RuntimeError: If there is no active trace to end.
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
None
|
|
221
|
-
"""
|
|
222
|
-
tracing = tracing_context.get()
|
|
223
|
-
|
|
224
|
-
logging.info(f"Closing trace {tracing.trace_id}")
|
|
225
|
-
|
|
226
|
-
trace_id = tracing.trace_id
|
|
227
|
-
|
|
228
|
-
if tracing.trace_id is None:
|
|
229
|
-
logging.error("Cannot close trace, no trace to close")
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
if not self.api_key:
|
|
233
|
-
logging.error("No API key")
|
|
234
|
-
else:
|
|
235
|
-
self._process_spans()
|
|
236
|
-
|
|
237
|
-
self._clear_trace_tags()
|
|
238
|
-
|
|
239
|
-
logging.info(f"Closed trace {trace_id}")
|
|
240
|
-
|
|
241
|
-
@debug()
|
|
242
|
-
def open_span(
|
|
243
|
-
self,
|
|
244
|
-
name: str,
|
|
245
|
-
spankind: str,
|
|
246
|
-
input: Dict[str, Any],
|
|
247
|
-
active: bool = True,
|
|
248
|
-
config: Optional[Dict[str, Any]] = None,
|
|
249
|
-
**kwargs,
|
|
250
|
-
) -> CreateSpan:
|
|
251
|
-
tracing = tracing_context.get()
|
|
252
|
-
|
|
253
|
-
span_id = self._create_span_id()
|
|
254
|
-
|
|
255
|
-
logging.info(f"Opening span {span_id} {spankind.upper()}")
|
|
256
|
-
|
|
257
|
-
### --- TO BE CLEANED --- >>>
|
|
258
|
-
now = datetime.now(timezone.utc)
|
|
259
|
-
|
|
260
|
-
span = CreateSpan(
|
|
261
|
-
id=span_id,
|
|
262
|
-
inputs=input,
|
|
263
|
-
name=name,
|
|
264
|
-
app_id=self.app_id,
|
|
265
|
-
config=config,
|
|
266
|
-
spankind=spankind.upper(),
|
|
267
|
-
attributes={},
|
|
268
|
-
status="UNSET",
|
|
269
|
-
start_time=now,
|
|
270
|
-
internals=None,
|
|
271
|
-
outputs=None,
|
|
272
|
-
tags=None,
|
|
273
|
-
user=None,
|
|
274
|
-
end_time=now,
|
|
275
|
-
tokens=None,
|
|
276
|
-
cost=None,
|
|
277
|
-
token_consumption=None,
|
|
278
|
-
parent_span_id=None,
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
if tracing.trace_id is None:
|
|
282
|
-
self.open_trace(span, config)
|
|
283
|
-
else:
|
|
284
|
-
span.parent_span_id = tracing.active_span.id # type: ignore
|
|
285
|
-
|
|
286
|
-
tracing.spans[span.id] = span
|
|
287
|
-
|
|
288
|
-
if active:
|
|
289
|
-
tracing.active_span = span
|
|
290
|
-
else:
|
|
291
|
-
# DETACHED SPAN
|
|
292
|
-
pass
|
|
293
|
-
|
|
294
|
-
### --- TO BE CLEANED --- <<<
|
|
295
|
-
|
|
296
|
-
logging.info(f"Opened span {span_id} {spankind.upper()}")
|
|
297
|
-
|
|
298
|
-
return span
|
|
299
|
-
|
|
300
|
-
@debug(req=True)
|
|
301
|
-
def set_attributes(
|
|
302
|
-
self, attributes: Dict[str, Any] = {}, span_id: Optional[str] = None
|
|
303
|
-
) -> None:
|
|
304
|
-
"""
|
|
305
|
-
Set attributes for the active span.
|
|
306
|
-
|
|
307
|
-
Args:
|
|
308
|
-
attributes (Dict[str, Any], optional): A dictionary of attributes to set. Defaults to {}.
|
|
309
|
-
"""
|
|
310
|
-
|
|
311
|
-
span = self._get_target_span(span_id)
|
|
312
|
-
|
|
313
|
-
logging.info(
|
|
314
|
-
f"Setting span {span.id} {span.spankind.upper()} attributes={attributes}"
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
for key, value in attributes.items():
|
|
318
|
-
span.attributes[key] = value # type: ignore
|
|
319
|
-
|
|
320
|
-
@debug()
|
|
321
|
-
def set_status(self, status: str, span_id: Optional[str] = None) -> None:
|
|
322
|
-
"""
|
|
323
|
-
Set status for the active span.
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
status: Enum ( UNSET, OK, ERROR )
|
|
327
|
-
"""
|
|
328
|
-
span = self._get_target_span(span_id)
|
|
329
|
-
|
|
330
|
-
logging.info(f"Setting span {span.id} {span.spankind.upper()} status={status}")
|
|
331
|
-
|
|
332
|
-
span.status = status
|
|
333
|
-
|
|
334
|
-
@debug()
|
|
335
|
-
def close_span(self, span_id: Optional[str] = None) -> None:
|
|
336
|
-
"""
|
|
337
|
-
Ends the active span, if it is a parent span, ends the trace too.
|
|
338
|
-
|
|
339
|
-
Args:
|
|
340
|
-
outputs (Dict[str, Any]): A dictionary containing the outputs of the span.
|
|
341
|
-
It should have the following keys:
|
|
342
|
-
- "message" (str): The message output of the span.
|
|
343
|
-
- "cost" (Optional[Any]): The cost of the span.
|
|
344
|
-
- "usage" (Optional[Any]): The number of tokens used in the span.
|
|
345
|
-
|
|
346
|
-
Raises:
|
|
347
|
-
ValueError: If there is no active span to end.
|
|
348
|
-
|
|
349
|
-
Returns:
|
|
350
|
-
None
|
|
351
|
-
"""
|
|
352
|
-
span = self._get_target_span(span_id)
|
|
353
|
-
|
|
354
|
-
spankind = span.spankind
|
|
355
|
-
|
|
356
|
-
logging.info(f"Closing span {span.id} {spankind}")
|
|
357
|
-
|
|
358
|
-
### --- TO BE CLEANED --- >>>
|
|
359
|
-
span.end_time = datetime.now(timezone.utc)
|
|
360
|
-
|
|
361
|
-
# TODO: Remove this whole part. Setting the cost should be done through set_span_attribute
|
|
362
|
-
if isinstance(span.outputs, dict):
|
|
363
|
-
self._update_span_cost(span, span.outputs.get("cost", None))
|
|
364
|
-
self._update_span_tokens(span, span.outputs.get("usage", None))
|
|
365
|
-
|
|
366
|
-
span_parent_id = span.parent_span_id
|
|
367
|
-
|
|
368
|
-
if span_parent_id is not None:
|
|
369
|
-
tracing = tracing_context.get()
|
|
370
|
-
|
|
371
|
-
parent_span = tracing.spans[span_parent_id]
|
|
372
|
-
self._update_span_cost(parent_span, span.cost)
|
|
373
|
-
self._update_span_tokens(parent_span, span.tokens)
|
|
374
|
-
|
|
375
|
-
if span_id is None:
|
|
376
|
-
tracing.active_span = parent_span
|
|
377
|
-
### --- TO BE CLEANED --- <<<
|
|
378
|
-
|
|
379
|
-
logging.info(f"Closed span {span.id} {spankind}")
|
|
380
|
-
|
|
381
|
-
@debug()
|
|
382
|
-
def store_internals(
|
|
383
|
-
self, internals: Dict[str, Any] = {}, span_id: Optional[str] = None
|
|
384
|
-
) -> None:
|
|
385
|
-
"""
|
|
386
|
-
Set internals for the active span.
|
|
387
|
-
|
|
388
|
-
Args:
|
|
389
|
-
internals (Dict[str, Any], optional): A dictionary of local variables to set. Defaults to {}.
|
|
390
|
-
"""
|
|
391
|
-
span = self._get_target_span(span_id)
|
|
392
|
-
|
|
393
|
-
logging.info(
|
|
394
|
-
f"Setting span {span.id} {span.spankind.upper()} internals={internals}"
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
if span.internals is None:
|
|
398
|
-
span.internals = dict()
|
|
399
|
-
|
|
400
|
-
for key, value in internals.items():
|
|
401
|
-
span.internals[key] = value # type: ignore
|
|
402
|
-
|
|
403
|
-
@debug()
|
|
404
|
-
def store_outputs(
|
|
405
|
-
self, outputs: Dict[str, Any] = {}, span_id: Optional[str] = None
|
|
406
|
-
) -> None:
|
|
407
|
-
"""
|
|
408
|
-
Set outputs for the active span.
|
|
409
|
-
|
|
410
|
-
Args:
|
|
411
|
-
outputs (Dict[str, Any], optional): A dictionary of output variables to set. Defaults to {}.
|
|
412
|
-
"""
|
|
413
|
-
span = self._get_target_span(span_id)
|
|
414
|
-
|
|
415
|
-
logging.info(
|
|
416
|
-
f"Setting span {span.id} {span.spankind.upper()} outputs={outputs}"
|
|
417
|
-
)
|
|
418
|
-
|
|
419
|
-
span.outputs = outputs
|
|
420
|
-
|
|
421
|
-
@debug()
|
|
422
|
-
def store_cost(self, cost: float = 0.0, span_id: Optional[str] = None) -> None:
|
|
423
|
-
"""
|
|
424
|
-
...
|
|
425
|
-
"""
|
|
426
|
-
span = self._get_target_span(span_id)
|
|
427
|
-
|
|
428
|
-
logging.info(f"Setting span {span.id} {span.spankind.upper()} cost={cost}")
|
|
429
|
-
|
|
430
|
-
self._update_span_cost(span, cost)
|
|
431
|
-
|
|
432
|
-
@debug()
|
|
433
|
-
def store_usage(self, tokens: dict = {}, span_id: Optional[str] = None) -> None:
|
|
434
|
-
"""
|
|
435
|
-
...
|
|
436
|
-
"""
|
|
437
|
-
span = self._get_target_span(span_id)
|
|
438
|
-
|
|
439
|
-
logging.info(
|
|
440
|
-
f"Setting span {span.id} {span.spankind.upper()} tokens={repr(tokens)}"
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
self._update_span_tokens(span, tokens)
|
|
444
|
-
|
|
445
|
-
@debug()
|
|
446
|
-
def dump_trace(self):
|
|
447
|
-
"""
|
|
448
|
-
Collects and organizes tracing information into a dictionary.
|
|
449
|
-
This function retrieves the current tracing context and extracts relevant data such as `trace_id`, `cost`, `tokens`, and `latency` for the whole trace.
|
|
450
|
-
It also dumps detailed span information using the `dump_spans` method and includes it in the trace dictionary.
|
|
451
|
-
If an error occurs during the process, it logs the error message and stack trace.
|
|
452
|
-
|
|
453
|
-
Returns:
|
|
454
|
-
dict: A dictionary containing the trace information.
|
|
455
|
-
"""
|
|
456
|
-
try:
|
|
457
|
-
trace = dict()
|
|
458
|
-
|
|
459
|
-
tracing = tracing_context.get()
|
|
460
|
-
|
|
461
|
-
trace["trace_id"] = tracing.trace_id
|
|
462
|
-
|
|
463
|
-
for span in tracing.spans.values():
|
|
464
|
-
if span.parent_span_id is None:
|
|
465
|
-
trace["cost"] = span.cost
|
|
466
|
-
trace["usage"] = (
|
|
467
|
-
None if span.tokens is None else json.loads(span.tokens.json())
|
|
468
|
-
)
|
|
469
|
-
trace["latency"] = (span.end_time - span.start_time).total_seconds()
|
|
470
|
-
|
|
471
|
-
spans = (
|
|
472
|
-
[]
|
|
473
|
-
if len(tracing.spans) == 0
|
|
474
|
-
else [json.loads(span.json()) for span in tracing.spans.values()]
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
if spans is not None:
|
|
478
|
-
trace["spans"] = spans
|
|
479
|
-
|
|
480
|
-
except Exception as e:
|
|
481
|
-
logging.error(e)
|
|
482
|
-
logging.error(traceback.format_exc())
|
|
483
|
-
|
|
484
|
-
return trace
|
|
485
|
-
|
|
486
|
-
def flush_spans(self) -> None:
|
|
487
|
-
self.close_trace()
|
|
488
|
-
|
|
489
|
-
### --- Legacy API --- ###
|
|
490
|
-
|
|
491
|
-
def start_trace(
|
|
492
|
-
self,
|
|
493
|
-
span: CreateSpan,
|
|
494
|
-
config: Optional[Dict[str, Any]] = None,
|
|
495
|
-
**kwargs,
|
|
496
|
-
) -> None: # Legacy
|
|
497
|
-
self.open_trace(span, config, **kwargs)
|
|
498
|
-
|
|
499
|
-
def end_trace(self, _: CreateSpan) -> None: # Legacy
|
|
500
|
-
self.close_trace()
|
|
501
|
-
|
|
502
|
-
def start_span(
|
|
503
|
-
self,
|
|
504
|
-
name: str,
|
|
505
|
-
spankind: str,
|
|
506
|
-
input: Dict[str, Any],
|
|
507
|
-
config: Optional[Dict[str, Any]] = None,
|
|
508
|
-
**kwargs,
|
|
509
|
-
) -> CreateSpan: # Legacy
|
|
510
|
-
return self.open_span(name, spankind, input, config, **kwargs)
|
|
511
|
-
|
|
512
|
-
def update_span_status(self, _: CreateSpan, status: str) -> None: # Legacy
|
|
513
|
-
self.update_span_status(status)
|
|
514
|
-
|
|
515
|
-
def set_span_attribute(
|
|
516
|
-
self,
|
|
517
|
-
attributes: Dict[str, Any] = {},
|
|
518
|
-
) -> None: # Legacy
|
|
519
|
-
self.set_attributes(attributes)
|
|
520
|
-
|
|
521
|
-
def end_span(self, outputs: Dict[str, Any]) -> None: # Legacy
|
|
522
|
-
self.store_outputs(outputs)
|
|
523
|
-
self.close_span()
|
|
524
|
-
|
|
525
|
-
### --- Helper Functions --- ###
|
|
526
|
-
|
|
527
|
-
def _create_trace_id(self) -> str:
|
|
528
|
-
"""Creates a 32HEXDIGL / ObjectId ID for the trace object.
|
|
529
|
-
|
|
530
|
-
Returns:
|
|
531
|
-
str: stringify oid of the trace
|
|
532
|
-
"""
|
|
533
|
-
|
|
534
|
-
# return uuid4().hex
|
|
535
|
-
return str(ObjectId())
|
|
536
|
-
|
|
537
|
-
def _clear_trace_tags(self) -> None:
|
|
538
|
-
tracing = tracing_context.get()
|
|
539
|
-
|
|
540
|
-
tracing.trace_tags.clear()
|
|
541
|
-
|
|
542
|
-
def _create_span_id(self) -> str:
|
|
543
|
-
"""Creates a 16HEXDIGL / ObjectId ID for the span object.
|
|
544
|
-
|
|
545
|
-
Returns:
|
|
546
|
-
str: stringify oid of the span
|
|
547
|
-
"""
|
|
548
|
-
|
|
549
|
-
# return uuid4().hex[:16]
|
|
550
|
-
return str(ObjectId())
|
|
551
|
-
|
|
552
|
-
def _get_target_span(self, span_id) -> CreateSpan:
|
|
553
|
-
tracing = tracing_context.get()
|
|
554
|
-
|
|
555
|
-
span = None
|
|
556
|
-
|
|
557
|
-
if span_id is None:
|
|
558
|
-
if tracing.active_span is None:
|
|
559
|
-
logging.error(f"Cannot set attributes, no active span")
|
|
560
|
-
return
|
|
561
|
-
|
|
562
|
-
span = tracing.active_span
|
|
563
|
-
else:
|
|
564
|
-
if span_id not in tracing.spans.keys():
|
|
565
|
-
logging.error(f"Cannot set attributes, span ({span_id}) not found")
|
|
566
|
-
return
|
|
567
|
-
|
|
568
|
-
span = tracing.spans[span_id]
|
|
569
|
-
|
|
570
|
-
return span
|
|
571
|
-
|
|
572
|
-
def _process_spans(self) -> None:
|
|
573
|
-
tracing = tracing_context.get()
|
|
574
|
-
|
|
575
|
-
spans = list(tracing.spans.values())
|
|
576
|
-
|
|
577
|
-
logging.info(f"Sending trace {tracing.trace_id} spans={len(spans)} ")
|
|
578
|
-
|
|
579
|
-
self.tasks_manager.add_task(
|
|
580
|
-
tracing.trace_id,
|
|
581
|
-
"send-trace",
|
|
582
|
-
# mock_create_traces(
|
|
583
|
-
self.client.create_traces(
|
|
584
|
-
trace=tracing.trace_id, spans=spans # type: ignore
|
|
585
|
-
),
|
|
586
|
-
self.client,
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
logging.info(f"Sent trace {tracing.trace_id}")
|
|
590
|
-
|
|
591
|
-
def _update_span_cost(self, span: CreateSpan, cost: Optional[float]) -> None:
|
|
592
|
-
if span is not None and cost is not None and isinstance(cost, float):
|
|
593
|
-
if span.cost is None:
|
|
594
|
-
span.cost = cost
|
|
595
|
-
else:
|
|
596
|
-
span.cost += cost
|
|
597
|
-
|
|
598
|
-
def _update_span_tokens(self, span: CreateSpan, tokens: Optional[dict]) -> None:
|
|
599
|
-
if isinstance(tokens, LlmTokens):
|
|
600
|
-
tokens = tokens.dict()
|
|
601
|
-
if span is not None and tokens is not None and isinstance(tokens, dict):
|
|
602
|
-
if span.tokens is None:
|
|
603
|
-
span.tokens = LlmTokens(**tokens)
|
|
604
|
-
else:
|
|
605
|
-
span.tokens.prompt_tokens += (
|
|
606
|
-
tokens["prompt_tokens"]
|
|
607
|
-
if tokens["prompt_tokens"] is not None
|
|
608
|
-
else 0
|
|
609
|
-
)
|
|
610
|
-
span.tokens.completion_tokens += (
|
|
611
|
-
tokens["completion_tokens"]
|
|
612
|
-
if tokens["completion_tokens"] is not None
|
|
613
|
-
else 0
|
|
614
|
-
)
|
|
615
|
-
span.tokens.total_tokens += (
|
|
616
|
-
tokens["total_tokens"] if tokens["total_tokens"] is not None else 0
|
|
617
|
-
)
|