trodo-python 2.10.9__py3-none-any.whl → 2.10.11__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.11"
44
44
 
45
45
  from typing import Any, Callable, Dict, List, Optional, Union
46
46
 
@@ -67,6 +67,100 @@ 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", "gen_ai.tool.call.arguments",
86
+ "ai.toolCall.args", "input", "prompt",
87
+ ]
88
+ _OUTPUT_SINGLE_KEYS = [
89
+ "gen_ai.output.messages", "ai.response.text", "ai.response.object", "ai.response.toolCalls",
90
+ "gen_ai.completion", "output.value", "traceloop.entity.output", "langfuse.observation.output",
91
+ "mlflow.spanOutputs", "llm.output_messages", "llm.completions", "gen_ai.tool.output",
92
+ "gen_ai.tool.call.result", "ai.toolCall.result", "output", "completion",
93
+ ]
94
+ _INPUT_INDEXED_BASES = ["gen_ai.prompt", "llm.input_messages"]
95
+ _OUTPUT_INDEXED_BASES = ["gen_ai.completion", "llm.output_messages"]
96
+
97
+
98
+ def _first_single(attrs: Dict[str, Any], keys) -> Optional[Any]:
99
+ for k in keys:
100
+ v = attrs.get(k)
101
+ if v is None:
102
+ continue
103
+ if isinstance(v, str):
104
+ if v:
105
+ return v
106
+ continue
107
+ return v # dict/list — keep structure
108
+ return None
109
+
110
+
111
+ def _set_nested(obj: Dict[str, Any], path: str, val: Any) -> None:
112
+ parts = path.split(".")
113
+ cur = obj
114
+ for p in parts[:-1]:
115
+ nxt = cur.get(p)
116
+ if not isinstance(nxt, dict):
117
+ nxt = {}
118
+ cur[p] = nxt
119
+ cur = nxt
120
+ cur[parts[-1]] = val
121
+
122
+
123
+ def _reconstruct_indexed(attrs: Dict[str, Any], bases) -> Optional[str]:
124
+ import json as _json
125
+ for base in bases:
126
+ prefix = base + "."
127
+ by_index: Dict[int, Dict[str, Any]] = {}
128
+ for key, val in attrs.items():
129
+ if not key.startswith(prefix):
130
+ continue
131
+ rest = key[len(prefix):]
132
+ dot = rest.find(".")
133
+ if dot < 0:
134
+ continue
135
+ idx_str = rest[:dot]
136
+ if not idx_str.isdigit():
137
+ continue
138
+ idx = int(idx_str)
139
+ by_index.setdefault(idx, {})
140
+ _set_nested(by_index[idx], rest[dot + 1:], val)
141
+ if by_index:
142
+ arr = [by_index[i] for i in sorted(by_index)]
143
+ return _json.dumps(arr)
144
+ return None
145
+
146
+
147
+ def _extract_input(attrs: Dict[str, Any]) -> Optional[Any]:
148
+ if not isinstance(attrs, dict):
149
+ return None
150
+ single = _first_single(attrs, _INPUT_SINGLE_KEYS)
151
+ if single is not None:
152
+ return single
153
+ return _reconstruct_indexed(attrs, _INPUT_INDEXED_BASES)
154
+
155
+
156
+ def _extract_output(attrs: Dict[str, Any]) -> Optional[Any]:
157
+ if not isinstance(attrs, dict):
158
+ return None
159
+ single = _first_single(attrs, _OUTPUT_SINGLE_KEYS)
160
+ if single is not None:
161
+ return single
162
+ return _reconstruct_indexed(attrs, _OUTPUT_INDEXED_BASES)
163
+
70
164
 
71
165
  def _span_id_to_uuid(span_id: Optional[str]) -> Optional[str]:
72
166
  """Convert an OTel hex span id into UUID format. Mirrors trodo-node's
@@ -206,26 +300,9 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
206
300
  or attrs.get("ai.response.model")
207
301
  )
208
302
  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
- )
303
+ # Convention-agnostic see _extract_input/_extract_output above.
304
+ prompt = _extract_input(attrs)
305
+ completion = _extract_output(attrs)
229
306
  tool_name = (
230
307
  attrs.get("gen_ai.tool.name")
231
308
  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.11
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=aUErMBjRUxOxfmsKnCMtBNIwcgAObOPu06zGy1YW9fc,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=hJesQMOOp86U66PampcGNINmBVvwVuU_WHVrbUMauug,20895
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.11.dist-info/METADATA,sha256=prtRZUT6xKP4qPbLFha6dIiOAaUbkKANEdBqsYRZGgY,20484
29
+ trodo_python-2.10.11.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
30
+ trodo_python-2.10.11.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
31
+ trodo_python-2.10.11.dist-info/RECORD,,