agenta 0.19.4__py3-none-any.whl → 0.19.6__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 CHANGED
@@ -14,6 +14,7 @@ from .sdk.types import (
14
14
  BinaryParam,
15
15
  )
16
16
 
17
+ from .sdk.tracing.logger import llm_logger as logging
17
18
  from .sdk.tracing.llm_tracing import Tracing
18
19
  from .sdk.decorators.tracing import instrument
19
20
  from .sdk.decorators.llm_entrypoint import entrypoint, app
agenta/sdk/agenta_init.py CHANGED
@@ -239,6 +239,7 @@ def init(
239
239
  singleton = AgentaSingleton()
240
240
 
241
241
  singleton.init(app_id=app_id, host=host, api_key=api_key, config_fname=config_fname)
242
+
242
243
  tracing = Tracing(
243
244
  host=singleton.host, # type: ignore
244
245
  app_id=singleton.app_id, # type: ignore
@@ -18,6 +18,7 @@ from fastapi import Body, FastAPI, UploadFile, HTTPException
18
18
  import agenta
19
19
  from agenta.sdk.context import save_context
20
20
  from agenta.sdk.router import router as router
21
+ from agenta.sdk.tracing.logger import llm_logger as logging
21
22
  from agenta.sdk.tracing.llm_tracing import Tracing
22
23
  from agenta.sdk.decorators.base import BaseDecorator
23
24
  from agenta.sdk.types import (
@@ -52,6 +53,12 @@ app.add_middleware(
52
53
  app.include_router(router, prefix="")
53
54
 
54
55
 
56
+ from agenta.sdk.utils.debug import debug, DEBUG, SHIFT
57
+
58
+
59
+ logging.setLevel("DEBUG")
60
+
61
+
55
62
  class entrypoint(BaseDecorator):
56
63
  """Decorator class to wrap a function for HTTP POST, terminal exposure and enable tracing.
57
64
 
@@ -72,6 +79,7 @@ class entrypoint(BaseDecorator):
72
79
  config_params = agenta.config.all()
73
80
  ingestible_files = self.extract_ingestible_files(func_signature)
74
81
 
82
+ @debug()
75
83
  @functools.wraps(func)
76
84
  async def wrapper(*args, **kwargs) -> Any:
77
85
  func_params, api_config_params = self.split_kwargs(kwargs, config_params)
@@ -79,7 +87,7 @@ class entrypoint(BaseDecorator):
79
87
  agenta.config.set(**api_config_params)
80
88
 
81
89
  # Set the configuration and environment of the LLM app parent span at run-time
82
- agenta.tracing.set_span_attribute(
90
+ agenta.tracing.update_baggage(
83
91
  {"config": config_params, "environment": "playground"}
84
92
  )
85
93
 
@@ -90,6 +98,7 @@ class entrypoint(BaseDecorator):
90
98
 
91
99
  return llm_result
92
100
 
101
+ @debug()
93
102
  @functools.wraps(func)
94
103
  async def wrapper_deployed(*args, **kwargs) -> Any:
95
104
  func_params = {
@@ -104,13 +113,14 @@ class entrypoint(BaseDecorator):
104
113
  agenta.config.pull(config_name="default")
105
114
 
106
115
  # Set the configuration and environment of the LLM app parent span at run-time
107
- agenta.tracing.set_span_attribute(
116
+ agenta.tracing.update_baggage(
108
117
  {"config": config_params, "environment": kwargs["environment"]}
109
118
  )
110
119
 
111
120
  llm_result = await self.execute_function(
112
121
  func, *args, params=func_params, config_params=config_params
113
122
  )
123
+
114
124
  return llm_result
115
125
 
116
126
  self.update_function_signature(
@@ -190,6 +200,7 @@ class entrypoint(BaseDecorator):
190
200
  """
191
201
  is_coroutine_function = inspect.iscoroutinefunction(func)
192
202
  start_time = time.perf_counter()
203
+
193
204
  if is_coroutine_function:
194
205
  result = await func(*args, **func_params["params"])
195
206
  else:
@@ -410,7 +421,7 @@ class entrypoint(BaseDecorator):
410
421
  agenta.config.set(**args_config_params)
411
422
 
412
423
  # Set the configuration and environment of the LLM app parent span at run-time
413
- agenta.tracing.set_span_attribute(
424
+ agenta.tracing.update_baggage(
414
425
  {"config": agenta.config.all(), "environment": "bash"}
415
426
  )
416
427
 
@@ -7,6 +7,12 @@ from typing import Any, Callable, Optional
7
7
  # Own Imports
8
8
  import agenta as ag
9
9
  from agenta.sdk.decorators.base import BaseDecorator
10
+ from agenta.sdk.tracing.logger import llm_logger as logging
11
+ from agenta.sdk.tracing.tracing_context import tracing_context, TracingContext
12
+ from agenta.sdk.utils.debug import debug, DEBUG, SHIFT
13
+
14
+
15
+ logging.setLevel("DEBUG")
10
16
 
11
17
 
12
18
  class instrument(BaseDecorator):
@@ -41,6 +47,7 @@ class instrument(BaseDecorator):
41
47
  def __call__(self, func: Callable[..., Any]):
42
48
  is_coroutine_function = inspect.iscoroutinefunction(func)
43
49
 
50
+ @debug()
44
51
  @wraps(func)
45
52
  async def async_wrapper(*args, **kwargs):
46
53
  result = None
@@ -48,34 +55,65 @@ class instrument(BaseDecorator):
48
55
  input_dict = {name: value for name, value in zip(func_args, args)}
49
56
  input_dict.update(kwargs)
50
57
 
51
- span = self.tracing.start_span(
52
- name=func.__name__,
53
- input=input_dict,
54
- spankind=self.spankind,
55
- config=self.config,
56
- )
57
-
58
- try:
59
- result = await func(*args, **kwargs)
60
- self.tracing.update_span_status(span=span, value="OK")
61
- self.tracing.end_span(
62
- outputs=(
63
- {"message": result} if not isinstance(result, dict) else result
64
- )
65
- )
66
- return result
67
-
68
- except Exception as e:
69
- result = {
70
- "message": str(e),
71
- "stacktrace": traceback.format_exc(),
72
- }
73
- self.tracing.set_span_attribute(
74
- {"traceback_exception": traceback.format_exc()}
58
+ async def wrapped_func(*args, **kwargs):
59
+ # logging.debug(" ".join([">..", str(tracing_context.get())]))
60
+
61
+ token = None
62
+ if tracing_context.get() is None:
63
+ token = tracing_context.set(TracingContext())
64
+
65
+ # logging.debug(" ".join([">>.", str(tracing_context.get())]))
66
+
67
+ self.tracing.start_span(
68
+ name=func.__name__,
69
+ input=input_dict,
70
+ spankind=self.spankind,
71
+ config=self.config,
75
72
  )
76
- self.tracing.update_span_status(span=span, value="ERROR")
77
- self.tracing.end_span(outputs=result)
78
- raise e
73
+
74
+ try:
75
+ result = await func(*args, **kwargs)
76
+
77
+ self.tracing.set_status(status="OK")
78
+ self.tracing.end_span(
79
+ outputs=(
80
+ {"message": result}
81
+ if not isinstance(result, dict)
82
+ else result
83
+ )
84
+ )
85
+
86
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
87
+
88
+ if token is not None:
89
+ tracing_context.reset(token)
90
+
91
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
92
+
93
+ return result
94
+
95
+ except Exception as e:
96
+ result = {
97
+ "message": str(e),
98
+ "stacktrace": traceback.format_exc(),
99
+ }
100
+
101
+ self.tracing.set_attributes(
102
+ {"traceback_exception": traceback.format_exc()}
103
+ )
104
+ self.tracing.set_status(status="ERROR")
105
+ self.tracing.end_span(outputs=result)
106
+
107
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
108
+
109
+ if token is not None:
110
+ tracing_context.reset(token)
111
+
112
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
113
+
114
+ raise e
115
+
116
+ return await wrapped_func(*args, **kwargs)
79
117
 
80
118
  @wraps(func)
81
119
  def sync_wrapper(*args, **kwargs):
@@ -84,33 +122,65 @@ class instrument(BaseDecorator):
84
122
  input_dict = {name: value for name, value in zip(func_args, args)}
85
123
  input_dict.update(kwargs)
86
124
 
87
- span = self.tracing.start_span(
88
- name=func.__name__,
89
- input=input_dict,
90
- spankind=self.spankind,
91
- config=self.config,
92
- )
93
-
94
- try:
95
- result = func(*args, **kwargs)
96
- self.tracing.update_span_status(span=span, value="OK")
97
- self.tracing.end_span(
98
- outputs=(
99
- {"message": result} if not isinstance(result, dict) else result
100
- )
101
- )
102
- return result
103
-
104
- except Exception as e:
105
- result = {
106
- "message": str(e),
107
- "stacktrace": traceback.format_exc(),
108
- }
109
- self.tracing.set_span_attribute(
110
- {"traceback_exception": traceback.format_exc()}
125
+ def wrapped_func(*args, **kwargs):
126
+ # logging.debug(" ".join([">..", str(tracing_context.get())]))
127
+
128
+ token = None
129
+ if tracing_context.get() is None:
130
+ token = tracing_context.set(TracingContext())
131
+
132
+ # logging.debug(" ".join([">>.", str(tracing_context.get())]))
133
+
134
+ span = self.tracing.start_span(
135
+ name=func.__name__,
136
+ input=input_dict,
137
+ spankind=self.spankind,
138
+ config=self.config,
111
139
  )
112
- self.tracing.update_span_status(span=span, value="ERROR")
113
- self.tracing.end_span(outputs=result)
114
- raise e
140
+
141
+ try:
142
+ result = func(*args, **kwargs)
143
+
144
+ self.tracing.set_status(status="OK")
145
+ self.tracing.end_span(
146
+ outputs=(
147
+ {"message": result}
148
+ if not isinstance(result, dict)
149
+ else result
150
+ )
151
+ )
152
+
153
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
154
+
155
+ if token is not None:
156
+ tracing_context.reset(token)
157
+
158
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
159
+
160
+ return result
161
+
162
+ except Exception as e:
163
+ result = {
164
+ "message": str(e),
165
+ "stacktrace": traceback.format_exc(),
166
+ }
167
+
168
+ self.tracing.set_attributes(
169
+ {"traceback_exception": traceback.format_exc()}
170
+ )
171
+
172
+ self.tracing.set_status(status="ERROR")
173
+ self.tracing.end_span(outputs=result)
174
+
175
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
176
+
177
+ if token is not None:
178
+ tracing_context.reset(token)
179
+
180
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
181
+
182
+ raise e
183
+
184
+ return wrapped_func(*args, **kwargs)
115
185
 
116
186
  return async_wrapper if is_coroutine_function else sync_wrapper
@@ -1,9 +1,12 @@
1
1
  import os
2
+ from uuid import uuid4
3
+
2
4
  from threading import Lock
3
5
  from datetime import datetime, timezone
4
6
  from typing import Optional, Dict, Any, List
5
7
 
6
- from agenta.sdk.tracing.logger import llm_logger
8
+ from agenta.sdk.tracing.tracing_context import tracing_context
9
+ from agenta.sdk.tracing.logger import llm_logger as logging
7
10
  from agenta.sdk.tracing.tasks_manager import TaskQueue
8
11
  from agenta.client.backend.client import AsyncAgentaApi
9
12
  from agenta.client.backend.client import AsyncObservabilityClient
@@ -15,9 +18,13 @@ from agenta.client.backend.types.create_span import (
15
18
 
16
19
  from bson.objectid import ObjectId
17
20
 
18
-
19
21
  VARIANT_TRACKING_FEATURE_FLAG = False
20
22
 
23
+ from agenta.sdk.utils.debug import debug, DEBUG, SHIFT
24
+
25
+
26
+ logging.setLevel("DEBUG")
27
+
21
28
 
22
29
  class SingletonMeta(type):
23
30
  """
@@ -54,7 +61,6 @@ class Tracing(metaclass=SingletonMeta):
54
61
  host (str): The URL of the backend host
55
62
  api_key (str): The API Key of the backend host
56
63
  tasks_manager (TaskQueue): The tasks manager dedicated to handling asynchronous tasks
57
- llm_logger (Logger): The logger associated with the LLM tracing
58
64
  max_workers (int): The maximum number of workers to run tracing
59
65
  """
60
66
 
@@ -67,19 +73,11 @@ class Tracing(metaclass=SingletonMeta):
67
73
  ):
68
74
  self.host = host + "/api"
69
75
  self.api_key = api_key if api_key is not None else ""
70
- self.llm_logger = llm_logger
71
76
  self.app_id = app_id
72
77
  self.tasks_manager = TaskQueue(
73
- max_workers if max_workers else 4, logger=llm_logger
78
+ max_workers if max_workers else 4, logger=logging
74
79
  )
75
- self.active_span: Optional[CreateSpan] = None
76
- self.active_trace_id: Optional[str] = None
77
- self.pending_spans: List[CreateSpan] = []
78
- self.tags: List[str] = []
79
- self.trace_config_cache: Dict[
80
- str, Any
81
- ] = {} # used to save the trace configuration before starting the first span
82
- self.span_dict: Dict[str, CreateSpan] = {} # type: ignore
80
+ self.baggage = None
83
81
 
84
82
  @property
85
83
  def client(self) -> AsyncObservabilityClient:
@@ -93,30 +91,104 @@ class Tracing(metaclass=SingletonMeta):
93
91
  base_url=self.host, api_key=self.api_key, timeout=120 # type: ignore
94
92
  ).observability
95
93
 
96
- def set_span_attribute(
94
+ ### --- API --- ###
95
+
96
+ @debug()
97
+ def get_context(self):
98
+ tracing = tracing_context.get()
99
+
100
+ return tracing
101
+
102
+ @debug()
103
+ def update_baggage(
97
104
  self,
98
105
  attributes: Dict[str, Any] = {},
99
106
  ):
107
+ if self.baggage is None:
108
+ self.baggage = {}
109
+
110
+ for key, value in attributes.items():
111
+ self.baggage[key] = value
112
+
113
+ @debug()
114
+ def open_trace(
115
+ self,
116
+ span: Optional[CreateSpan],
117
+ config: Optional[Dict[str, Any]] = None,
118
+ **kwargs,
119
+ ) -> None:
120
+ tracing = tracing_context.get()
121
+
122
+ tracing.trace_id = self._create_trace_id()
123
+
124
+ logging.info(f"Opening trace {tracing.trace_id}")
125
+
126
+ if span is not None:
127
+ ### --- TO BE CLEANED --- >>>
128
+ span.environment = (
129
+ self.baggage.get("environment")
130
+ if self.baggage is not None
131
+ else os.environ.get("environment", "unset")
132
+ )
133
+
134
+ span.config = (
135
+ self.baggage.get("config")
136
+ if not config and self.baggage is not None
137
+ else None
138
+ )
139
+ if VARIANT_TRACKING_FEATURE_FLAG:
140
+ # TODO: we should get the variant_id and variant_name (and environment) from the config object
141
+ span.variant_id = config.variant_id # type: ignore
142
+ span.variant_name = (config.variant_name,) # type: ignore
143
+ ### --- TO BE CLEANED --- <<<
144
+
145
+ logging.info(f"Opened trace {tracing.trace_id}")
146
+
147
+ @debug()
148
+ def set_trace_tags(self, tags: List[str]) -> None:
149
+ tracing = tracing_context.get()
150
+
151
+ tracing.trace_tags.extend(tags)
152
+
153
+ @debug()
154
+ def close_trace(self) -> None:
100
155
  """
101
- Set attributes for the active span.
156
+ Ends the active trace and sends the recorded spans for processing.
102
157
 
103
158
  Args:
104
- attributes (Dict[str, Any], optional): A dictionary of attributes to set. Defaults to {}.
159
+ parent_span (CreateSpan): The parent span of the trace.
160
+
161
+ Raises:
162
+ RuntimeError: If there is no active trace to end.
163
+
164
+ Returns:
165
+ None
105
166
  """
167
+ tracing = tracing_context.get()
168
+
169
+ logging.info(f"Closing trace {tracing.trace_id}")
170
+
171
+ trace_id = tracing.trace_id
106
172
 
107
- if (
108
- self.active_span is None
109
- ): # This is the case where entrypoint wants to save the trace information but the parent span has not been initialized yet
110
- for key, value in attributes.items():
111
- self.trace_config_cache[key] = value
173
+ if tracing.trace_id is None:
174
+ logging.error("Cannot close trace, no trace to close")
175
+ return
176
+
177
+ if not self.api_key:
178
+ logging.error("No API key")
112
179
  else:
113
- for key, value in attributes.items():
114
- self.active_span.attributes[key] = value # type: ignore
180
+ self._process_closed_spans()
115
181
 
116
- def set_trace_tags(self, tags: List[str]):
117
- self.tags.extend(tags)
182
+ self._clear_closed_spans()
183
+ self._clear_tracked_spans()
184
+ self._clear_active_span()
118
185
 
119
- def start_span(
186
+ self._clear_trace_tags()
187
+
188
+ logging.info(f"Closed trace {trace_id}")
189
+
190
+ @debug()
191
+ def open_span(
120
192
  self,
121
193
  name: str,
122
194
  spankind: str,
@@ -124,10 +196,13 @@ class Tracing(metaclass=SingletonMeta):
124
196
  config: Optional[Dict[str, Any]] = None,
125
197
  **kwargs,
126
198
  ) -> CreateSpan:
199
+ tracing = tracing_context.get()
200
+
127
201
  span_id = self._create_span_id()
128
- self.llm_logger.info(
129
- f"Recording {'parent' if spankind == 'workflow' else spankind} span..."
130
- )
202
+
203
+ logging.info(f"Opening span {span_id} {spankind.upper()}")
204
+
205
+ ### --- TO BE CLEANED --- >>>
131
206
  span = CreateSpan(
132
207
  id=span_id,
133
208
  inputs=input,
@@ -148,54 +223,66 @@ class Tracing(metaclass=SingletonMeta):
148
223
  parent_span_id=None,
149
224
  )
150
225
 
151
- if self.active_trace_id is None: # This is a parent span
152
- self.active_trace_id = self._create_trace_id()
153
- span.environment = (
154
- self.trace_config_cache.get("environment")
155
- if self.trace_config_cache is not None
156
- else os.environ.get("environment", "unset")
157
- )
158
- span.config = (
159
- self.trace_config_cache.get("config")
160
- if not config and self.trace_config_cache is not None
161
- else None
162
- )
163
- if VARIANT_TRACKING_FEATURE_FLAG:
164
- # TODO: we should get the variant_id and variant_name (and environment) from the config object
165
- span.variant_id = config.variant_id # type: ignore
166
- span.variant_name = (config.variant_name,) # type: ignore
167
-
226
+ if tracing.trace_id is None:
227
+ self.start_trace(span, config)
168
228
  else:
169
- span.parent_span_id = self.active_span.id # type: ignore
229
+ span.parent_span_id = tracing.active_span.id # type: ignore
170
230
 
171
- self.span_dict[span.id] = span
172
- self.active_span = span
231
+ tracing.tracked_spans[span.id] = span
232
+ tracing.active_span = span
233
+ ### --- TO BE CLEANED --- <<<
234
+
235
+ logging.info(f"Opened span {span_id} {spankind.upper()}")
173
236
 
174
- self.llm_logger.info(f"Recorded span and setting parent_span_id: {span.id}")
175
237
  return span
176
238
 
177
- def update_span_status(self, span: CreateSpan, value: str):
178
- span.status = value
239
+ @debug(req=True)
240
+ def set_attributes(
241
+ self,
242
+ attributes: Dict[str, Any] = {},
243
+ ) -> None:
244
+ """
245
+ Set attributes for the active span.
179
246
 
180
- def _update_span_cost(self, span: CreateSpan, cost: Optional[float]):
181
- if cost is not None and isinstance(cost, float):
182
- if span.cost is None:
183
- span.cost = cost
184
- else:
185
- span.cost += cost
247
+ Args:
248
+ attributes (Dict[str, Any], optional): A dictionary of attributes to set. Defaults to {}.
249
+ """
186
250
 
187
- def _update_span_tokens(self, span: CreateSpan, tokens: Optional[dict]):
188
- if isinstance(tokens, LlmTokens):
189
- tokens = tokens.dict()
190
- if tokens is not None and isinstance(tokens, dict):
191
- if span.tokens is None:
192
- span.tokens = LlmTokens(**tokens)
193
- else:
194
- span.tokens.prompt_tokens += tokens["prompt_tokens"]
195
- span.tokens.completion_tokens += tokens["completion_tokens"]
196
- span.tokens.total_tokens += tokens["total_tokens"]
251
+ tracing = tracing_context.get()
197
252
 
198
- def end_span(self, outputs: Dict[str, Any]):
253
+ if tracing.active_span is None:
254
+ logging.error(f"Cannot set attributes ({set(attributes)}), no active span")
255
+ return
256
+
257
+ logging.info(
258
+ f"Setting span {tracing.active_span.id} {tracing.active_span.spankind.upper()} attributes={attributes}"
259
+ )
260
+
261
+ for key, value in attributes.items():
262
+ tracing.active_span.attributes[key] = value # type: ignore
263
+
264
+ @debug()
265
+ def set_status(self, status: str) -> None:
266
+ """
267
+ Set status for the active span.
268
+
269
+ Args:
270
+ status: Enum ( UNSET, OK, ERROR )
271
+ """
272
+ tracing = tracing_context.get()
273
+
274
+ if tracing.active_span is None:
275
+ logging.error(f"Cannot set status ({status}), no active span")
276
+ return
277
+
278
+ logging.info(
279
+ f"Setting span {tracing.active_span.id} {tracing.active_span.spankind.upper()} status={status}"
280
+ )
281
+
282
+ tracing.active_span.status = status
283
+
284
+ @debug()
285
+ def close_span(self, outputs: Dict[str, Any]) -> None:
199
286
  """
200
287
  Ends the active span, if it is a parent span, ends the trace too.
201
288
 
@@ -213,123 +300,160 @@ class Tracing(metaclass=SingletonMeta):
213
300
  None
214
301
  """
215
302
 
216
- if self.active_span is None:
217
- raise ValueError("There is no active span to end.")
303
+ tracing = tracing_context.get()
304
+
305
+ if tracing.active_span is None:
306
+ logging.error("Cannot close span, no active span")
307
+
308
+ span_id = tracing.active_span.id
309
+ spankind = tracing.active_span.spankind
310
+
311
+ logging.info(f"Closing span {span_id} {spankind}")
312
+
313
+ ### --- TO BE CLEANED --- >>>
314
+ tracing.active_span.end_time = datetime.now(timezone.utc)
315
+
316
+ tracing.active_span.outputs = [outputs.get("message", "")]
218
317
 
219
- self.active_span.end_time = datetime.now(timezone.utc)
220
- self.active_span.outputs = [outputs.get("message", "")]
221
- if self.active_span.spankind in [
318
+ if tracing.active_span.spankind.upper() in [
222
319
  "LLM",
223
320
  "RETRIEVER",
224
321
  ]: # TODO: Remove this whole part. Setting the cost should be done through set_span_attribute
225
- self._update_span_cost(self.active_span, outputs.get("cost", None))
226
- self._update_span_tokens(self.active_span, outputs.get("usage", None))
322
+ self._update_span_cost(tracing.active_span, outputs.get("cost", None))
323
+ self._update_span_tokens(tracing.active_span, outputs.get("usage", None))
227
324
 
228
- # Push span to list of recorded spans
229
- self.pending_spans.append(self.active_span)
325
+ tracing.closed_spans.append(tracing.active_span)
230
326
 
231
- active_span_parent_id = self.active_span.parent_span_id
232
- if (
233
- self.active_span.status == SpanStatusCode.ERROR.value
234
- and active_span_parent_id is not None
235
- ):
236
- self.record_exception_and_end_trace(span_parent_id=active_span_parent_id)
327
+ active_span_parent_id = tracing.active_span.parent_span_id
237
328
 
238
329
  if active_span_parent_id is None:
239
- self.end_trace(parent_span=self.active_span)
330
+ self.end_trace(parent_span=tracing.active_span)
240
331
 
241
332
  else:
242
- parent_span = self.span_dict[active_span_parent_id]
243
- self._update_span_cost(parent_span, self.active_span.cost)
244
- self._update_span_tokens(parent_span, self.active_span.tokens)
245
- self.active_span = parent_span
333
+ parent_span = tracing.tracked_spans[active_span_parent_id]
334
+ self._update_span_cost(parent_span, tracing.active_span.cost)
335
+ self._update_span_tokens(parent_span, tracing.active_span.tokens)
336
+ tracing.active_span = parent_span
337
+ ### --- TO BE CLEANED --- <<<
246
338
 
247
- def record_exception_and_end_trace(self, span_parent_id: str):
248
- """
249
- Record an exception and end the trace.
339
+ logging.info(f"Closed span {span_id} {spankind}")
250
340
 
251
- Args:
252
- span_parent_id (str): The ID of the parent span.
341
+ ### --- Legacy API --- ###
253
342
 
254
- Returns:
255
- None
256
- """
343
+ def start_trace(
344
+ self,
345
+ span: CreateSpan,
346
+ config: Optional[Dict[str, Any]] = None,
347
+ **kwargs,
348
+ ) -> None: # Legacy
349
+ self.open_trace(span, config, **kwargs)
257
350
 
258
- parent_span = self.span_dict.get(span_parent_id)
259
- if parent_span is not None:
260
- # Update parent span of active span
261
- parent_span.outputs = self.active_span.outputs # type: ignore
262
- parent_span.status = "ERROR"
263
- parent_span.end_time = datetime.now(timezone.utc)
351
+ def end_trace(self, parent_span: CreateSpan) -> None: # Legacy
352
+ self.close_trace()
264
353
 
265
- # Push parent span to list of recorded spans and end trace
266
- self.pending_spans.append(parent_span)
267
- self.end_trace(parent_span=parent_span)
354
+ def start_span(
355
+ self,
356
+ name: str,
357
+ spankind: str,
358
+ input: Dict[str, Any],
359
+ config: Optional[Dict[str, Any]] = None,
360
+ **kwargs,
361
+ ) -> CreateSpan: # Legacy
362
+ return self.open_span(name, spankind, input, config, **kwargs)
268
363
 
269
- # TODO: improve exception logic here.
364
+ def update_span_status(self, _: CreateSpan, status: str) -> None: # Legacy
365
+ self.update_span_status(status)
270
366
 
271
- def end_trace(self, parent_span: CreateSpan):
367
+ def set_span_attribute(
368
+ self,
369
+ attributes: Dict[str, Any] = {},
370
+ ) -> None: # Legacy
371
+ self.set_attributes(attributes)
372
+
373
+ def end_span(self, outputs: Dict[str, Any]) -> None: # Legacy
374
+ self.close_span(outputs)
375
+
376
+ ### --- Helper Functions --- ###
377
+
378
+ def _create_trace_id(self) -> str:
379
+ """Creates a 32HEXDIGL / ObjectId ID for the trace object.
380
+
381
+ Returns:
382
+ str: stringify oid of the trace
272
383
  """
273
- Ends the active trace and sends the recorded spans for processing.
274
384
 
275
- Args:
276
- parent_span (CreateSpan): The parent span of the trace.
385
+ # return uuid4().hex
386
+ return str(ObjectId())
277
387
 
278
- Raises:
279
- RuntimeError: If there is no active trace to end.
388
+ def _clear_trace_tags(self) -> None:
389
+ tracing = tracing_context.get()
390
+
391
+ tracing.trace_tags.clear()
392
+
393
+ def _create_span_id(self) -> str:
394
+ """Creates a 16HEXDIGL / ObjectId ID for the span object.
280
395
 
281
396
  Returns:
282
- None
397
+ str: stringify oid of the span
283
398
  """
284
399
 
285
- if self.api_key == "":
286
- return
400
+ # return uuid4().hex[:16]
401
+ return str(ObjectId())
402
+
403
+ def _process_closed_spans(self) -> None:
404
+ tracing = tracing_context.get()
405
+
406
+ logging.info(f"Sending spans {tracing.trace_id} #={len(tracing.closed_spans)} ")
287
407
 
288
- if not self.active_trace_id:
289
- raise RuntimeError("No active trace to end.")
408
+ # async def mock_create_traces(trace, spans):
409
+ # print("trace-id", trace)
410
+ # print("spans", spans)
290
411
 
291
- self.llm_logger.info("Preparing to send recorded spans for processing.")
292
- self.llm_logger.info(f"Recorded spans => {len(self.pending_spans)}")
293
412
  self.tasks_manager.add_task(
294
- self.active_trace_id,
413
+ tracing.trace_id,
295
414
  "trace",
415
+ # mock_create_traces(
296
416
  self.client.create_traces(
297
- trace=self.active_trace_id, spans=self.pending_spans # type: ignore
417
+ trace=tracing.trace_id, spans=tracing.closed_spans # type: ignore
298
418
  ),
299
419
  self.client,
300
420
  )
301
- self.llm_logger.info(
302
- f"Tracing for {parent_span.id} recorded successfully and sent for processing."
303
- )
304
- self._clear_pending_spans()
305
- self.active_trace_id = None
306
- self.active_span = None
307
- self.trace_config_cache.clear()
308
421
 
309
- def _create_trace_id(self) -> str:
310
- """Creates a unique mongo id for the trace object.
422
+ logging.info(f"Sent spans {tracing.trace_id} #={len(tracing.closed_spans)}")
311
423
 
312
- Returns:
313
- str: stringify oid of the trace
314
- """
424
+ def _clear_closed_spans(self) -> None:
425
+ tracing = tracing_context.get()
315
426
 
316
- return str(ObjectId())
427
+ tracing.closed_spans.clear()
317
428
 
318
- def _create_span_id(self) -> str:
319
- """Creates a unique mongo id for the span object.
429
+ def _clear_tracked_spans(self) -> None:
430
+ tracing = tracing_context.get()
320
431
 
321
- Returns:
322
- str: stringify oid of the span
323
- """
432
+ tracing.tracked_spans.clear()
324
433
 
325
- return str(ObjectId())
434
+ def _clear_active_span(self) -> None:
435
+ tracing = tracing_context.get()
326
436
 
327
- def _clear_pending_spans(self) -> None:
328
- """
329
- Clear the list of recorded spans to prepare for next batch processing.
330
- """
437
+ span_id = tracing.active_span.id
331
438
 
332
- self.pending_spans = []
333
- self.llm_logger.info(
334
- f"Cleared all recorded spans from batch: {self.pending_spans}"
335
- )
439
+ tracing.active_span = None
440
+
441
+ logging.debug(f"Cleared active span {span_id}")
442
+
443
+ def _update_span_cost(self, span: CreateSpan, cost: Optional[float]) -> None:
444
+ if span is not None and cost is not None and isinstance(cost, float):
445
+ if span.cost is None:
446
+ span.cost = cost
447
+ else:
448
+ span.cost += cost
449
+
450
+ def _update_span_tokens(self, span: CreateSpan, tokens: Optional[dict]) -> None:
451
+ if isinstance(tokens, LlmTokens):
452
+ tokens = tokens.dict()
453
+ if span is not None and tokens is not None and isinstance(tokens, dict):
454
+ if span.tokens is None:
455
+ span.tokens = LlmTokens(**tokens)
456
+ else:
457
+ span.tokens.prompt_tokens += tokens["prompt_tokens"]
458
+ span.tokens.completion_tokens += tokens["completion_tokens"]
459
+ span.tokens.total_tokens += tokens["total_tokens"]
@@ -0,0 +1,28 @@
1
+ import contextvars
2
+
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from agenta.client.backend.types.create_span import CreateSpan
6
+
7
+ CURRENT_TRACING_CONTEXT_KEY = "current_tracing_context"
8
+
9
+
10
+ class TracingContext:
11
+ def __init__(self):
12
+ ### --- TRACE --- ###
13
+ self.trace_id: Optional[str] = None
14
+ self.trace_tags: List[str] = []
15
+
16
+ ### --- SPANS --- ###
17
+ self.active_span: Optional[CreateSpan] = None
18
+ self.tracked_spans: Dict[str, CreateSpan] = {}
19
+ self.closed_spans: List[CreateSpan] = []
20
+
21
+ def __repr__(self) -> str:
22
+ return f"TracingContext(trace_id=[{self.trace_id}], active_span=[{self.active_span.id if self.active_span else None}{' ' + self.active_span.spankind if self.active_span else ''}])"
23
+
24
+ def __str__(self) -> str:
25
+ return self.__repr__()
26
+
27
+
28
+ tracing_context = contextvars.ContextVar(CURRENT_TRACING_CONTEXT_KEY, default=None)
@@ -0,0 +1,68 @@
1
+ from inspect import iscoroutinefunction
2
+ from functools import wraps
3
+
4
+ from agenta.sdk.tracing.logger import llm_logger as logging
5
+
6
+ DEBUG = False
7
+ SHIFT = 7
8
+
9
+
10
+ def debug(shift=1, req=False, res=False, chars=[">", "<"]):
11
+ def log_decorator(f):
12
+ is_async = iscoroutinefunction(f)
13
+
14
+ @wraps(f)
15
+ async def async_log_wrapper(*args, **kwargs):
16
+ if DEBUG:
17
+ logging.debug(
18
+ " ".join(
19
+ [
20
+ chars[0] * shift + " " * (SHIFT - shift),
21
+ f.__name__ + " ()",
22
+ str(args) if req else "",
23
+ str(kwargs) if req else "",
24
+ ]
25
+ )
26
+ )
27
+ result = await f(*args, **kwargs)
28
+ if DEBUG:
29
+ logging.debug(
30
+ " ".join(
31
+ [
32
+ chars[1] * shift + " " * (SHIFT - shift),
33
+ f.__name__ + " <-",
34
+ str(result) if res else "",
35
+ ]
36
+ )
37
+ )
38
+ return result
39
+
40
+ @wraps(f)
41
+ def log_wrapper(*args, **kwargs):
42
+ if DEBUG:
43
+ logging.debug(
44
+ " ".join(
45
+ [
46
+ chars[0] * shift + " " * (SHIFT - shift),
47
+ f.__name__ + " ()",
48
+ str(args) if req else "",
49
+ str(kwargs) if req else "",
50
+ ]
51
+ )
52
+ )
53
+ result = f(*args, **kwargs)
54
+ if DEBUG:
55
+ logging.debug(
56
+ " ".join(
57
+ [
58
+ chars[1] * shift + " " * (SHIFT - shift),
59
+ f.__name__ + " <-",
60
+ str(result) if res else "",
61
+ ]
62
+ )
63
+ )
64
+ return result
65
+
66
+ return async_log_wrapper if is_async else log_wrapper
67
+
68
+ return log_decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agenta
3
- Version: 0.19.4
3
+ Version: 0.19.6
4
4
  Summary: The SDK for agenta is an open-source LLMOps platform.
5
5
  Home-page: https://agenta.ai
6
6
  Keywords: LLMOps,LLM,evaluation,prompt engineering
@@ -1,4 +1,4 @@
1
- agenta/__init__.py,sha256=geaQThRuZzebZncauJW2SZzKqIT7-ZkIJJf-zZlWHCs,833
1
+ agenta/__init__.py,sha256=6ZfEnXNHoJVXO9HT_U-WuitFvE6Oq_UTrjAZGIRzaDY,887
2
2
  agenta/cli/evaluation_commands.py,sha256=fs6492tprPId9p8eGO02Xy-NCBm2RZNJLZWcUxugwd8,474
3
3
  agenta/cli/helper.py,sha256=vRxHyeNaltzNIGrfU2vO0H28_rXDzx9QqIZ_S-W6zL4,6212
4
4
  agenta/cli/main.py,sha256=Wz0ODhoeKK3Qg_CFUhu6D909szk05tc8ZVBB6H1-w7k,9763
@@ -127,20 +127,22 @@ agenta/docker/docker-assets/lambda_function.py,sha256=h4UZSSfqwpfsCgERv6frqwm_4J
127
127
  agenta/docker/docker-assets/main.py,sha256=7MI-21n81U7N7A0GxebNi0cmGWtJKcR2sPB6FcH2QfA,251
128
128
  agenta/docker/docker_utils.py,sha256=5uHMCzXkCvIsDdEiwbnnn97KkzsFbBvyMwogCsv_Z5U,3509
129
129
  agenta/sdk/__init__.py,sha256=cF0de6DiH-NZWEm0XvPN8_TeC1whPBnDf1WYYE1qK2g,762
130
- agenta/sdk/agenta_init.py,sha256=zhMYCH6QMDzQADrOmFBaqZRvAYrA6CC35XsePY7IMxg,9789
130
+ agenta/sdk/agenta_init.py,sha256=8MfDuypxohd0qRTdtGjX7L17KW-1UGmzNVdiqF15_ak,9790
131
131
  agenta/sdk/client.py,sha256=trKyBOYFZRk0v5Eptxvh87yPf50Y9CqY6Qgv4Fy-VH4,2142
132
132
  agenta/sdk/context.py,sha256=q-PxL05-I84puunUAs9LGsffEXcYhDxhQxjuOz2vK90,901
133
133
  agenta/sdk/decorators/base.py,sha256=9aNdX5h8a2mFweuhdO-BQPwXGKY9ONPIdLRhSGAGMfY,217
134
- agenta/sdk/decorators/llm_entrypoint.py,sha256=awdd2fSjVKIz3MVmElHw8_nJxFvePI_zMdPd_QWxIM0,22827
135
- agenta/sdk/decorators/tracing.py,sha256=4Ta9UN3pjQhxST6lnPaNz1GwnoXz2EHjq5JGqd4g8qw,3817
134
+ agenta/sdk/decorators/llm_entrypoint.py,sha256=umcniOOQfKVdPgHVb_jRhoGKei14Yc3PIZmEC8CU2Wg,22996
135
+ agenta/sdk/decorators/tracing.py,sha256=c9LwQJkhJcyO7Uq-sNpDSwfwOUTAmlqNuJjz4bSx-k0,6172
136
136
  agenta/sdk/router.py,sha256=0sbajvn5C7t18anH6yNo7-oYxldHnYfwcbmQnIXBePw,269
137
137
  agenta/sdk/tracing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
138
  agenta/sdk/tracing/callbacks.py,sha256=8XYcGC4EZQ0lBn2IWmtPH_yC5Hu6Yu-NkdZRuY7CoTw,6816
139
139
  agenta/sdk/tracing/context_manager.py,sha256=HskDaiORoOhjeN375gm05wYnieQzh5UnoIsnSAHkAyc,252
140
- agenta/sdk/tracing/llm_tracing.py,sha256=fHtc1tKgdR2CDnFMrfDIxSvyjwFW3Qk2RE8P-y_tCM0,11361
140
+ agenta/sdk/tracing/llm_tracing.py,sha256=b94Fx5eIOxOvB6S2zeINq8o2taeRpZgxcfNQlh3Jeq0,13689
141
141
  agenta/sdk/tracing/logger.py,sha256=GfH7V-jBHcn7h5dbdrnkDMe_ml3wkXFBeoQiqR4KVRc,474
142
142
  agenta/sdk/tracing/tasks_manager.py,sha256=ROrWIaqS2J2HHiJtRWiHKlLY8CCsqToP5VeXu7mamck,3748
143
+ agenta/sdk/tracing/tracing_context.py,sha256=EOi1zfqpb2cBjhBtHIphUkVHi4jiWes-RRNdbgk1kMc,906
143
144
  agenta/sdk/types.py,sha256=KMnQUOdjaHSWctDLIiMHnk0o3c-C47Vm4Mn2kIZ88YI,5740
145
+ agenta/sdk/utils/debug.py,sha256=QyuPsSoN0425UD13x_msPxSF_VT6YwHiQunZUibI-jg,2149
144
146
  agenta/sdk/utils/globals.py,sha256=JmhJcCOSbwvjQ6GDyUc2_SYR27DZk7YcrRH80ktHHOM,435
145
147
  agenta/sdk/utils/helper/openai_cost.py,sha256=1VkgvucDnNZm1pTfcVLz9icWunntp1d7zwMmnviy3Uw,5877
146
148
  agenta/sdk/utils/preinit.py,sha256=YlJL7RLfel0R7DFp-jK7OV-z4ZIQJM0oupYlk7g8b5o,1278
@@ -159,7 +161,7 @@ agenta/templates/simple_prompt/app.py,sha256=kODgF6lhzsaJPdgL5b21bUki6jkvqjWZzWR
159
161
  agenta/templates/simple_prompt/env.example,sha256=g9AE5bYcGPpxawXMJ96gh8oenEPCHTabsiOnfQo3c5k,70
160
162
  agenta/templates/simple_prompt/requirements.txt,sha256=ywRglRy7pPkw8bljmMEJJ4aOOQKrt9FGKULZ-DGkoBU,23
161
163
  agenta/templates/simple_prompt/template.toml,sha256=DQBtRrF4GU8LBEXOZ-GGuINXMQDKGTEG5y37tnvIUIE,60
162
- agenta-0.19.4.dist-info/METADATA,sha256=eS83a2QK24f3aAXei-SPL6Xfeqa0MKW0koqmI-aov3g,26460
163
- agenta-0.19.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
164
- agenta-0.19.4.dist-info/entry_points.txt,sha256=PDiu8_8AsL7ibU9v4iNoOKR1S7F2rdxjlEprjM9QOgo,46
165
- agenta-0.19.4.dist-info/RECORD,,
164
+ agenta-0.19.6.dist-info/METADATA,sha256=9tRG-XDUvW0HDdCk7pM7SwjPRdJ5Y2_w4_ON3ikinKQ,26460
165
+ agenta-0.19.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
166
+ agenta-0.19.6.dist-info/entry_points.txt,sha256=PDiu8_8AsL7ibU9v4iNoOKR1S7F2rdxjlEprjM9QOgo,46
167
+ agenta-0.19.6.dist-info/RECORD,,