trodo-python 2.10.8__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 +1 -1
- trodo/otel/auto_instrument.py +125 -7
- {trodo_python-2.10.8.dist-info → trodo_python-2.10.10.dist-info}/METADATA +1 -1
- {trodo_python-2.10.8.dist-info → trodo_python-2.10.10.dist-info}/RECORD +6 -6
- {trodo_python-2.10.8.dist-info → trodo_python-2.10.10.dist-info}/WHEEL +0 -0
- {trodo_python-2.10.8.dist-info → trodo_python-2.10.10.dist-info}/top_level.txt +0 -0
trodo/__init__.py
CHANGED
trodo/otel/auto_instrument.py
CHANGED
|
@@ -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"
|
|
@@ -57,6 +67,99 @@ def _infer_kind(attrs: Dict[str, Any]) -> str:
|
|
|
57
67
|
_ATTR_TRODO_RUN_ID = "trodo.run_id"
|
|
58
68
|
_ATTR_TRODO_PARENT_SPAN_ID = "trodo.parent_span_id"
|
|
59
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
|
+
|
|
60
163
|
|
|
61
164
|
def _span_id_to_uuid(span_id: Optional[str]) -> Optional[str]:
|
|
62
165
|
"""Convert an OTel hex span id into UUID format. Mirrors trodo-node's
|
|
@@ -178,20 +281,35 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
|
|
|
178
281
|
attrs.get("gen_ai.usage.input_tokens")
|
|
179
282
|
or attrs.get("gen_ai.usage.prompt_tokens")
|
|
180
283
|
or attrs.get("llm.usage.prompt_tokens")
|
|
284
|
+
or attrs.get("ai.usage.promptTokens")
|
|
285
|
+
or attrs.get("ai.usage.inputTokens")
|
|
181
286
|
)
|
|
182
287
|
out_toks = (
|
|
183
288
|
attrs.get("gen_ai.usage.output_tokens")
|
|
184
289
|
or attrs.get("gen_ai.usage.completion_tokens")
|
|
185
290
|
or attrs.get("llm.usage.completion_tokens")
|
|
291
|
+
or attrs.get("ai.usage.completionTokens")
|
|
292
|
+
or attrs.get("ai.usage.outputTokens")
|
|
186
293
|
)
|
|
187
294
|
model = (
|
|
188
295
|
attrs.get("gen_ai.request.model")
|
|
189
296
|
or attrs.get("gen_ai.response.model")
|
|
190
297
|
or attrs.get("llm.request.model")
|
|
298
|
+
or attrs.get("ai.model.id")
|
|
299
|
+
or attrs.get("ai.response.model")
|
|
300
|
+
)
|
|
301
|
+
provider = attrs.get("gen_ai.system") or attrs.get("llm.vendor") or attrs.get("ai.model.provider")
|
|
302
|
+
# Convention-agnostic — see _extract_input/_extract_output above.
|
|
303
|
+
prompt = _extract_input(attrs)
|
|
304
|
+
completion = _extract_output(attrs)
|
|
305
|
+
tool_name = (
|
|
306
|
+
attrs.get("gen_ai.tool.name")
|
|
307
|
+
or attrs.get("ai.toolCall.name")
|
|
308
|
+
or attrs.get("ai.functionCall.name")
|
|
191
309
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
310
|
+
temperature = attrs.get("gen_ai.request.temperature")
|
|
311
|
+
if temperature is None:
|
|
312
|
+
temperature = attrs.get("ai.settings.temperature")
|
|
195
313
|
|
|
196
314
|
return TrodoSpan(
|
|
197
315
|
span_id=span_id,
|
|
@@ -212,8 +330,8 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
|
|
|
212
330
|
provider=provider,
|
|
213
331
|
input_tokens=int(in_toks) if in_toks is not None else None,
|
|
214
332
|
output_tokens=int(out_toks) if out_toks is not None else None,
|
|
215
|
-
temperature=
|
|
216
|
-
tool_name=
|
|
333
|
+
temperature=temperature,
|
|
334
|
+
tool_name=tool_name,
|
|
217
335
|
input=prompt,
|
|
218
336
|
output=completion,
|
|
219
337
|
attributes=attrs or None,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
trodo/__init__.py,sha256=
|
|
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=
|
|
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.
|
|
29
|
-
trodo_python-2.10.
|
|
30
|
-
trodo_python-2.10.
|
|
31
|
-
trodo_python-2.10.
|
|
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,,
|
|
File without changes
|
|
File without changes
|