trodo-python 1.0.0__tar.gz → 1.2.0__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-1.2.0/PKG-INFO +358 -0
- trodo_python-1.2.0/README.md +331 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/pyproject.toml +43 -43
- {trodo_python-1.0.0 → trodo_python-1.2.0}/setup.cfg +4 -4
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/__init__.py +177 -134
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/api/async_client.py +96 -96
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/api/endpoints.py +21 -20
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/api/http_client.py +90 -87
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/auto/auto_event_manager.py +134 -134
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/client.py +318 -195
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/managers/group_manager.py +106 -106
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/managers/people_manager.py +77 -77
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/queue/batch_flusher.py +52 -52
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/queue/event_queue.py +32 -32
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/session/server_session.py +74 -74
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/session/session_manager.py +74 -74
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/types.py +154 -79
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/user_context.py +224 -224
- trodo_python-1.2.0/trodo_python.egg-info/PKG-INFO +358 -0
- trodo_python-1.0.0/PKG-INFO +0 -227
- trodo_python-1.0.0/README.md +0 -200
- trodo_python-1.0.0/trodo_python.egg-info/PKG-INFO +0 -227
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/api/__init__.py +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/auto/__init__.py +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/managers/__init__.py +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/queue/__init__.py +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo/session/__init__.py +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo_python.egg-info/SOURCES.txt +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-1.0.0 → trodo_python-1.2.0}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trodo-python
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Trodo Analytics SDK for Python — server-side event tracking
|
|
5
|
+
License: ISC
|
|
6
|
+
Keywords: analytics,tracking,trodo,server-side
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: requests>=2.28.0
|
|
20
|
+
Provides-Extra: async
|
|
21
|
+
Requires-Dist: httpx>=0.27.0; extra == "async"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
|
+
Requires-Dist: responses>=0.25.0; extra == "dev"
|
|
26
|
+
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# trodo-python
|
|
29
|
+
|
|
30
|
+
Server-side Python SDK for [Trodo Analytics](https://trodo.ai). Track backend events, identify users, manage people/groups, and instrument AI agents — all unified with your frontend data under the same `site_id`.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install trodo-python
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Python 3.8+.
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import trodo
|
|
44
|
+
|
|
45
|
+
trodo.init(site_id='your-site-id')
|
|
46
|
+
|
|
47
|
+
# User-bound context (recommended)
|
|
48
|
+
user = trodo.for_user('user-123')
|
|
49
|
+
user.track('purchase_completed', {'amount': 99.99, 'plan': 'pro'})
|
|
50
|
+
user.people.set({'plan': 'pro', 'company': 'Acme'})
|
|
51
|
+
|
|
52
|
+
# Flush before process exit if using batching
|
|
53
|
+
trodo.shutdown()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Core API
|
|
57
|
+
|
|
58
|
+
### `trodo.init(config)`
|
|
59
|
+
|
|
60
|
+
Call once at app startup.
|
|
61
|
+
|
|
62
|
+
| Parameter | Default | Description |
|
|
63
|
+
|-----------|---------|-------------|
|
|
64
|
+
| `site_id` | required | Your Trodo site ID |
|
|
65
|
+
| `api_base` | `https://sdkapi.trodo.ai` | API base URL |
|
|
66
|
+
| `timeout` | `10` s | HTTP request timeout |
|
|
67
|
+
| `retries` | `2` | Retries on network/5xx errors |
|
|
68
|
+
| `auto_events` | `False` | Hook `sys.excepthook` / `threading.excepthook` as `server_error` events |
|
|
69
|
+
| `batch_enabled` | `False` | Queue events and flush in batches |
|
|
70
|
+
| `batch_size` | `50` | Flush when this many events are queued |
|
|
71
|
+
| `batch_flush_interval` | `5.0` s | Also flush every N seconds |
|
|
72
|
+
| `on_error` | — | Callable on API errors (silent by default) |
|
|
73
|
+
| `debug` | `False` | Log API calls to stderr |
|
|
74
|
+
|
|
75
|
+
### `trodo.for_user(distinct_id, session_id=None)`
|
|
76
|
+
|
|
77
|
+
Returns a user-bound context. No API call is made until you track an event.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
user = trodo.for_user('user-123', session_id=request.cookies.get('trodo_session'))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `trodo.identify(identify_id, session_id=None)`
|
|
84
|
+
|
|
85
|
+
Creates the session and fires `POST /api/sdk/identify`. Use to link a `distinct_id` to an external identifier (email, DB id). Returns the user context.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
user = trodo.identify('user@example.com', session_id=request.cookies.get('trodo_session'))
|
|
89
|
+
# distinct_id is now id_user@example.com — merges with browser events
|
|
90
|
+
user.track('login')
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### User context methods
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
user.track(event_name, properties=None) # Custom event
|
|
97
|
+
user.identify(identify_id) # Merge identity
|
|
98
|
+
user.wallet_address(address) # Set wallet address
|
|
99
|
+
user.reset() # Clear session
|
|
100
|
+
user.capture_error(exc, severity='error') # Track server_error ('critical' | 'error' | 'warning')
|
|
101
|
+
|
|
102
|
+
# People profile
|
|
103
|
+
user.people.set(properties)
|
|
104
|
+
user.people.set_once(properties)
|
|
105
|
+
user.people.unset(keys)
|
|
106
|
+
user.people.increment(key, amount=1)
|
|
107
|
+
user.people.append(key, values)
|
|
108
|
+
user.people.union(key, values)
|
|
109
|
+
user.people.remove(key, values)
|
|
110
|
+
user.people.track_charge(amount, properties=None)
|
|
111
|
+
user.people.clear_charges()
|
|
112
|
+
user.people.delete_user()
|
|
113
|
+
|
|
114
|
+
# Groups
|
|
115
|
+
user.set_group(group_key, group_id)
|
|
116
|
+
user.add_group(group_key, group_id)
|
|
117
|
+
user.remove_group(group_key, group_id)
|
|
118
|
+
group = user.get_group(group_key, group_id)
|
|
119
|
+
group.set(properties)
|
|
120
|
+
group.set_once(properties)
|
|
121
|
+
group.increment(key, amount=1)
|
|
122
|
+
group.append(key, values)
|
|
123
|
+
group.union(key, values)
|
|
124
|
+
group.remove(key, values)
|
|
125
|
+
group.unset(keys)
|
|
126
|
+
group.delete()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Direct call pattern
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
trodo.track('user-123', 'event_name', {'key': 'value'})
|
|
133
|
+
trodo.people_set('user-123', {'plan': 'pro'})
|
|
134
|
+
trodo.set_group('user-123', 'company', 'acme')
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Agent Analytics
|
|
140
|
+
|
|
141
|
+
Track every step of your LLM agents. Each call counts as one event toward your plan limit.
|
|
142
|
+
|
|
143
|
+
**Before you start:** register your agent in **Integrations → AI Agents** in the dashboard to get an `agent_id` (`agt_xxxxxxxx`).
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from trodo import (
|
|
147
|
+
AgentCallProps, ToolUseProps, AgentResponseProps,
|
|
148
|
+
AgentErrorProps, FeedbackProps,
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `track_agent_call` — inbound message / LLM invocation
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
trodo.track_agent_call(AgentCallProps(
|
|
156
|
+
agent_id='agt_abc12345',
|
|
157
|
+
conversation_id='conv_xyz',
|
|
158
|
+
message_id='msg_001',
|
|
159
|
+
prompt=user_message,
|
|
160
|
+
model='claude-3-5-sonnet',
|
|
161
|
+
provider='anthropic',
|
|
162
|
+
system_prompt_version='v2', # optional — track prompt iterations
|
|
163
|
+
distinct_id=user_id, # optional — link to a Trodo user
|
|
164
|
+
))
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `track_tool_use` — tool/function call within a turn
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
trodo.track_tool_use(ToolUseProps(
|
|
171
|
+
agent_id='agt_abc12345',
|
|
172
|
+
conversation_id='conv_xyz',
|
|
173
|
+
message_id='msg_001',
|
|
174
|
+
tool_name='fetch_billing_info',
|
|
175
|
+
latency_ms=143,
|
|
176
|
+
status='success', # 'success' | 'failure'
|
|
177
|
+
input={'user_id': '123'}, # optional
|
|
178
|
+
output={'plan': 'pro'}, # optional
|
|
179
|
+
))
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `track_agent_response` — LLM output and token usage
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
trodo.track_agent_response(AgentResponseProps(
|
|
186
|
+
agent_id='agt_abc12345',
|
|
187
|
+
conversation_id='conv_xyz',
|
|
188
|
+
message_id='msg_001',
|
|
189
|
+
model='claude-3-5-sonnet',
|
|
190
|
+
completion_tokens=response.usage.output_tokens,
|
|
191
|
+
prompt_tokens=response.usage.input_tokens,
|
|
192
|
+
total_tokens=response.usage.input_tokens + response.usage.output_tokens,
|
|
193
|
+
finish_reason=response.stop_reason,
|
|
194
|
+
distinct_id=user_id,
|
|
195
|
+
))
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### `track_agent_error` — errors and failures
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
import traceback
|
|
202
|
+
|
|
203
|
+
trodo.track_agent_error(AgentErrorProps(
|
|
204
|
+
agent_id='agt_abc12345',
|
|
205
|
+
conversation_id='conv_xyz',
|
|
206
|
+
message_id='msg_001',
|
|
207
|
+
error_type='rate_limit', # 'timeout' | 'rate_limit' | 'guardrail_block' | ...
|
|
208
|
+
error_message=str(exc),
|
|
209
|
+
failed_tool='fetch_billing_info', # optional
|
|
210
|
+
traceback=traceback.format_exc(), # optional
|
|
211
|
+
))
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `track_feedback` — user thumbs up/down
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
trodo.track_feedback(FeedbackProps(
|
|
218
|
+
agent_id='agt_abc12345',
|
|
219
|
+
conversation_id='conv_xyz',
|
|
220
|
+
message_id='msg_001', # same message_id as the response it refers to
|
|
221
|
+
feedback='positive', # 'positive' | 'negative' | 'unreact'
|
|
222
|
+
distinct_id=user_id,
|
|
223
|
+
))
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Full turn example
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
import traceback
|
|
230
|
+
from trodo import AgentCallProps, ToolUseProps, AgentResponseProps, AgentErrorProps
|
|
231
|
+
|
|
232
|
+
def run_agent_turn(user_id, conversation_id, user_message):
|
|
233
|
+
agent_id = 'agt_abc12345'
|
|
234
|
+
message_id = f'msg_{int(time.time() * 1000)}'
|
|
235
|
+
|
|
236
|
+
trodo.track_agent_call(AgentCallProps(
|
|
237
|
+
agent_id=agent_id, conversation_id=conversation_id,
|
|
238
|
+
message_id=message_id, prompt=user_message, distinct_id=user_id,
|
|
239
|
+
))
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
trodo.track_tool_use(ToolUseProps(
|
|
243
|
+
agent_id=agent_id, conversation_id=conversation_id,
|
|
244
|
+
message_id=message_id, tool_name='search', status='success', latency_ms=80,
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
response = llm_client.complete(user_message)
|
|
248
|
+
|
|
249
|
+
trodo.track_agent_response(AgentResponseProps(
|
|
250
|
+
agent_id=agent_id, conversation_id=conversation_id, message_id=message_id,
|
|
251
|
+
model=response.model,
|
|
252
|
+
completion_tokens=response.usage.output_tokens,
|
|
253
|
+
prompt_tokens=response.usage.input_tokens,
|
|
254
|
+
total_tokens=response.usage.input_tokens + response.usage.output_tokens,
|
|
255
|
+
distinct_id=user_id,
|
|
256
|
+
))
|
|
257
|
+
|
|
258
|
+
return response.text
|
|
259
|
+
|
|
260
|
+
except Exception as exc:
|
|
261
|
+
trodo.track_agent_error(AgentErrorProps(
|
|
262
|
+
agent_id=agent_id, conversation_id=conversation_id, message_id=message_id,
|
|
263
|
+
error_type=type(exc).__name__, error_message=str(exc),
|
|
264
|
+
traceback=traceback.format_exc(), distinct_id=user_id,
|
|
265
|
+
))
|
|
266
|
+
raise
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Identity Merging (Cross-SDK)
|
|
272
|
+
|
|
273
|
+
Call `identify()` with the **same value** on the browser and server to merge all events under one user profile:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# Python
|
|
277
|
+
user.identify('user@example.com') # → id_user@example.com
|
|
278
|
+
|
|
279
|
+
# Browser (same value)
|
|
280
|
+
# Trodo.identify('user@example.com') → id_user@example.com
|
|
281
|
+
# Events from both sides now appear together in the dashboard
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Flask / FastAPI Example
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
# Flask
|
|
290
|
+
from flask import Flask, request
|
|
291
|
+
import trodo
|
|
292
|
+
|
|
293
|
+
app = Flask(__name__)
|
|
294
|
+
trodo.init(site_id='your-site-id')
|
|
295
|
+
|
|
296
|
+
@app.route('/purchase', methods=['POST'])
|
|
297
|
+
def purchase():
|
|
298
|
+
user = trodo.for_user(request.json['user_id'])
|
|
299
|
+
user.track('purchase_completed', {'amount': request.json['amount']})
|
|
300
|
+
return {'ok': True}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
# FastAPI
|
|
305
|
+
from fastapi import FastAPI, Request
|
|
306
|
+
import trodo
|
|
307
|
+
|
|
308
|
+
app = FastAPI()
|
|
309
|
+
trodo.init(site_id='your-site-id')
|
|
310
|
+
|
|
311
|
+
@app.post('/purchase')
|
|
312
|
+
async def purchase(request: Request):
|
|
313
|
+
body = await request.json()
|
|
314
|
+
user = trodo.for_user(body['user_id'])
|
|
315
|
+
user.track('purchase_completed', {'amount': body['amount']})
|
|
316
|
+
return {'ok': True}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Batching
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
trodo.init(
|
|
325
|
+
site_id='your-site-id',
|
|
326
|
+
batch_enabled=True,
|
|
327
|
+
batch_size=50,
|
|
328
|
+
batch_flush_interval=5.0,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Always flush before process exit
|
|
332
|
+
import atexit
|
|
333
|
+
atexit.register(trodo.shutdown)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Auto Events
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
trodo.init(site_id='your-site-id', auto_events=True)
|
|
342
|
+
# Hooks sys.excepthook and threading.excepthook
|
|
343
|
+
# Sends server_error events with distinct_id: 'server_global'
|
|
344
|
+
|
|
345
|
+
# Toggle at runtime
|
|
346
|
+
trodo.enable_auto_events()
|
|
347
|
+
trodo.disable_auto_events()
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Thread Safety
|
|
353
|
+
|
|
354
|
+
The SDK is thread-safe. `SessionManager`, `EventQueue`, and `BatchFlusher` all use `threading.Lock` internally. Safe for multi-threaded Flask/Django/FastAPI apps.
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
ISC
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# trodo-python
|
|
2
|
+
|
|
3
|
+
Server-side Python SDK for [Trodo Analytics](https://trodo.ai). Track backend events, identify users, manage people/groups, and instrument AI agents — all unified with your frontend data under the same `site_id`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install trodo-python
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.8+.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import trodo
|
|
17
|
+
|
|
18
|
+
trodo.init(site_id='your-site-id')
|
|
19
|
+
|
|
20
|
+
# User-bound context (recommended)
|
|
21
|
+
user = trodo.for_user('user-123')
|
|
22
|
+
user.track('purchase_completed', {'amount': 99.99, 'plan': 'pro'})
|
|
23
|
+
user.people.set({'plan': 'pro', 'company': 'Acme'})
|
|
24
|
+
|
|
25
|
+
# Flush before process exit if using batching
|
|
26
|
+
trodo.shutdown()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Core API
|
|
30
|
+
|
|
31
|
+
### `trodo.init(config)`
|
|
32
|
+
|
|
33
|
+
Call once at app startup.
|
|
34
|
+
|
|
35
|
+
| Parameter | Default | Description |
|
|
36
|
+
|-----------|---------|-------------|
|
|
37
|
+
| `site_id` | required | Your Trodo site ID |
|
|
38
|
+
| `api_base` | `https://sdkapi.trodo.ai` | API base URL |
|
|
39
|
+
| `timeout` | `10` s | HTTP request timeout |
|
|
40
|
+
| `retries` | `2` | Retries on network/5xx errors |
|
|
41
|
+
| `auto_events` | `False` | Hook `sys.excepthook` / `threading.excepthook` as `server_error` events |
|
|
42
|
+
| `batch_enabled` | `False` | Queue events and flush in batches |
|
|
43
|
+
| `batch_size` | `50` | Flush when this many events are queued |
|
|
44
|
+
| `batch_flush_interval` | `5.0` s | Also flush every N seconds |
|
|
45
|
+
| `on_error` | — | Callable on API errors (silent by default) |
|
|
46
|
+
| `debug` | `False` | Log API calls to stderr |
|
|
47
|
+
|
|
48
|
+
### `trodo.for_user(distinct_id, session_id=None)`
|
|
49
|
+
|
|
50
|
+
Returns a user-bound context. No API call is made until you track an event.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
user = trodo.for_user('user-123', session_id=request.cookies.get('trodo_session'))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `trodo.identify(identify_id, session_id=None)`
|
|
57
|
+
|
|
58
|
+
Creates the session and fires `POST /api/sdk/identify`. Use to link a `distinct_id` to an external identifier (email, DB id). Returns the user context.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
user = trodo.identify('user@example.com', session_id=request.cookies.get('trodo_session'))
|
|
62
|
+
# distinct_id is now id_user@example.com — merges with browser events
|
|
63
|
+
user.track('login')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### User context methods
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
user.track(event_name, properties=None) # Custom event
|
|
70
|
+
user.identify(identify_id) # Merge identity
|
|
71
|
+
user.wallet_address(address) # Set wallet address
|
|
72
|
+
user.reset() # Clear session
|
|
73
|
+
user.capture_error(exc, severity='error') # Track server_error ('critical' | 'error' | 'warning')
|
|
74
|
+
|
|
75
|
+
# People profile
|
|
76
|
+
user.people.set(properties)
|
|
77
|
+
user.people.set_once(properties)
|
|
78
|
+
user.people.unset(keys)
|
|
79
|
+
user.people.increment(key, amount=1)
|
|
80
|
+
user.people.append(key, values)
|
|
81
|
+
user.people.union(key, values)
|
|
82
|
+
user.people.remove(key, values)
|
|
83
|
+
user.people.track_charge(amount, properties=None)
|
|
84
|
+
user.people.clear_charges()
|
|
85
|
+
user.people.delete_user()
|
|
86
|
+
|
|
87
|
+
# Groups
|
|
88
|
+
user.set_group(group_key, group_id)
|
|
89
|
+
user.add_group(group_key, group_id)
|
|
90
|
+
user.remove_group(group_key, group_id)
|
|
91
|
+
group = user.get_group(group_key, group_id)
|
|
92
|
+
group.set(properties)
|
|
93
|
+
group.set_once(properties)
|
|
94
|
+
group.increment(key, amount=1)
|
|
95
|
+
group.append(key, values)
|
|
96
|
+
group.union(key, values)
|
|
97
|
+
group.remove(key, values)
|
|
98
|
+
group.unset(keys)
|
|
99
|
+
group.delete()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Direct call pattern
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
trodo.track('user-123', 'event_name', {'key': 'value'})
|
|
106
|
+
trodo.people_set('user-123', {'plan': 'pro'})
|
|
107
|
+
trodo.set_group('user-123', 'company', 'acme')
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Agent Analytics
|
|
113
|
+
|
|
114
|
+
Track every step of your LLM agents. Each call counts as one event toward your plan limit.
|
|
115
|
+
|
|
116
|
+
**Before you start:** register your agent in **Integrations → AI Agents** in the dashboard to get an `agent_id` (`agt_xxxxxxxx`).
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from trodo import (
|
|
120
|
+
AgentCallProps, ToolUseProps, AgentResponseProps,
|
|
121
|
+
AgentErrorProps, FeedbackProps,
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `track_agent_call` — inbound message / LLM invocation
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
trodo.track_agent_call(AgentCallProps(
|
|
129
|
+
agent_id='agt_abc12345',
|
|
130
|
+
conversation_id='conv_xyz',
|
|
131
|
+
message_id='msg_001',
|
|
132
|
+
prompt=user_message,
|
|
133
|
+
model='claude-3-5-sonnet',
|
|
134
|
+
provider='anthropic',
|
|
135
|
+
system_prompt_version='v2', # optional — track prompt iterations
|
|
136
|
+
distinct_id=user_id, # optional — link to a Trodo user
|
|
137
|
+
))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `track_tool_use` — tool/function call within a turn
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
trodo.track_tool_use(ToolUseProps(
|
|
144
|
+
agent_id='agt_abc12345',
|
|
145
|
+
conversation_id='conv_xyz',
|
|
146
|
+
message_id='msg_001',
|
|
147
|
+
tool_name='fetch_billing_info',
|
|
148
|
+
latency_ms=143,
|
|
149
|
+
status='success', # 'success' | 'failure'
|
|
150
|
+
input={'user_id': '123'}, # optional
|
|
151
|
+
output={'plan': 'pro'}, # optional
|
|
152
|
+
))
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `track_agent_response` — LLM output and token usage
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
trodo.track_agent_response(AgentResponseProps(
|
|
159
|
+
agent_id='agt_abc12345',
|
|
160
|
+
conversation_id='conv_xyz',
|
|
161
|
+
message_id='msg_001',
|
|
162
|
+
model='claude-3-5-sonnet',
|
|
163
|
+
completion_tokens=response.usage.output_tokens,
|
|
164
|
+
prompt_tokens=response.usage.input_tokens,
|
|
165
|
+
total_tokens=response.usage.input_tokens + response.usage.output_tokens,
|
|
166
|
+
finish_reason=response.stop_reason,
|
|
167
|
+
distinct_id=user_id,
|
|
168
|
+
))
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `track_agent_error` — errors and failures
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import traceback
|
|
175
|
+
|
|
176
|
+
trodo.track_agent_error(AgentErrorProps(
|
|
177
|
+
agent_id='agt_abc12345',
|
|
178
|
+
conversation_id='conv_xyz',
|
|
179
|
+
message_id='msg_001',
|
|
180
|
+
error_type='rate_limit', # 'timeout' | 'rate_limit' | 'guardrail_block' | ...
|
|
181
|
+
error_message=str(exc),
|
|
182
|
+
failed_tool='fetch_billing_info', # optional
|
|
183
|
+
traceback=traceback.format_exc(), # optional
|
|
184
|
+
))
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `track_feedback` — user thumbs up/down
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
trodo.track_feedback(FeedbackProps(
|
|
191
|
+
agent_id='agt_abc12345',
|
|
192
|
+
conversation_id='conv_xyz',
|
|
193
|
+
message_id='msg_001', # same message_id as the response it refers to
|
|
194
|
+
feedback='positive', # 'positive' | 'negative' | 'unreact'
|
|
195
|
+
distinct_id=user_id,
|
|
196
|
+
))
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Full turn example
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
import traceback
|
|
203
|
+
from trodo import AgentCallProps, ToolUseProps, AgentResponseProps, AgentErrorProps
|
|
204
|
+
|
|
205
|
+
def run_agent_turn(user_id, conversation_id, user_message):
|
|
206
|
+
agent_id = 'agt_abc12345'
|
|
207
|
+
message_id = f'msg_{int(time.time() * 1000)}'
|
|
208
|
+
|
|
209
|
+
trodo.track_agent_call(AgentCallProps(
|
|
210
|
+
agent_id=agent_id, conversation_id=conversation_id,
|
|
211
|
+
message_id=message_id, prompt=user_message, distinct_id=user_id,
|
|
212
|
+
))
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
trodo.track_tool_use(ToolUseProps(
|
|
216
|
+
agent_id=agent_id, conversation_id=conversation_id,
|
|
217
|
+
message_id=message_id, tool_name='search', status='success', latency_ms=80,
|
|
218
|
+
))
|
|
219
|
+
|
|
220
|
+
response = llm_client.complete(user_message)
|
|
221
|
+
|
|
222
|
+
trodo.track_agent_response(AgentResponseProps(
|
|
223
|
+
agent_id=agent_id, conversation_id=conversation_id, message_id=message_id,
|
|
224
|
+
model=response.model,
|
|
225
|
+
completion_tokens=response.usage.output_tokens,
|
|
226
|
+
prompt_tokens=response.usage.input_tokens,
|
|
227
|
+
total_tokens=response.usage.input_tokens + response.usage.output_tokens,
|
|
228
|
+
distinct_id=user_id,
|
|
229
|
+
))
|
|
230
|
+
|
|
231
|
+
return response.text
|
|
232
|
+
|
|
233
|
+
except Exception as exc:
|
|
234
|
+
trodo.track_agent_error(AgentErrorProps(
|
|
235
|
+
agent_id=agent_id, conversation_id=conversation_id, message_id=message_id,
|
|
236
|
+
error_type=type(exc).__name__, error_message=str(exc),
|
|
237
|
+
traceback=traceback.format_exc(), distinct_id=user_id,
|
|
238
|
+
))
|
|
239
|
+
raise
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Identity Merging (Cross-SDK)
|
|
245
|
+
|
|
246
|
+
Call `identify()` with the **same value** on the browser and server to merge all events under one user profile:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
# Python
|
|
250
|
+
user.identify('user@example.com') # → id_user@example.com
|
|
251
|
+
|
|
252
|
+
# Browser (same value)
|
|
253
|
+
# Trodo.identify('user@example.com') → id_user@example.com
|
|
254
|
+
# Events from both sides now appear together in the dashboard
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Flask / FastAPI Example
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# Flask
|
|
263
|
+
from flask import Flask, request
|
|
264
|
+
import trodo
|
|
265
|
+
|
|
266
|
+
app = Flask(__name__)
|
|
267
|
+
trodo.init(site_id='your-site-id')
|
|
268
|
+
|
|
269
|
+
@app.route('/purchase', methods=['POST'])
|
|
270
|
+
def purchase():
|
|
271
|
+
user = trodo.for_user(request.json['user_id'])
|
|
272
|
+
user.track('purchase_completed', {'amount': request.json['amount']})
|
|
273
|
+
return {'ok': True}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
# FastAPI
|
|
278
|
+
from fastapi import FastAPI, Request
|
|
279
|
+
import trodo
|
|
280
|
+
|
|
281
|
+
app = FastAPI()
|
|
282
|
+
trodo.init(site_id='your-site-id')
|
|
283
|
+
|
|
284
|
+
@app.post('/purchase')
|
|
285
|
+
async def purchase(request: Request):
|
|
286
|
+
body = await request.json()
|
|
287
|
+
user = trodo.for_user(body['user_id'])
|
|
288
|
+
user.track('purchase_completed', {'amount': body['amount']})
|
|
289
|
+
return {'ok': True}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Batching
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
trodo.init(
|
|
298
|
+
site_id='your-site-id',
|
|
299
|
+
batch_enabled=True,
|
|
300
|
+
batch_size=50,
|
|
301
|
+
batch_flush_interval=5.0,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Always flush before process exit
|
|
305
|
+
import atexit
|
|
306
|
+
atexit.register(trodo.shutdown)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Auto Events
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
trodo.init(site_id='your-site-id', auto_events=True)
|
|
315
|
+
# Hooks sys.excepthook and threading.excepthook
|
|
316
|
+
# Sends server_error events with distinct_id: 'server_global'
|
|
317
|
+
|
|
318
|
+
# Toggle at runtime
|
|
319
|
+
trodo.enable_auto_events()
|
|
320
|
+
trodo.disable_auto_events()
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Thread Safety
|
|
326
|
+
|
|
327
|
+
The SDK is thread-safe. `SessionManager`, `EventQueue`, and `BatchFlusher` all use `threading.Lock` internally. Safe for multi-threaded Flask/Django/FastAPI apps.
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
ISC
|