trodo-python 2.10.9__py3-none-any.whl → 2.10.10__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 CHANGED
@@ -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.9"
43
+ __version__ = "2.10.10"
44
44
 
45
45
  from typing import Any, Callable, Dict, List, Optional, Union
46
46
 
@@ -67,6 +67,99 @@ def _infer_kind(attrs: Dict[str, Any]) -> str:
67
67
  _ATTR_TRODO_RUN_ID = "trodo.run_id"
68
68
  _ATTR_TRODO_PARENT_SPAN_ID = "trodo.parent_span_id"
69
69
 
70
+ # ---------------------------------------------------------------------------
71
+ # Convention-agnostic input/output extraction — covers every common LLM
72
+ # instrumentor convention, not just Vercel AI. Mirrors the backend's
73
+ # services/ingest/spanContent.js.
74
+ # - Vercel AI ...... ai.prompt(.messages) / ai.response.text|object|toolCalls,
75
+ # ai.toolCall.args / ai.toolCall.result
76
+ # - OTel GenAI ..... gen_ai.input.messages / gen_ai.output.messages (current);
77
+ # gen_ai.prompt / gen_ai.completion (legacy single value)
78
+ # - OpenLLMetry .... INDEXED gen_ai.prompt.{i}.role/content, gen_ai.completion.{i}.*
79
+ # - OpenInference .. input.value / output.value + INDEXED llm.input_messages.{i}.message.*
80
+ # - Traceloop/Langfuse/MLflow, plus generic llm.prompts / input / prompt
81
+ # ---------------------------------------------------------------------------
82
+ _INPUT_SINGLE_KEYS = [
83
+ "gen_ai.input.messages", "ai.prompt.messages", "ai.prompt", "gen_ai.prompt",
84
+ "input.value", "traceloop.entity.input", "langfuse.observation.input", "mlflow.spanInputs",
85
+ "llm.input_messages", "llm.prompts", "gen_ai.tool.input", "ai.toolCall.args", "input", "prompt",
86
+ ]
87
+ _OUTPUT_SINGLE_KEYS = [
88
+ "gen_ai.output.messages", "ai.response.text", "ai.response.object", "ai.response.toolCalls",
89
+ "gen_ai.completion", "output.value", "traceloop.entity.output", "langfuse.observation.output",
90
+ "mlflow.spanOutputs", "llm.output_messages", "llm.completions", "gen_ai.tool.output",
91
+ "ai.toolCall.result", "output", "completion",
92
+ ]
93
+ _INPUT_INDEXED_BASES = ["gen_ai.prompt", "llm.input_messages"]
94
+ _OUTPUT_INDEXED_BASES = ["gen_ai.completion", "llm.output_messages"]
95
+
96
+
97
+ def _first_single(attrs: Dict[str, Any], keys) -> Optional[Any]:
98
+ for k in keys:
99
+ v = attrs.get(k)
100
+ if v is None:
101
+ continue
102
+ if isinstance(v, str):
103
+ if v:
104
+ return v
105
+ continue
106
+ return v # dict/list — keep structure
107
+ return None
108
+
109
+
110
+ def _set_nested(obj: Dict[str, Any], path: str, val: Any) -> None:
111
+ parts = path.split(".")
112
+ cur = obj
113
+ for p in parts[:-1]:
114
+ nxt = cur.get(p)
115
+ if not isinstance(nxt, dict):
116
+ nxt = {}
117
+ cur[p] = nxt
118
+ cur = nxt
119
+ cur[parts[-1]] = val
120
+
121
+
122
+ def _reconstruct_indexed(attrs: Dict[str, Any], bases) -> Optional[str]:
123
+ import json as _json
124
+ for base in bases:
125
+ prefix = base + "."
126
+ by_index: Dict[int, Dict[str, Any]] = {}
127
+ for key, val in attrs.items():
128
+ if not key.startswith(prefix):
129
+ continue
130
+ rest = key[len(prefix):]
131
+ dot = rest.find(".")
132
+ if dot < 0:
133
+ continue
134
+ idx_str = rest[:dot]
135
+ if not idx_str.isdigit():
136
+ continue
137
+ idx = int(idx_str)
138
+ by_index.setdefault(idx, {})
139
+ _set_nested(by_index[idx], rest[dot + 1:], val)
140
+ if by_index:
141
+ arr = [by_index[i] for i in sorted(by_index)]
142
+ return _json.dumps(arr)
143
+ return None
144
+
145
+
146
+ def _extract_input(attrs: Dict[str, Any]) -> Optional[Any]:
147
+ if not isinstance(attrs, dict):
148
+ return None
149
+ single = _first_single(attrs, _INPUT_SINGLE_KEYS)
150
+ if single is not None:
151
+ return single
152
+ return _reconstruct_indexed(attrs, _INPUT_INDEXED_BASES)
153
+
154
+
155
+ def _extract_output(attrs: Dict[str, Any]) -> Optional[Any]:
156
+ if not isinstance(attrs, dict):
157
+ return None
158
+ single = _first_single(attrs, _OUTPUT_SINGLE_KEYS)
159
+ if single is not None:
160
+ return single
161
+ return _reconstruct_indexed(attrs, _OUTPUT_INDEXED_BASES)
162
+
70
163
 
71
164
  def _span_id_to_uuid(span_id: Optional[str]) -> Optional[str]:
72
165
  """Convert an OTel hex span id into UUID format. Mirrors trodo-node's
@@ -206,26 +299,9 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
206
299
  or attrs.get("ai.response.model")
207
300
  )
208
301
  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
- )
302
+ # Convention-agnostic see _extract_input/_extract_output above.
303
+ prompt = _extract_input(attrs)
304
+ completion = _extract_output(attrs)
229
305
  tool_name = (
230
306
  attrs.get("gen_ai.tool.name")
231
307
  or attrs.get("ai.toolCall.name")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.10.9
3
+ Version: 2.10.10
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
@@ -1,4 +1,4 @@
1
- trodo/__init__.py,sha256=C1zk-TLozRzrOUg23zjHKuR44cKhiM1vNEnYSvWugb8,18166
1
+ trodo/__init__.py,sha256=NdFjtAqmhBo4Ij9SAl7ByzTxUsVWSKbBGwGiaXKGioc,18167
2
2
  trodo/client.py,sha256=dhGiOJmxdWmXDDDabHN865_cAxttJs9LWPeDwEFbAwI,19107
3
3
  trodo/types.py,sha256=eySgUvCXROG2TxtxgiU0MNr5iH0DEcduK8bmYtTKG44,3138
4
4
  trodo/user_context.py,sha256=9la6azzwEanVmdP4ps_xMoufbeWVeIGU-M8ychmgajg,7859
@@ -12,7 +12,7 @@ trodo/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  trodo/managers/group_manager.py,sha256=ki3Se3qEoSZfREX63oeDeBmEfZF-ISHLE8azEtLg0tM,3542
13
13
  trodo/managers/people_manager.py,sha256=mMVnx40Mlifx6NGgChvohC9ViK6dQu2mkXNHbV8pK1E,2882
14
14
  trodo/otel/__init__.py,sha256=yiRFXWUU45bAM2CV37XeO7zf1hmnmjufdP4XO50yEyE,624
15
- trodo/otel/auto_instrument.py,sha256=ERAj60N9_Okmq3psZ7CF7dempez25Gsh7Zwf-cT-HLk,17882
15
+ trodo/otel/auto_instrument.py,sha256=JvXXssoCbX3vE_3GrqDZPIqKHXqVrFbW2DEnmuYtFoE,20834
16
16
  trodo/otel/context.py,sha256=iJ1rE42-SbO8VZHAxhIl2ZJXgNwLIVps5xLg8GKgfFc,1165
17
17
  trodo/otel/helpers.py,sha256=4HsjMOrE-7zuvaRSiGXxV7ZyfXQ5gLxtR3HdpLut9sk,20054
18
18
  trodo/otel/processor.py,sha256=aqcTmzTw9cESgIp829pu_XCa5_dG_2MaeJNsqJZeqQU,7495
@@ -25,7 +25,7 @@ trodo/queue/event_queue.py,sha256=EVFZrhlq_kwC3jJ2GK0wMhHISf9UzLCZNDnT_aZ2I2A,87
25
25
  trodo/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  trodo/session/server_session.py,sha256=McsudEiq33XDq3nqxgzBcUvIjQxCMscwEuAPnYXrTjs,2136
27
27
  trodo/session/session_manager.py,sha256=JrgH1VeicmtlxPR4dXEuJbxhi23OelkgwW3-9Slv80o,2525
28
- trodo_python-2.10.9.dist-info/METADATA,sha256=SMDqPlJivMYbIfWM7qRoRfH9HXmKT_XzAXnbK4F0uqs,20483
29
- trodo_python-2.10.9.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
30
- trodo_python-2.10.9.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
31
- trodo_python-2.10.9.dist-info/RECORD,,
28
+ trodo_python-2.10.10.dist-info/METADATA,sha256=5QKvyxt9X3UnpLrhJy2f4BBqCumdmkZX2y9oahc-sdg,20484
29
+ trodo_python-2.10.10.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
30
+ trodo_python-2.10.10.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
31
+ trodo_python-2.10.10.dist-info/RECORD,,