trodo-python 1.2.0__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trodo/__init__.py +300 -47
- trodo/api/endpoints.py +5 -1
- trodo/api/http_client.py +30 -2
- trodo/client.py +193 -112
- trodo/otel/__init__.py +21 -0
- trodo/otel/auto_instrument.py +281 -0
- trodo/otel/context.py +44 -0
- trodo/otel/helpers.py +407 -0
- trodo/otel/processor.py +165 -0
- trodo/otel/wrap_agent.py +438 -0
- trodo/types.py +12 -68
- {trodo_python-1.2.0.dist-info → trodo_python-2.1.0.dist-info}/METADATA +155 -3
- {trodo_python-1.2.0.dist-info → trodo_python-2.1.0.dist-info}/RECORD +15 -9
- {trodo_python-1.2.0.dist-info → trodo_python-2.1.0.dist-info}/WHEEL +0 -0
- {trodo_python-1.2.0.dist-info → trodo_python-2.1.0.dist-info}/top_level.txt +0 -0
trodo/otel/wrap_agent.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""wrap_agent + join_run + span — user-facing surface for agent tracking.
|
|
2
|
+
|
|
3
|
+
Users never generate IDs. The SDK creates run_id on wrap_agent and threads
|
|
4
|
+
span_id through contextvars. Any nested ``span()`` call (or OTel
|
|
5
|
+
auto-instrumented span from Anthropic/OpenAI/LangChain/etc.) inherits the
|
|
6
|
+
current span as its parent.
|
|
7
|
+
|
|
8
|
+
Usage (root service):
|
|
9
|
+
with trodo.wrap_agent('customer-support',
|
|
10
|
+
distinct_id=uid, conversation_id=conv) as run:
|
|
11
|
+
result = agent.run(query)
|
|
12
|
+
run.set_output(result)
|
|
13
|
+
|
|
14
|
+
Usage (downstream microservice — becomes a SPAN on the caller's run):
|
|
15
|
+
with trodo.join_run(run_id=headers['X-Trodo-Run-Id'],
|
|
16
|
+
parent_span_id=headers['X-Trodo-Parent-Span-Id']):
|
|
17
|
+
# LLM calls here are auto-captured as spans under the caller's run
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
Decorator form:
|
|
21
|
+
@trodo.agent('nightly-report')
|
|
22
|
+
def nightly_report(): ...
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import time
|
|
29
|
+
import uuid
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
from typing import Any, Callable, Dict, Optional
|
|
32
|
+
|
|
33
|
+
from .context import ActiveSpanContext, get_active_context, run_with_context
|
|
34
|
+
from .processor import TrodoSpanProcessor, TrodoRun, TrodoSpan
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _now_iso() -> str:
|
|
38
|
+
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _truncate(value: Any, max_len: int = 64_000) -> Optional[str]:
|
|
42
|
+
if value is None:
|
|
43
|
+
return None
|
|
44
|
+
if isinstance(value, str):
|
|
45
|
+
s = value
|
|
46
|
+
else:
|
|
47
|
+
try:
|
|
48
|
+
s = json.dumps(value, default=str)
|
|
49
|
+
except Exception:
|
|
50
|
+
s = str(value)
|
|
51
|
+
return s[:max_len] if len(s) > max_len else s
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def current_run_id() -> Optional[str]:
|
|
55
|
+
"""Return the run_id of the currently active agent run, if any."""
|
|
56
|
+
ctx = get_active_context()
|
|
57
|
+
return ctx.run_id if ctx else None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def current_span_id() -> Optional[str]:
|
|
61
|
+
"""Return the span_id of the currently active span, if any."""
|
|
62
|
+
ctx = get_active_context()
|
|
63
|
+
return ctx.span_id if ctx else None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _aggregate(spans: list[TrodoSpan]) -> Dict[str, Any]:
|
|
67
|
+
total_in = 0
|
|
68
|
+
total_out = 0
|
|
69
|
+
total_cost = 0.0
|
|
70
|
+
tool_count = 0
|
|
71
|
+
error_count = 0
|
|
72
|
+
for s in spans:
|
|
73
|
+
if s.input_tokens:
|
|
74
|
+
total_in += int(s.input_tokens)
|
|
75
|
+
if s.output_tokens:
|
|
76
|
+
total_out += int(s.output_tokens)
|
|
77
|
+
if s.cost:
|
|
78
|
+
total_cost += float(s.cost)
|
|
79
|
+
if s.kind == "tool":
|
|
80
|
+
tool_count += 1
|
|
81
|
+
if s.status == "error":
|
|
82
|
+
error_count += 1
|
|
83
|
+
return {
|
|
84
|
+
"total_tokens_in": total_in,
|
|
85
|
+
"total_tokens_out": total_out,
|
|
86
|
+
"total_cost": round(total_cost, 8) if total_cost else 0.0,
|
|
87
|
+
"span_count": len(spans),
|
|
88
|
+
"tool_count": tool_count,
|
|
89
|
+
"error_count": error_count,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class RunHandle:
|
|
94
|
+
"""Handle returned by wrap_agent for setting input/output and getting run_id."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, run_id: str, agent_name: str) -> None:
|
|
97
|
+
self.run_id = run_id
|
|
98
|
+
self.agent_name = agent_name
|
|
99
|
+
self.input: Optional[str] = None
|
|
100
|
+
self.output: Optional[str] = None
|
|
101
|
+
self.metadata: Dict[str, Any] = {}
|
|
102
|
+
|
|
103
|
+
def set_input(self, value: Any) -> None:
|
|
104
|
+
self.input = _truncate(value)
|
|
105
|
+
|
|
106
|
+
def set_output(self, value: Any) -> None:
|
|
107
|
+
self.output = _truncate(value)
|
|
108
|
+
|
|
109
|
+
def set_metadata(self, **kwargs: Any) -> None:
|
|
110
|
+
self.metadata.update(kwargs)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SpanHandle:
|
|
114
|
+
"""Handle returned by span context manager for setting output/attrs."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, span_id: str, name: str) -> None:
|
|
117
|
+
self.span_id = span_id
|
|
118
|
+
self.name = name
|
|
119
|
+
self.input: Optional[str] = None
|
|
120
|
+
self.output: Optional[str] = None
|
|
121
|
+
self.attributes: Dict[str, Any] = {}
|
|
122
|
+
self.model: Optional[str] = None
|
|
123
|
+
self.provider: Optional[str] = None
|
|
124
|
+
self.input_tokens: Optional[int] = None
|
|
125
|
+
self.output_tokens: Optional[int] = None
|
|
126
|
+
self.cost: Optional[float] = None
|
|
127
|
+
self.temperature: Optional[float] = None
|
|
128
|
+
self.tool_name: Optional[str] = None
|
|
129
|
+
|
|
130
|
+
def set_input(self, value: Any) -> None:
|
|
131
|
+
self.input = _truncate(value)
|
|
132
|
+
|
|
133
|
+
def set_output(self, value: Any) -> None:
|
|
134
|
+
self.output = _truncate(value)
|
|
135
|
+
|
|
136
|
+
def set_attribute(self, key: str, value: Any) -> None:
|
|
137
|
+
self.attributes[key] = value
|
|
138
|
+
|
|
139
|
+
def set_llm(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
model: Optional[str] = None,
|
|
143
|
+
provider: Optional[str] = None,
|
|
144
|
+
input_tokens: Optional[int] = None,
|
|
145
|
+
output_tokens: Optional[int] = None,
|
|
146
|
+
cost: Optional[float] = None,
|
|
147
|
+
temperature: Optional[float] = None,
|
|
148
|
+
) -> None:
|
|
149
|
+
if model is not None:
|
|
150
|
+
self.model = model
|
|
151
|
+
if provider is not None:
|
|
152
|
+
self.provider = provider
|
|
153
|
+
if input_tokens is not None:
|
|
154
|
+
self.input_tokens = int(input_tokens)
|
|
155
|
+
if output_tokens is not None:
|
|
156
|
+
self.output_tokens = int(output_tokens)
|
|
157
|
+
if cost is not None:
|
|
158
|
+
self.cost = float(cost)
|
|
159
|
+
if temperature is not None:
|
|
160
|
+
self.temperature = float(temperature)
|
|
161
|
+
|
|
162
|
+
def set_tool(self, tool_name: str) -> None:
|
|
163
|
+
self.tool_name = tool_name
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class wrap_agent:
|
|
167
|
+
"""Context manager wrapping an agent run.
|
|
168
|
+
|
|
169
|
+
Every span created inside the ``with`` block (whether by the user's
|
|
170
|
+
manual ``span()`` calls or by OTel auto-instrumentation of the LLM SDK)
|
|
171
|
+
is automatically parented to this run via contextvars.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
processor: TrodoSpanProcessor,
|
|
177
|
+
team_site_id: str,
|
|
178
|
+
agent_name: str,
|
|
179
|
+
distinct_id: Optional[str] = None,
|
|
180
|
+
conversation_id: Optional[str] = None,
|
|
181
|
+
parent_run_id: Optional[str] = None,
|
|
182
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
183
|
+
) -> None:
|
|
184
|
+
self._processor = processor
|
|
185
|
+
self._team_site_id = team_site_id
|
|
186
|
+
self._agent_name = agent_name
|
|
187
|
+
self._distinct_id = distinct_id
|
|
188
|
+
self._conversation_id = conversation_id
|
|
189
|
+
self._parent_run_id = parent_run_id
|
|
190
|
+
self._metadata = metadata
|
|
191
|
+
self._ctx_mgr: Optional[run_with_context] = None
|
|
192
|
+
self._started_ms: float = 0.0
|
|
193
|
+
self._started_iso: str = ""
|
|
194
|
+
self.handle: Optional[RunHandle] = None
|
|
195
|
+
|
|
196
|
+
def __enter__(self) -> RunHandle:
|
|
197
|
+
run_id = str(uuid.uuid4())
|
|
198
|
+
root_span_id = str(uuid.uuid4())
|
|
199
|
+
self._started_iso = _now_iso()
|
|
200
|
+
self._started_ms = time.time() * 1000.0
|
|
201
|
+
|
|
202
|
+
self.handle = RunHandle(run_id, self._agent_name)
|
|
203
|
+
ctx = ActiveSpanContext(
|
|
204
|
+
run_id=run_id,
|
|
205
|
+
span_id=root_span_id,
|
|
206
|
+
parent_span_id=None,
|
|
207
|
+
team_site_id=self._team_site_id,
|
|
208
|
+
processor=self._processor,
|
|
209
|
+
)
|
|
210
|
+
self._ctx_mgr = run_with_context(ctx)
|
|
211
|
+
self._ctx_mgr.__enter__()
|
|
212
|
+
return self.handle
|
|
213
|
+
|
|
214
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
215
|
+
assert self.handle is not None
|
|
216
|
+
ended_iso = _now_iso()
|
|
217
|
+
duration_ms = int(time.time() * 1000.0 - self._started_ms)
|
|
218
|
+
status = "error" if exc is not None else "ok"
|
|
219
|
+
error_summary = None
|
|
220
|
+
if exc is not None:
|
|
221
|
+
error_summary = _truncate(str(exc), 4_000)
|
|
222
|
+
|
|
223
|
+
pending = self._processor.get_pending(self.handle.run_id)
|
|
224
|
+
agg = _aggregate(pending)
|
|
225
|
+
|
|
226
|
+
run = TrodoRun(
|
|
227
|
+
run_id=self.handle.run_id,
|
|
228
|
+
agent_name=self._agent_name,
|
|
229
|
+
distinct_id=self._distinct_id,
|
|
230
|
+
conversation_id=self._conversation_id,
|
|
231
|
+
parent_run_id=self._parent_run_id,
|
|
232
|
+
status=status,
|
|
233
|
+
input=self.handle.input,
|
|
234
|
+
output=self.handle.output,
|
|
235
|
+
started_at=self._started_iso,
|
|
236
|
+
ended_at=ended_iso,
|
|
237
|
+
duration_ms=duration_ms,
|
|
238
|
+
error_summary=error_summary,
|
|
239
|
+
metadata={**(self._metadata or {}), **self.handle.metadata} or None,
|
|
240
|
+
total_tokens_in=agg["total_tokens_in"],
|
|
241
|
+
total_tokens_out=agg["total_tokens_out"],
|
|
242
|
+
total_cost=agg["total_cost"],
|
|
243
|
+
span_count=agg["span_count"],
|
|
244
|
+
tool_count=agg["tool_count"],
|
|
245
|
+
error_count=agg["error_count"],
|
|
246
|
+
)
|
|
247
|
+
try:
|
|
248
|
+
self._processor.ingest_run(run)
|
|
249
|
+
finally:
|
|
250
|
+
if self._ctx_mgr is not None:
|
|
251
|
+
self._ctx_mgr.__exit__(exc_type, exc, tb)
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class join_run:
|
|
256
|
+
"""Join an existing agent run owned by a remote service.
|
|
257
|
+
|
|
258
|
+
Opens a new SPAN (not a run) in the context of the caller's run_id.
|
|
259
|
+
Every span produced inside the ``with`` block — including OTel
|
|
260
|
+
auto-instrumented LLM spans — is parented to ``parent_span_id`` and
|
|
261
|
+
flushed incrementally via ``append_spans`` (the remote service owns
|
|
262
|
+
run lifecycle; we never call ``ingest_run`` here).
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
def __init__(
|
|
266
|
+
self,
|
|
267
|
+
processor: TrodoSpanProcessor,
|
|
268
|
+
team_site_id: str,
|
|
269
|
+
run_id: str,
|
|
270
|
+
parent_span_id: Optional[str] = None,
|
|
271
|
+
name: str = "remote.handler",
|
|
272
|
+
kind: str = "agent",
|
|
273
|
+
input: Any = None,
|
|
274
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
275
|
+
) -> None:
|
|
276
|
+
self._processor = processor
|
|
277
|
+
self._team_site_id = team_site_id
|
|
278
|
+
self._run_id = run_id
|
|
279
|
+
self._parent_span_id = parent_span_id
|
|
280
|
+
self._name = name
|
|
281
|
+
self._kind = kind
|
|
282
|
+
self._input = _truncate(input) if input is not None else None
|
|
283
|
+
self._attributes = attributes
|
|
284
|
+
self._ctx_mgr: Optional[run_with_context] = None
|
|
285
|
+
self._started_ms: float = 0.0
|
|
286
|
+
self._started_iso: str = ""
|
|
287
|
+
self._span_id: str = ""
|
|
288
|
+
self.handle: Optional[SpanHandle] = None
|
|
289
|
+
|
|
290
|
+
def __enter__(self) -> SpanHandle:
|
|
291
|
+
self._span_id = str(uuid.uuid4())
|
|
292
|
+
self._started_iso = _now_iso()
|
|
293
|
+
self._started_ms = time.time() * 1000.0
|
|
294
|
+
self.handle = SpanHandle(self._span_id, self._name)
|
|
295
|
+
if self._input is not None:
|
|
296
|
+
self.handle.input = self._input
|
|
297
|
+
if self._attributes:
|
|
298
|
+
self.handle.attributes.update(self._attributes)
|
|
299
|
+
|
|
300
|
+
self._processor.mark_joined(self._run_id)
|
|
301
|
+
ctx = ActiveSpanContext(
|
|
302
|
+
run_id=self._run_id,
|
|
303
|
+
span_id=self._span_id,
|
|
304
|
+
parent_span_id=self._parent_span_id,
|
|
305
|
+
team_site_id=self._team_site_id,
|
|
306
|
+
processor=self._processor,
|
|
307
|
+
)
|
|
308
|
+
self._ctx_mgr = run_with_context(ctx)
|
|
309
|
+
self._ctx_mgr.__enter__()
|
|
310
|
+
return self.handle
|
|
311
|
+
|
|
312
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
313
|
+
if self.handle is None:
|
|
314
|
+
return None
|
|
315
|
+
ended_iso = _now_iso()
|
|
316
|
+
duration_ms = int(time.time() * 1000.0 - self._started_ms)
|
|
317
|
+
status = "error" if exc is not None else "ok"
|
|
318
|
+
error_type = exc_type.__name__ if exc_type else None
|
|
319
|
+
error_message = _truncate(str(exc), 4_000) if exc else None
|
|
320
|
+
|
|
321
|
+
trodo_span = TrodoSpan(
|
|
322
|
+
span_id=self._span_id,
|
|
323
|
+
run_id=self._run_id,
|
|
324
|
+
parent_span_id=self._parent_span_id,
|
|
325
|
+
kind=self._kind,
|
|
326
|
+
name=self._name,
|
|
327
|
+
status=status,
|
|
328
|
+
started_at=self._started_iso,
|
|
329
|
+
ended_at=ended_iso,
|
|
330
|
+
duration_ms=duration_ms,
|
|
331
|
+
input=self.handle.input,
|
|
332
|
+
output=self.handle.output,
|
|
333
|
+
error_type=error_type,
|
|
334
|
+
error_message=error_message,
|
|
335
|
+
model=self.handle.model,
|
|
336
|
+
provider=self.handle.provider,
|
|
337
|
+
input_tokens=self.handle.input_tokens,
|
|
338
|
+
output_tokens=self.handle.output_tokens,
|
|
339
|
+
cost=self.handle.cost,
|
|
340
|
+
temperature=self.handle.temperature,
|
|
341
|
+
tool_name=self.handle.tool_name,
|
|
342
|
+
attributes=self.handle.attributes or None,
|
|
343
|
+
)
|
|
344
|
+
try:
|
|
345
|
+
self._processor.append_spans(self._run_id, [trodo_span])
|
|
346
|
+
finally:
|
|
347
|
+
self._processor.unmark_joined(self._run_id)
|
|
348
|
+
if self._ctx_mgr is not None:
|
|
349
|
+
self._ctx_mgr.__exit__(exc_type, exc, tb)
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class span:
|
|
354
|
+
"""Context manager for a nested span under the active run.
|
|
355
|
+
|
|
356
|
+
Outside of a wrap_agent / join_run this is a no-op that still runs the
|
|
357
|
+
inner code.
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
def __init__(
|
|
361
|
+
self,
|
|
362
|
+
name: str,
|
|
363
|
+
kind: str = "generic",
|
|
364
|
+
input: Any = None,
|
|
365
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
366
|
+
) -> None:
|
|
367
|
+
self._name = name
|
|
368
|
+
self._kind = kind
|
|
369
|
+
self._input = _truncate(input) if input is not None else None
|
|
370
|
+
self._attributes = attributes
|
|
371
|
+
self._ctx_mgr: Optional[run_with_context] = None
|
|
372
|
+
self._started_ms: float = 0.0
|
|
373
|
+
self._started_iso: str = ""
|
|
374
|
+
self._active: Optional[ActiveSpanContext] = None
|
|
375
|
+
self._span_id: str = ""
|
|
376
|
+
self.handle: Optional[SpanHandle] = None
|
|
377
|
+
|
|
378
|
+
def __enter__(self) -> SpanHandle:
|
|
379
|
+
self._active = get_active_context()
|
|
380
|
+
self._span_id = str(uuid.uuid4())
|
|
381
|
+
self.handle = SpanHandle(self._span_id, self._name)
|
|
382
|
+
if self._input is not None:
|
|
383
|
+
self.handle.input = self._input
|
|
384
|
+
if self._attributes:
|
|
385
|
+
self.handle.attributes.update(self._attributes)
|
|
386
|
+
if self._active is None:
|
|
387
|
+
# No active run — still execute user code, just don't track.
|
|
388
|
+
return self.handle
|
|
389
|
+
child = ActiveSpanContext(
|
|
390
|
+
run_id=self._active.run_id,
|
|
391
|
+
span_id=self._span_id,
|
|
392
|
+
parent_span_id=self._active.span_id,
|
|
393
|
+
team_site_id=self._active.team_site_id,
|
|
394
|
+
processor=self._active.processor,
|
|
395
|
+
)
|
|
396
|
+
self._ctx_mgr = run_with_context(child)
|
|
397
|
+
self._ctx_mgr.__enter__()
|
|
398
|
+
self._started_iso = _now_iso()
|
|
399
|
+
self._started_ms = time.time() * 1000.0
|
|
400
|
+
return self.handle
|
|
401
|
+
|
|
402
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
403
|
+
if self._active is None or self.handle is None:
|
|
404
|
+
return None
|
|
405
|
+
ended_iso = _now_iso()
|
|
406
|
+
duration_ms = int(time.time() * 1000.0 - self._started_ms)
|
|
407
|
+
status = "error" if exc is not None else "ok"
|
|
408
|
+
error_type = exc_type.__name__ if exc_type else None
|
|
409
|
+
error_message = _truncate(str(exc), 4_000) if exc else None
|
|
410
|
+
|
|
411
|
+
trodo_span = TrodoSpan(
|
|
412
|
+
span_id=self._span_id,
|
|
413
|
+
run_id=self._active.run_id,
|
|
414
|
+
parent_span_id=self._active.span_id,
|
|
415
|
+
kind=self._kind,
|
|
416
|
+
name=self._name,
|
|
417
|
+
status=status,
|
|
418
|
+
started_at=self._started_iso,
|
|
419
|
+
ended_at=ended_iso,
|
|
420
|
+
duration_ms=duration_ms,
|
|
421
|
+
input=self.handle.input,
|
|
422
|
+
output=self.handle.output,
|
|
423
|
+
error_type=error_type,
|
|
424
|
+
error_message=error_message,
|
|
425
|
+
model=self.handle.model,
|
|
426
|
+
provider=self.handle.provider,
|
|
427
|
+
input_tokens=self.handle.input_tokens,
|
|
428
|
+
output_tokens=self.handle.output_tokens,
|
|
429
|
+
cost=self.handle.cost,
|
|
430
|
+
temperature=self.handle.temperature,
|
|
431
|
+
tool_name=self.handle.tool_name,
|
|
432
|
+
attributes=self.handle.attributes or None,
|
|
433
|
+
)
|
|
434
|
+
processor: TrodoSpanProcessor = self._active.processor # type: ignore[assignment]
|
|
435
|
+
processor.enqueue_span(trodo_span)
|
|
436
|
+
if self._ctx_mgr is not None:
|
|
437
|
+
self._ctx_mgr.__exit__(exc_type, exc, tb)
|
|
438
|
+
return None
|
trodo/types.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Any, Dict, List,
|
|
6
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
7
7
|
|
|
8
8
|
# ----------------------------------------------------------------------------
|
|
9
9
|
# Configuration
|
|
@@ -21,6 +21,7 @@ class TrodoConfig:
|
|
|
21
21
|
auto_events: bool = False
|
|
22
22
|
on_error: Optional[Any] = None # Callable[[Exception], None]
|
|
23
23
|
debug: bool = False
|
|
24
|
+
auto_instrument: bool = False
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
# ----------------------------------------------------------------------------
|
|
@@ -80,75 +81,18 @@ ResetResult = Dict[str, Any]
|
|
|
80
81
|
|
|
81
82
|
|
|
82
83
|
# ----------------------------------------------------------------------------
|
|
83
|
-
# Agent
|
|
84
|
+
# Agent Runs (Lemma-style)
|
|
84
85
|
# ----------------------------------------------------------------------------
|
|
85
86
|
|
|
86
|
-
@dataclass
|
|
87
|
-
class AgentCallProps:
|
|
88
|
-
agent_id: str
|
|
89
|
-
conversation_id: str
|
|
90
|
-
message_id: str
|
|
91
|
-
distinct_id: Optional[str] = None
|
|
92
|
-
prompt: Optional[str] = None
|
|
93
|
-
model: Optional[str] = None
|
|
94
|
-
temperature: Optional[float] = None
|
|
95
|
-
system_prompt_version: Optional[str] = None
|
|
96
|
-
provider: Optional[str] = None
|
|
97
|
-
timestamp: Optional[str] = None
|
|
98
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@dataclass
|
|
102
|
-
class ToolUseProps:
|
|
103
|
-
agent_id: str
|
|
104
|
-
conversation_id: str
|
|
105
|
-
message_id: str
|
|
106
|
-
tool_name: str
|
|
107
|
-
distinct_id: Optional[str] = None
|
|
108
|
-
input: Optional[Any] = None
|
|
109
|
-
output: Optional[Any] = None
|
|
110
|
-
latency_ms: Optional[int] = None
|
|
111
|
-
status: Optional[str] = None # 'success' | 'failure'
|
|
112
|
-
timestamp: Optional[str] = None
|
|
113
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@dataclass
|
|
117
|
-
class AgentResponseProps:
|
|
118
|
-
agent_id: str
|
|
119
|
-
conversation_id: str
|
|
120
|
-
message_id: str
|
|
121
|
-
distinct_id: Optional[str] = None
|
|
122
|
-
output: Optional[str] = None
|
|
123
|
-
model: Optional[str] = None
|
|
124
|
-
completion_tokens: Optional[int] = None
|
|
125
|
-
prompt_tokens: Optional[int] = None
|
|
126
|
-
total_tokens: Optional[int] = None
|
|
127
|
-
finish_reason: Optional[str] = None
|
|
128
|
-
timestamp: Optional[str] = None
|
|
129
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@dataclass
|
|
133
|
-
class AgentErrorProps:
|
|
134
|
-
agent_id: str
|
|
135
|
-
conversation_id: str
|
|
136
|
-
message_id: str
|
|
137
|
-
distinct_id: Optional[str] = None
|
|
138
|
-
error_type: Optional[str] = None
|
|
139
|
-
error_message: Optional[str] = None
|
|
140
|
-
failed_tool: Optional[str] = None
|
|
141
|
-
traceback: Optional[str] = None
|
|
142
|
-
timestamp: Optional[str] = None
|
|
143
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
144
|
-
|
|
145
|
-
|
|
146
87
|
@dataclass
|
|
147
88
|
class FeedbackProps:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
89
|
+
"""Feedback attached to a run. ``run_id`` is returned by ``wrap_agent``.
|
|
90
|
+
|
|
91
|
+
At least one of ``satisfaction`` / ``rating`` / ``comment`` must be provided.
|
|
92
|
+
"""
|
|
93
|
+
satisfaction: Optional[Literal["positive", "negative"]] = None
|
|
94
|
+
rating: Optional[float] = None
|
|
95
|
+
comment: Optional[str] = None
|
|
96
|
+
feedback: Optional[str] = None # alias for comment
|
|
152
97
|
distinct_id: Optional[str] = None
|
|
153
|
-
|
|
154
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
98
|
+
metadata: Optional[Dict[str, Any]] = None
|