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.
Files changed (74) hide show
  1. {trodo_python-3.0.0 → trodo_python-3.0.1}/PKG-INFO +1 -1
  2. {trodo_python-3.0.0 → trodo_python-3.0.1}/pyproject.toml +1 -1
  3. trodo_python-3.0.1/tests/test_long_runs.py +34 -0
  4. trodo_python-3.0.1/trodo/_version.py +1 -0
  5. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/processor.py +31 -6
  6. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/PKG-INFO +1 -1
  7. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/SOURCES.txt +1 -0
  8. trodo_python-3.0.0/trodo/_version.py +0 -1
  9. {trodo_python-3.0.0 → trodo_python-3.0.1}/README.md +0 -0
  10. {trodo_python-3.0.0 → trodo_python-3.0.1}/setup.cfg +0 -0
  11. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_anon_distinct_id.py +0 -0
  12. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_auto_instrument_fixes.py +0 -0
  13. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_cross_process_session.py +0 -0
  14. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_datasets.py +0 -0
  15. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_end_run.py +0 -0
  16. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_end_run_order.py +0 -0
  17. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_error_enrichment.py +0 -0
  18. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_framework_deference.py +0 -0
  19. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_get_prompt_version_types.py +0 -0
  20. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_instrumentor_resolution.py +0 -0
  21. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_llm_usage_cost.py +0 -0
  22. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_multi_agent_shape.py +0 -0
  23. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_processor_methods.py +0 -0
  24. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_prompt_fingerprint.py +0 -0
  25. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_prompt_trace.py +0 -0
  26. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_prompts.py +0 -0
  27. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_register_otel.py +0 -0
  28. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_sender.py +0 -0
  29. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_start_run.py +0 -0
  30. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_user_traits.py +0 -0
  31. {trodo_python-3.0.0 → trodo_python-3.0.1}/tests/test_wrap_agent_unchanged.py +0 -0
  32. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/__init__.py +0 -0
  33. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/__init__.py +0 -0
  34. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/_retry.py +0 -0
  35. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/async_client.py +0 -0
  36. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/endpoints.py +0 -0
  37. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/http_client.py +0 -0
  38. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/api/management.py +0 -0
  39. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/auto/__init__.py +0 -0
  40. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/auto/auto_event_manager.py +0 -0
  41. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/client.py +0 -0
  42. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/delivery/__init__.py +0 -0
  43. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/delivery/sender.py +0 -0
  44. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/__init__.py +0 -0
  45. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/dataset_manager.py +0 -0
  46. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/group_manager.py +0 -0
  47. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/people_manager.py +0 -0
  48. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/prompt_manager.py +0 -0
  49. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/managers/user_manager.py +0 -0
  50. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/__init__.py +0 -0
  51. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/auto_instrument.py +0 -0
  52. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/context.py +0 -0
  53. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/helpers.py +0 -0
  54. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/prompt_trace.py +0 -0
  55. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/register.py +0 -0
  56. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/transport.py +0 -0
  57. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/otel/wrap_agent.py +0 -0
  58. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/__init__.py +0 -0
  59. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/compile.py +0 -0
  60. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/template.py +0 -0
  61. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/prompts/types.py +0 -0
  62. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/queue/__init__.py +0 -0
  63. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/queue/batch_flusher.py +0 -0
  64. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/queue/event_queue.py +0 -0
  65. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/session/__init__.py +0 -0
  66. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/session/server_session.py +0 -0
  67. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/session/session_manager.py +0 -0
  68. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/types.py +0 -0
  69. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/user_context.py +0 -0
  70. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/util/__init__.py +0 -0
  71. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo/util/lru.py +0 -0
  72. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/dependency_links.txt +0 -0
  73. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/requires.txt +0 -0
  74. {trodo_python-3.0.0 → trodo_python-3.0.1}/trodo_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 3.0.0
3
+ Version: 3.0.1
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trodo-python"
7
- version = "3.0.0"
7
+ version = "3.0.1"
8
8
  description = "Trodo Analytics SDK for Python — server-side event tracking"
9
9
  readme = "README.md"
10
10
  license = { text = "ISC" }
@@ -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
- if len(bucket) < self._max_spans:
149
- bucket.append(span)
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
- return list(self._pending.get(run_id, []))
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 3.0.0
3
+ Version: 3.0.1
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
@@ -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