trodo-python 2.23.2__tar.gz → 2.23.3__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 (66) hide show
  1. {trodo_python-2.23.2 → trodo_python-2.23.3}/PKG-INFO +1 -1
  2. {trodo_python-2.23.2 → trodo_python-2.23.3}/pyproject.toml +1 -1
  3. trodo_python-2.23.3/tests/test_framework_deference.py +41 -0
  4. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/auto_instrument.py +45 -0
  5. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo_python.egg-info/PKG-INFO +1 -1
  6. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo_python.egg-info/SOURCES.txt +1 -0
  7. {trodo_python-2.23.2 → trodo_python-2.23.3}/README.md +0 -0
  8. {trodo_python-2.23.2 → trodo_python-2.23.3}/setup.cfg +0 -0
  9. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_anon_distinct_id.py +0 -0
  10. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_auto_instrument_fixes.py +0 -0
  11. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_cross_process_session.py +0 -0
  12. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_datasets.py +0 -0
  13. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_end_run.py +0 -0
  14. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_error_enrichment.py +0 -0
  15. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_experiments.py +0 -0
  16. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_get_prompt_version_types.py +0 -0
  17. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_instrumentor_resolution.py +0 -0
  18. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_llm_usage_cost.py +0 -0
  19. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_multi_agent_shape.py +0 -0
  20. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_processor_methods.py +0 -0
  21. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_prompt_trace.py +0 -0
  22. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_prompts.py +0 -0
  23. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_register_otel.py +0 -0
  24. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_start_run.py +0 -0
  25. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_user_traits.py +0 -0
  26. {trodo_python-2.23.2 → trodo_python-2.23.3}/tests/test_wrap_agent_unchanged.py +0 -0
  27. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/__init__.py +0 -0
  28. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/api/__init__.py +0 -0
  29. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/api/async_client.py +0 -0
  30. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/api/endpoints.py +0 -0
  31. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/api/http_client.py +0 -0
  32. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/auto/__init__.py +0 -0
  33. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/auto/auto_event_manager.py +0 -0
  34. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/client.py +0 -0
  35. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/__init__.py +0 -0
  36. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/dataset_manager.py +0 -0
  37. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/experiment_manager.py +0 -0
  38. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/group_manager.py +0 -0
  39. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/people_manager.py +0 -0
  40. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/prompt_manager.py +0 -0
  41. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/managers/user_manager.py +0 -0
  42. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/__init__.py +0 -0
  43. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/context.py +0 -0
  44. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/helpers.py +0 -0
  45. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/processor.py +0 -0
  46. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/prompt_trace.py +0 -0
  47. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/register.py +0 -0
  48. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/transport.py +0 -0
  49. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/otel/wrap_agent.py +0 -0
  50. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/prompts/__init__.py +0 -0
  51. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/prompts/compile.py +0 -0
  52. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/prompts/template.py +0 -0
  53. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/prompts/types.py +0 -0
  54. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/queue/__init__.py +0 -0
  55. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/queue/batch_flusher.py +0 -0
  56. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/queue/event_queue.py +0 -0
  57. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/session/__init__.py +0 -0
  58. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/session/server_session.py +0 -0
  59. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/session/session_manager.py +0 -0
  60. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/types.py +0 -0
  61. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/user_context.py +0 -0
  62. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/util/__init__.py +0 -0
  63. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo/util/lru.py +0 -0
  64. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo_python.egg-info/dependency_links.txt +0 -0
  65. {trodo_python-2.23.2 → trodo_python-2.23.3}/trodo_python.egg-info/requires.txt +0 -0
  66. {trodo_python-2.23.2 → trodo_python-2.23.3}/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.2
3
+ Version: 2.23.3
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.2"
7
+ version = "2.23.3"
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,41 @@
1
+ """One model call must produce one llm span.
2
+
3
+ langchain-openai depends on the `openai` package, so a chain invocation IS a
4
+ provider client call. With both instrumentors registered that single call emits
5
+ two sibling llm spans carrying identical tokens, and the run's totals double.
6
+ Measured on the Node side against real traffic: a 45/2 call reported 90/4. Cost
7
+ escaped doubling only by luck -- the framework's duplicate reported
8
+ model=unknown and so could not be priced at all.
9
+
10
+ The framework instrumentor therefore defers to the provider one, which knows the
11
+ real model id and can be priced.
12
+ """
13
+ from trodo.otel.auto_instrument import _superseding_providers
14
+
15
+
16
+ def test_langchain_defers_to_openai():
17
+ assert "openai" in _superseding_providers("langchain", ["anthropic", "openai"])
18
+
19
+
20
+ def test_langchain_defers_to_anthropic():
21
+ assert "anthropic" in _superseding_providers("langchain", ["anthropic"])
22
+
23
+
24
+ def test_langchain_registers_when_no_provider_it_routes_through_is_active():
25
+ # LangChain over a provider we do not instrument: its span is then the ONLY
26
+ # capture, and suppressing it would lose the call entirely.
27
+ assert _superseding_providers("langchain", ["google_generativeai"]) == []
28
+ assert _superseding_providers("langchain", []) == []
29
+
30
+
31
+ def test_providers_never_defer_to_each_other():
32
+ # Two providers are two different clients; there is nothing to double.
33
+ assert _superseding_providers("openai", ["anthropic"]) == []
34
+ assert _superseding_providers("anthropic", ["openai"]) == []
35
+
36
+
37
+ def test_only_lists_providers_registered_before_the_framework():
38
+ # The check reads `active`, built in registration order (anthropic, openai,
39
+ # openai_v2, langchain, ...). Listing bedrock would read right and never fire.
40
+ got = _superseding_providers("langchain", ["openai", "anthropic", "bedrock", "cohere"])
41
+ assert "bedrock" not in got and "cohere" not in got
@@ -554,6 +554,33 @@ def _register_instrumentors() -> None:
554
554
  ]
555
555
 
556
556
 
557
+ # Framework instrumentors that call a provider client we ALSO instrument.
558
+ #
559
+ # langchain-openai depends on the `openai` package, langchain-anthropic on
560
+ # `anthropic` -- so one chain invocation is also a genuine provider client call.
561
+ # With both instrumentors registered that single call emits TWO sibling llm
562
+ # spans carrying the same tokens, and the run's totals come out doubled.
563
+ # Measured on the Node side against real traffic: a 45/2 call reported 90/4.
564
+ #
565
+ # The provider span is the better of the two: it carries the real model id, so
566
+ # it can be priced, while the framework's reported model=unknown and could not
567
+ # be. So the framework instrumentor defers.
568
+ #
569
+ # Only providers registered BEFORE the framework in _INSTRUMENTORS may appear
570
+ # here -- the check reads `active`, which is built in registration order. That
571
+ # order is anthropic, openai, openai_v2, langchain, ... so those are covered;
572
+ # listing bedrock or cohere would read correctly and never fire.
573
+ _ROUTES_THROUGH: dict[str, tuple[str, ...]] = {
574
+ "langchain": ("openai", "openai_v2", "anthropic"),
575
+ "llama_index": ("openai", "openai_v2", "anthropic"),
576
+ }
577
+
578
+
579
+ def _superseding_providers(name: str, active: List[str]) -> List[str]:
580
+ """Provider instrumentors already active that `name` would double-count."""
581
+ return [p for p in _ROUTES_THROUGH.get(name, ()) if p in active]
582
+
583
+
557
584
  def enable_auto_instrument(
558
585
  processor: TrodoSpanProcessor,
559
586
  disable: Optional[Iterable[str]] = None,
@@ -590,6 +617,24 @@ def enable_auto_instrument(
590
617
  for name, register in _INSTRUMENTORS:
591
618
  if name in disabled:
592
619
  continue
620
+ # BEFORE registering: a framework instrumentor whose model calls are
621
+ # already captured by an active provider instrumentor would double every
622
+ # token. Checked here rather than after, because instrumentors patch on
623
+ # instrument() and undoing it afterwards is too late.
624
+ superseded = _superseding_providers(name, active)
625
+ if superseded:
626
+ _warn_once(
627
+ f"superseded-{name}",
628
+ f"auto-instrument: '{name}' was NOT loaded because "
629
+ f"{', '.join(superseded)} instrumentation is already active and "
630
+ f"{name} calls those clients directly -- running both makes one "
631
+ f"model call emit two llm spans and doubles its tokens and cost. "
632
+ f"Your {name} calls are still captured, by the provider "
633
+ f"instrumentation, which also records the real model id so the "
634
+ f"span can be priced. To choose the other way round, pass "
635
+ f"disable=['{superseded[0]}'].",
636
+ )
637
+ continue
593
638
  try:
594
639
  register()
595
640
  active.append(name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.23.2
3
+ Version: 2.23.3
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,6 +7,7 @@ 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_framework_deference.py
10
11
  tests/test_get_prompt_version_types.py
11
12
  tests/test_instrumentor_resolution.py
12
13
  tests/test_llm_usage_cost.py
File without changes
File without changes