trodo-python 1.2.0__py3-none-any.whl → 2.2.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 +348 -47
- trodo/api/endpoints.py +5 -1
- trodo/api/http_client.py +30 -2
- trodo/client.py +239 -111
- trodo/otel/__init__.py +21 -0
- trodo/otel/auto_instrument.py +281 -0
- trodo/otel/context.py +44 -0
- trodo/otel/helpers.py +407 -0
- trodo/otel/processor.py +184 -0
- trodo/otel/wrap_agent.py +508 -0
- trodo/types.py +12 -68
- {trodo_python-1.2.0.dist-info → trodo_python-2.2.0.dist-info}/METADATA +183 -3
- {trodo_python-1.2.0.dist-info → trodo_python-2.2.0.dist-info}/RECORD +15 -9
- {trodo_python-1.2.0.dist-info → trodo_python-2.2.0.dist-info}/WHEEL +0 -0
- {trodo_python-1.2.0.dist-info → trodo_python-2.2.0.dist-info}/top_level.txt +0 -0
trodo/__init__.py
CHANGED
|
@@ -1,35 +1,71 @@
|
|
|
1
1
|
"""
|
|
2
2
|
trodo-python — Trodo Analytics SDK for Python
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Quick start (any stack):
|
|
5
5
|
import trodo
|
|
6
|
-
trodo.init(site_id='your-site-id')
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
trodo.init(site_id='your-site-id') # auto-instrument on by default
|
|
7
|
+
|
|
8
|
+
# Anthropic / OpenAI / LangChain / LlamaIndex — nothing else to do.
|
|
9
|
+
with trodo.wrap_agent('customer-support',
|
|
10
|
+
distinct_id=uid, conversation_id=conv) as run:
|
|
11
|
+
result = agent.run(query)
|
|
12
|
+
run.set_output(result)
|
|
13
|
+
|
|
14
|
+
trodo.feedback(run.run_id, satisfaction='positive', rating=5)
|
|
15
|
+
|
|
16
|
+
Raw-HTTP LLM caller (no OTel integration for your client):
|
|
17
|
+
resp = httpx.post(..., json=body).json()
|
|
18
|
+
trodo.track_llm_call(
|
|
19
|
+
model='gemini-2.5-flash', provider='google',
|
|
20
|
+
input_tokens=resp['usageMetadata']['promptTokenCount'],
|
|
21
|
+
output_tokens=resp['usageMetadata']['candidatesTokenCount'],
|
|
22
|
+
prompt=body, completion=resp,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Custom tool:
|
|
26
|
+
@trodo.tool()
|
|
27
|
+
def run_funnel_query(team_id, preset): ...
|
|
28
|
+
|
|
29
|
+
Downstream microservice (join the caller's run instead of making a new one):
|
|
30
|
+
# In FastAPI:
|
|
31
|
+
app.middleware('http')(trodo.fastapi_middleware())
|
|
32
|
+
|
|
33
|
+
# Or manually:
|
|
34
|
+
with trodo.join_run(
|
|
35
|
+
run_id=headers['X-Trodo-Run-Id'],
|
|
36
|
+
parent_span_id=headers['X-Trodo-Parent-Span-Id'],
|
|
37
|
+
):
|
|
38
|
+
...
|
|
15
39
|
"""
|
|
16
40
|
|
|
17
41
|
from __future__ import annotations
|
|
18
42
|
|
|
19
|
-
|
|
43
|
+
__version__ = "2.2.0"
|
|
44
|
+
|
|
45
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
20
46
|
|
|
21
47
|
from .client import TrodoClient
|
|
22
48
|
from .user_context import UserContext
|
|
23
49
|
from .managers.group_manager import GroupProfile
|
|
50
|
+
from .otel.wrap_agent import (
|
|
51
|
+
wrap_agent as _wrap_agent_ctx,
|
|
52
|
+
span as _span_ctx,
|
|
53
|
+
join_run as _join_run_ctx,
|
|
54
|
+
RunHandle,
|
|
55
|
+
SpanHandle,
|
|
56
|
+
)
|
|
24
57
|
from .types import (
|
|
25
58
|
ApiResult, ResetResult, WalletAddressResult,
|
|
26
|
-
|
|
59
|
+
FeedbackProps,
|
|
27
60
|
)
|
|
28
61
|
|
|
29
62
|
__all__ = [
|
|
30
63
|
"TrodoClient",
|
|
31
64
|
"UserContext",
|
|
32
65
|
"GroupProfile",
|
|
66
|
+
"RunHandle",
|
|
67
|
+
"SpanHandle",
|
|
68
|
+
"FeedbackProps",
|
|
33
69
|
"init",
|
|
34
70
|
"for_user",
|
|
35
71
|
"track",
|
|
@@ -40,17 +76,25 @@ __all__ = [
|
|
|
40
76
|
"disable_auto_events",
|
|
41
77
|
"flush",
|
|
42
78
|
"shutdown",
|
|
43
|
-
# Agent
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
79
|
+
# Agent runs
|
|
80
|
+
"wrap_agent",
|
|
81
|
+
"agent",
|
|
82
|
+
"span",
|
|
83
|
+
"tool",
|
|
84
|
+
"trace",
|
|
85
|
+
"llm",
|
|
86
|
+
"retrieval",
|
|
87
|
+
"join_run",
|
|
88
|
+
"start_run",
|
|
89
|
+
"end_run",
|
|
90
|
+
"track_llm_call",
|
|
91
|
+
"feedback",
|
|
92
|
+
"get_tracer",
|
|
93
|
+
# Cross-service propagation
|
|
94
|
+
"fastapi_middleware",
|
|
95
|
+
"propagation_headers",
|
|
96
|
+
"current_run_id",
|
|
97
|
+
"current_span_id",
|
|
54
98
|
]
|
|
55
99
|
|
|
56
100
|
# ============================================================================
|
|
@@ -68,6 +112,10 @@ def _get_client() -> TrodoClient:
|
|
|
68
112
|
return _client
|
|
69
113
|
|
|
70
114
|
|
|
115
|
+
def _maybe_client() -> Optional[TrodoClient]:
|
|
116
|
+
return _client
|
|
117
|
+
|
|
118
|
+
|
|
71
119
|
def init(
|
|
72
120
|
site_id: str,
|
|
73
121
|
api_base: str = "https://sdkapi.trodo.ai",
|
|
@@ -79,8 +127,15 @@ def init(
|
|
|
79
127
|
auto_events: bool = False,
|
|
80
128
|
on_error: Optional[Any] = None,
|
|
81
129
|
debug: bool = False,
|
|
130
|
+
auto_instrument: bool = True,
|
|
82
131
|
) -> TrodoClient:
|
|
83
|
-
"""Initialise the singleton SDK instance.
|
|
132
|
+
"""Initialise the singleton SDK instance.
|
|
133
|
+
|
|
134
|
+
``auto_instrument=True`` (default) registers OTel adapters for
|
|
135
|
+
Anthropic, OpenAI, LangChain, LlamaIndex, Google Generative AI and
|
|
136
|
+
any other installed opentelemetry-instrumentation-* package, so
|
|
137
|
+
LLM calls emit token/cost spans with no further code.
|
|
138
|
+
"""
|
|
84
139
|
global _client
|
|
85
140
|
_client = TrodoClient(
|
|
86
141
|
site_id=site_id,
|
|
@@ -93,6 +148,7 @@ def init(
|
|
|
93
148
|
auto_events=auto_events,
|
|
94
149
|
on_error=on_error,
|
|
95
150
|
debug=debug,
|
|
151
|
+
auto_instrument=auto_instrument,
|
|
96
152
|
)
|
|
97
153
|
return _client
|
|
98
154
|
|
|
@@ -101,7 +157,6 @@ def for_user(
|
|
|
101
157
|
distinct_id: str,
|
|
102
158
|
session_id: Optional[str] = None,
|
|
103
159
|
) -> UserContext:
|
|
104
|
-
"""Return a UserContext bound to the given distinctId."""
|
|
105
160
|
return _get_client().for_user(distinct_id, session_id)
|
|
106
161
|
|
|
107
162
|
|
|
@@ -111,22 +166,18 @@ def track(
|
|
|
111
166
|
properties: Optional[Dict[str, Any]] = None,
|
|
112
167
|
category: str = "custom",
|
|
113
168
|
) -> None:
|
|
114
|
-
"""Track an event for a user (direct-call pattern)."""
|
|
115
169
|
_get_client().track(distinct_id, event_name, properties, category)
|
|
116
170
|
|
|
117
171
|
|
|
118
172
|
def identify(identify_id: str, session_id: Optional[str] = None) -> UserContext:
|
|
119
|
-
"""Create or retrieve a UserContext for an identified user. Fires identify API on first call."""
|
|
120
173
|
return _get_client().identify(identify_id, session_id)
|
|
121
174
|
|
|
122
175
|
|
|
123
176
|
def wallet_address(distinct_id: str, wallet_addr: str) -> WalletAddressResult:
|
|
124
|
-
"""Associate a wallet address with a user."""
|
|
125
177
|
return _get_client().wallet_address(distinct_id, wallet_addr)
|
|
126
178
|
|
|
127
179
|
|
|
128
180
|
def reset(distinct_id: str) -> ResetResult:
|
|
129
|
-
"""Reset a user's session."""
|
|
130
181
|
return _get_client().reset(distinct_id)
|
|
131
182
|
|
|
132
183
|
|
|
@@ -139,39 +190,289 @@ def disable_auto_events() -> None:
|
|
|
139
190
|
|
|
140
191
|
|
|
141
192
|
def flush() -> None:
|
|
142
|
-
"""Flush any queued batch events."""
|
|
143
193
|
_get_client().flush()
|
|
144
194
|
|
|
145
195
|
|
|
146
196
|
def shutdown() -> None:
|
|
147
|
-
"""Flush, stop timers, and disable auto events."""
|
|
148
197
|
_get_client().shutdown()
|
|
149
198
|
|
|
150
199
|
|
|
151
200
|
# ----------------------------------------------------------------------------
|
|
152
|
-
# Agent
|
|
201
|
+
# Agent Runs
|
|
153
202
|
# ----------------------------------------------------------------------------
|
|
154
203
|
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
|
|
204
|
+
def wrap_agent(
|
|
205
|
+
agent_name: str,
|
|
206
|
+
*,
|
|
207
|
+
distinct_id: Optional[str] = None,
|
|
208
|
+
conversation_id: Optional[str] = None,
|
|
209
|
+
parent_run_id: Optional[str] = None,
|
|
210
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
211
|
+
) -> _wrap_agent_ctx:
|
|
212
|
+
"""Wrap a block as an agent run — returns a context manager yielding RunHandle."""
|
|
213
|
+
return _get_client().wrap_agent(
|
|
214
|
+
agent_name,
|
|
215
|
+
distinct_id=distinct_id,
|
|
216
|
+
conversation_id=conversation_id,
|
|
217
|
+
parent_run_id=parent_run_id,
|
|
218
|
+
metadata=metadata,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def agent(
|
|
223
|
+
agent_name: str,
|
|
224
|
+
*,
|
|
225
|
+
distinct_id: Optional[str] = None,
|
|
226
|
+
conversation_id: Optional[str] = None,
|
|
227
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
228
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
229
|
+
"""Decorator form of wrap_agent."""
|
|
230
|
+
return _get_client().agent(
|
|
231
|
+
agent_name,
|
|
232
|
+
distinct_id=distinct_id,
|
|
233
|
+
conversation_id=conversation_id,
|
|
234
|
+
metadata=metadata,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def span(
|
|
239
|
+
name: str,
|
|
240
|
+
*,
|
|
241
|
+
kind: str = "generic",
|
|
242
|
+
input: Any = None,
|
|
243
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
244
|
+
) -> _span_ctx:
|
|
245
|
+
"""Nested span inside the current run."""
|
|
246
|
+
return _get_client().span(name, kind=kind, input=input, attributes=attributes)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def join_run(
|
|
250
|
+
run_id: str,
|
|
251
|
+
parent_span_id: Optional[str] = None,
|
|
252
|
+
*,
|
|
253
|
+
name: str = "remote.handler",
|
|
254
|
+
kind: str = "agent",
|
|
255
|
+
input: Any = None,
|
|
256
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
257
|
+
) -> _join_run_ctx:
|
|
258
|
+
"""Open a span on an existing run owned by a remote service."""
|
|
259
|
+
return _get_client().join_run(
|
|
260
|
+
run_id,
|
|
261
|
+
parent_span_id,
|
|
262
|
+
name=name,
|
|
263
|
+
kind=kind,
|
|
264
|
+
input=input,
|
|
265
|
+
attributes=attributes,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def start_run(
|
|
270
|
+
agent_name: str,
|
|
271
|
+
*,
|
|
272
|
+
run_id: Optional[str] = None,
|
|
273
|
+
distinct_id: Optional[str] = None,
|
|
274
|
+
conversation_id: Optional[str] = None,
|
|
275
|
+
parent_run_id: Optional[str] = None,
|
|
276
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
277
|
+
input: Any = None,
|
|
278
|
+
) -> str:
|
|
279
|
+
"""Open a Run record without a context manager.
|
|
280
|
+
|
|
281
|
+
Pairs with :func:`end_run` for sessions that span multiple processes or
|
|
282
|
+
HTTP requests. Returns the run_id (caller-supplied or freshly minted).
|
|
283
|
+
Between start_run and end_run any process can use ``join_run(run_id, ...)``
|
|
284
|
+
to add spans.
|
|
285
|
+
"""
|
|
286
|
+
return _get_client().start_run(
|
|
287
|
+
agent_name,
|
|
288
|
+
run_id=run_id,
|
|
289
|
+
distinct_id=distinct_id,
|
|
290
|
+
conversation_id=conversation_id,
|
|
291
|
+
parent_run_id=parent_run_id,
|
|
292
|
+
metadata=metadata,
|
|
293
|
+
input=input,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def end_run(
|
|
298
|
+
run_id: str,
|
|
299
|
+
*,
|
|
300
|
+
output: Any = None,
|
|
301
|
+
status: str = "ok",
|
|
302
|
+
error_summary: Optional[str] = None,
|
|
303
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Finalise a Run opened by :func:`start_run`."""
|
|
306
|
+
_get_client().end_run(
|
|
307
|
+
run_id,
|
|
308
|
+
output=output,
|
|
309
|
+
status=status,
|
|
310
|
+
error_summary=error_summary,
|
|
311
|
+
metadata=metadata,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def tool(
|
|
316
|
+
name: Any = None,
|
|
317
|
+
fn: Optional[Callable[..., Any]] = None,
|
|
318
|
+
*,
|
|
319
|
+
kind: str = "tool",
|
|
320
|
+
) -> Any:
|
|
321
|
+
"""Wrap a function as a tool span — dual-form helper and decorator.
|
|
322
|
+
|
|
323
|
+
Helper form::
|
|
324
|
+
|
|
325
|
+
run_funnel_query = trodo.tool('run_funnel_query', run_funnel_query)
|
|
326
|
+
|
|
327
|
+
Decorator form (backward-compatible)::
|
|
328
|
+
|
|
329
|
+
@trodo.tool()
|
|
330
|
+
def run_funnel_query(team_id, preset): ...
|
|
331
|
+
|
|
332
|
+
@trodo.tool(name='custom-name')
|
|
333
|
+
async def fetch(...): ...
|
|
334
|
+
"""
|
|
335
|
+
# Deferred import: allow @trodo.tool() at import-time before init().
|
|
336
|
+
from .otel.helpers import tool as _tool_helper
|
|
337
|
+
return _tool_helper(name, fn, kind=kind)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def trace(
|
|
341
|
+
name: Any = None,
|
|
342
|
+
fn: Optional[Callable[..., Any]] = None,
|
|
343
|
+
) -> Any:
|
|
344
|
+
"""Wrap a function as a generic span — dual-form helper and decorator."""
|
|
345
|
+
from .otel.helpers import trace as _trace_helper
|
|
346
|
+
return _trace_helper(name, fn)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def llm(
|
|
350
|
+
name: Any = None,
|
|
351
|
+
fn: Optional[Callable[..., Any]] = None,
|
|
352
|
+
*,
|
|
353
|
+
model: Optional[str] = None,
|
|
354
|
+
provider: Optional[str] = None,
|
|
355
|
+
temperature: Optional[float] = None,
|
|
356
|
+
extract_usage: Optional[Callable[[Any], Any]] = None,
|
|
357
|
+
) -> Any:
|
|
358
|
+
"""Wrap an LLM call — auto-captures tokens from OpenAI/Anthropic/Gemini
|
|
359
|
+
response shapes. Dual-form helper and decorator."""
|
|
360
|
+
from .otel.helpers import llm as _llm_helper
|
|
361
|
+
return _llm_helper(
|
|
362
|
+
name,
|
|
363
|
+
fn,
|
|
364
|
+
model=model,
|
|
365
|
+
provider=provider,
|
|
366
|
+
temperature=temperature,
|
|
367
|
+
extract_usage=extract_usage,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def retrieval(
|
|
372
|
+
name: Any = None,
|
|
373
|
+
fn: Optional[Callable[..., Any]] = None,
|
|
374
|
+
) -> Any:
|
|
375
|
+
"""Wrap a retriever / vector search as a kind='retrieval' span."""
|
|
376
|
+
from .otel.helpers import retrieval as _retrieval_helper
|
|
377
|
+
return _retrieval_helper(name, fn)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def get_tracer(name: str = "trodo") -> Any:
|
|
381
|
+
"""Return a raw OTel tracer — the Trodo processor is already subscribed.
|
|
382
|
+
|
|
383
|
+
Use for advanced cases where you want to emit spans with the raw OTel
|
|
384
|
+
API; they'll be captured and forwarded to Trodo like any other span.
|
|
385
|
+
|
|
386
|
+
tracer = trodo.get_tracer('my.module')
|
|
387
|
+
with tracer.start_as_current_span('custom') as sp:
|
|
388
|
+
sp.set_attribute('foo', 'bar')
|
|
389
|
+
"""
|
|
390
|
+
try:
|
|
391
|
+
from opentelemetry import trace as _otel_trace # type: ignore
|
|
392
|
+
except Exception as e: # pragma: no cover
|
|
393
|
+
raise RuntimeError(
|
|
394
|
+
"trodo.get_tracer requires the opentelemetry-api package"
|
|
395
|
+
) from e
|
|
396
|
+
return _otel_trace.get_tracer(name)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def track_llm_call(
|
|
400
|
+
*,
|
|
401
|
+
model: Optional[str] = None,
|
|
402
|
+
provider: Optional[str] = None,
|
|
403
|
+
input_tokens: Optional[int] = None,
|
|
404
|
+
output_tokens: Optional[int] = None,
|
|
405
|
+
prompt: Any = None,
|
|
406
|
+
completion: Any = None,
|
|
407
|
+
temperature: Optional[float] = None,
|
|
408
|
+
cost: Optional[float] = None,
|
|
409
|
+
name: Optional[str] = None,
|
|
410
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
411
|
+
) -> None:
|
|
412
|
+
"""Record a one-shot LLM span for a raw-HTTP caller."""
|
|
413
|
+
from .otel.helpers import track_llm_call as _fn
|
|
414
|
+
_fn(
|
|
415
|
+
model=model,
|
|
416
|
+
provider=provider,
|
|
417
|
+
input_tokens=input_tokens,
|
|
418
|
+
output_tokens=output_tokens,
|
|
419
|
+
prompt=prompt,
|
|
420
|
+
completion=completion,
|
|
421
|
+
temperature=temperature,
|
|
422
|
+
cost=cost,
|
|
423
|
+
name=name,
|
|
424
|
+
metadata=metadata,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def feedback(
|
|
429
|
+
run_id: str,
|
|
430
|
+
*,
|
|
431
|
+
satisfaction: Optional[str] = None,
|
|
432
|
+
rating: Optional[float] = None,
|
|
433
|
+
comment: Optional[str] = None,
|
|
434
|
+
feedback: Optional[str] = None,
|
|
435
|
+
distinct_id: Optional[str] = None,
|
|
436
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
437
|
+
) -> ApiResult:
|
|
438
|
+
"""Attach feedback to a completed run."""
|
|
439
|
+
return _get_client().feedback(
|
|
440
|
+
run_id,
|
|
441
|
+
satisfaction=satisfaction,
|
|
442
|
+
rating=rating,
|
|
443
|
+
comment=comment,
|
|
444
|
+
feedback=feedback,
|
|
445
|
+
distinct_id=distinct_id,
|
|
446
|
+
metadata=metadata,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ----------------------------------------------------------------------------
|
|
451
|
+
# Cross-service propagation
|
|
452
|
+
# ----------------------------------------------------------------------------
|
|
158
453
|
|
|
454
|
+
def fastapi_middleware() -> Callable:
|
|
455
|
+
"""Return a FastAPI/Starlette middleware that auto-joins inbound runs.
|
|
159
456
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
457
|
+
Usage:
|
|
458
|
+
app = FastAPI()
|
|
459
|
+
trodo.init(site_id='...')
|
|
460
|
+
app.middleware('http')(trodo.fastapi_middleware())
|
|
461
|
+
"""
|
|
462
|
+
return _get_client().fastapi_middleware()
|
|
163
463
|
|
|
164
464
|
|
|
165
|
-
def
|
|
166
|
-
"""
|
|
167
|
-
|
|
465
|
+
def propagation_headers() -> Dict[str, str]:
|
|
466
|
+
"""Return outbound HTTP headers carrying the current run/span id."""
|
|
467
|
+
from .otel.helpers import propagation_headers as _fn
|
|
468
|
+
return _fn()
|
|
168
469
|
|
|
169
470
|
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
|
|
471
|
+
def current_run_id() -> Optional[str]:
|
|
472
|
+
from .otel.wrap_agent import current_run_id as _fn
|
|
473
|
+
return _fn()
|
|
173
474
|
|
|
174
475
|
|
|
175
|
-
def
|
|
176
|
-
|
|
177
|
-
|
|
476
|
+
def current_span_id() -> Optional[str]:
|
|
477
|
+
from .otel.wrap_agent import current_span_id as _fn
|
|
478
|
+
return _fn()
|
trodo/api/endpoints.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
TRACK = "/api/sdk/track"
|
|
2
|
-
TRACK_AGENT = "/api/sdk/track-agent"
|
|
3
2
|
EVENTS = "/api/events"
|
|
4
3
|
EVENTS_BULK = "/api/events/bulk"
|
|
5
4
|
IDENTIFY = "/api/sdk/identify"
|
|
@@ -19,3 +18,8 @@ GROUPS_SET = "/api/sdk/groups/set_group"
|
|
|
19
18
|
GROUPS_ADD = "/api/sdk/groups/add_group"
|
|
20
19
|
GROUPS_REMOVE = "/api/sdk/groups/remove_group"
|
|
21
20
|
GROUPS_PROFILE = "/api/sdk/groups/profile"
|
|
21
|
+
# Agent run tracking (Lemma-style, run-centric)
|
|
22
|
+
RUNS_INGEST = "/api/sdk/runs/ingest"
|
|
23
|
+
RUNS_START = "/api/sdk/runs/start"
|
|
24
|
+
RUNS_BASE = "/api/sdk/runs" # /runs/{run_id}/end, /spans, /feedback
|
|
25
|
+
OTLP_TRACES = "/api/sdk/otel/v1/traces"
|
trodo/api/http_client.py
CHANGED
|
@@ -86,5 +86,33 @@ class HttpClient:
|
|
|
86
86
|
def post_group(self, path: str, payload: Dict[str, Any]) -> ApiResult:
|
|
87
87
|
return self._request(path, payload)
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
# Agent Runs (Lemma-style)
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def post_run_ingest(self, payload: Dict[str, Any]) -> ApiResult:
|
|
94
|
+
return self._request("/api/sdk/runs/ingest", payload)
|
|
95
|
+
|
|
96
|
+
def post_run_start(self, payload: Dict[str, Any]) -> ApiResult:
|
|
97
|
+
return self._request("/api/sdk/runs/start", payload)
|
|
98
|
+
|
|
99
|
+
def post_run_end(self, run_id: str, payload: Dict[str, Any]) -> ApiResult:
|
|
100
|
+
from urllib.parse import quote
|
|
101
|
+
return self._request(f"/api/sdk/runs/{quote(run_id, safe='')}/end", payload)
|
|
102
|
+
|
|
103
|
+
def post_spans_append(self, run_id: str, spans: list) -> ApiResult:
|
|
104
|
+
from urllib.parse import quote
|
|
105
|
+
return self._request(
|
|
106
|
+
f"/api/sdk/runs/{quote(run_id, safe='')}/spans",
|
|
107
|
+
{"spans": spans},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def post_run_feedback(self, run_id: str, payload: Dict[str, Any]) -> ApiResult:
|
|
111
|
+
from urllib.parse import quote
|
|
112
|
+
return self._request(
|
|
113
|
+
f"/api/sdk/runs/{quote(run_id, safe='')}/feedback",
|
|
114
|
+
payload,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def post_otlp_traces(self, payload: Dict[str, Any]) -> ApiResult:
|
|
118
|
+
return self._request("/api/sdk/otel/v1/traces", payload)
|