trodo-python 1.0.1__tar.gz → 2.1.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 (39) hide show
  1. trodo_python-2.1.0/PKG-INFO +510 -0
  2. trodo_python-2.1.0/README.md +483 -0
  3. {trodo_python-1.0.1 → trodo_python-2.1.0}/pyproject.toml +1 -1
  4. trodo_python-2.1.0/trodo/__init__.py +430 -0
  5. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/api/endpoints.py +5 -0
  6. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/api/http_client.py +31 -0
  7. trodo_python-2.1.0/trodo/client.py +399 -0
  8. trodo_python-2.1.0/trodo/otel/__init__.py +21 -0
  9. trodo_python-2.1.0/trodo/otel/auto_instrument.py +281 -0
  10. trodo_python-2.1.0/trodo/otel/context.py +44 -0
  11. trodo_python-2.1.0/trodo/otel/helpers.py +407 -0
  12. trodo_python-2.1.0/trodo/otel/processor.py +165 -0
  13. trodo_python-2.1.0/trodo/otel/wrap_agent.py +438 -0
  14. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/types.py +20 -1
  15. trodo_python-2.1.0/trodo_python.egg-info/PKG-INFO +510 -0
  16. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo_python.egg-info/SOURCES.txt +6 -0
  17. trodo_python-1.0.1/PKG-INFO +0 -227
  18. trodo_python-1.0.1/README.md +0 -200
  19. trodo_python-1.0.1/trodo/__init__.py +0 -134
  20. trodo_python-1.0.1/trodo/client.py +0 -195
  21. trodo_python-1.0.1/trodo_python.egg-info/PKG-INFO +0 -227
  22. {trodo_python-1.0.1 → trodo_python-2.1.0}/setup.cfg +0 -0
  23. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/api/__init__.py +0 -0
  24. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/api/async_client.py +0 -0
  25. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/auto/__init__.py +0 -0
  26. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/auto/auto_event_manager.py +0 -0
  27. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/managers/__init__.py +0 -0
  28. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/managers/group_manager.py +0 -0
  29. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/managers/people_manager.py +0 -0
  30. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/queue/__init__.py +0 -0
  31. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/queue/batch_flusher.py +0 -0
  32. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/queue/event_queue.py +0 -0
  33. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/session/__init__.py +0 -0
  34. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/session/server_session.py +0 -0
  35. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/session/session_manager.py +0 -0
  36. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo/user_context.py +0 -0
  37. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo_python.egg-info/dependency_links.txt +0 -0
  38. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo_python.egg-info/requires.txt +0 -0
  39. {trodo_python-1.0.1 → trodo_python-2.1.0}/trodo_python.egg-info/top_level.txt +0 -0
@@ -0,0 +1,510 @@
1
+ Metadata-Version: 2.4
2
+ Name: trodo-python
3
+ Version: 2.1.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
+ ## AI Agent Tracing (recommended)
140
+
141
+ One wrap around your agent captures every LLM call, tool call, and
142
+ nested step as a tree of spans — token counts, costs, inputs, outputs,
143
+ errors. Works with any stack: OpenAI, Anthropic, LangChain, LlamaIndex,
144
+ Gemini, raw HTTP, custom tools. Cost is derived server-side from
145
+ `(provider, model)` — the SDK only sends tokens.
146
+
147
+ ### 30-second quickstart
148
+
149
+ ```python
150
+ import trodo
151
+ trodo.init(site_id='your-site-id') # auto-instrument on by default
152
+
153
+ with trodo.wrap_agent('customer-support',
154
+ distinct_id=user_id,
155
+ conversation_id=session_id) as run:
156
+ run.set_input({'query': 'where did sales drop'})
157
+ answer = agent.run(query) # OpenAI/Anthropic/LangChain auto-captured
158
+ run.set_output(answer)
159
+ ```
160
+
161
+ Open the Agent Runs dashboard — the row shows tokens in/out, cost,
162
+ span count, tool count, error count, plus the full trace tree.
163
+
164
+ ### Auto-instrumentation
165
+
166
+ `trodo.init()` calls `enable_auto_instrument()` which registers every
167
+ installed OpenTelemetry instrumentor. No extra code required.
168
+
169
+ | Framework | Install |
170
+ |-----------|---------|
171
+ | OpenAI | `pip install opentelemetry-instrumentation-openai` |
172
+ | Anthropic | `pip install opentelemetry-instrumentation-anthropic` |
173
+ | LangChain | `pip install opentelemetry-instrumentation-langchain` |
174
+ | LlamaIndex | `pip install opentelemetry-instrumentation-llama-index` |
175
+ | Google Gemini | `pip install opentelemetry-instrumentation-google-generativeai` |
176
+ | Vertex AI | `pip install opentelemetry-instrumentation-vertexai` |
177
+ | Bedrock | `pip install opentelemetry-instrumentation-bedrock` |
178
+ | Cohere | `pip install opentelemetry-instrumentation-cohere` |
179
+ | Mistral | `pip install opentelemetry-instrumentation-mistralai` |
180
+ | Haystack | `pip install opentelemetry-instrumentation-haystack` |
181
+ | httpx / requests | bundled — generic HTTP spans for raw-HTTP callers |
182
+
183
+ Opt out with `trodo.init(site_id=..., auto_instrument=False)`.
184
+
185
+ ### Span helpers
186
+
187
+ Typed function wrappers for custom code — every call becomes a span
188
+ with the args auto-captured as `input`, return value as `output`,
189
+ exception as `error`. Dual-form: helper **and** decorator.
190
+
191
+ ```python
192
+ # trace — generic span
193
+ prepared = trodo.trace('prepare', prepare_fn)(payload)
194
+
195
+ @trodo.trace('step')
196
+ def step(): ...
197
+
198
+ # tool — tool span (auto tool_name, kind='tool')
199
+ run_funnel = trodo.tool('run_funnel_query', run_funnel_query)
200
+ result = run_funnel(team_id=1, preset='day7')
201
+
202
+ @trodo.tool(name='fetch_user')
203
+ async def fetch_user(uid): ...
204
+
205
+ # llm — LLM span, auto-extracts OpenAI / Anthropic / Gemini usage
206
+ answer = trodo.llm(
207
+ 'answer', call_openai, model='gpt-4o-mini', provider='openai',
208
+ )(messages)
209
+ # Records input_tokens / output_tokens from response['usage'].
210
+
211
+ # retrieval — vector search / RAG retriever span
212
+ search = trodo.retrieval('vector_search', vector_search)
213
+ docs = search(query)
214
+ ```
215
+
216
+ ### Raw-HTTP escape hatches
217
+
218
+ If your LLM client isn't OTel-instrumented and you can't wrap it as a
219
+ function, record a span post-hoc:
220
+
221
+ ```python
222
+ resp = httpx.post(url, json=body).json()
223
+ trodo.track_llm_call(
224
+ model='gemini-2.5-flash', provider='google',
225
+ input_tokens=resp['usageMetadata']['promptTokenCount'],
226
+ output_tokens=resp['usageMetadata']['candidatesTokenCount'],
227
+ prompt=body, completion=resp,
228
+ )
229
+ ```
230
+
231
+ For advanced cases, get a raw OTel tracer — the Trodo processor is
232
+ already subscribed:
233
+
234
+ ```python
235
+ tracer = trodo.get_tracer('my.module')
236
+ with tracer.start_as_current_span('custom') as sp:
237
+ sp.set_attribute('gen_ai.system', 'my-llm')
238
+ ```
239
+
240
+ ### Cross-service runs
241
+
242
+ When one service calls another, the downstream service **joins** the
243
+ caller's run instead of creating its own. All spans nest under a single
244
+ timeline in the dashboard.
245
+
246
+ ```python
247
+ # Caller (FastAPI / Flask / Django / …) — outbound:
248
+ import httpx
249
+ httpx.post(url, headers=trodo.propagation_headers(), json=body)
250
+
251
+ # Downstream (FastAPI):
252
+ from fastapi import FastAPI
253
+ app = FastAPI()
254
+ app.middleware('http')(trodo.fastapi_middleware())
255
+ # Every LLM call / @tool / trace helper inside handlers now nests under
256
+ # the caller's run — no extra wiring.
257
+
258
+ # Or manually:
259
+ with trodo.join_run(
260
+ run_id=headers['x-trodo-run-id'],
261
+ parent_span_id=headers['x-trodo-parent-span-id'],
262
+ ):
263
+ ...
264
+ ```
265
+
266
+ ### Conversation binding & feedback
267
+
268
+ ```python
269
+ with trodo.wrap_agent(
270
+ 'chat', distinct_id=user_id, conversation_id=session_id,
271
+ ) as run:
272
+ ...
273
+ # Later:
274
+ trodo.feedback(run.run_id, satisfaction='positive', rating=5)
275
+ ```
276
+
277
+ ### Cookbook
278
+
279
+ Runnable scenarios that double as integration tests live in
280
+ `sandbox/scenarios/` — `span_helpers.py`, `raw_http.py`,
281
+ `custom_tools.py`, `cross_service.py`, `concurrent_100.py`,
282
+ `long_run.py`, plus opt-in `openai_auto.py`, `anthropic_auto.py`,
283
+ `langchain_chain.py`. Run them with
284
+ `python -m sandbox.run_all` from the SDK root.
285
+
286
+ ---
287
+
288
+ ## Agent Analytics (legacy event-based API)
289
+
290
+ The older per-event API below is still supported but superseded by
291
+ `wrap_agent` + span helpers above. Use it only if you're already wired
292
+ into it; new integrations should prefer the tracing API.
293
+
294
+ **Before you start:** register your agent in **Integrations → AI Agents** in the dashboard to get an `agent_id` (`agt_xxxxxxxx`).
295
+
296
+ ```python
297
+ from trodo import (
298
+ AgentCallProps, ToolUseProps, AgentResponseProps,
299
+ AgentErrorProps, FeedbackProps,
300
+ )
301
+ ```
302
+
303
+ ### `track_agent_call` — inbound message / LLM invocation
304
+
305
+ ```python
306
+ trodo.track_agent_call(AgentCallProps(
307
+ agent_id='agt_abc12345',
308
+ conversation_id='conv_xyz',
309
+ message_id='msg_001',
310
+ prompt=user_message,
311
+ model='claude-3-5-sonnet',
312
+ provider='anthropic',
313
+ system_prompt_version='v2', # optional — track prompt iterations
314
+ distinct_id=user_id, # optional — link to a Trodo user
315
+ metadata={'thread_source': 'slack', 'locale': 'en'}, # optional — agent_calls.metadata JSONB
316
+ ))
317
+ ```
318
+
319
+ ### `track_tool_use` — tool/function call within a turn
320
+
321
+ ```python
322
+ trodo.track_tool_use(ToolUseProps(
323
+ agent_id='agt_abc12345',
324
+ conversation_id='conv_xyz',
325
+ message_id='msg_001',
326
+ tool_name='fetch_billing_info',
327
+ latency_ms=143,
328
+ status='success', # 'success' | 'failure'
329
+ input={'user_id': '123'}, # optional
330
+ output={'plan': 'pro'}, # optional
331
+ ))
332
+ ```
333
+
334
+ ### `track_agent_response` — LLM output and token usage
335
+
336
+ ```python
337
+ trodo.track_agent_response(AgentResponseProps(
338
+ agent_id='agt_abc12345',
339
+ conversation_id='conv_xyz',
340
+ message_id='msg_001',
341
+ model='claude-3-5-sonnet',
342
+ completion_tokens=response.usage.output_tokens,
343
+ prompt_tokens=response.usage.input_tokens,
344
+ total_tokens=response.usage.input_tokens + response.usage.output_tokens,
345
+ finish_reason=response.stop_reason,
346
+ distinct_id=user_id,
347
+ ))
348
+ ```
349
+
350
+ ### `track_agent_error` — errors and failures
351
+
352
+ ```python
353
+ import traceback
354
+
355
+ trodo.track_agent_error(AgentErrorProps(
356
+ agent_id='agt_abc12345',
357
+ conversation_id='conv_xyz',
358
+ message_id='msg_001',
359
+ error_type='rate_limit', # 'timeout' | 'rate_limit' | 'guardrail_block' | ...
360
+ error_message=str(exc),
361
+ failed_tool='fetch_billing_info', # optional
362
+ traceback=traceback.format_exc(), # optional
363
+ ))
364
+ ```
365
+
366
+ ### `track_feedback` — user thumbs up/down
367
+
368
+ ```python
369
+ trodo.track_feedback(FeedbackProps(
370
+ agent_id='agt_abc12345',
371
+ conversation_id='conv_xyz',
372
+ message_id='msg_001', # same message_id as the response it refers to
373
+ feedback='positive', # 'positive' | 'negative' | 'unreact'
374
+ distinct_id=user_id,
375
+ ))
376
+ ```
377
+
378
+ ### Full turn example
379
+
380
+ ```python
381
+ import traceback
382
+ from trodo import AgentCallProps, ToolUseProps, AgentResponseProps, AgentErrorProps
383
+
384
+ def run_agent_turn(user_id, conversation_id, user_message):
385
+ agent_id = 'agt_abc12345'
386
+ message_id = f'msg_{int(time.time() * 1000)}'
387
+
388
+ trodo.track_agent_call(AgentCallProps(
389
+ agent_id=agent_id, conversation_id=conversation_id,
390
+ message_id=message_id, prompt=user_message, distinct_id=user_id,
391
+ ))
392
+
393
+ try:
394
+ trodo.track_tool_use(ToolUseProps(
395
+ agent_id=agent_id, conversation_id=conversation_id,
396
+ message_id=message_id, tool_name='search', status='success', latency_ms=80,
397
+ ))
398
+
399
+ response = llm_client.complete(user_message)
400
+
401
+ trodo.track_agent_response(AgentResponseProps(
402
+ agent_id=agent_id, conversation_id=conversation_id, message_id=message_id,
403
+ model=response.model,
404
+ completion_tokens=response.usage.output_tokens,
405
+ prompt_tokens=response.usage.input_tokens,
406
+ total_tokens=response.usage.input_tokens + response.usage.output_tokens,
407
+ distinct_id=user_id,
408
+ ))
409
+
410
+ return response.text
411
+
412
+ except Exception as exc:
413
+ trodo.track_agent_error(AgentErrorProps(
414
+ agent_id=agent_id, conversation_id=conversation_id, message_id=message_id,
415
+ error_type=type(exc).__name__, error_message=str(exc),
416
+ traceback=traceback.format_exc(), distinct_id=user_id,
417
+ ))
418
+ raise
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Identity Merging (Cross-SDK)
424
+
425
+ Call `identify()` with the **same value** on the browser and server to merge all events under one user profile:
426
+
427
+ ```python
428
+ # Python
429
+ user.identify('user@example.com') # → id_user@example.com
430
+
431
+ # Browser (same value)
432
+ # Trodo.identify('user@example.com') → id_user@example.com
433
+ # Events from both sides now appear together in the dashboard
434
+ ```
435
+
436
+ ---
437
+
438
+ ## Flask / FastAPI Example
439
+
440
+ ```python
441
+ # Flask
442
+ from flask import Flask, request
443
+ import trodo
444
+
445
+ app = Flask(__name__)
446
+ trodo.init(site_id='your-site-id')
447
+
448
+ @app.route('/purchase', methods=['POST'])
449
+ def purchase():
450
+ user = trodo.for_user(request.json['user_id'])
451
+ user.track('purchase_completed', {'amount': request.json['amount']})
452
+ return {'ok': True}
453
+ ```
454
+
455
+ ```python
456
+ # FastAPI
457
+ from fastapi import FastAPI, Request
458
+ import trodo
459
+
460
+ app = FastAPI()
461
+ trodo.init(site_id='your-site-id')
462
+
463
+ @app.post('/purchase')
464
+ async def purchase(request: Request):
465
+ body = await request.json()
466
+ user = trodo.for_user(body['user_id'])
467
+ user.track('purchase_completed', {'amount': body['amount']})
468
+ return {'ok': True}
469
+ ```
470
+
471
+ ---
472
+
473
+ ## Batching
474
+
475
+ ```python
476
+ trodo.init(
477
+ site_id='your-site-id',
478
+ batch_enabled=True,
479
+ batch_size=50,
480
+ batch_flush_interval=5.0,
481
+ )
482
+
483
+ # Always flush before process exit
484
+ import atexit
485
+ atexit.register(trodo.shutdown)
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Auto Events
491
+
492
+ ```python
493
+ trodo.init(site_id='your-site-id', auto_events=True)
494
+ # Hooks sys.excepthook and threading.excepthook
495
+ # Sends server_error events with distinct_id: 'server_global'
496
+
497
+ # Toggle at runtime
498
+ trodo.enable_auto_events()
499
+ trodo.disable_auto_events()
500
+ ```
501
+
502
+ ---
503
+
504
+ ## Thread Safety
505
+
506
+ The SDK is thread-safe. `SessionManager`, `EventQueue`, and `BatchFlusher` all use `threading.Lock` internally. Safe for multi-threaded Flask/Django/FastAPI apps.
507
+
508
+ ## License
509
+
510
+ ISC