trodo-python 2.18.0__tar.gz → 2.18.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 (64) hide show
  1. {trodo_python-2.18.0 → trodo_python-2.18.2}/PKG-INFO +1 -1
  2. {trodo_python-2.18.0 → trodo_python-2.18.2}/pyproject.toml +1 -1
  3. trodo_python-2.18.2/tests/test_get_prompt_version_types.py +54 -0
  4. trodo_python-2.18.2/tests/test_multi_agent_shape.py +106 -0
  5. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/__init__.py +18 -3
  6. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/client.py +4 -0
  7. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/PKG-INFO +1 -1
  8. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/SOURCES.txt +2 -0
  9. {trodo_python-2.18.0 → trodo_python-2.18.2}/README.md +0 -0
  10. {trodo_python-2.18.0 → trodo_python-2.18.2}/setup.cfg +0 -0
  11. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_anon_distinct_id.py +0 -0
  12. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_auto_instrument_fixes.py +0 -0
  13. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_cross_process_session.py +0 -0
  14. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_datasets.py +0 -0
  15. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_end_run.py +0 -0
  16. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_error_enrichment.py +0 -0
  17. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_experiments.py +0 -0
  18. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_llm_usage_cost.py +0 -0
  19. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_processor_methods.py +0 -0
  20. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_prompt_trace.py +0 -0
  21. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_prompts.py +0 -0
  22. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_register_otel.py +0 -0
  23. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_start_run.py +0 -0
  24. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_user_traits.py +0 -0
  25. {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_wrap_agent_unchanged.py +0 -0
  26. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/__init__.py +0 -0
  27. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/async_client.py +0 -0
  28. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/endpoints.py +0 -0
  29. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/http_client.py +0 -0
  30. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/auto/__init__.py +0 -0
  31. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/auto/auto_event_manager.py +0 -0
  32. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/__init__.py +0 -0
  33. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/dataset_manager.py +0 -0
  34. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/experiment_manager.py +0 -0
  35. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/group_manager.py +0 -0
  36. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/people_manager.py +0 -0
  37. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/prompt_manager.py +0 -0
  38. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/user_manager.py +0 -0
  39. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/__init__.py +0 -0
  40. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/auto_instrument.py +0 -0
  41. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/context.py +0 -0
  42. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/helpers.py +0 -0
  43. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/processor.py +0 -0
  44. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/prompt_trace.py +0 -0
  45. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/register.py +0 -0
  46. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/transport.py +0 -0
  47. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/wrap_agent.py +0 -0
  48. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/__init__.py +0 -0
  49. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/compile.py +0 -0
  50. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/template.py +0 -0
  51. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/types.py +0 -0
  52. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/queue/__init__.py +0 -0
  53. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/queue/batch_flusher.py +0 -0
  54. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/queue/event_queue.py +0 -0
  55. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/session/__init__.py +0 -0
  56. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/session/server_session.py +0 -0
  57. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/session/session_manager.py +0 -0
  58. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/types.py +0 -0
  59. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/user_context.py +0 -0
  60. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/util/__init__.py +0 -0
  61. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/util/lru.py +0 -0
  62. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/dependency_links.txt +0 -0
  63. {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/requires.txt +0 -0
  64. {trodo_python-2.18.0 → trodo_python-2.18.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.18.0
3
+ Version: 2.18.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.18.0"
7
+ version = "2.18.2"
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,54 @@
1
+ """The public get_prompt must accept a version_hash, not just an int.
2
+
3
+ The manager has always taken ``Union[int, str]`` and documents hash support —
4
+ the hash is the stable identity the dashboard shows and the SDK returns. But
5
+ the top-level ``trodo.get_prompt`` wrapper narrowed the hint to ``int``, so a
6
+ type checker flagged the documented usage, and the dashboard's own "Use this
7
+ prompt" snippet emits ``version="<hash>"`` for Python.
8
+
9
+ Checked by signature rather than by a network call: the wrapper only forwards,
10
+ so the contract IS the signature.
11
+ """
12
+ import inspect
13
+ import typing
14
+
15
+ import trodo
16
+ from trodo.managers.prompt_manager import PromptManager
17
+
18
+
19
+ def _version_annotation(fn):
20
+ return typing.get_type_hints(fn)["version"]
21
+
22
+
23
+ def test_public_get_prompt_accepts_int_or_str():
24
+ ann = _version_annotation(trodo.get_prompt)
25
+ args = typing.get_args(ann)
26
+ assert int in args, f"version should accept int, got {ann}"
27
+ assert str in args, f"version should accept a version_hash (str), got {ann}"
28
+
29
+
30
+ def test_matches_the_manager_it_forwards_to():
31
+ assert _version_annotation(trodo.get_prompt) == _version_annotation(PromptManager.get)
32
+
33
+
34
+ def test_forwards_version_through_untouched():
35
+ """A hash must reach the manager as-is — not coerced, not dropped."""
36
+ seen = {}
37
+
38
+ class _Stub:
39
+ def get(self, name, **kwargs):
40
+ seen.update(kwargs, name=name)
41
+ return "sentinel"
42
+
43
+ class _Client:
44
+ prompts = _Stub()
45
+
46
+ original = trodo._get_client
47
+ trodo._get_client = lambda: _Client()
48
+ try:
49
+ assert trodo.get_prompt("p", version="a3f9c2") == "sentinel"
50
+ finally:
51
+ trodo._get_client = original
52
+
53
+ assert seen["version"] == "a3f9c2"
54
+ assert seen["label"] is None
@@ -0,0 +1,106 @@
1
+ """Multi-agent run shape — one run, sub-agents as ``agent``-kind child spans.
2
+
3
+ The shape the ``trodo-tracing`` skill teaches: a request that fans out to
4
+ sub-agents produces ONE run whose tree carries an ``agent`` span per sub-agent,
5
+ with each sub-agent's own work nested underneath it. The failure mode this
6
+ guards against is a ``wrap_agent`` per sub-agent, which shatters one request
7
+ into N disconnected runs and loses whole-request cost, latency and the
8
+ delegation tree.
9
+
10
+ Also covers the module-level ``user`` / ``parent_run_id`` kwargs, which the
11
+ skill's guidance writes and which the top-level wrappers used to silently drop.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import inspect
17
+
18
+ import trodo
19
+ from trodo.otel.wrap_agent import span as span_ctx
20
+ from trodo.otel.wrap_agent import wrap_agent as wrap_agent_ctx
21
+
22
+
23
+ def _tree(payload):
24
+ """{span_id: (name, kind, parent_span_id)} for one ingested run payload."""
25
+ return {s["span_id"]: (s["name"], s["kind"], s["parent_span_id"]) for s in payload["spans"]}
26
+
27
+
28
+ def test_sub_agents_nest_as_agent_spans_in_one_run(processor, http):
29
+ """A supervisor with two workers and a writer = 1 run, 3 agent spans."""
30
+ with wrap_agent_ctx(
31
+ processor=processor, team_site_id="site", agent_name="research_assistant",
32
+ distinct_id="u_1", conversation_id="thread_1",
33
+ ) as run:
34
+ run.set_input([{"role": "user", "content": "why did revenue drop"}])
35
+
36
+ with span_ctx("planner", kind="agent") as s:
37
+ s.set_output({"subtasks": ["pricing", "churn"]})
38
+
39
+ for topic in ("pricing", "churn"):
40
+ with span_ctx(f"researcher:{topic}", kind="agent") as s:
41
+ s.set_attribute("topic", topic)
42
+ # The sub-agent's own tool call nests under the sub-agent, not the run.
43
+ with span_ctx("web_search", kind="tool") as t:
44
+ t.set_tool("web_search")
45
+ t.set_output({"hits": 3})
46
+ s.set_output({"topic": topic})
47
+
48
+ run.set_output({"answer": "pricing change in March"})
49
+
50
+ # Exactly ONE run — not one per sub-agent.
51
+ assert len(http.run_ingest) == 1
52
+ payload = http.run_ingest[0]
53
+ assert payload["run"]["agent_name"] == "research_assistant"
54
+ assert payload["run"]["conversation_id"] == "thread_1"
55
+
56
+ tree = _tree(payload)
57
+ by_name = {v[0]: (sid, v[1], v[2]) for sid, v in tree.items()}
58
+
59
+ # Three agent-kind sub-agent spans, all siblings directly under the run root.
60
+ parents = set()
61
+ for name in ("planner", "researcher:pricing", "researcher:churn"):
62
+ sid, kind, parent = by_name[name]
63
+ assert kind == "agent", f"{name} should be an agent span, got {kind}"
64
+ parents.add(parent)
65
+ assert len(parents) == 1, "sub-agents should be siblings under one run root"
66
+
67
+ # Each tool span nests under its own researcher, preserving the delegation tree.
68
+ tool_parents = {v[2] for sid, v in tree.items() if v[1] == "tool"}
69
+ researcher_ids = {by_name[n][0] for n in ("researcher:pricing", "researcher:churn")}
70
+ assert tool_parents == researcher_ids
71
+
72
+ # The run rolls up the whole request, which is the point of keeping it one run.
73
+ assert payload["run"]["span_count"] == 5
74
+ assert payload["run"]["tool_count"] == 2
75
+
76
+
77
+ def test_independently_triggered_sub_agent_links_via_parent_run_id(processor, http):
78
+ """The other shape: separate runs, linked — for independently-triggered work."""
79
+ with wrap_agent_ctx(
80
+ processor=processor, team_site_id="site", agent_name="intake", distinct_id="u_1",
81
+ ) as parent:
82
+ parent.set_output({"job": "enrich"})
83
+
84
+ with wrap_agent_ctx(
85
+ processor=processor, team_site_id="site", agent_name="enrichment",
86
+ distinct_id="u_1", parent_run_id=parent.run_id,
87
+ ) as child:
88
+ child.set_output({"done": True})
89
+
90
+ assert len(http.run_ingest) == 2
91
+ assert http.run_ingest[1]["run"]["parent_run_id"] == parent.run_id
92
+
93
+
94
+ def test_module_level_wrappers_forward_user_and_parent_run_id():
95
+ """``trodo.wrap_agent(..., user=...)`` is what the skill tells people to write.
96
+
97
+ The module-level wrappers used to drop ``user`` on the floor (and ``agent``
98
+ also dropped ``parent_run_id``), so the documented call raised TypeError.
99
+ """
100
+ for fn, expected in (
101
+ (trodo.wrap_agent, {"user", "parent_run_id", "conversation_id", "metadata"}),
102
+ (trodo.agent, {"user", "parent_run_id", "conversation_id", "metadata"}),
103
+ (trodo.start_run, {"user", "parent_run_id", "conversation_id", "metadata", "input"}),
104
+ ):
105
+ params = set(inspect.signature(fn).parameters)
106
+ assert expected <= params, f"{fn.__name__} is missing {expected - params}"
@@ -322,7 +322,7 @@ def reset(distinct_id: str) -> ResetResult:
322
322
  def get_prompt(
323
323
  name: str,
324
324
  label: Optional[str] = None,
325
- version: Optional[int] = None,
325
+ version: Optional[Union[int, str]] = None,
326
326
  cache_ttl_seconds: Optional[float] = None,
327
327
  fallback: Optional[Dict[str, Any]] = None,
328
328
  max_retries: int = 2,
@@ -330,7 +330,9 @@ def get_prompt(
330
330
  """Fetch a managed prompt by name.
331
331
 
332
332
  Follows the ``production`` label by default; pass ``label`` for a different
333
- one or ``version`` to pin exactly.
333
+ one or ``version`` to pin exactly. ``version`` may be the integer
334
+ ``version_no`` or a ``version_hash`` (full, or an unambiguous short prefix
335
+ like ``a3f9c2``) — the hash is the stable identity, so prefer it.
334
336
 
335
337
  Cached for 60s with stale-while-revalidate, so a Trodo outage degrades
336
338
  instead of taking your app down. Pass ``fallback`` to cover cold start::
@@ -524,14 +526,21 @@ def wrap_agent(
524
526
  conversation_id: Optional[str] = None,
525
527
  parent_run_id: Optional[str] = None,
526
528
  metadata: Optional[Dict[str, Any]] = None,
529
+ user: Optional[Dict[str, Any]] = None,
527
530
  ) -> _wrap_agent_ctx:
528
- """Wrap a block as an agent run — returns a context manager yielding RunHandle."""
531
+ """Wrap a block as an agent run — returns a context manager yielding RunHandle.
532
+
533
+ ``user`` carries profile traits alongside the run — ``{"properties": ...,
534
+ "set_once": ..., "fixed_properties": ...}`` — so a request handler can
535
+ attribute the run and enrich the profile in one round trip.
536
+ """
529
537
  return _get_client().wrap_agent(
530
538
  agent_name,
531
539
  distinct_id=distinct_id,
532
540
  conversation_id=conversation_id,
533
541
  parent_run_id=parent_run_id,
534
542
  metadata=metadata,
543
+ user=user,
535
544
  )
536
545
 
537
546
 
@@ -540,14 +549,18 @@ def agent(
540
549
  *,
541
550
  distinct_id: Optional[str] = None,
542
551
  conversation_id: Optional[str] = None,
552
+ parent_run_id: Optional[str] = None,
543
553
  metadata: Optional[Dict[str, Any]] = None,
554
+ user: Optional[Dict[str, Any]] = None,
544
555
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
545
556
  """Decorator form of wrap_agent."""
546
557
  return _get_client().agent(
547
558
  agent_name,
548
559
  distinct_id=distinct_id,
549
560
  conversation_id=conversation_id,
561
+ parent_run_id=parent_run_id,
550
562
  metadata=metadata,
563
+ user=user,
551
564
  )
552
565
 
553
566
 
@@ -591,6 +604,7 @@ def start_run(
591
604
  parent_run_id: Optional[str] = None,
592
605
  metadata: Optional[Dict[str, Any]] = None,
593
606
  input: Any = None,
607
+ user: Optional[Dict[str, Any]] = None,
594
608
  ) -> str:
595
609
  """Open a Run record without a context manager.
596
610
 
@@ -607,6 +621,7 @@ def start_run(
607
621
  parent_run_id=parent_run_id,
608
622
  metadata=metadata,
609
623
  input=input,
624
+ user=user,
610
625
  )
611
626
 
612
627
 
@@ -357,7 +357,9 @@ class TrodoClient:
357
357
  *,
358
358
  distinct_id: Optional[str] = None,
359
359
  conversation_id: Optional[str] = None,
360
+ parent_run_id: Optional[str] = None,
360
361
  metadata: Optional[Dict[str, Any]] = None,
362
+ user: Optional[Dict[str, Any]] = None,
361
363
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
362
364
  """Decorator form of wrap_agent."""
363
365
 
@@ -368,7 +370,9 @@ class TrodoClient:
368
370
  agent_name,
369
371
  distinct_id=distinct_id,
370
372
  conversation_id=conversation_id,
373
+ parent_run_id=parent_run_id,
371
374
  metadata=metadata,
375
+ user=user,
372
376
  ) as run:
373
377
  result = fn(*args, **kwargs)
374
378
  run.set_output(result)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.18.0
3
+ Version: 2.18.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
@@ -7,7 +7,9 @@ tests/test_datasets.py
7
7
  tests/test_end_run.py
8
8
  tests/test_error_enrichment.py
9
9
  tests/test_experiments.py
10
+ tests/test_get_prompt_version_types.py
10
11
  tests/test_llm_usage_cost.py
12
+ tests/test_multi_agent_shape.py
11
13
  tests/test_processor_methods.py
12
14
  tests/test_prompt_trace.py
13
15
  tests/test_prompts.py
File without changes
File without changes