trodo-python 1.0.1__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.
Files changed (31) hide show
  1. trodo_python-1.2.0/PKG-INFO +358 -0
  2. trodo_python-1.2.0/README.md +331 -0
  3. {trodo_python-1.0.1 → trodo_python-1.2.0}/pyproject.toml +1 -1
  4. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/__init__.py +47 -4
  5. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/api/endpoints.py +1 -0
  6. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/api/http_client.py +3 -0
  7. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/client.py +126 -3
  8. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/types.py +75 -0
  9. trodo_python-1.2.0/trodo_python.egg-info/PKG-INFO +358 -0
  10. trodo_python-1.0.1/PKG-INFO +0 -227
  11. trodo_python-1.0.1/README.md +0 -200
  12. trodo_python-1.0.1/trodo_python.egg-info/PKG-INFO +0 -227
  13. {trodo_python-1.0.1 → trodo_python-1.2.0}/setup.cfg +0 -0
  14. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/api/__init__.py +0 -0
  15. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/api/async_client.py +0 -0
  16. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/auto/__init__.py +0 -0
  17. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/auto/auto_event_manager.py +0 -0
  18. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/managers/__init__.py +0 -0
  19. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/managers/group_manager.py +0 -0
  20. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/managers/people_manager.py +0 -0
  21. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/queue/__init__.py +0 -0
  22. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/queue/batch_flusher.py +0 -0
  23. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/queue/event_queue.py +0 -0
  24. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/session/__init__.py +0 -0
  25. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/session/server_session.py +0 -0
  26. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/session/session_manager.py +0 -0
  27. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo/user_context.py +0 -0
  28. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo_python.egg-info/SOURCES.txt +0 -0
  29. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo_python.egg-info/dependency_links.txt +0 -0
  30. {trodo_python-1.0.1 → trodo_python-1.2.0}/trodo_python.egg-info/requires.txt +0 -0
  31. {trodo_python-1.0.1 → 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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trodo-python"
7
- version = "1.0.1"
7
+ version = "1.2.0"
8
8
  description = "Trodo Analytics SDK for Python — server-side event tracking"
9
9
  readme = "README.md"
10
10
  license = { text = "ISC" }