trodo-python 3.0.0__tar.gz → 3.0.1__tar.gz
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_python-3.0.0 → trodo_python-3.0.1}/PKG-INFO +1 -1
- {trodo_python-3.0.0 → trodo_python-3.0.1}/pyproject.toml +1 -1
- trodo_python-3.0.1/tests/test_long_runs.py +34 -0
- trodo_python-3.0.1/trodo/_version.py +1 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/processor.py +31 -6
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/PKG-INFO +1 -1
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/SOURCES.txt +1 -0
- trodo_python-3.0.0/trodo/_version.py +0 -1
- {trodo_python-3.0.0 → trodo_python-3.0.1}/README.md +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/setup.cfg +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_anon_distinct_id.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_auto_instrument_fixes.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_cross_process_session.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_datasets.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_end_run.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_end_run_order.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_error_enrichment.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_framework_deference.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_get_prompt_version_types.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_instrumentor_resolution.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_llm_usage_cost.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_multi_agent_shape.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_processor_methods.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_prompt_fingerprint.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_prompt_trace.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_prompts.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_register_otel.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_sender.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_start_run.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_user_traits.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_wrap_agent_unchanged.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/_retry.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/async_client.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/endpoints.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/http_client.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/management.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/auto/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/auto/auto_event_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/client.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/delivery/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/delivery/sender.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/dataset_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/group_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/people_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/prompt_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/user_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/auto_instrument.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/context.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/helpers.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/prompt_trace.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/register.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/transport.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/wrap_agent.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/compile.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/template.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/types.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/queue/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/queue/batch_flusher.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/queue/event_queue.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/session/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/session/server_session.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/session/session_manager.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/types.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/user_context.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/util/__init__.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/util/lru.py +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""A run with more spans than one buffer holds keeps every span (it used to
|
|
2
|
+
drop everything after the 1,000th) and its totals still count all of them."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from trodo.otel.processor import TrodoRun, TrodoSpan, TrodoSpanProcessor
|
|
6
|
+
from trodo.otel.wrap_agent import _aggregate
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def span(i, run="r1"):
|
|
10
|
+
return TrodoSpan(span_id=f"s{i}", run_id=run, parent_span_id=None, kind="tool" if i % 2 else "llm",
|
|
11
|
+
name="step", status="error" if i == 4 else "ok", input_tokens=10, output_tokens=1)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_a_full_buffer_is_sent_ahead_and_nothing_is_dropped(http):
|
|
15
|
+
p = TrodoSpanProcessor(http, max_spans_per_run=3)
|
|
16
|
+
for i in range(8):
|
|
17
|
+
p.enqueue_span(span(i))
|
|
18
|
+
# Two full chunks went ahead through append; two spans wait for the run.
|
|
19
|
+
assert [(rid, [s["span_id"] for s in spans]) for rid, spans in http.spans_append] == [
|
|
20
|
+
("r1", ["s0", "s1", "s2"]), ("r1", ["s3", "s4", "s5"])]
|
|
21
|
+
agg = _aggregate(p.get_pending("r1"))
|
|
22
|
+
assert agg["span_count"] == 8 and agg["total_tokens_in"] == 80 and agg["tool_count"] == 4 and agg["error_count"] == 1
|
|
23
|
+
p.ingest_run(TrodoRun(run_id="r1", agent_name="deep", **{k: v for k, v in agg.items() if k in ("span_count", "tool_count", "error_count")}))
|
|
24
|
+
sent = [s["span_id"] for _, spans in http.spans_append for s in spans] + [s["span_id"] for s in http.run_ingest[-1]["spans"]]
|
|
25
|
+
assert sorted(sent) == sorted(f"s{i}" for i in range(8))
|
|
26
|
+
assert len(sent) == len(set(sent)), "no span is sent twice"
|
|
27
|
+
assert p.get_pending("r1") == []
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_runs_do_not_share_buffers(http):
|
|
31
|
+
p = TrodoSpanProcessor(http, max_spans_per_run=2)
|
|
32
|
+
p.enqueue_span(span(1, "a")); p.enqueue_span(span(2, "b")); p.enqueue_span(span(3, "a"))
|
|
33
|
+
assert [rid for rid, _ in http.spans_append] == ["a"]
|
|
34
|
+
assert len(p.get_pending("b")) == 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SDK_VERSION = "3.0.1"
|
|
@@ -97,6 +97,19 @@ class TrodoSpan:
|
|
|
97
97
|
return {k: v for k, v in asdict(self).items() if v is not None}
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
class _SentSpan:
|
|
101
|
+
"""What the run's totals need of a span already sent ahead: no payloads."""
|
|
102
|
+
|
|
103
|
+
__slots__ = ("input_tokens", "output_tokens", "cost", "kind", "status")
|
|
104
|
+
|
|
105
|
+
def __init__(self, span: TrodoSpan) -> None:
|
|
106
|
+
self.input_tokens = span.input_tokens
|
|
107
|
+
self.output_tokens = span.output_tokens
|
|
108
|
+
self.cost = span.cost
|
|
109
|
+
self.kind = span.kind
|
|
110
|
+
self.status = span.status
|
|
111
|
+
|
|
112
|
+
|
|
100
113
|
class TrodoSpanProcessor:
|
|
101
114
|
"""Buffers spans per run_id; flushes to /runs/ingest when the run ends.
|
|
102
115
|
|
|
@@ -118,6 +131,10 @@ class TrodoSpanProcessor:
|
|
|
118
131
|
self._flush_interval = flush_interval_s
|
|
119
132
|
self._max_spans = max_spans_per_run
|
|
120
133
|
self._pending: Dict[str, List[TrodoSpan]] = {}
|
|
134
|
+
# A run past max_spans_per_run sends each full chunk ahead (the server
|
|
135
|
+
# stores spans before their run arrives) and keeps only what its totals
|
|
136
|
+
# need. Before this, every span after the 1,000th was dropped.
|
|
137
|
+
self._sent_ahead: Dict[str, List[_SentSpan]] = {}
|
|
121
138
|
self._lock = threading.Lock()
|
|
122
139
|
self._shutdown = False
|
|
123
140
|
# run_ids that are "joined" (owned by a remote service) — their spans
|
|
@@ -133,22 +150,27 @@ class TrodoSpanProcessor:
|
|
|
133
150
|
with self._lock:
|
|
134
151
|
self._joined_runs.discard(run_id)
|
|
135
152
|
self._pending.pop(run_id, None)
|
|
153
|
+
self._sent_ahead.pop(run_id, None)
|
|
136
154
|
|
|
137
155
|
def enqueue_span(self, span: TrodoSpan) -> None:
|
|
138
156
|
"""Buffer a completed span under its run_id.
|
|
139
157
|
|
|
140
158
|
Joined runs flush immediately (append_spans) instead of batching.
|
|
141
159
|
"""
|
|
160
|
+
chunk: List[TrodoSpan] = []
|
|
142
161
|
with self._lock:
|
|
143
162
|
joined = span.run_id in self._joined_runs
|
|
144
|
-
if joined:
|
|
145
|
-
pass
|
|
146
|
-
else:
|
|
163
|
+
if not joined:
|
|
147
164
|
bucket = self._pending.setdefault(span.run_id, [])
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
bucket.append(span)
|
|
166
|
+
if len(bucket) >= self._max_spans:
|
|
167
|
+
chunk = bucket
|
|
168
|
+
self._pending[span.run_id] = []
|
|
169
|
+
self._sent_ahead.setdefault(span.run_id, []).extend(_SentSpan(x) for x in chunk)
|
|
150
170
|
if joined:
|
|
151
171
|
self._send_spans(span.run_id, [span.to_dict()])
|
|
172
|
+
elif chunk:
|
|
173
|
+
self._send_spans(span.run_id, [x.to_dict() for x in chunk])
|
|
152
174
|
|
|
153
175
|
def _send_spans(self, run_id: str, spans: List[Dict[str, Any]]) -> None:
|
|
154
176
|
if self._sender is not None:
|
|
@@ -161,12 +183,14 @@ class TrodoSpanProcessor:
|
|
|
161
183
|
|
|
162
184
|
def get_pending(self, run_id: str) -> List[TrodoSpan]:
|
|
163
185
|
with self._lock:
|
|
164
|
-
|
|
186
|
+
# The run's totals cover every span, including those sent ahead.
|
|
187
|
+
return [*self._sent_ahead.get(run_id, []), *self._pending.get(run_id, [])]
|
|
165
188
|
|
|
166
189
|
def ingest_run(self, run: TrodoRun) -> None:
|
|
167
190
|
"""Flush a run + all its buffered spans in one call."""
|
|
168
191
|
with self._lock:
|
|
169
192
|
spans = self._pending.pop(run.run_id, [])
|
|
193
|
+
self._sent_ahead.pop(run.run_id, None)
|
|
170
194
|
payload: Dict[str, Any] = {"run": run.to_dict()}
|
|
171
195
|
if spans:
|
|
172
196
|
payload["spans"] = [s.to_dict() for s in spans]
|
|
@@ -201,6 +225,7 @@ class TrodoSpanProcessor:
|
|
|
201
225
|
try:
|
|
202
226
|
with self._lock:
|
|
203
227
|
buffered = self._pending.pop(run_id, None) or []
|
|
228
|
+
self._sent_ahead.pop(run_id, None)
|
|
204
229
|
if buffered:
|
|
205
230
|
self._send_spans(run_id, [s.to_dict() for s in buffered])
|
|
206
231
|
if self._sender is not None:
|
|
@@ -11,6 +11,7 @@ tests/test_framework_deference.py
|
|
|
11
11
|
tests/test_get_prompt_version_types.py
|
|
12
12
|
tests/test_instrumentor_resolution.py
|
|
13
13
|
tests/test_llm_usage_cost.py
|
|
14
|
+
tests/test_long_runs.py
|
|
14
15
|
tests/test_multi_agent_shape.py
|
|
15
16
|
tests/test_processor_methods.py
|
|
16
17
|
tests/test_prompt_fingerprint.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
SDK_VERSION = "3.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|