trodo-python 2.10.8__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.
Files changed (47) hide show
  1. {trodo_python-2.10.8 → trodo_python-2.10.9}/PKG-INFO +1 -1
  2. {trodo_python-2.10.8 → trodo_python-2.10.9}/pyproject.toml +1 -1
  3. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_auto_instrument_fixes.py +50 -0
  4. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/__init__.py +1 -1
  5. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/auto_instrument.py +49 -7
  6. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo_python.egg-info/PKG-INFO +1 -1
  7. {trodo_python-2.10.8 → trodo_python-2.10.9}/README.md +0 -0
  8. {trodo_python-2.10.8 → trodo_python-2.10.9}/setup.cfg +0 -0
  9. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_anon_distinct_id 2.py +0 -0
  10. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_anon_distinct_id.py +0 -0
  11. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_cross_process_session.py +0 -0
  12. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_end_run.py +0 -0
  13. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_error_enrichment.py +0 -0
  14. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_llm_usage_cost.py +0 -0
  15. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_processor_methods.py +0 -0
  16. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_register_otel.py +0 -0
  17. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_start_run.py +0 -0
  18. {trodo_python-2.10.8 → trodo_python-2.10.9}/tests/test_wrap_agent_unchanged.py +0 -0
  19. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/api/__init__.py +0 -0
  20. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/api/async_client.py +0 -0
  21. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/api/endpoints.py +0 -0
  22. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/api/http_client.py +0 -0
  23. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/auto/__init__.py +0 -0
  24. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/auto/auto_event_manager.py +0 -0
  25. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/client.py +0 -0
  26. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/managers/__init__.py +0 -0
  27. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/managers/group_manager.py +0 -0
  28. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/managers/people_manager.py +0 -0
  29. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/__init__.py +0 -0
  30. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/context.py +0 -0
  31. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/helpers.py +0 -0
  32. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/processor.py +0 -0
  33. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/register.py +0 -0
  34. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/transport.py +0 -0
  35. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/otel/wrap_agent.py +0 -0
  36. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/queue/__init__.py +0 -0
  37. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/queue/batch_flusher.py +0 -0
  38. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/queue/event_queue.py +0 -0
  39. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/session/__init__.py +0 -0
  40. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/session/server_session.py +0 -0
  41. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/session/session_manager.py +0 -0
  42. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/types.py +0 -0
  43. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo/user_context.py +0 -0
  44. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo_python.egg-info/SOURCES.txt +0 -0
  45. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo_python.egg-info/dependency_links.txt +0 -0
  46. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo_python.egg-info/requires.txt +0 -0
  47. {trodo_python-2.10.8 → trodo_python-2.10.9}/trodo_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.10.8
3
+ Version: 2.10.9
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trodo-python"
7
- version = "2.10.8"
7
+ version = "2.10.9"
8
8
  description = "Trodo Analytics SDK for Python — server-side event tracking"
9
9
  readme = "README.md"
10
10
  license = { text = "ISC" }
@@ -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.8"
43
+ __version__ = "2.10.9"
44
44
 
45
45
  from typing import Any, Callable, Dict, List, Optional, Union
46
46
 
@@ -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
- if attrs.get("gen_ai.tool.name"):
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 attrs.get("gen_ai.operation.name") or attrs.get("gen_ai.request.model"):
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
- provider = attrs.get("gen_ai.system") or attrs.get("llm.vendor")
193
- prompt = attrs.get("gen_ai.prompt") or attrs.get("llm.prompts")
194
- completion = attrs.get("gen_ai.completion") or attrs.get("llm.completion")
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=attrs.get("gen_ai.request.temperature"),
216
- tool_name=attrs.get("gen_ai.tool.name"),
257
+ temperature=temperature,
258
+ tool_name=tool_name,
217
259
  input=prompt,
218
260
  output=completion,
219
261
  attributes=attrs or None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.10.8
3
+ Version: 2.10.9
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
File without changes
File without changes