trodo-python 2.23.1__tar.gz → 2.23.2__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 (65) hide show
  1. {trodo_python-2.23.1 → trodo_python-2.23.2}/PKG-INFO +1 -1
  2. {trodo_python-2.23.1 → trodo_python-2.23.2}/pyproject.toml +1 -1
  3. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_prompt_trace.py +77 -0
  4. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/prompt_trace.py +25 -3
  5. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/wrap_agent.py +4 -2
  6. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo_python.egg-info/PKG-INFO +1 -1
  7. {trodo_python-2.23.1 → trodo_python-2.23.2}/README.md +0 -0
  8. {trodo_python-2.23.1 → trodo_python-2.23.2}/setup.cfg +0 -0
  9. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_anon_distinct_id.py +0 -0
  10. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_auto_instrument_fixes.py +0 -0
  11. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_cross_process_session.py +0 -0
  12. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_datasets.py +0 -0
  13. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_end_run.py +0 -0
  14. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_error_enrichment.py +0 -0
  15. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_experiments.py +0 -0
  16. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_get_prompt_version_types.py +0 -0
  17. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_instrumentor_resolution.py +0 -0
  18. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_llm_usage_cost.py +0 -0
  19. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_multi_agent_shape.py +0 -0
  20. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_processor_methods.py +0 -0
  21. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_prompts.py +0 -0
  22. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_register_otel.py +0 -0
  23. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_start_run.py +0 -0
  24. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_user_traits.py +0 -0
  25. {trodo_python-2.23.1 → trodo_python-2.23.2}/tests/test_wrap_agent_unchanged.py +0 -0
  26. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/__init__.py +0 -0
  27. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/api/__init__.py +0 -0
  28. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/api/async_client.py +0 -0
  29. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/api/endpoints.py +0 -0
  30. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/api/http_client.py +0 -0
  31. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/auto/__init__.py +0 -0
  32. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/auto/auto_event_manager.py +0 -0
  33. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/client.py +0 -0
  34. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/__init__.py +0 -0
  35. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/dataset_manager.py +0 -0
  36. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/experiment_manager.py +0 -0
  37. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/group_manager.py +0 -0
  38. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/people_manager.py +0 -0
  39. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/prompt_manager.py +0 -0
  40. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/managers/user_manager.py +0 -0
  41. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/__init__.py +0 -0
  42. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/auto_instrument.py +0 -0
  43. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/context.py +0 -0
  44. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/helpers.py +0 -0
  45. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/processor.py +0 -0
  46. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/register.py +0 -0
  47. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/otel/transport.py +0 -0
  48. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/prompts/__init__.py +0 -0
  49. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/prompts/compile.py +0 -0
  50. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/prompts/template.py +0 -0
  51. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/prompts/types.py +0 -0
  52. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/queue/__init__.py +0 -0
  53. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/queue/batch_flusher.py +0 -0
  54. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/queue/event_queue.py +0 -0
  55. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/session/__init__.py +0 -0
  56. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/session/server_session.py +0 -0
  57. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/session/session_manager.py +0 -0
  58. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/types.py +0 -0
  59. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/user_context.py +0 -0
  60. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/util/__init__.py +0 -0
  61. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo/util/lru.py +0 -0
  62. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo_python.egg-info/SOURCES.txt +0 -0
  63. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo_python.egg-info/dependency_links.txt +0 -0
  64. {trodo_python-2.23.1 → trodo_python-2.23.2}/trodo_python.egg-info/requires.txt +0 -0
  65. {trodo_python-2.23.1 → trodo_python-2.23.2}/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: 2.23.1
3
+ Version: 2.23.2
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 = "2.23.1"
7
+ version = "2.23.2"
8
8
  description = "Trodo Analytics SDK for Python — server-side event tracking"
9
9
  readme = "README.md"
10
10
  license = { text = "ISC" }
@@ -259,3 +259,80 @@ def test_run_set_input_with_ordinary_input_records_nothing(processor, http):
259
259
 
260
260
  attrs = http.run_ingest[0]["run"].get("attributes") or {}
261
261
  assert "trodo.prompts" not in attrs
262
+
263
+
264
+ # ── Prompts are SPAN-level, not run-level ───────────────────────────────────
265
+ #
266
+ # One run's spans routinely use different prompts, different versions, or none.
267
+ # The run's "most recently compiled" prompt is a fallback, and applying it to
268
+ # every span meant compiling anywhere in a run attributed that prompt to every
269
+ # LATER span. Caught in production: a span named 'no-prompt' was labelled with
270
+ # whichever prompt had been compiled last.
271
+
272
+
273
+ def test_does_not_leak_last_prompt_onto_a_later_unrelated_span(processor, http):
274
+ mgr = PromptManager(PromptHttp(prompt=PROMPT_WIRE))
275
+ with wrap_agent(processor=processor, team_site_id="site-x", agent_name="chat"):
276
+ p = mgr.get("refund-agent", cache_ttl_seconds=0)
277
+ with span("used-it", kind="llm"):
278
+ p.compile(q="x")
279
+ with span("unrelated"):
280
+ pass
281
+
282
+ spans = http.run_ingest[0]["spans"]
283
+ attrs = next(s for s in spans if s["name"] == "unrelated").get("attributes") or {}
284
+ assert "trodo.prompt.name" not in attrs
285
+
286
+
287
+ def test_still_labels_a_model_span_following_a_compile(processor, http):
288
+ mgr = PromptManager(PromptHttp(prompt=PROMPT_WIRE))
289
+ with wrap_agent(processor=processor, team_site_id="site-x", agent_name="chat"):
290
+ p = mgr.get("refund-agent", cache_ttl_seconds=0)
291
+ p.compile(q="x")
292
+ with span("model-call", kind="llm"):
293
+ pass
294
+
295
+ spans = http.run_ingest[0]["spans"]
296
+ attrs = next(s for s in spans if s["name"] == "model-call")["attributes"]
297
+ assert attrs["trodo.prompt.name"] == "refund-agent"
298
+
299
+
300
+ def test_labels_the_span_the_prompt_was_compiled_inside(processor, http):
301
+ """A plain span wrapping a hand-rolled model call plainly used the prompt
302
+ it compiled; gating purely on kind would have lost it."""
303
+ mgr = PromptManager(PromptHttp(prompt=PROMPT_WIRE))
304
+ with wrap_agent(processor=processor, team_site_id="site-x", agent_name="chat"):
305
+ p = mgr.get("refund-agent", cache_ttl_seconds=0)
306
+ with span("hand-rolled"):
307
+ p.compile(q="x")
308
+
309
+ spans = http.run_ingest[0]["spans"]
310
+ attrs = next(s for s in spans if s["name"] == "hand-rolled")["attributes"]
311
+ assert attrs["trodo.prompt.name"] == "refund-agent"
312
+
313
+
314
+ def test_each_span_gets_its_own_prompt_and_the_run_rolls_up_both(processor, http):
315
+ other = dict(PROMPT_WIRE, name="other-agent", version_hash="bbbb2222")
316
+
317
+ class Two:
318
+ def get_prompt(self, name, label=None, version=None):
319
+ return {"prompt": dict(other if name == "other-agent" else PROMPT_WIRE)}
320
+
321
+ mgr = PromptManager(Two())
322
+ with wrap_agent(processor=processor, team_site_id="site-x", agent_name="chat"):
323
+ a = mgr.get("refund-agent", cache_ttl_seconds=0)
324
+ b = mgr.get("other-agent", cache_ttl_seconds=0)
325
+ with span("span-a", kind="llm"):
326
+ a.compile(q="x")
327
+ with span("span-b", kind="llm"):
328
+ b.compile(q="y")
329
+ with span("span-c"):
330
+ pass
331
+
332
+ spans = http.run_ingest[0]["spans"]
333
+ at = lambda n: (next(s for s in spans if s["name"] == n).get("attributes") or {})
334
+ assert at("span-a")["trodo.prompt.name"] == "refund-agent"
335
+ assert at("span-b")["trodo.prompt.name"] == "other-agent"
336
+ assert "trodo.prompt.name" not in at("span-c")
337
+ used = http.run_ingest[0]["run"]["attributes"]["trodo.prompts"]
338
+ assert sorted(r["name"] for r in used) == ["other-agent", "refund-agent"]
@@ -25,7 +25,7 @@ PromptRef = Dict[str, Any]
25
25
 
26
26
  def new_prompt_state() -> Dict[str, Any]:
27
27
  """Per-run accumulator, shared by reference across the run's span tree."""
28
- return {"current": None, "all": []}
28
+ return {"current": None, "current_span_id": None, "all": []}
29
29
 
30
30
 
31
31
  # Compiled prompt (or its messages list) -> the version it came from.
@@ -93,6 +93,7 @@ def record_compiled_prompt(ref: PromptRef) -> None:
93
93
  state = getattr(active, "prompt_state", None) if active is not None else None
94
94
  if isinstance(state, dict):
95
95
  state["current"] = ref
96
+ state["current_span_id"] = getattr(active, "span_id", None)
96
97
  key = _ref_key(ref)
97
98
  used: List[PromptRef] = state["all"]
98
99
  if not any(_ref_key(r) == key for r in used):
@@ -116,7 +117,11 @@ def prompt_attributes(ref: Optional[PromptRef]) -> Dict[str, str]:
116
117
 
117
118
 
118
119
  def merge_prompt_attrs(
119
- active: Any, attrs: Optional[Dict[str, Any]], from_input: Optional[PromptRef] = None
120
+ active: Any,
121
+ attrs: Optional[Dict[str, Any]],
122
+ from_input: Optional[PromptRef] = None,
123
+ kind: Optional[str] = None,
124
+ span_id: Optional[str] = None,
120
125
  ) -> Optional[Dict[str, Any]]:
121
126
  """Merge the prompt this span used into its attributes.
122
127
 
@@ -134,7 +139,24 @@ def merge_prompt_attrs(
134
139
  remember_prompt(state, from_input)
135
140
  merged.update(prompt_attributes(from_input))
136
141
  elif isinstance(state, dict):
137
- merged.update(prompt_attributes(state.get("current")))
142
+ # The run's "most recently compiled" prompt is a FALLBACK, used only
143
+ # where it is defensible. Applying it to every span meant compiling a
144
+ # prompt anywhere in a run attributed it to every LATER span -- a tool,
145
+ # a retrieval, a bit of plain work all claiming a prompt they never ran.
146
+ # Prompts belong to SPANS: one run's spans routinely use different
147
+ # prompts, different versions, or none at all.
148
+ #
149
+ # Two cases survive: a MODEL call (kind 'llm'), the only thing that runs
150
+ # a prompt and the one shape whose input we cannot read when it comes
151
+ # from a provider instrumentation; and the very span the prompt was
152
+ # compiled INSIDE, which plainly used it whatever its kind.
153
+ compiled_here = (
154
+ state.get("current") is not None
155
+ and span_id is not None
156
+ and state.get("current_span_id") == span_id
157
+ )
158
+ if kind == "llm" or compiled_here:
159
+ merged.update(prompt_attributes(state.get("current")))
138
160
  return merged or None
139
161
 
140
162
 
@@ -876,7 +876,8 @@ class join_run:
876
876
  temperature=self.handle.temperature,
877
877
  tool_name=self.handle.tool_name,
878
878
  attributes=merge_prompt_attrs(
879
- self._ctx, self.handle.attributes, self.handle.prompt_ref
879
+ self._ctx, self.handle.attributes, self.handle.prompt_ref,
880
+ self._kind, self._span_id,
880
881
  ),
881
882
  )
882
883
  try:
@@ -988,7 +989,8 @@ class span:
988
989
  temperature=self.handle.temperature,
989
990
  tool_name=self.handle.tool_name,
990
991
  attributes=merge_prompt_attrs(
991
- self._active, self.handle.attributes, self.handle.prompt_ref
992
+ self._active, self.handle.attributes, self.handle.prompt_ref,
993
+ self._kind, self._span_id,
992
994
  ),
993
995
  )
994
996
  processor: TrodoSpanProcessor = self._active.processor # type: ignore[assignment]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.23.1
3
+ Version: 2.23.2
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
File without changes
File without changes