agenta 0.27.0a1__py3-none-any.whl → 0.27.0a5__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.

@@ -1,289 +1,241 @@
1
- import inspect
2
- import traceback
1
+ from typing import Callable, Optional, Any, Dict, List, Union
3
2
  from functools import wraps
4
3
  from itertools import chain
5
- from typing import Callable, Optional, Any, Dict, List
6
-
7
- import agenta as ag
8
-
4
+ from inspect import iscoroutinefunction, getfullargspec
9
5
 
10
6
  from agenta.sdk.utils.exceptions import suppress
11
-
12
7
  from agenta.sdk.context.tracing import tracing_context
13
8
  from agenta.sdk.tracing.conventions import parse_span_kind
14
9
 
10
+ import agenta as ag
11
+
15
12
 
16
13
  class instrument:
17
14
  DEFAULT_KEY = "__default__"
18
15
 
19
16
  def __init__(
20
17
  self,
21
- kind: str = "task",
18
+ type: str = "task",
22
19
  config: Optional[Dict[str, Any]] = None,
23
20
  ignore_inputs: Optional[bool] = None,
24
21
  ignore_outputs: Optional[bool] = None,
25
22
  max_depth: Optional[int] = 2,
26
- # DEPRECATED
23
+ # DEPRECATING
24
+ kind: str = "task",
27
25
  spankind: Optional[str] = "TASK",
28
26
  ) -> None:
29
- self.kind = spankind if spankind is not None else kind
27
+ self.type = spankind or kind or type
28
+ self.kind = None
30
29
  self.config = config
31
30
  self.ignore_inputs = ignore_inputs
32
31
  self.ignore_outputs = ignore_outputs
33
32
  self.max_depth = max_depth
34
33
 
35
34
  def __call__(self, func: Callable[..., Any]):
36
- is_coroutine_function = inspect.iscoroutinefunction(func)
37
-
38
- def parse(*args, **kwargs) -> Dict[str, Any]:
39
- inputs = {
40
- key: value
41
- for key, value in chain(
42
- zip(inspect.getfullargspec(func).args, args),
43
- kwargs.items(),
44
- )
45
- }
46
-
47
- return inputs
48
-
49
- def redact(
50
- io: Dict[str, Any], ignore: List[str] | bool = False
51
- ) -> Dict[str, Any]:
52
- """
53
- Redact user-defined sensitive information from inputs and outputs as defined by the ignore list or boolean flag.
54
-
55
- Example:
56
- - ignore = ["password"] -> {"username": "admin", "password": "********"} -> {"username": "admin"}
57
- - ignore = True -> {"username": "admin", "password": "********"} -> {}
58
- - ignore = False -> {"username": "admin", "password": "********"} -> {"username": "admin", "password": "********"}
59
- """
60
- io = {
61
- key: value
62
- for key, value in io.items()
63
- if key
64
- not in (
65
- ignore
66
- if isinstance(ignore, list)
67
- else io.keys()
68
- if ignore is True
69
- else []
70
- )
71
- }
72
-
73
- return io
74
-
75
- def patch(result: Any) -> Dict[str, Any]:
76
- """
77
- Patch the result to ensure that it is a dictionary, with a default key when necessary.
78
-
79
- Example:
80
- - result = "Hello, World!" -> {"__default__": "Hello, World!"}
81
- - result = {"message": "Hello, World!", "cost": 0.0, "usage": {}} -> {"__default__": "Hello, World!"}
82
- - result = {"message": "Hello, World!"} -> {"message": "Hello, World!"}
83
- """
84
- outputs = (
85
- {instrument.DEFAULT_KEY: result}
86
- if not isinstance(result, dict)
87
- else (
88
- {instrument.DEFAULT_KEY: result["message"]}
89
- if all(key in result for key in ["message", "cost", "usage"])
90
- else result
91
- )
92
- )
93
-
94
- return outputs
35
+ is_coroutine_function = iscoroutinefunction(func)
95
36
 
96
37
  @wraps(func)
97
38
  async def async_wrapper(*args, **kwargs):
98
- async def wrapped_func(*args, **kwargs):
99
- if not ag.tracing.get_current_span().is_recording():
100
- self.kind = "workflow"
101
-
102
- kind = parse_span_kind(self.kind)
103
-
104
- with ag.tracer.start_as_current_span(func.__name__, kind=kind):
105
- span = ag.tracing.get_current_span()
106
-
107
- with suppress():
108
- span.set_attributes(
109
- attributes={"node": self.kind},
110
- namespace="type",
111
- )
112
-
113
- if span.parent is None:
114
- rctx = tracing_context.get()
115
-
116
- span.set_attributes(
117
- attributes={"configuration": rctx.get("config", {})},
118
- namespace="meta",
119
- )
120
- span.set_attributes(
121
- attributes={"environment": rctx.get("environment", {})},
122
- namespace="meta",
123
- )
124
- span.set_attributes(
125
- attributes={"version": rctx.get("version", {})},
126
- namespace="meta",
127
- )
128
- span.set_attributes(
129
- attributes={"variant": rctx.get("variant", {})},
130
- namespace="meta",
131
- )
132
-
133
- _inputs = redact(parse(*args, **kwargs), self.ignore_inputs)
134
- span.set_attributes(
135
- attributes={"inputs": _inputs},
136
- namespace="data",
137
- max_depth=self.max_depth,
138
- )
39
+ async def _async_auto_instrumented(*args, **kwargs):
40
+ self._parse_type_and_kind()
41
+
42
+ with ag.tracer.start_as_current_span(func.__name__, kind=self.kind):
43
+ self._pre_instrument(self, func, *args, **kwargs)
139
44
 
140
45
  result = await func(*args, **kwargs)
141
46
 
142
- with suppress():
143
- cost = None
144
- usage = {}
145
-
146
- if isinstance(result, dict):
147
- cost = result.get("cost", None)
148
- usage = result.get("usage", {})
149
-
150
- if isinstance(usage, (int, float)):
151
- usage = {"total_tokens": usage}
152
-
153
- span.set_attributes(
154
- attributes={"total": cost},
155
- namespace="metrics.unit.costs",
156
- )
157
- span.set_attributes(
158
- attributes=(
159
- {
160
- "prompt": usage.get("prompt_tokens", None),
161
- "completion": usage.get("completion_tokens", None),
162
- "total": usage.get("total_tokens", None),
163
- }
164
- ),
165
- namespace="metrics.unit.tokens",
166
- )
167
-
168
- _outputs = redact(patch(result), self.ignore_outputs)
169
- span.set_attributes(
170
- attributes={"outputs": _outputs},
171
- namespace="data",
172
- max_depth=self.max_depth,
173
- )
174
-
175
- span.set_status("OK")
176
-
177
- with suppress():
178
- if hasattr(span, "parent") and span.parent is None:
179
- tracing_context.set(
180
- tracing_context.get()
181
- | {
182
- "root": {
183
- "trace_id": span.get_span_context().trace_id,
184
- "span_id": span.get_span_context().span_id,
185
- }
186
- }
187
- )
47
+ self._post_instrument(result)
188
48
 
189
49
  return result
190
50
 
191
- return await wrapped_func(*args, **kwargs)
51
+ return await _async_auto_instrumented(*args, **kwargs)
192
52
 
193
53
  @wraps(func)
194
54
  def sync_wrapper(*args, **kwargs):
195
- def wrapped_func(*args, **kwargs):
196
- if not ag.tracing.get_current_span().is_recording():
197
- self.kind = "workflow"
198
-
199
- kind = parse_span_kind(self.kind)
200
-
201
- with ag.tracer.start_as_current_span(func.__name__, kind=kind):
202
- span = ag.tracing.get_current_span()
203
-
204
- with suppress():
205
- span.set_attributes(
206
- attributes={"node": self.kind},
207
- namespace="type",
208
- )
209
-
210
- if span.parent is None:
211
- rctx = tracing_context.get()
212
-
213
- span.set_attributes(
214
- attributes={"configuration": rctx.get("config", {})},
215
- namespace="meta",
216
- )
217
- span.set_attributes(
218
- attributes={"environment": rctx.get("environment", {})},
219
- namespace="meta",
220
- )
221
- span.set_attributes(
222
- attributes={"version": rctx.get("version", {})},
223
- namespace="meta",
224
- )
225
- span.set_attributes(
226
- attributes={"variant": rctx.get("variant", {})},
227
- namespace="meta",
228
- )
229
-
230
- _inputs = redact(parse(*args, **kwargs), self.ignore_inputs)
231
- span.set_attributes(
232
- attributes={"inputs": _inputs},
233
- namespace="data",
234
- max_depth=self.max_depth,
235
- )
55
+ def _sync_auto_instrumented(*args, **kwargs):
56
+ self._parse_type_and_kind()
57
+
58
+ with ag.tracer.start_as_current_span(func.__name__, kind=self.kind):
59
+ self._pre_instrument(self, func, *args, **kwargs)
236
60
 
237
61
  result = func(*args, **kwargs)
238
62
 
239
- with suppress():
240
- cost = None
241
- usage = {}
242
- if isinstance(result, dict):
243
- cost = result.get("cost", None)
244
- usage = result.get("usage", {})
245
-
246
- if isinstance(usage, (int, float)):
247
- usage = {"total_tokens": usage}
248
-
249
- span.set_attributes(
250
- attributes={"total": cost},
251
- namespace="metrics.unit.costs",
252
- )
253
- span.set_attributes(
254
- attributes=(
255
- {
256
- "prompt": usage.get("prompt_tokens", None),
257
- "completion": usage.get("completion_tokens", None),
258
- "total": usage.get("total_tokens", None),
259
- }
260
- ),
261
- namespace="metrics.unit.tokens",
262
- )
263
-
264
- _outputs = redact(patch(result), self.ignore_outputs)
265
- span.set_attributes(
266
- attributes={"outputs": _outputs},
267
- namespace="data",
268
- max_depth=self.max_depth,
269
- )
270
-
271
- span.set_status("OK")
272
-
273
- with suppress():
274
- if hasattr(span, "parent") and span.parent is None:
275
- tracing_context.set(
276
- tracing_context.get()
277
- | {
278
- "root": {
279
- "trace_id": span.get_span_context().trace_id,
280
- "span_id": span.get_span_context().span_id,
281
- }
282
- }
283
- )
63
+ self._post_instrument(result)
284
64
 
285
65
  return result
286
66
 
287
- return wrapped_func(*args, **kwargs)
67
+ return _sync_auto_instrumented(*args, **kwargs)
288
68
 
289
69
  return async_wrapper if is_coroutine_function else sync_wrapper
70
+
71
+ def _parse_type_and_kind(self):
72
+ if not ag.tracing.get_current_span().is_recording():
73
+ self.type = "workflow"
74
+
75
+ self.kind = parse_span_kind(self.type)
76
+
77
+ def _pre_instrument(
78
+ self,
79
+ func,
80
+ *args,
81
+ **kwargs,
82
+ ):
83
+ span = ag.tracing.get_current_span()
84
+
85
+ with suppress():
86
+ span.set_attributes(
87
+ attributes={"node": self.type},
88
+ namespace="type",
89
+ )
90
+
91
+ if span.parent is None:
92
+ rctx = tracing_context.get()
93
+
94
+ span.set_attributes(
95
+ attributes={"configuration": rctx.get("config", {})},
96
+ namespace="meta",
97
+ )
98
+ span.set_attributes(
99
+ attributes={"environment": rctx.get("environment", {})},
100
+ namespace="meta",
101
+ )
102
+ span.set_attributes(
103
+ attributes={"version": rctx.get("version", {})},
104
+ namespace="meta",
105
+ )
106
+ span.set_attributes(
107
+ attributes={"variant": rctx.get("variant", {})},
108
+ namespace="meta",
109
+ )
110
+
111
+ _inputs = self._redact(
112
+ self._parse(
113
+ func,
114
+ *args,
115
+ **kwargs,
116
+ ),
117
+ self.ignore_inputs,
118
+ )
119
+ span.set_attributes(
120
+ attributes={"inputs": _inputs},
121
+ namespace="data",
122
+ max_depth=self.max_depth,
123
+ )
124
+
125
+ def _post_instrument(
126
+ self,
127
+ result,
128
+ ):
129
+ span = ag.tracing.get_current_span()
130
+ with suppress():
131
+ cost = None
132
+ usage = {}
133
+
134
+ if isinstance(result, dict):
135
+ cost = result.get("cost", None)
136
+ usage = result.get("usage", {})
137
+
138
+ if isinstance(usage, (int, float)):
139
+ usage = {"total_tokens": usage}
140
+
141
+ span.set_attributes(
142
+ attributes={"total": cost},
143
+ namespace="metrics.unit.costs",
144
+ )
145
+ span.set_attributes(
146
+ attributes=(
147
+ {
148
+ "prompt": usage.get("prompt_tokens", None),
149
+ "completion": usage.get("completion_tokens", None),
150
+ "total": usage.get("total_tokens", None),
151
+ }
152
+ ),
153
+ namespace="metrics.unit.tokens",
154
+ )
155
+
156
+ _outputs = self._redact(self._patch(result), self.ignore_outputs)
157
+ span.set_attributes(
158
+ attributes={"outputs": _outputs},
159
+ namespace="data",
160
+ max_depth=self.max_depth,
161
+ )
162
+
163
+ span.set_status("OK")
164
+
165
+ with suppress():
166
+ if hasattr(span, "parent") and span.parent is None:
167
+ tracing_context.set(
168
+ tracing_context.get()
169
+ | {
170
+ "root": {
171
+ "trace_id": span.get_span_context().trace_id,
172
+ "span_id": span.get_span_context().span_id,
173
+ }
174
+ }
175
+ )
176
+
177
+ def _parse(
178
+ self,
179
+ func,
180
+ *args,
181
+ **kwargs,
182
+ ) -> Dict[str, Any]:
183
+ inputs = {
184
+ key: value
185
+ for key, value in chain(
186
+ zip(getfullargspec(func).args, args),
187
+ kwargs.items(),
188
+ )
189
+ }
190
+
191
+ return inputs
192
+
193
+ def _redact(
194
+ self,
195
+ io: Dict[str, Any],
196
+ ignore: Union[List[str], bool] = False,
197
+ ) -> Dict[str, Any]:
198
+ """
199
+ Redact user-defined sensitive information from inputs and outputs as defined by the ignore list or boolean flag.
200
+
201
+ Example:
202
+ - ignore = ["password"] -> {"username": "admin", "password": "********"} -> {"username": "admin"}
203
+ - ignore = True -> {"username": "admin", "password": "********"} -> {}
204
+ - ignore = False -> {"username": "admin", "password": "********"} -> {"username": "admin", "password": "********"}
205
+ """
206
+ io = {
207
+ key: value
208
+ for key, value in io.items()
209
+ if key
210
+ not in (
211
+ ignore
212
+ if isinstance(ignore, list)
213
+ else io.keys() if ignore is True else []
214
+ )
215
+ }
216
+
217
+ return io
218
+
219
+ def _patch(
220
+ self,
221
+ result: Any,
222
+ ) -> Dict[str, Any]:
223
+ """
224
+ Patch the result to ensure that it is a dictionary, with a default key when necessary.
225
+
226
+ Example:
227
+ - result = "Hello, World!" -> {"__default__": "Hello, World!"}
228
+ - result = {"message": "Hello, World!", "cost": 0.0, "usage": {}} -> {"__default__": "Hello, World!"}
229
+ - result = {"message": "Hello, World!"} -> {"message": "Hello, World!"}
230
+ """
231
+ outputs = (
232
+ {instrument.DEFAULT_KEY: result}
233
+ if not isinstance(result, dict)
234
+ else (
235
+ {instrument.DEFAULT_KEY: result["message"]}
236
+ if all(key in result for key in ["message", "cost", "usage"])
237
+ else result
238
+ )
239
+ )
240
+
241
+ return outputs
@@ -93,14 +93,19 @@ def _marshal(
93
93
  return marshalled
94
94
 
95
95
 
96
- def _encode_key(namespace: Optional[str] = None, key: str = "") -> str:
96
+ def _encode_key(
97
+ namespace: Optional[str] = None,
98
+ key: str = "",
99
+ ) -> str:
97
100
  if namespace is None:
98
101
  return key
99
102
 
100
103
  return f"ag.{namespace}.{key}"
101
104
 
102
105
 
103
- def _encode_value(value: Any) -> Optional[Attribute]:
106
+ def _encode_value(
107
+ value: Any,
108
+ ) -> Optional[Attribute]:
104
109
  if value is None:
105
110
  return None
106
111
 
@@ -15,7 +15,10 @@ def tracing_context_manager():
15
15
  try:
16
16
  yield
17
17
  except Exception as e:
18
- log.error(f"Error with tracing context: {_tracing_context}")
19
- log.error(f"Exception: {format_exc()}")
18
+ log.error("----------------------------------------------")
19
+ log.error("Agenta SDK - handling tracing exception below:")
20
+ log.error("----------------------------------------------")
21
+ log.error(format_exc().strip("\n"))
22
+ log.error("----------------------------------------------")
20
23
  finally:
21
24
  tracing_context.reset(token)
@@ -1,14 +1,7 @@
1
- from opentelemetry.trace import SpanKind
2
-
3
1
  from enum import Enum
4
-
5
2
  from re import fullmatch
6
3
 
7
- _PATTERN = r"[A-Za-z0-9._-]+"
8
-
9
-
10
- def is_valid_attribute_key(string):
11
- return bool(fullmatch(_PATTERN, string))
4
+ from opentelemetry.trace import SpanKind
12
5
 
13
6
 
14
7
  class Reference(str, Enum):
@@ -26,6 +19,15 @@ class Reference(str, Enum):
26
19
  #
27
20
 
28
21
 
22
+ _PATTERN = r"[A-Za-z0-9._-]+"
23
+
24
+
25
+ def is_valid_attribute_key(
26
+ string: str,
27
+ ):
28
+ return bool(fullmatch(_PATTERN, string))
29
+
30
+
29
31
  def parse_span_kind(type: str) -> SpanKind:
30
32
  kind = SpanKind.INTERNAL
31
33
  if type in [
@@ -1,14 +1,12 @@
1
1
  from typing import Sequence, Dict, List
2
2
 
3
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
3
4
  from opentelemetry.sdk.trace.export import (
4
5
  ConsoleSpanExporter,
5
6
  SpanExporter,
6
7
  SpanExportResult,
7
8
  ReadableSpan,
8
9
  )
9
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
10
- OTLPSpanExporter,
11
- )
12
10
 
13
11
  from agenta.sdk.utils.exceptions import suppress
14
12
 
@@ -40,7 +38,10 @@ class InlineTraceExporter(SpanExporter):
40
38
  def force_flush(self, timeout_millis: int = 30000) -> bool:
41
39
  return True
42
40
 
43
- def fetch(self, trace_id: int) -> List[ReadableSpan]:
41
+ def fetch(
42
+ self,
43
+ trace_id: int,
44
+ ) -> List[ReadableSpan]:
44
45
  trace = self._registry.get(trace_id, [])
45
46
 
46
47
  del self._registry[trace_id]
@@ -50,7 +51,6 @@ class InlineTraceExporter(SpanExporter):
50
51
 
51
52
  OTLPSpanExporter._MAX_RETRY_TIMEOUT = 2
52
53
 
53
-
54
54
  ConsoleExporter = ConsoleSpanExporter
55
55
  InlineExporter = InlineTraceExporter
56
56
  OTLPExporter = OTLPSpanExporter