trodo-python 2.10.3__tar.gz → 2.10.4__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-2.10.3 → trodo_python-2.10.4}/PKG-INFO +1 -1
- {trodo_python-2.10.3 → trodo_python-2.10.4}/pyproject.toml +1 -1
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/__init__.py +1 -1
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/client.py +9 -1
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/register.py +44 -2
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/wrap_agent.py +87 -2
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo_python.egg-info/PKG-INFO +1 -1
- {trodo_python-2.10.3 → trodo_python-2.10.4}/README.md +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/setup.cfg +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_anon_distinct_id 2.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_anon_distinct_id.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_auto_instrument_fixes.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_cross_process_session.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_end_run.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_error_enrichment.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_llm_usage_cost.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_processor_methods.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_register_otel.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_start_run.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/tests/test_wrap_agent_unchanged.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/api/__init__.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/api/async_client.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/api/endpoints.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/api/http_client.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/auto/__init__.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/auto/auto_event_manager.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/managers/__init__.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/managers/group_manager.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/managers/people_manager.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/__init__.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/auto_instrument.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/context.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/helpers.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/processor.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/otel/transport.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/queue/__init__.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/queue/batch_flusher.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/queue/event_queue.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/session/__init__.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/session/server_session.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/session/session_manager.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/types.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo/user_context.py +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo_python.egg-info/SOURCES.txt +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-2.10.3 → trodo_python-2.10.4}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -23,7 +23,11 @@ from .otel.wrap_agent import (
|
|
|
23
23
|
current_span_id as _current_span_id,
|
|
24
24
|
)
|
|
25
25
|
from .otel.auto_instrument import enable_auto_instrument
|
|
26
|
-
from .otel.register import
|
|
26
|
+
from .otel.register import (
|
|
27
|
+
register_otel as _register_otel,
|
|
28
|
+
flush_otlp_pipeline as _flush_otlp_pipeline,
|
|
29
|
+
shutdown_otlp_pipeline as _shutdown_otlp_pipeline,
|
|
30
|
+
)
|
|
27
31
|
from .otel.helpers import (
|
|
28
32
|
tool as tool_decorator,
|
|
29
33
|
track_llm_call as track_llm_call_fn,
|
|
@@ -233,6 +237,9 @@ class TrodoClient:
|
|
|
233
237
|
if self._batch_flusher:
|
|
234
238
|
self._batch_flusher.flush()
|
|
235
239
|
self._span_processor.force_flush()
|
|
240
|
+
# 'otlp' mode: drain the OTel BatchSpanProcessor too — it batches for
|
|
241
|
+
# up to 5s, so without this a short-lived process drops its spans.
|
|
242
|
+
_flush_otlp_pipeline()
|
|
236
243
|
|
|
237
244
|
def shutdown(self) -> None:
|
|
238
245
|
self._auto_event_manager.disable()
|
|
@@ -240,6 +247,7 @@ class TrodoClient:
|
|
|
240
247
|
self._batch_flusher.stop()
|
|
241
248
|
self._batch_flusher.flush()
|
|
242
249
|
self._span_processor.shutdown()
|
|
250
|
+
_shutdown_otlp_pipeline()
|
|
243
251
|
|
|
244
252
|
# --------------------------------------------------------------------------
|
|
245
253
|
# Agent Runs — Lemma-style seamless wrapping
|
|
@@ -36,6 +36,33 @@ class RegisterOtelResult:
|
|
|
36
36
|
|
|
37
37
|
_registered: Optional[RegisterOtelResult] = None
|
|
38
38
|
|
|
39
|
+
# Flush/shutdown handle for the 'otlp'-mode pipeline. Without this, the
|
|
40
|
+
# BatchSpanProcessor created in _setup_otlp_mode (default 5s schedule) is
|
|
41
|
+
# unreachable — trodo.flush()/shutdown() only drained the events batcher and
|
|
42
|
+
# TrodoSpanProcessor, so short-lived processes silently dropped every
|
|
43
|
+
# OTLP-exported span. TrodoClient.flush()/shutdown() call these.
|
|
44
|
+
_otlp_processor: Optional[Any] = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def flush_otlp_pipeline() -> None:
|
|
48
|
+
"""Force-flush the 'otlp'-mode export pipeline. No-op in 'trodo' mode."""
|
|
49
|
+
if _otlp_processor is not None:
|
|
50
|
+
try:
|
|
51
|
+
_otlp_processor.force_flush()
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def shutdown_otlp_pipeline() -> None:
|
|
57
|
+
"""Shut down the 'otlp'-mode export pipeline (flushes first). No-op in 'trodo' mode."""
|
|
58
|
+
global _otlp_processor
|
|
59
|
+
if _otlp_processor is not None:
|
|
60
|
+
proc, _otlp_processor = _otlp_processor, None
|
|
61
|
+
try:
|
|
62
|
+
proc.shutdown()
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
|
|
39
66
|
|
|
40
67
|
def register_otel(
|
|
41
68
|
*,
|
|
@@ -86,8 +113,9 @@ def register_otel(
|
|
|
86
113
|
|
|
87
114
|
def _reset_for_tests() -> None:
|
|
88
115
|
"""Reset internal state — for tests only. Not exported from __init__.py."""
|
|
89
|
-
global _registered
|
|
116
|
+
global _registered, _otlp_processor
|
|
90
117
|
_registered = None
|
|
118
|
+
_otlp_processor = None
|
|
91
119
|
_reset_transport_for_tests()
|
|
92
120
|
|
|
93
121
|
|
|
@@ -131,6 +159,11 @@ def _setup_otlp_mode(
|
|
|
131
159
|
headers={"Authorization": f"Bearer {site_id}"},
|
|
132
160
|
)
|
|
133
161
|
batch_processor = BatchSpanProcessor(exporter)
|
|
162
|
+
# Expose flush/shutdown so trodo.flush()/shutdown() can drain the batch —
|
|
163
|
+
# BatchSpanProcessor holds spans for up to 5s by default, so without this
|
|
164
|
+
# a short-lived process exits before the exporter ever sends them.
|
|
165
|
+
global _otlp_processor
|
|
166
|
+
_otlp_processor = batch_processor
|
|
134
167
|
|
|
135
168
|
# Coexistence: if a non-default TracerProvider is already registered,
|
|
136
169
|
# attach our exporter to it instead of replacing it.
|
|
@@ -162,7 +195,16 @@ def _setup_otlp_mode(
|
|
|
162
195
|
active = _build_instrumentations(disable)
|
|
163
196
|
|
|
164
197
|
# Activate the OTel transport so wrap_agent / span dispatch via tracer.
|
|
165
|
-
|
|
198
|
+
# Pass the use_span/Status/StatusCode helpers (same as the coexistence
|
|
199
|
+
# branch above) — without use_span, wrap_agent's root span is never made
|
|
200
|
+
# current, so trodo.span() children and auto-instrumented spans can't
|
|
201
|
+
# join its trace, and error status can never be stamped.
|
|
202
|
+
set_otel_transport(
|
|
203
|
+
otel_trace.get_tracer("trodo-agent"),
|
|
204
|
+
use_span=use_span,
|
|
205
|
+
status_cls=Status,
|
|
206
|
+
status_code=StatusCode,
|
|
207
|
+
)
|
|
166
208
|
return active
|
|
167
209
|
|
|
168
210
|
|
|
@@ -642,10 +642,12 @@ class wrap_agent:
|
|
|
642
642
|
assert self._otel_span is not None
|
|
643
643
|
otel_span = self._otel_span
|
|
644
644
|
try:
|
|
645
|
+
# OTel attribute values must be primitives — dicts/lists are
|
|
646
|
+
# rejected (with a warning) and silently dropped. Serialize.
|
|
645
647
|
if self.handle.input is not None:
|
|
646
|
-
otel_span.set_attribute("ai.prompt", self.handle.input)
|
|
648
|
+
otel_span.set_attribute("ai.prompt", _serialize_attr(self.handle.input))
|
|
647
649
|
if self.handle.output is not None:
|
|
648
|
-
otel_span.set_attribute("ai.response.text", self.handle.output)
|
|
650
|
+
otel_span.set_attribute("ai.response.text", _serialize_attr(self.handle.output))
|
|
649
651
|
for k, v in self.handle.metadata.items():
|
|
650
652
|
otel_span.set_attribute(f"trodo.metadata.{k}", _serialize_attr(v))
|
|
651
653
|
if exc is not None:
|
|
@@ -793,8 +795,17 @@ class span:
|
|
|
793
795
|
self._active: Optional[ActiveSpanContext] = None
|
|
794
796
|
self._span_id: str = ""
|
|
795
797
|
self.handle: Optional[SpanHandle] = None
|
|
798
|
+
# 'otlp' transport state — populated only when register_otel(mode='otlp').
|
|
799
|
+
self._otel_span: Optional[Any] = None
|
|
800
|
+
self._otel_cm: Optional[Any] = None
|
|
796
801
|
|
|
797
802
|
def __enter__(self) -> SpanHandle:
|
|
803
|
+
# Dispatch: 'otlp' transport routes through the OTel tracer so this
|
|
804
|
+
# span joins the wrap_agent root's trace (mirrors Node withSpanOtel).
|
|
805
|
+
# Without this branch, otlp-mode children hit the "no active run"
|
|
806
|
+
# no-op below and were silently dropped.
|
|
807
|
+
if get_transport_mode() == "otlp" and get_otel_tracer() is not None:
|
|
808
|
+
return self._enter_otel()
|
|
798
809
|
self._active = get_active_context()
|
|
799
810
|
self._span_id = str(uuid.uuid4())
|
|
800
811
|
self.handle = SpanHandle(self._span_id, self._name)
|
|
@@ -819,6 +830,8 @@ class span:
|
|
|
819
830
|
return self.handle
|
|
820
831
|
|
|
821
832
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
833
|
+
if self._otel_span is not None:
|
|
834
|
+
return self._exit_otel(exc_type, exc, tb)
|
|
822
835
|
if self._active is None or self.handle is None:
|
|
823
836
|
return None
|
|
824
837
|
ended_iso = _now_iso()
|
|
@@ -860,3 +873,75 @@ class span:
|
|
|
860
873
|
if self._ctx_mgr is not None:
|
|
861
874
|
self._ctx_mgr.__exit__(exc_type, exc, tb)
|
|
862
875
|
return None
|
|
876
|
+
|
|
877
|
+
# ----------------------------------------------------------------------
|
|
878
|
+
# 'otlp' transport — OTel tracer path (mirrors Node withSpanOtel).
|
|
879
|
+
# ----------------------------------------------------------------------
|
|
880
|
+
|
|
881
|
+
def _enter_otel(self) -> SpanHandle:
|
|
882
|
+
tracer = get_otel_tracer()
|
|
883
|
+
# start_span parents to the CURRENT OTel context — the wrap_agent root
|
|
884
|
+
# (made current via use_span in _enter_otel of wrap_agent).
|
|
885
|
+
otel_span = tracer.start_span(self._name)
|
|
886
|
+
use_span_helper, _, _ = get_otel_helpers()
|
|
887
|
+
if use_span_helper is not None:
|
|
888
|
+
self._otel_cm = use_span_helper(otel_span, end_on_exit=False)
|
|
889
|
+
self._otel_cm.__enter__()
|
|
890
|
+
self._otel_span = otel_span
|
|
891
|
+
self._span_id = str(uuid.uuid4())
|
|
892
|
+
self.handle = SpanHandle(self._span_id, self._name)
|
|
893
|
+
if self._input is not None:
|
|
894
|
+
self.handle.input = self._input
|
|
895
|
+
if self._attributes:
|
|
896
|
+
self.handle.attributes.update(self._attributes)
|
|
897
|
+
return self.handle
|
|
898
|
+
|
|
899
|
+
def _exit_otel(self, exc_type, exc, tb) -> None:
|
|
900
|
+
otel_span = self._otel_span
|
|
901
|
+
h = self.handle
|
|
902
|
+
try:
|
|
903
|
+
# Stamp Trodo kind via gen_ai.* / retrieval.* convention so the
|
|
904
|
+
# backend's kind inference picks it up (same keys as trodo-node).
|
|
905
|
+
if self._kind == "tool":
|
|
906
|
+
otel_span.set_attribute("gen_ai.tool.name", h.tool_name or self._name)
|
|
907
|
+
if self._kind == "retrieval":
|
|
908
|
+
otel_span.set_attribute("retrieval.query", self._name)
|
|
909
|
+
if h.input is not None:
|
|
910
|
+
otel_span.set_attribute("input.value", _serialize_attr(h.input))
|
|
911
|
+
if h.output is not None:
|
|
912
|
+
otel_span.set_attribute("output.value", _serialize_attr(h.output))
|
|
913
|
+
if h.model:
|
|
914
|
+
otel_span.set_attribute("gen_ai.request.model", h.model)
|
|
915
|
+
if h.provider:
|
|
916
|
+
otel_span.set_attribute("gen_ai.system", h.provider)
|
|
917
|
+
if h.input_tokens is not None:
|
|
918
|
+
otel_span.set_attribute("gen_ai.usage.input_tokens", h.input_tokens)
|
|
919
|
+
if h.output_tokens is not None:
|
|
920
|
+
otel_span.set_attribute("gen_ai.usage.output_tokens", h.output_tokens)
|
|
921
|
+
if h.cost is not None:
|
|
922
|
+
otel_span.set_attribute("gen_ai.usage.cost", h.cost)
|
|
923
|
+
if h.temperature is not None:
|
|
924
|
+
otel_span.set_attribute("gen_ai.request.temperature", h.temperature)
|
|
925
|
+
if h.tool_name:
|
|
926
|
+
otel_span.set_attribute("gen_ai.tool.name", h.tool_name)
|
|
927
|
+
for k, v in (h.attributes or {}).items():
|
|
928
|
+
otel_span.set_attribute(f"trodo.span.{k}", _serialize_attr(v))
|
|
929
|
+
einfo = _resolve_error(h, exc_type, exc, tb)
|
|
930
|
+
if einfo["status"] == "error":
|
|
931
|
+
if exc is not None and hasattr(otel_span, "record_exception"):
|
|
932
|
+
otel_span.record_exception(exc)
|
|
933
|
+
_, status_cls, status_code = get_otel_helpers()
|
|
934
|
+
if status_cls is not None and status_code is not None:
|
|
935
|
+
otel_span.set_status(
|
|
936
|
+
status_cls(status_code.ERROR, einfo["error_message"] or "")
|
|
937
|
+
)
|
|
938
|
+
finally:
|
|
939
|
+
otel_span.end()
|
|
940
|
+
if self._otel_cm is not None:
|
|
941
|
+
try:
|
|
942
|
+
self._otel_cm.__exit__(exc_type, exc, tb)
|
|
943
|
+
except Exception:
|
|
944
|
+
pass
|
|
945
|
+
self._otel_span = None
|
|
946
|
+
self._otel_cm = None
|
|
947
|
+
return None
|
|
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
|