trodo-python 2.1.0__py3-none-any.whl → 2.2.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 +49 -1
- trodo/client.py +47 -0
- trodo/otel/processor.py +19 -0
- trodo/otel/wrap_agent.py +70 -0
- {trodo_python-2.1.0.dist-info → trodo_python-2.2.0.dist-info}/METADATA +29 -1
- {trodo_python-2.1.0.dist-info → trodo_python-2.2.0.dist-info}/RECORD +8 -8
- {trodo_python-2.1.0.dist-info → trodo_python-2.2.0.dist-info}/WHEEL +0 -0
- {trodo_python-2.1.0.dist-info → trodo_python-2.2.0.dist-info}/top_level.txt +0 -0
trodo/__init__.py
CHANGED
|
@@ -40,7 +40,7 @@ Downstream microservice (join the caller's run instead of making a new one):
|
|
|
40
40
|
|
|
41
41
|
from __future__ import annotations
|
|
42
42
|
|
|
43
|
-
__version__ = "2.
|
|
43
|
+
__version__ = "2.2.0"
|
|
44
44
|
|
|
45
45
|
from typing import Any, Callable, Dict, List, Optional, Union
|
|
46
46
|
|
|
@@ -85,6 +85,8 @@ __all__ = [
|
|
|
85
85
|
"llm",
|
|
86
86
|
"retrieval",
|
|
87
87
|
"join_run",
|
|
88
|
+
"start_run",
|
|
89
|
+
"end_run",
|
|
88
90
|
"track_llm_call",
|
|
89
91
|
"feedback",
|
|
90
92
|
"get_tracer",
|
|
@@ -264,6 +266,52 @@ def join_run(
|
|
|
264
266
|
)
|
|
265
267
|
|
|
266
268
|
|
|
269
|
+
def start_run(
|
|
270
|
+
agent_name: str,
|
|
271
|
+
*,
|
|
272
|
+
run_id: Optional[str] = None,
|
|
273
|
+
distinct_id: Optional[str] = None,
|
|
274
|
+
conversation_id: Optional[str] = None,
|
|
275
|
+
parent_run_id: Optional[str] = None,
|
|
276
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
277
|
+
input: Any = None,
|
|
278
|
+
) -> str:
|
|
279
|
+
"""Open a Run record without a context manager.
|
|
280
|
+
|
|
281
|
+
Pairs with :func:`end_run` for sessions that span multiple processes or
|
|
282
|
+
HTTP requests. Returns the run_id (caller-supplied or freshly minted).
|
|
283
|
+
Between start_run and end_run any process can use ``join_run(run_id, ...)``
|
|
284
|
+
to add spans.
|
|
285
|
+
"""
|
|
286
|
+
return _get_client().start_run(
|
|
287
|
+
agent_name,
|
|
288
|
+
run_id=run_id,
|
|
289
|
+
distinct_id=distinct_id,
|
|
290
|
+
conversation_id=conversation_id,
|
|
291
|
+
parent_run_id=parent_run_id,
|
|
292
|
+
metadata=metadata,
|
|
293
|
+
input=input,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def end_run(
|
|
298
|
+
run_id: str,
|
|
299
|
+
*,
|
|
300
|
+
output: Any = None,
|
|
301
|
+
status: str = "ok",
|
|
302
|
+
error_summary: Optional[str] = None,
|
|
303
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Finalise a Run opened by :func:`start_run`."""
|
|
306
|
+
_get_client().end_run(
|
|
307
|
+
run_id,
|
|
308
|
+
output=output,
|
|
309
|
+
status=status,
|
|
310
|
+
error_summary=error_summary,
|
|
311
|
+
metadata=metadata,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
267
315
|
def tool(
|
|
268
316
|
name: Any = None,
|
|
269
317
|
fn: Optional[Callable[..., Any]] = None,
|
trodo/client.py
CHANGED
|
@@ -17,6 +17,8 @@ from .otel.wrap_agent import (
|
|
|
17
17
|
wrap_agent as wrap_agent_ctx,
|
|
18
18
|
span as span_ctx,
|
|
19
19
|
join_run as join_run_ctx,
|
|
20
|
+
start_run as start_run_fn,
|
|
21
|
+
end_run as end_run_fn,
|
|
20
22
|
current_run_id as _current_run_id,
|
|
21
23
|
current_span_id as _current_span_id,
|
|
22
24
|
)
|
|
@@ -284,6 +286,51 @@ class TrodoClient:
|
|
|
284
286
|
"""Create a nested span inside the current run."""
|
|
285
287
|
return span_ctx(name=name, kind=kind, input=input, attributes=attributes)
|
|
286
288
|
|
|
289
|
+
def start_run(
|
|
290
|
+
self,
|
|
291
|
+
agent_name: str,
|
|
292
|
+
*,
|
|
293
|
+
run_id: Optional[str] = None,
|
|
294
|
+
distinct_id: Optional[str] = None,
|
|
295
|
+
conversation_id: Optional[str] = None,
|
|
296
|
+
parent_run_id: Optional[str] = None,
|
|
297
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
298
|
+
input: Any = None,
|
|
299
|
+
) -> str:
|
|
300
|
+
"""Open a Run record outside a context manager. Returns the run_id.
|
|
301
|
+
|
|
302
|
+
Use ``end_run`` to finalise, ``join_run`` from any process to add spans.
|
|
303
|
+
"""
|
|
304
|
+
return start_run_fn(
|
|
305
|
+
processor=self._span_processor,
|
|
306
|
+
agent_name=agent_name,
|
|
307
|
+
run_id=run_id,
|
|
308
|
+
distinct_id=distinct_id,
|
|
309
|
+
conversation_id=conversation_id,
|
|
310
|
+
parent_run_id=parent_run_id,
|
|
311
|
+
metadata=metadata,
|
|
312
|
+
input=input,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def end_run(
|
|
316
|
+
self,
|
|
317
|
+
run_id: str,
|
|
318
|
+
*,
|
|
319
|
+
output: Any = None,
|
|
320
|
+
status: str = "ok",
|
|
321
|
+
error_summary: Optional[str] = None,
|
|
322
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
323
|
+
) -> None:
|
|
324
|
+
"""Finalise a Run previously opened by :meth:`start_run`."""
|
|
325
|
+
end_run_fn(
|
|
326
|
+
run_id,
|
|
327
|
+
processor=self._span_processor,
|
|
328
|
+
output=output,
|
|
329
|
+
status=status,
|
|
330
|
+
error_summary=error_summary,
|
|
331
|
+
metadata=metadata,
|
|
332
|
+
)
|
|
333
|
+
|
|
287
334
|
def join_run(
|
|
288
335
|
self,
|
|
289
336
|
run_id: str,
|
trodo/otel/processor.py
CHANGED
|
@@ -136,6 +136,25 @@ class TrodoSpanProcessor:
|
|
|
136
136
|
except Exception:
|
|
137
137
|
pass
|
|
138
138
|
|
|
139
|
+
def start_run(self, run: TrodoRun) -> None:
|
|
140
|
+
"""Open a Run row server-side without holding a context manager.
|
|
141
|
+
|
|
142
|
+
Pairs with ``end_run`` for sessions that span multiple processes or
|
|
143
|
+
HTTP requests. Spans emitted between start_run and end_run flush
|
|
144
|
+
incrementally via append_spans (callers are expected to mark_joined).
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
self._http.post_run_start({"run": run.to_dict()})
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
def end_run(self, run_id: str, payload: Dict[str, Any]) -> None:
|
|
152
|
+
"""Finalise a Run opened by start_run."""
|
|
153
|
+
try:
|
|
154
|
+
self._http.post_run_end(run_id, payload)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
|
|
139
158
|
def append_spans(self, run_id: str, spans: List[TrodoSpan]) -> None:
|
|
140
159
|
"""Stream spans for a long-running or joined run without finalising."""
|
|
141
160
|
if not spans:
|
trodo/otel/wrap_agent.py
CHANGED
|
@@ -163,6 +163,76 @@ class SpanHandle:
|
|
|
163
163
|
self.tool_name = tool_name
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def start_run(
|
|
167
|
+
*,
|
|
168
|
+
processor: TrodoSpanProcessor,
|
|
169
|
+
agent_name: str,
|
|
170
|
+
run_id: Optional[str] = None,
|
|
171
|
+
distinct_id: Optional[str] = None,
|
|
172
|
+
conversation_id: Optional[str] = None,
|
|
173
|
+
parent_run_id: Optional[str] = None,
|
|
174
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
175
|
+
input: Any = None,
|
|
176
|
+
) -> str:
|
|
177
|
+
"""Open a Run record without holding a context manager.
|
|
178
|
+
|
|
179
|
+
Pairs with :func:`end_run` for sessions that span multiple processes or
|
|
180
|
+
HTTP requests (e.g. an MCP server where ``initialize`` opens a run and
|
|
181
|
+
later ``tools/call`` requests append spans before a final close).
|
|
182
|
+
|
|
183
|
+
Returns the ``run_id`` (caller-supplied or freshly minted UUID). Between
|
|
184
|
+
``start_run`` and ``end_run`` any process can use ``join_run(run_id, ...)``
|
|
185
|
+
to add spans — they flush incrementally via ``append_spans``.
|
|
186
|
+
"""
|
|
187
|
+
rid = run_id or str(uuid.uuid4())
|
|
188
|
+
run = TrodoRun(
|
|
189
|
+
run_id=rid,
|
|
190
|
+
agent_name=agent_name,
|
|
191
|
+
distinct_id=distinct_id,
|
|
192
|
+
conversation_id=conversation_id,
|
|
193
|
+
parent_run_id=parent_run_id,
|
|
194
|
+
status="running",
|
|
195
|
+
input=_truncate(input),
|
|
196
|
+
started_at=_now_iso(),
|
|
197
|
+
metadata=metadata,
|
|
198
|
+
)
|
|
199
|
+
processor.mark_joined(rid)
|
|
200
|
+
processor.start_run(run)
|
|
201
|
+
return rid
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def end_run(
|
|
205
|
+
run_id: str,
|
|
206
|
+
*,
|
|
207
|
+
processor: TrodoSpanProcessor,
|
|
208
|
+
output: Any = None,
|
|
209
|
+
status: str = "ok",
|
|
210
|
+
error_summary: Optional[str] = None,
|
|
211
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Finalise a Run opened by :func:`start_run`.
|
|
214
|
+
|
|
215
|
+
Aggregates any locally-buffered spans for ``run_id``, POSTs to the
|
|
216
|
+
``/runs/{id}/end`` endpoint, and unmarks the run as joined. Idempotent
|
|
217
|
+
on the local-state side; the backend treats a second call as a row update.
|
|
218
|
+
"""
|
|
219
|
+
pending = processor.get_pending(run_id)
|
|
220
|
+
agg = _aggregate(pending)
|
|
221
|
+
payload: Dict[str, Any] = {
|
|
222
|
+
"ended_at": _now_iso(),
|
|
223
|
+
"status": status,
|
|
224
|
+
**agg,
|
|
225
|
+
}
|
|
226
|
+
if output is not None:
|
|
227
|
+
payload["output"] = _truncate(output)
|
|
228
|
+
if error_summary is not None:
|
|
229
|
+
payload["error_summary"] = _truncate(error_summary, 4_000)
|
|
230
|
+
if metadata is not None:
|
|
231
|
+
payload["metadata"] = metadata
|
|
232
|
+
processor.end_run(run_id, payload)
|
|
233
|
+
processor.unmark_joined(run_id)
|
|
234
|
+
|
|
235
|
+
|
|
166
236
|
class wrap_agent:
|
|
167
237
|
"""Context manager wrapping an agent run.
|
|
168
238
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trodo-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Trodo Analytics SDK for Python — server-side event tracking
|
|
5
5
|
License: ISC
|
|
6
6
|
Keywords: analytics,tracking,trodo,server-side
|
|
@@ -263,6 +263,34 @@ with trodo.join_run(
|
|
|
263
263
|
...
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
### Long-lived sessions across processes — `start_run` / `end_run`
|
|
267
|
+
|
|
268
|
+
`wrap_agent` is a context manager — it opens *and* closes the run in one
|
|
269
|
+
call stack. For sessions that live across many HTTP requests (an MCP
|
|
270
|
+
server, a websocket-pinned chat, scheduled jobs that resume on different
|
|
271
|
+
workers), use `start_run` to open the run from one process and `end_run`
|
|
272
|
+
to finalise it later. Between the two, any process can use `join_run` to
|
|
273
|
+
add child spans. Same `run_id` threads through everything.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# Process A — open the run for an MCP session.
|
|
277
|
+
run_id = trodo.start_run(
|
|
278
|
+
'external_mcp_session',
|
|
279
|
+
distinct_id=str(user_id),
|
|
280
|
+
conversation_id=mcp_session_id,
|
|
281
|
+
)
|
|
282
|
+
redis.set(f"mcp:run:{mcp_session_id}", run_id, ex=3600)
|
|
283
|
+
|
|
284
|
+
# Process B (later, possibly a different worker) — append a tool span.
|
|
285
|
+
run_id = redis.get(f"mcp:run:{mcp_session_id}").decode()
|
|
286
|
+
with trodo.join_run(run_id, name='tool.run_funnel_query', kind='tool') as span:
|
|
287
|
+
span.set_input(args)
|
|
288
|
+
span.set_output(result)
|
|
289
|
+
|
|
290
|
+
# When the session ends (timeout sweeper, explicit close):
|
|
291
|
+
trodo.end_run(run_id, status='ok')
|
|
292
|
+
```
|
|
293
|
+
|
|
266
294
|
### Conversation binding & feedback
|
|
267
295
|
|
|
268
296
|
```python
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
trodo/__init__.py,sha256=
|
|
2
|
-
trodo/client.py,sha256=
|
|
1
|
+
trodo/__init__.py,sha256=lo_QFNEk_4ylQ_4-v0RBfdA8ed30M5QjlA7ylJAiAjU,12870
|
|
2
|
+
trodo/client.py,sha256=x_HjIyLMowUU-w73GYETEZWWHKjWBC-4LpG6lfLBRmU,15499
|
|
3
3
|
trodo/types.py,sha256=eySgUvCXROG2TxtxgiU0MNr5iH0DEcduK8bmYtTKG44,3138
|
|
4
4
|
trodo/user_context.py,sha256=9la6azzwEanVmdP4ps_xMoufbeWVeIGU-M8ychmgajg,7859
|
|
5
5
|
trodo/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -15,15 +15,15 @@ trodo/otel/__init__.py,sha256=yiRFXWUU45bAM2CV37XeO7zf1hmnmjufdP4XO50yEyE,624
|
|
|
15
15
|
trodo/otel/auto_instrument.py,sha256=J_neFxvO-3YACUvtetY4RdM8xYA_79SZUgPry6hXrm8,9434
|
|
16
16
|
trodo/otel/context.py,sha256=iJ1rE42-SbO8VZHAxhIl2ZJXgNwLIVps5xLg8GKgfFc,1165
|
|
17
17
|
trodo/otel/helpers.py,sha256=cvgFrdT8yP92P9mttloiHPr_eTCe8cC4NVrxrJo_I-A,13234
|
|
18
|
-
trodo/otel/processor.py,sha256=
|
|
19
|
-
trodo/otel/wrap_agent.py,sha256=
|
|
18
|
+
trodo/otel/processor.py,sha256=jVZkslZlw50G5uRAa7-GMRgn_yvae58EmlWTZL8tMkQ,6285
|
|
19
|
+
trodo/otel/wrap_agent.py,sha256=mwHYwxtg9A9VUBdlbCKLswrNP0v5oY8ELpvt1JVYE8Q,17495
|
|
20
20
|
trodo/queue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
trodo/queue/batch_flusher.py,sha256=4Lg6T3Urwi9U0Q4FpFGPmjDYKg4ZliCTR-ND8BJvWaY,1298
|
|
22
22
|
trodo/queue/event_queue.py,sha256=EVFZrhlq_kwC3jJ2GK0wMhHISf9UzLCZNDnT_aZ2I2A,872
|
|
23
23
|
trodo/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
trodo/session/server_session.py,sha256=uBAq1QSYPUUaHFSeoOyM5Yr65dLb8T82OOx3D1BrdrE,1970
|
|
25
25
|
trodo/session/session_manager.py,sha256=JrgH1VeicmtlxPR4dXEuJbxhi23OelkgwW3-9Slv80o,2525
|
|
26
|
-
trodo_python-2.
|
|
27
|
-
trodo_python-2.
|
|
28
|
-
trodo_python-2.
|
|
29
|
-
trodo_python-2.
|
|
26
|
+
trodo_python-2.2.0.dist-info/METADATA,sha256=2WsoZx5P03S5LG7JDUsCfBwTLVGSCVYa3R8ZS8KKLqw,16353
|
|
27
|
+
trodo_python-2.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
28
|
+
trodo_python-2.2.0.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
|
|
29
|
+
trodo_python-2.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|