trodo-python 2.10.7__tar.gz → 2.10.9__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-2.10.7 → trodo_python-2.10.9}/PKG-INFO +1 -1
- {trodo_python-2.10.7 → trodo_python-2.10.9}/pyproject.toml +1 -1
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_auto_instrument_fixes.py +50 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/__init__.py +8 -1
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/client.py +3 -1
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/auto_instrument.py +49 -7
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/register.py +1 -1
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo_python.egg-info/PKG-INFO +1 -1
- {trodo_python-2.10.7 → trodo_python-2.10.9}/README.md +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/setup.cfg +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_anon_distinct_id 2.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_anon_distinct_id.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_cross_process_session.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_end_run.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_error_enrichment.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_llm_usage_cost.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_processor_methods.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_register_otel.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_start_run.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/tests/test_wrap_agent_unchanged.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/api/__init__.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/api/async_client.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/api/endpoints.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/api/http_client.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/auto/__init__.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/auto/auto_event_manager.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/managers/__init__.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/managers/group_manager.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/managers/people_manager.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/__init__.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/context.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/helpers.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/processor.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/transport.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/otel/wrap_agent.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/queue/__init__.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/queue/batch_flusher.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/queue/event_queue.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/session/__init__.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/session/server_session.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/session/session_manager.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/types.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo/user_context.py +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo_python.egg-info/SOURCES.txt +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-2.10.7 → trodo_python-2.10.9}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -109,3 +109,53 @@ class TestOnStartStamping:
|
|
|
109
109
|
def test_drops_span_when_no_run_id_anywhere(self):
|
|
110
110
|
otel = _fake_otel_span(start_ns=1, end_ns=2, attrs={})
|
|
111
111
|
assert otel_span_to_trodo_span(otel) is None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestVercelAiContentMapping:
|
|
115
|
+
"""Regression: the bridge read input/output only from gen_ai.*/llm.*, so
|
|
116
|
+
Vercel AI SDK spans (ai.* convention) landed with input/output = None and
|
|
117
|
+
the prompt/response stranded in attributes -> nothing embedded or scored."""
|
|
118
|
+
|
|
119
|
+
def _span(self, name, attrs):
|
|
120
|
+
s = _fake_otel_span(start_ns=1_000_000_000, end_ns=2_000_000_000, attrs=attrs)
|
|
121
|
+
s.name = name
|
|
122
|
+
return s
|
|
123
|
+
|
|
124
|
+
def test_maps_ai_prompt_messages_and_response_text(self):
|
|
125
|
+
otel = self._span(
|
|
126
|
+
"ai.streamText.doStream",
|
|
127
|
+
{
|
|
128
|
+
"trodo.run_id": RUN_ID,
|
|
129
|
+
"gen_ai.system": "openrouter",
|
|
130
|
+
"ai.model.id": "gemini-2.5-flash-lite",
|
|
131
|
+
"ai.usage.promptTokens": 766,
|
|
132
|
+
"ai.usage.completionTokens": 5,
|
|
133
|
+
"ai.prompt.messages": '[{"role":"user","content":"gaming monitor specs"}]',
|
|
134
|
+
"ai.response.text": "Here are some gaming monitors...",
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
span = otel_span_to_trodo_span(otel)
|
|
138
|
+
assert span is not None
|
|
139
|
+
assert span.kind == "llm"
|
|
140
|
+
assert "gaming monitor specs" in span.input
|
|
141
|
+
assert "gaming monitors" in span.output
|
|
142
|
+
assert span.model == "gemini-2.5-flash-lite"
|
|
143
|
+
assert span.input_tokens == 766
|
|
144
|
+
assert span.output_tokens == 5
|
|
145
|
+
|
|
146
|
+
def test_maps_ai_toolcall_args_and_result(self):
|
|
147
|
+
otel = self._span(
|
|
148
|
+
"ai.toolCall",
|
|
149
|
+
{
|
|
150
|
+
"trodo.run_id": RUN_ID,
|
|
151
|
+
"ai.toolCall.name": "create_object",
|
|
152
|
+
"ai.toolCall.args": '{"isValid":true}',
|
|
153
|
+
"ai.toolCall.result": '{"ok":1}',
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
span = otel_span_to_trodo_span(otel)
|
|
157
|
+
assert span is not None
|
|
158
|
+
assert span.kind == "tool"
|
|
159
|
+
assert span.tool_name == "create_object"
|
|
160
|
+
assert span.input == '{"isValid":true}'
|
|
161
|
+
assert span.output == '{"ok":1}'
|
|
@@ -40,7 +40,7 @@ Downstream microservice (join the caller's run instead of making a new one):
|
|
|
40
40
|
|
|
41
41
|
from __future__ import annotations
|
|
42
42
|
|
|
43
|
-
__version__ = "2.10.
|
|
43
|
+
__version__ = "2.10.9"
|
|
44
44
|
|
|
45
45
|
from typing import Any, Callable, Dict, List, Optional, Union
|
|
46
46
|
|
|
@@ -132,6 +132,7 @@ def init(
|
|
|
132
132
|
on_error: Optional[Any] = None,
|
|
133
133
|
debug: bool = False,
|
|
134
134
|
auto_instrument: bool = True,
|
|
135
|
+
disable_instrumentations: Optional[List[str]] = None,
|
|
135
136
|
otel_mode: str = "trodo",
|
|
136
137
|
) -> TrodoClient:
|
|
137
138
|
"""Initialise the singleton SDK instance.
|
|
@@ -140,6 +141,11 @@ def init(
|
|
|
140
141
|
Anthropic, OpenAI, LangChain, LlamaIndex, Google Generative AI and
|
|
141
142
|
any other installed opentelemetry-instrumentation-* package, so
|
|
142
143
|
LLM calls emit token/cost spans with no further code.
|
|
144
|
+
|
|
145
|
+
``disable_instrumentations`` turns OFF specific instrumentors by id
|
|
146
|
+
(e.g. ``['openai']`` when LangChain is your primary framework, to avoid
|
|
147
|
+
double-counting the same model call). Mirrors Node's
|
|
148
|
+
``disableInstrumentations``.
|
|
143
149
|
"""
|
|
144
150
|
global _client
|
|
145
151
|
_client = TrodoClient(
|
|
@@ -154,6 +160,7 @@ def init(
|
|
|
154
160
|
on_error=on_error,
|
|
155
161
|
debug=debug,
|
|
156
162
|
auto_instrument=auto_instrument,
|
|
163
|
+
disable_instrumentations=disable_instrumentations,
|
|
157
164
|
otel_mode=otel_mode,
|
|
158
165
|
)
|
|
159
166
|
return _client
|
|
@@ -57,6 +57,7 @@ class TrodoClient:
|
|
|
57
57
|
on_error: Optional[Any] = None,
|
|
58
58
|
debug: bool = False,
|
|
59
59
|
auto_instrument: bool = True,
|
|
60
|
+
disable_instrumentations: Optional[List[str]] = None,
|
|
60
61
|
otel_mode: str = "trodo",
|
|
61
62
|
) -> None:
|
|
62
63
|
if not site_id:
|
|
@@ -100,13 +101,14 @@ class TrodoClient:
|
|
|
100
101
|
import os
|
|
101
102
|
if os.environ.get("TRODO_LEGACY_INIT") == "1":
|
|
102
103
|
# Pre-2.4 escape hatch for one minor version.
|
|
103
|
-
enable_auto_instrument(self._span_processor)
|
|
104
|
+
enable_auto_instrument(self._span_processor, disable=disable_instrumentations)
|
|
104
105
|
else:
|
|
105
106
|
_register_otel(
|
|
106
107
|
site_id=site_id,
|
|
107
108
|
processor=self._span_processor,
|
|
108
109
|
mode=otel_mode,
|
|
109
110
|
endpoint=api_base,
|
|
111
|
+
disable_instrumentations=disable_instrumentations,
|
|
110
112
|
)
|
|
111
113
|
|
|
112
114
|
def _get_span_processor(self) -> TrodoSpanProcessor:
|
|
@@ -45,9 +45,19 @@ def _trunc(s: Any, max_len: int) -> Optional[str]:
|
|
|
45
45
|
def _infer_kind(attrs: Dict[str, Any]) -> str:
|
|
46
46
|
if not attrs:
|
|
47
47
|
return "generic"
|
|
48
|
-
|
|
48
|
+
# Vercel AI SDK tool spans (`ai.toolCall`) carry ai.toolCall.name, not
|
|
49
|
+
# gen_ai.tool.name — without this they landed as 'generic'.
|
|
50
|
+
if attrs.get("gen_ai.tool.name") or attrs.get("ai.toolCall.name") or attrs.get("ai.functionCall.name"):
|
|
49
51
|
return "tool"
|
|
50
|
-
if
|
|
52
|
+
if (
|
|
53
|
+
attrs.get("gen_ai.operation.name")
|
|
54
|
+
or attrs.get("gen_ai.request.model")
|
|
55
|
+
or attrs.get("gen_ai.response.model")
|
|
56
|
+
or attrs.get("ai.model.id")
|
|
57
|
+
or attrs.get("ai.response.model")
|
|
58
|
+
or attrs.get("gen_ai.usage.input_tokens") is not None
|
|
59
|
+
or attrs.get("ai.usage.promptTokens") is not None
|
|
60
|
+
):
|
|
51
61
|
return "llm"
|
|
52
62
|
if attrs.get("db.system") or attrs.get("retrieval.query"):
|
|
53
63
|
return "retrieval"
|
|
@@ -178,20 +188,52 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
|
|
|
178
188
|
attrs.get("gen_ai.usage.input_tokens")
|
|
179
189
|
or attrs.get("gen_ai.usage.prompt_tokens")
|
|
180
190
|
or attrs.get("llm.usage.prompt_tokens")
|
|
191
|
+
or attrs.get("ai.usage.promptTokens")
|
|
192
|
+
or attrs.get("ai.usage.inputTokens")
|
|
181
193
|
)
|
|
182
194
|
out_toks = (
|
|
183
195
|
attrs.get("gen_ai.usage.output_tokens")
|
|
184
196
|
or attrs.get("gen_ai.usage.completion_tokens")
|
|
185
197
|
or attrs.get("llm.usage.completion_tokens")
|
|
198
|
+
or attrs.get("ai.usage.completionTokens")
|
|
199
|
+
or attrs.get("ai.usage.outputTokens")
|
|
186
200
|
)
|
|
187
201
|
model = (
|
|
188
202
|
attrs.get("gen_ai.request.model")
|
|
189
203
|
or attrs.get("gen_ai.response.model")
|
|
190
204
|
or attrs.get("llm.request.model")
|
|
205
|
+
or attrs.get("ai.model.id")
|
|
206
|
+
or attrs.get("ai.response.model")
|
|
207
|
+
)
|
|
208
|
+
provider = attrs.get("gen_ai.system") or attrs.get("llm.vendor") or attrs.get("ai.model.provider")
|
|
209
|
+
# Vercel AI SDK uses the `ai.*` convention, NOT gen_ai.prompt/completion:
|
|
210
|
+
# ai.prompt.messages / ai.prompt (input), ai.response.text / ai.response.object
|
|
211
|
+
# (output), ai.toolCall.args / ai.toolCall.result (tool spans). Omitting these
|
|
212
|
+
# left input/output empty -> NULL in the DB -> nothing embedded or scored.
|
|
213
|
+
prompt = (
|
|
214
|
+
attrs.get("gen_ai.prompt")
|
|
215
|
+
or attrs.get("llm.prompts")
|
|
216
|
+
or attrs.get("ai.prompt")
|
|
217
|
+
or attrs.get("ai.prompt.messages")
|
|
218
|
+
or attrs.get("ai.toolCall.args")
|
|
219
|
+
or attrs.get("input.value")
|
|
220
|
+
)
|
|
221
|
+
completion = (
|
|
222
|
+
attrs.get("gen_ai.completion")
|
|
223
|
+
or attrs.get("llm.completion")
|
|
224
|
+
or attrs.get("ai.response.text")
|
|
225
|
+
or attrs.get("ai.response.object")
|
|
226
|
+
or attrs.get("ai.toolCall.result")
|
|
227
|
+
or attrs.get("output.value")
|
|
228
|
+
)
|
|
229
|
+
tool_name = (
|
|
230
|
+
attrs.get("gen_ai.tool.name")
|
|
231
|
+
or attrs.get("ai.toolCall.name")
|
|
232
|
+
or attrs.get("ai.functionCall.name")
|
|
191
233
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
234
|
+
temperature = attrs.get("gen_ai.request.temperature")
|
|
235
|
+
if temperature is None:
|
|
236
|
+
temperature = attrs.get("ai.settings.temperature")
|
|
195
237
|
|
|
196
238
|
return TrodoSpan(
|
|
197
239
|
span_id=span_id,
|
|
@@ -212,8 +254,8 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
|
|
|
212
254
|
provider=provider,
|
|
213
255
|
input_tokens=int(in_toks) if in_toks is not None else None,
|
|
214
256
|
output_tokens=int(out_toks) if out_toks is not None else None,
|
|
215
|
-
temperature=
|
|
216
|
-
tool_name=
|
|
257
|
+
temperature=temperature,
|
|
258
|
+
tool_name=tool_name,
|
|
217
259
|
input=prompt,
|
|
218
260
|
output=completion,
|
|
219
261
|
attributes=attrs or None,
|
|
@@ -93,7 +93,7 @@ def register_otel(
|
|
|
93
93
|
endpoint = (endpoint or "https://sdkapi.trodo.ai").rstrip("/")
|
|
94
94
|
|
|
95
95
|
if mode == "trodo":
|
|
96
|
-
active = enable_auto_instrument(processor)
|
|
96
|
+
active = enable_auto_instrument(processor, disable=disable_instrumentations)
|
|
97
97
|
elif mode == "otlp":
|
|
98
98
|
active = _setup_otlp_mode(
|
|
99
99
|
site_id=site_id,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|