agenta 0.19.5__py3-none-any.whl → 0.19.6a0__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,12 @@ 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
+
243
+ if os.environ.get("AGENTA_LOCAL", False):
244
+ singleton.host = singleton.host.replace(
245
+ "http://localhost", "http://host.docker.internal"
246
+ )
247
+
242
248
  tracing = Tracing(
243
249
  host=singleton.host, # type: ignore
244
250
  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,66 @@ 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
+
60
+ # logging.debug(" ".join([">..", str(tracing_context.get())]))
61
+
62
+ token = None
63
+ if tracing_context.get() is None:
64
+ token = tracing_context.set(TracingContext())
65
+
66
+ # logging.debug(" ".join([">>.", str(tracing_context.get())]))
67
+
68
+ self.tracing.start_span(
69
+ name=func.__name__,
70
+ input=input_dict,
71
+ spankind=self.spankind,
72
+ config=self.config,
75
73
  )
76
- self.tracing.update_span_status(span=span, value="ERROR")
77
- self.tracing.end_span(outputs=result)
78
- raise e
74
+
75
+ try:
76
+ result = await func(*args, **kwargs)
77
+
78
+ self.tracing.set_status(status="OK")
79
+ self.tracing.end_span(
80
+ outputs=(
81
+ {"message": result}
82
+ if not isinstance(result, dict)
83
+ else result
84
+ )
85
+ )
86
+
87
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
88
+
89
+ if token is not None:
90
+ tracing_context.reset(token)
91
+
92
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
93
+
94
+ return result
95
+
96
+ except Exception as e:
97
+ result = {
98
+ "message": str(e),
99
+ "stacktrace": traceback.format_exc(),
100
+ }
101
+
102
+ self.tracing.set_attributes(
103
+ {"traceback_exception": traceback.format_exc()}
104
+ )
105
+ self.tracing.set_status(status="ERROR")
106
+ self.tracing.end_span(outputs=result)
107
+
108
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
109
+
110
+ if token is not None:
111
+ tracing_context.reset(token)
112
+
113
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
114
+
115
+ raise e
116
+
117
+ return await wrapped_func(*args, **kwargs)
79
118
 
80
119
  @wraps(func)
81
120
  def sync_wrapper(*args, **kwargs):
@@ -84,33 +123,66 @@ class instrument(BaseDecorator):
84
123
  input_dict = {name: value for name, value in zip(func_args, args)}
85
124
  input_dict.update(kwargs)
86
125
 
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()}
126
+ def wrapped_func(*args, **kwargs):
127
+
128
+ # logging.debug(" ".join([">..", str(tracing_context.get())]))
129
+
130
+ token = None
131
+ if tracing_context.get() is None:
132
+ token = tracing_context.set(TracingContext())
133
+
134
+ # logging.debug(" ".join([">>.", str(tracing_context.get())]))
135
+
136
+ span = self.tracing.start_span(
137
+ name=func.__name__,
138
+ input=input_dict,
139
+ spankind=self.spankind,
140
+ config=self.config,
111
141
  )
112
- self.tracing.update_span_status(span=span, value="ERROR")
113
- self.tracing.end_span(outputs=result)
114
- raise e
142
+
143
+ try:
144
+ result = func(*args, **kwargs)
145
+
146
+ self.tracing.set_status(status="OK")
147
+ self.tracing.end_span(
148
+ outputs=(
149
+ {"message": result}
150
+ if not isinstance(result, dict)
151
+ else result
152
+ )
153
+ )
154
+
155
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
156
+
157
+ if token is not None:
158
+ tracing_context.reset(token)
159
+
160
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
161
+
162
+ return result
163
+
164
+ except Exception as e:
165
+ result = {
166
+ "message": str(e),
167
+ "stacktrace": traceback.format_exc(),
168
+ }
169
+
170
+ self.tracing.set_attributes(
171
+ {"traceback_exception": traceback.format_exc()}
172
+ )
173
+
174
+ self.tracing.set_status(status="ERROR")
175
+ self.tracing.end_span(outputs=result)
176
+
177
+ # logging.debug(" ".join(["<<.", str(tracing_context.get())]))
178
+
179
+ if token is not None:
180
+ tracing_context.reset(token)
181
+
182
+ # logging.debug(" ".join(["<..", str(tracing_context.get())]))
183
+
184
+ raise e
185
+
186
+ return wrapped_func(*args, **kwargs)
115
187
 
116
188
  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,14 @@ class Tracing(metaclass=SingletonMeta):
124
196
  config: Optional[Dict[str, Any]] = None,
125
197
  **kwargs,
126
198
  ) -> CreateSpan:
199
+
200
+ tracing = tracing_context.get()
201
+
127
202
  span_id = self._create_span_id()
128
- self.llm_logger.info(
129
- f"Recording {'parent' if spankind == 'workflow' else spankind} span..."
130
- )
203
+
204
+ logging.info(f"Opening span {span_id} {spankind.upper()}")
205
+
206
+ ### --- TO BE CLEANED --- >>>
131
207
  span = CreateSpan(
132
208
  id=span_id,
133
209
  inputs=input,
@@ -148,54 +224,66 @@ class Tracing(metaclass=SingletonMeta):
148
224
  parent_span_id=None,
149
225
  )
150
226
 
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
-
227
+ if tracing.trace_id is None:
228
+ self.start_trace(span, config)
168
229
  else:
169
- span.parent_span_id = self.active_span.id # type: ignore
230
+ span.parent_span_id = tracing.active_span.id # type: ignore
231
+
232
+ tracing.tracked_spans[span.id] = span
233
+ tracing.active_span = span
234
+ ### --- TO BE CLEANED --- <<<
170
235
 
171
- self.span_dict[span.id] = span
172
- self.active_span = span
236
+ logging.info(f"Opened span {span_id} {spankind.upper()}")
173
237
 
174
- self.llm_logger.info(f"Recorded span and setting parent_span_id: {span.id}")
175
238
  return span
176
239
 
177
- def update_span_status(self, span: CreateSpan, value: str):
178
- span.status = value
240
+ @debug(req=True)
241
+ def set_attributes(
242
+ self,
243
+ attributes: Dict[str, Any] = {},
244
+ ) -> None:
245
+ """
246
+ Set attributes for the active span.
179
247
 
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
248
+ Args:
249
+ attributes (Dict[str, Any], optional): A dictionary of attributes to set. Defaults to {}.
250
+ """
186
251
 
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"]
252
+ tracing = tracing_context.get()
253
+
254
+ if tracing.active_span is None:
255
+ logging.error(f"Cannot set attributes ({set(attributes)}), no active span")
256
+ return
197
257
 
198
- def end_span(self, outputs: Dict[str, Any]):
258
+ logging.info(
259
+ f"Setting span {tracing.active_span.id} {tracing.active_span.spankind.upper()} attributes={attributes}"
260
+ )
261
+
262
+ for key, value in attributes.items():
263
+ tracing.active_span.attributes[key] = value # type: ignore
264
+
265
+ @debug()
266
+ def set_status(self, status: str) -> None:
267
+ """
268
+ Set status for the active span.
269
+
270
+ Args:
271
+ status: Enum ( UNSET, OK, ERROR )
272
+ """
273
+ tracing = tracing_context.get()
274
+
275
+ if tracing.active_span is None:
276
+ logging.error(f"Cannot set status ({status}), no active span")
277
+ return
278
+
279
+ logging.info(
280
+ f"Setting span {tracing.active_span.id} {tracing.active_span.spankind.upper()} status={status}"
281
+ )
282
+
283
+ tracing.active_span.status = status
284
+
285
+ @debug()
286
+ def close_span(self, outputs: Dict[str, Any]) -> None:
199
287
  """
200
288
  Ends the active span, if it is a parent span, ends the trace too.
201
289
 
@@ -213,123 +301,160 @@ class Tracing(metaclass=SingletonMeta):
213
301
  None
214
302
  """
215
303
 
216
- if self.active_span is None:
217
- raise ValueError("There is no active span to end.")
304
+ tracing = tracing_context.get()
305
+
306
+ if tracing.active_span is None:
307
+ logging.error("Cannot close span, no active span")
308
+
309
+ span_id = tracing.active_span.id
310
+ spankind = tracing.active_span.spankind
311
+
312
+ logging.info(f"Closing span {span_id} {spankind}")
313
+
314
+ ### --- TO BE CLEANED --- >>>
315
+ tracing.active_span.end_time = datetime.now(timezone.utc)
218
316
 
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 [
317
+ tracing.active_span.outputs = [outputs.get("message", "")]
318
+
319
+ if tracing.active_span.spankind.upper() in [
222
320
  "LLM",
223
321
  "RETRIEVER",
224
322
  ]: # 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))
323
+ self._update_span_cost(tracing.active_span, outputs.get("cost", None))
324
+ self._update_span_tokens(tracing.active_span, outputs.get("usage", None))
227
325
 
228
- # Push span to list of recorded spans
229
- self.pending_spans.append(self.active_span)
326
+ tracing.closed_spans.append(tracing.active_span)
230
327
 
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)
328
+ active_span_parent_id = tracing.active_span.parent_span_id
237
329
 
238
330
  if active_span_parent_id is None:
239
- self.end_trace(parent_span=self.active_span)
331
+ self.end_trace(parent_span=tracing.active_span)
240
332
 
241
333
  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
334
+ parent_span = tracing.tracked_spans[active_span_parent_id]
335
+ self._update_span_cost(parent_span, tracing.active_span.cost)
336
+ self._update_span_tokens(parent_span, tracing.active_span.tokens)
337
+ tracing.active_span = parent_span
338
+ ### --- TO BE CLEANED --- <<<
246
339
 
247
- def record_exception_and_end_trace(self, span_parent_id: str):
248
- """
249
- Record an exception and end the trace.
340
+ logging.info(f"Closed span {span_id} {spankind}")
250
341
 
251
- Args:
252
- span_parent_id (str): The ID of the parent span.
342
+ ### --- Legacy API --- ###
253
343
 
254
- Returns:
255
- None
256
- """
344
+ def start_trace(
345
+ self,
346
+ span: CreateSpan,
347
+ config: Optional[Dict[str, Any]] = None,
348
+ **kwargs,
349
+ ) -> None: # Legacy
350
+ self.open_trace(span, config, **kwargs)
351
+
352
+ def end_trace(self, parent_span: CreateSpan) -> None: # Legacy
353
+ self.close_trace()
354
+
355
+ def start_span(
356
+ self,
357
+ name: str,
358
+ spankind: str,
359
+ input: Dict[str, Any],
360
+ config: Optional[Dict[str, Any]] = None,
361
+ **kwargs,
362
+ ) -> CreateSpan: # Legacy
363
+ return self.open_span(name, spankind, input, config, **kwargs)
364
+
365
+ def update_span_status(self, _: CreateSpan, status: str) -> None: # Legacy
366
+ self.update_span_status(status)
257
367
 
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)
368
+ def set_span_attribute(
369
+ self,
370
+ attributes: Dict[str, Any] = {},
371
+ ) -> None: # Legacy
372
+ self.set_attributes(attributes)
264
373
 
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)
374
+ def end_span(self, outputs: Dict[str, Any]) -> None: # Legacy
375
+ self.close_span(outputs)
268
376
 
269
- # TODO: improve exception logic here.
377
+ ### --- Helper Functions --- ###
270
378
 
271
- def end_trace(self, parent_span: CreateSpan):
379
+ def _create_trace_id(self) -> str:
380
+ """Creates a 32HEXDIGL / ObjectId ID for the trace object.
381
+
382
+ Returns:
383
+ str: stringify oid of the trace
272
384
  """
273
- Ends the active trace and sends the recorded spans for processing.
274
385
 
275
- Args:
276
- parent_span (CreateSpan): The parent span of the trace.
386
+ # return uuid4().hex
387
+ return str(ObjectId())
277
388
 
278
- Raises:
279
- RuntimeError: If there is no active trace to end.
389
+ def _clear_trace_tags(self) -> None:
390
+ tracing = tracing_context.get()
391
+
392
+ tracing.trace_tags.clear()
393
+
394
+ def _create_span_id(self) -> str:
395
+ """Creates a 16HEXDIGL / ObjectId ID for the span object.
280
396
 
281
397
  Returns:
282
- None
398
+ str: stringify oid of the span
283
399
  """
284
400
 
285
- if self.api_key == "":
286
- return
401
+ # return uuid4().hex[:16]
402
+ return str(ObjectId())
403
+
404
+ def _process_closed_spans(self) -> None:
405
+ tracing = tracing_context.get()
406
+
407
+ logging.info(f"Sending spans {tracing.trace_id} #={len(tracing.closed_spans)} ")
287
408
 
288
- if not self.active_trace_id:
289
- raise RuntimeError("No active trace to end.")
409
+ # async def mock_create_traces(trace, spans):
410
+ # print("trace-id", trace)
411
+ # print("spans", spans)
290
412
 
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
413
  self.tasks_manager.add_task(
294
- self.active_trace_id,
414
+ tracing.trace_id,
295
415
  "trace",
416
+ # mock_create_traces(
296
417
  self.client.create_traces(
297
- trace=self.active_trace_id, spans=self.pending_spans # type: ignore
418
+ trace=tracing.trace_id, spans=tracing.closed_spans # type: ignore
298
419
  ),
299
420
  self.client,
300
421
  )
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
422
 
309
- def _create_trace_id(self) -> str:
310
- """Creates a unique mongo id for the trace object.
423
+ logging.info(f"Sent spans {tracing.trace_id} #={len(tracing.closed_spans)}")
311
424
 
312
- Returns:
313
- str: stringify oid of the trace
314
- """
425
+ def _clear_closed_spans(self) -> None:
426
+ tracing = tracing_context.get()
315
427
 
316
- return str(ObjectId())
428
+ tracing.closed_spans.clear()
317
429
 
318
- def _create_span_id(self) -> str:
319
- """Creates a unique mongo id for the span object.
430
+ def _clear_tracked_spans(self) -> None:
431
+ tracing = tracing_context.get()
320
432
 
321
- Returns:
322
- str: stringify oid of the span
323
- """
433
+ tracing.tracked_spans.clear()
324
434
 
325
- return str(ObjectId())
435
+ def _clear_active_span(self) -> None:
436
+ tracing = tracing_context.get()
326
437
 
327
- def _clear_pending_spans(self) -> None:
328
- """
329
- Clear the list of recorded spans to prepare for next batch processing.
330
- """
438
+ span_id = tracing.active_span.id
331
439
 
332
- self.pending_spans = []
333
- self.llm_logger.info(
334
- f"Cleared all recorded spans from batch: {self.pending_spans}"
335
- )
440
+ tracing.active_span = None
441
+
442
+ logging.debug(f"Cleared active span {span_id}")
443
+
444
+ def _update_span_cost(self, span: CreateSpan, cost: Optional[float]) -> None:
445
+ if span is not None and cost is not None and isinstance(cost, float):
446
+ if span.cost is None:
447
+ span.cost = cost
448
+ else:
449
+ span.cost += cost
450
+
451
+ def _update_span_tokens(self, span: CreateSpan, tokens: Optional[dict]) -> None:
452
+ if isinstance(tokens, LlmTokens):
453
+ tokens = tokens.dict()
454
+ if span is not None and tokens is not None and isinstance(tokens, dict):
455
+ if span.tokens is None:
456
+ span.tokens = LlmTokens(**tokens)
457
+ else:
458
+ span.tokens.prompt_tokens += tokens["prompt_tokens"]
459
+ span.tokens.completion_tokens += tokens["completion_tokens"]
460
+ 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.5
3
+ Version: 0.19.6a0
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=xZEuuSnKm1zgG7YFpMCMXxabbgVyPqnW6am0Qu3n3TU,9958
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=_RCiT6VqOshUtFm7rZhv5mQGl5PioXPDJxb3N8EDOUY,6174
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=k8SfZ7mo2oDUijne00lGkHh1AryzWydZEefT4RtnnWU,13690
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.5.dist-info/METADATA,sha256=q7UnPP-EBCpAOWpWvmGkbok-BvnoQq496WHooLfsHMo,26460
163
- agenta-0.19.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
164
- agenta-0.19.5.dist-info/entry_points.txt,sha256=PDiu8_8AsL7ibU9v4iNoOKR1S7F2rdxjlEprjM9QOgo,46
165
- agenta-0.19.5.dist-info/RECORD,,
164
+ agenta-0.19.6a0.dist-info/METADATA,sha256=0uvnsMgJUeCyZC51VbA-_C67WR4gpzEYNfKQ5onan1E,26462
165
+ agenta-0.19.6a0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
166
+ agenta-0.19.6a0.dist-info/entry_points.txt,sha256=PDiu8_8AsL7ibU9v4iNoOKR1S7F2rdxjlEprjM9QOgo,46
167
+ agenta-0.19.6a0.dist-info/RECORD,,