trodo-python 2.5.0__py3-none-any.whl → 2.7.0__py3-none-any.whl
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/__init__.py +1 -1
- trodo/otel/processor.py +5 -5
- trodo/otel/wrap_agent.py +38 -14
- trodo/session/server_session.py +21 -32
- {trodo_python-2.5.0.dist-info → trodo_python-2.7.0.dist-info}/METADATA +1 -1
- {trodo_python-2.5.0.dist-info → trodo_python-2.7.0.dist-info}/RECORD +8 -8
- {trodo_python-2.5.0.dist-info → trodo_python-2.7.0.dist-info}/WHEEL +0 -0
- {trodo_python-2.5.0.dist-info → trodo_python-2.7.0.dist-info}/top_level.txt +0 -0
trodo/__init__.py
CHANGED
trodo/otel/processor.py
CHANGED
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import threading
|
|
11
11
|
from dataclasses import dataclass, asdict
|
|
12
|
-
from typing import Any, Dict, List, Optional
|
|
12
|
+
from typing import Any, Dict, List, Optional, Union
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@dataclass
|
|
@@ -20,8 +20,8 @@ class TrodoRun:
|
|
|
20
20
|
conversation_id: Optional[str] = None
|
|
21
21
|
parent_run_id: Optional[str] = None
|
|
22
22
|
status: str = "ok" # 'running' | 'ok' | 'error'
|
|
23
|
-
input: Optional[str] = None
|
|
24
|
-
output: Optional[str] = None
|
|
23
|
+
input: Optional[Union[str, Dict[str, Any]]] = None
|
|
24
|
+
output: Optional[Union[str, Dict[str, Any]]] = None
|
|
25
25
|
started_at: Optional[str] = None
|
|
26
26
|
ended_at: Optional[str] = None
|
|
27
27
|
duration_ms: Optional[int] = None
|
|
@@ -50,8 +50,8 @@ class TrodoSpan:
|
|
|
50
50
|
started_at: Optional[str] = None
|
|
51
51
|
ended_at: Optional[str] = None
|
|
52
52
|
duration_ms: Optional[int] = None
|
|
53
|
-
input: Optional[str] = None
|
|
54
|
-
output: Optional[str] = None
|
|
53
|
+
input: Optional[Union[str, Dict[str, Any]]] = None
|
|
54
|
+
output: Optional[Union[str, Dict[str, Any]]] = None
|
|
55
55
|
error_type: Optional[str] = None
|
|
56
56
|
error_message: Optional[str] = None
|
|
57
57
|
model: Optional[str] = None
|
trodo/otel/wrap_agent.py
CHANGED
|
@@ -28,7 +28,7 @@ import json
|
|
|
28
28
|
import time
|
|
29
29
|
import uuid
|
|
30
30
|
from datetime import datetime, timezone
|
|
31
|
-
from typing import Any, Callable, Dict, Optional
|
|
31
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
32
32
|
|
|
33
33
|
from .context import ActiveSpanContext, get_active_context, run_with_context
|
|
34
34
|
from .processor import TrodoSpanProcessor, TrodoRun, TrodoSpan
|
|
@@ -62,7 +62,11 @@ def _now_iso() -> str:
|
|
|
62
62
|
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
_MAX_VALUE_LEN = 1_000_000 # 1 MB
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _truncate(value: Any, max_len: int = _MAX_VALUE_LEN) -> Optional[str]:
|
|
69
|
+
"""Coerce to string and truncate. Use for string-only contexts (OTel attrs, error summaries)."""
|
|
66
70
|
if value is None:
|
|
67
71
|
return None
|
|
68
72
|
if isinstance(value, str):
|
|
@@ -75,6 +79,26 @@ def _truncate(value: Any, max_len: int = 64_000) -> Optional[str]:
|
|
|
75
79
|
return s[:max_len] if len(s) > max_len else s
|
|
76
80
|
|
|
77
81
|
|
|
82
|
+
def _prepare_value(value: Any, max_len: int = _MAX_VALUE_LEN) -> Optional[Union[str, Dict[str, Any]]]:
|
|
83
|
+
"""Prepare a value for storage in the JSONB input/output column.
|
|
84
|
+
|
|
85
|
+
Dicts/lists pass through as-is (stored as JSONB objects/arrays).
|
|
86
|
+
Strings are truncated at max_len.
|
|
87
|
+
Everything else is JSON-serialised then truncated.
|
|
88
|
+
"""
|
|
89
|
+
if value is None:
|
|
90
|
+
return None
|
|
91
|
+
if isinstance(value, dict):
|
|
92
|
+
return value
|
|
93
|
+
if isinstance(value, str):
|
|
94
|
+
return value[:max_len] if len(value) > max_len else value
|
|
95
|
+
try:
|
|
96
|
+
s = json.dumps(value, default=str)
|
|
97
|
+
except Exception:
|
|
98
|
+
s = str(value)
|
|
99
|
+
return s[:max_len] if len(s) > max_len else s
|
|
100
|
+
|
|
101
|
+
|
|
78
102
|
def current_run_id() -> Optional[str]:
|
|
79
103
|
"""Return the run_id of the currently active agent run, if any."""
|
|
80
104
|
ctx = get_active_context()
|
|
@@ -145,15 +169,15 @@ class RunHandle:
|
|
|
145
169
|
# Always populated — wrap_agent mints anon if caller didn't pass one
|
|
146
170
|
# so downstream ``trodo.feedback(distinct_id=...)`` always has a target.
|
|
147
171
|
self.distinct_id = distinct_id
|
|
148
|
-
self.input: Optional[str] = None
|
|
149
|
-
self.output: Optional[str] = None
|
|
172
|
+
self.input: Optional[Union[str, Dict[str, Any]]] = None
|
|
173
|
+
self.output: Optional[Union[str, Dict[str, Any]]] = None
|
|
150
174
|
self.metadata: Dict[str, Any] = {}
|
|
151
175
|
|
|
152
176
|
def set_input(self, value: Any) -> None:
|
|
153
|
-
self.input =
|
|
177
|
+
self.input = _prepare_value(value)
|
|
154
178
|
|
|
155
179
|
def set_output(self, value: Any) -> None:
|
|
156
|
-
self.output =
|
|
180
|
+
self.output = _prepare_value(value)
|
|
157
181
|
|
|
158
182
|
def set_metadata(self, **kwargs: Any) -> None:
|
|
159
183
|
self.metadata.update(kwargs)
|
|
@@ -165,8 +189,8 @@ class SpanHandle:
|
|
|
165
189
|
def __init__(self, span_id: str, name: str) -> None:
|
|
166
190
|
self.span_id = span_id
|
|
167
191
|
self.name = name
|
|
168
|
-
self.input: Optional[str] = None
|
|
169
|
-
self.output: Optional[str] = None
|
|
192
|
+
self.input: Optional[Union[str, Dict[str, Any]]] = None
|
|
193
|
+
self.output: Optional[Union[str, Dict[str, Any]]] = None
|
|
170
194
|
self.attributes: Dict[str, Any] = {}
|
|
171
195
|
self.model: Optional[str] = None
|
|
172
196
|
self.provider: Optional[str] = None
|
|
@@ -177,10 +201,10 @@ class SpanHandle:
|
|
|
177
201
|
self.tool_name: Optional[str] = None
|
|
178
202
|
|
|
179
203
|
def set_input(self, value: Any) -> None:
|
|
180
|
-
self.input =
|
|
204
|
+
self.input = _prepare_value(value)
|
|
181
205
|
|
|
182
206
|
def set_output(self, value: Any) -> None:
|
|
183
|
-
self.output =
|
|
207
|
+
self.output = _prepare_value(value)
|
|
184
208
|
|
|
185
209
|
def set_attribute(self, key: str, value: Any) -> None:
|
|
186
210
|
self.attributes[key] = value
|
|
@@ -247,7 +271,7 @@ def start_run(
|
|
|
247
271
|
conversation_id=conversation_id,
|
|
248
272
|
parent_run_id=parent_run_id,
|
|
249
273
|
status="running",
|
|
250
|
-
input=
|
|
274
|
+
input=_prepare_value(input),
|
|
251
275
|
started_at=_now_iso(),
|
|
252
276
|
metadata=metadata,
|
|
253
277
|
)
|
|
@@ -279,7 +303,7 @@ def end_run(
|
|
|
279
303
|
**agg,
|
|
280
304
|
}
|
|
281
305
|
if output is not None:
|
|
282
|
-
payload["output"] =
|
|
306
|
+
payload["output"] = _prepare_value(output)
|
|
283
307
|
if error_summary is not None:
|
|
284
308
|
payload["error_summary"] = _truncate(error_summary, 4_000)
|
|
285
309
|
if metadata is not None:
|
|
@@ -493,7 +517,7 @@ class join_run:
|
|
|
493
517
|
self._parent_span_id = parent_span_id
|
|
494
518
|
self._name = name
|
|
495
519
|
self._kind = kind
|
|
496
|
-
self._input =
|
|
520
|
+
self._input = _prepare_value(input) if input is not None else None
|
|
497
521
|
self._attributes = attributes
|
|
498
522
|
self._ctx_mgr: Optional[run_with_context] = None
|
|
499
523
|
self._started_ms: float = 0.0
|
|
@@ -580,7 +604,7 @@ class span:
|
|
|
580
604
|
) -> None:
|
|
581
605
|
self._name = name
|
|
582
606
|
self._kind = kind
|
|
583
|
-
self._input =
|
|
607
|
+
self._input = _prepare_value(input) if input is not None else None
|
|
584
608
|
self._attributes = attributes
|
|
585
609
|
self._ctx_mgr: Optional[run_with_context] = None
|
|
586
610
|
self._started_ms: float = 0.0
|
trodo/session/server_session.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
|
-
import uuid
|
|
7
6
|
from datetime import datetime, timezone
|
|
8
7
|
from typing import Any, Dict, Optional
|
|
9
8
|
|
|
@@ -14,13 +13,24 @@ def now_iso() -> str:
|
|
|
14
13
|
return datetime.now(timezone.utc).isoformat()
|
|
15
14
|
|
|
16
15
|
|
|
16
|
+
def server_session_id(distinct_id: str) -> str:
|
|
17
|
+
"""Deterministic, "backend consistent" session id for a backend user.
|
|
18
|
+
|
|
19
|
+
Backend SDKs are stateless: the same distinct_id must resolve to the SAME
|
|
20
|
+
session across processes and restarts. Using ``server:{distinct_id}`` instead
|
|
21
|
+
of a per-process ``uuid4()`` produces exactly one session row per backend
|
|
22
|
+
user (no per-process bloat) and is idempotent server-side.
|
|
23
|
+
"""
|
|
24
|
+
return f"server:{distinct_id}"
|
|
25
|
+
|
|
26
|
+
|
|
17
27
|
def create_server_session(
|
|
18
28
|
site_id: str,
|
|
19
29
|
distinct_id: str,
|
|
20
30
|
session_id: Optional[str] = None,
|
|
21
31
|
) -> ServerSession:
|
|
22
32
|
return ServerSession(
|
|
23
|
-
session_id=session_id or
|
|
33
|
+
session_id=session_id or server_session_id(distinct_id),
|
|
24
34
|
site_id=site_id,
|
|
25
35
|
distinct_id=distinct_id,
|
|
26
36
|
start_time=now_iso(),
|
|
@@ -30,6 +40,15 @@ def create_server_session(
|
|
|
30
40
|
|
|
31
41
|
|
|
32
42
|
def build_session_payload(session: ServerSession) -> Dict[str, Any]:
|
|
43
|
+
"""Minimal server-session payload.
|
|
44
|
+
|
|
45
|
+
Backend SDKs cannot know browser-only signals (geo, device, browser, UTM,
|
|
46
|
+
referrer, wallet), so those fields are OMITTED rather than sent as ~30
|
|
47
|
+
explicit nulls — ingestion defaults missing fields to null. This saves
|
|
48
|
+
ingestion bandwidth and is more accurate. Only the markers ingestion keys
|
|
49
|
+
on are retained: ``is_server_session`` (drives identity-level browser-field
|
|
50
|
+
guards) and ``device_type='server'`` (server-origin fallback detector).
|
|
51
|
+
"""
|
|
33
52
|
return {
|
|
34
53
|
"session_id": session.session_id,
|
|
35
54
|
"site_id": session.site_id,
|
|
@@ -37,39 +56,9 @@ def build_session_payload(session: ServerSession) -> Dict[str, Any]:
|
|
|
37
56
|
"distinct_id": session.distinct_id,
|
|
38
57
|
"team_id": None,
|
|
39
58
|
"start_time": session.start_time,
|
|
40
|
-
"end_time": None,
|
|
41
59
|
"last_activity": int(session.last_activity * 1000),
|
|
42
|
-
"duration": 0,
|
|
43
|
-
"pages_viewed": 0,
|
|
44
60
|
"is_bounce": False,
|
|
45
|
-
"previous_session_id": None,
|
|
46
|
-
"time_since_last_session": None,
|
|
47
|
-
"entry_page": None,
|
|
48
|
-
"exit_page": None,
|
|
49
61
|
"referrer": "server",
|
|
50
|
-
"ip_address": None,
|
|
51
|
-
"city": None,
|
|
52
|
-
"region": None,
|
|
53
|
-
"country": None,
|
|
54
|
-
"browser_name": None,
|
|
55
|
-
"browser_version": None,
|
|
56
62
|
"device_type": "server",
|
|
57
|
-
"os": None,
|
|
58
|
-
"resolution": None,
|
|
59
|
-
"user_agent": None,
|
|
60
|
-
"language": None,
|
|
61
|
-
"wallet_address": None,
|
|
62
|
-
"wallet_type": None,
|
|
63
|
-
"chain_name": None,
|
|
64
|
-
"is_web3_user": False,
|
|
65
|
-
"wallet_connected": False,
|
|
66
|
-
"utm_source": None,
|
|
67
|
-
"utm_medium": None,
|
|
68
|
-
"utm_campaign": None,
|
|
69
|
-
"utm_term": None,
|
|
70
|
-
"utm_content": None,
|
|
71
|
-
"utm_id": None,
|
|
72
|
-
"visited_pages": [],
|
|
73
|
-
"active_time_ms": 0,
|
|
74
63
|
"is_server_session": True,
|
|
75
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
trodo/__init__.py,sha256=
|
|
1
|
+
trodo/__init__.py,sha256=NapUF153zVbbqH45gud9g9M_UyQgxuqw_yAYERTGJlw,16678
|
|
2
2
|
trodo/client.py,sha256=8DsKoLh_eaNxj93qkHynfeee-QsdomB_kXfUQjGnWDk,18607
|
|
3
3
|
trodo/types.py,sha256=eySgUvCXROG2TxtxgiU0MNr5iH0DEcduK8bmYtTKG44,3138
|
|
4
4
|
trodo/user_context.py,sha256=9la6azzwEanVmdP4ps_xMoufbeWVeIGU-M8ychmgajg,7859
|
|
@@ -15,17 +15,17 @@ trodo/otel/__init__.py,sha256=yiRFXWUU45bAM2CV37XeO7zf1hmnmjufdP4XO50yEyE,624
|
|
|
15
15
|
trodo/otel/auto_instrument.py,sha256=7uKhir0o0Mo_od1H2oMf5PHZovcUocHtgV18mRm2Erc,11193
|
|
16
16
|
trodo/otel/context.py,sha256=iJ1rE42-SbO8VZHAxhIl2ZJXgNwLIVps5xLg8GKgfFc,1165
|
|
17
17
|
trodo/otel/helpers.py,sha256=IEAHxAEN-Bvv_ZODrmRzC6PCGGhGTXU7IPcp6iO2nbA,16405
|
|
18
|
-
trodo/otel/processor.py,sha256=
|
|
18
|
+
trodo/otel/processor.py,sha256=9y5gUMg7iQ1anXf2vGLbmgdT8iRXUGu7QivAqxg4JS4,6384
|
|
19
19
|
trodo/otel/register.py,sha256=YV2EnkUoa-_54YAuChOe-Mg28UUKg8JO7-qhVP9G6u4,7644
|
|
20
20
|
trodo/otel/transport.py,sha256=hzZz8gwSMGJ8CxdijmLn1Ljt18owr9XTWy13DLbwYbw,2441
|
|
21
|
-
trodo/otel/wrap_agent.py,sha256=
|
|
21
|
+
trodo/otel/wrap_agent.py,sha256=T5pbD2iGE2B7oilKszPsFTRyRtEvdXe2cz20xpg20ik,25529
|
|
22
22
|
trodo/queue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
trodo/queue/batch_flusher.py,sha256=4Lg6T3Urwi9U0Q4FpFGPmjDYKg4ZliCTR-ND8BJvWaY,1298
|
|
24
24
|
trodo/queue/event_queue.py,sha256=EVFZrhlq_kwC3jJ2GK0wMhHISf9UzLCZNDnT_aZ2I2A,872
|
|
25
25
|
trodo/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
trodo/session/server_session.py,sha256=
|
|
26
|
+
trodo/session/server_session.py,sha256=McsudEiq33XDq3nqxgzBcUvIjQxCMscwEuAPnYXrTjs,2136
|
|
27
27
|
trodo/session/session_manager.py,sha256=JrgH1VeicmtlxPR4dXEuJbxhi23OelkgwW3-9Slv80o,2525
|
|
28
|
-
trodo_python-2.
|
|
29
|
-
trodo_python-2.
|
|
30
|
-
trodo_python-2.
|
|
31
|
-
trodo_python-2.
|
|
28
|
+
trodo_python-2.7.0.dist-info/METADATA,sha256=K19DXcIe8jCHbOCEWXkyy0mxGhjCz_fH_VIdm0vuM9E,17882
|
|
29
|
+
trodo_python-2.7.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
30
|
+
trodo_python-2.7.0.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
|
|
31
|
+
trodo_python-2.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|