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 +1 -1
- trodo/otel/auto_instrument.py +97 -20
- {trodo_python-2.10.9.dist-info → trodo_python-2.10.11.dist-info}/METADATA +1 -1
- {trodo_python-2.10.9.dist-info → trodo_python-2.10.11.dist-info}/RECORD +6 -6
- {trodo_python-2.10.9.dist-info → trodo_python-2.10.11.dist-info}/WHEEL +0 -0
- {trodo_python-2.10.9.dist-info → trodo_python-2.10.11.dist-info}/top_level.txt +0 -0
trodo/__init__.py
CHANGED
trodo/otel/auto_instrument.py
CHANGED
|
@@ -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
|
-
#
|
|
210
|
-
|
|
211
|
-
|
|
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,4 +1,4 @@
|
|
|
1
|
-
trodo/__init__.py,sha256=
|
|
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=
|
|
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.
|
|
29
|
-
trodo_python-2.10.
|
|
30
|
-
trodo_python-2.10.
|
|
31
|
-
trodo_python-2.10.
|
|
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,,
|
|
File without changes
|
|
File without changes
|