splunk-otel-util-genai 0.1.3__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.
- opentelemetry/util/genai/__init__.py +17 -0
- opentelemetry/util/genai/_fsspec_upload/__init__.py +39 -0
- opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py +184 -0
- opentelemetry/util/genai/attributes.py +60 -0
- opentelemetry/util/genai/callbacks.py +24 -0
- opentelemetry/util/genai/config.py +184 -0
- opentelemetry/util/genai/debug.py +183 -0
- opentelemetry/util/genai/emitters/__init__.py +25 -0
- opentelemetry/util/genai/emitters/composite.py +186 -0
- opentelemetry/util/genai/emitters/configuration.py +324 -0
- opentelemetry/util/genai/emitters/content_events.py +153 -0
- opentelemetry/util/genai/emitters/evaluation.py +519 -0
- opentelemetry/util/genai/emitters/metrics.py +308 -0
- opentelemetry/util/genai/emitters/span.py +774 -0
- opentelemetry/util/genai/emitters/spec.py +48 -0
- opentelemetry/util/genai/emitters/utils.py +961 -0
- opentelemetry/util/genai/environment_variables.py +200 -0
- opentelemetry/util/genai/handler.py +1002 -0
- opentelemetry/util/genai/instruments.py +44 -0
- opentelemetry/util/genai/interfaces.py +58 -0
- opentelemetry/util/genai/plugins.py +114 -0
- opentelemetry/util/genai/span_context.py +80 -0
- opentelemetry/util/genai/types.py +440 -0
- opentelemetry/util/genai/upload_hook.py +119 -0
- opentelemetry/util/genai/utils.py +182 -0
- opentelemetry/util/genai/version.py +15 -0
- splunk_otel_util_genai-0.1.3.dist-info/METADATA +70 -0
- splunk_otel_util_genai-0.1.3.dist-info/RECORD +31 -0
- splunk_otel_util_genai-0.1.3.dist-info/WHEEL +4 -0
- splunk_otel_util_genai-0.1.3.dist-info/entry_points.txt +5 -0
- splunk_otel_util_genai-0.1.3.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from opentelemetry import trace
|
|
6
|
+
from opentelemetry.metrics import Histogram, Meter, get_meter
|
|
7
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
8
|
+
error_attributes as ErrorAttributes,
|
|
9
|
+
)
|
|
10
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
11
|
+
gen_ai_attributes as GenAI,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from ..instruments import Instruments
|
|
15
|
+
from ..interfaces import EmitterMeta
|
|
16
|
+
from ..types import (
|
|
17
|
+
AgentInvocation,
|
|
18
|
+
EmbeddingInvocation,
|
|
19
|
+
Error,
|
|
20
|
+
LLMInvocation,
|
|
21
|
+
ToolCall,
|
|
22
|
+
Workflow,
|
|
23
|
+
)
|
|
24
|
+
from .utils import (
|
|
25
|
+
_get_metric_attributes,
|
|
26
|
+
_record_duration,
|
|
27
|
+
_record_token_metrics,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MetricsEmitter(EmitterMeta):
|
|
32
|
+
"""Emits GenAI metrics (duration + token usage).
|
|
33
|
+
|
|
34
|
+
Supports LLMInvocation, EmbeddingInvocation, ToolCall, Workflow, and Agent.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
role = "metric"
|
|
38
|
+
name = "semconv_metrics"
|
|
39
|
+
|
|
40
|
+
def __init__(self, meter: Optional[Meter] = None):
|
|
41
|
+
_meter: Meter = meter or get_meter(__name__)
|
|
42
|
+
instruments = Instruments(_meter)
|
|
43
|
+
self._duration_histogram: Histogram = (
|
|
44
|
+
instruments.operation_duration_histogram
|
|
45
|
+
)
|
|
46
|
+
self._token_histogram: Histogram = instruments.token_usage_histogram
|
|
47
|
+
self._workflow_duration_histogram: Histogram = (
|
|
48
|
+
instruments.workflow_duration_histogram
|
|
49
|
+
)
|
|
50
|
+
self._agent_duration_histogram: Histogram = (
|
|
51
|
+
instruments.agent_duration_histogram
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def on_start(self, obj: Any) -> None: # no-op for metrics
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def on_end(self, obj: Any) -> None:
|
|
58
|
+
if isinstance(obj, Workflow):
|
|
59
|
+
self._record_workflow_metrics(obj)
|
|
60
|
+
return
|
|
61
|
+
if isinstance(obj, AgentInvocation):
|
|
62
|
+
self._record_agent_metrics(obj)
|
|
63
|
+
return
|
|
64
|
+
# Step metrics removed
|
|
65
|
+
|
|
66
|
+
if isinstance(obj, LLMInvocation):
|
|
67
|
+
llm_invocation = obj
|
|
68
|
+
metric_attrs = _get_metric_attributes(
|
|
69
|
+
llm_invocation.request_model,
|
|
70
|
+
llm_invocation.response_model_name,
|
|
71
|
+
llm_invocation.operation,
|
|
72
|
+
llm_invocation.provider,
|
|
73
|
+
llm_invocation.framework,
|
|
74
|
+
)
|
|
75
|
+
# Add agent context if available
|
|
76
|
+
if llm_invocation.agent_name:
|
|
77
|
+
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
|
|
78
|
+
llm_invocation.agent_name
|
|
79
|
+
)
|
|
80
|
+
if llm_invocation.agent_id:
|
|
81
|
+
metric_attrs[GenAI.GEN_AI_AGENT_ID] = llm_invocation.agent_id
|
|
82
|
+
|
|
83
|
+
_record_token_metrics(
|
|
84
|
+
self._token_histogram,
|
|
85
|
+
llm_invocation.input_tokens,
|
|
86
|
+
llm_invocation.output_tokens,
|
|
87
|
+
metric_attrs,
|
|
88
|
+
span=getattr(llm_invocation, "span", None),
|
|
89
|
+
)
|
|
90
|
+
_record_duration(
|
|
91
|
+
self._duration_histogram,
|
|
92
|
+
llm_invocation,
|
|
93
|
+
metric_attrs,
|
|
94
|
+
span=getattr(llm_invocation, "span", None),
|
|
95
|
+
)
|
|
96
|
+
return
|
|
97
|
+
if isinstance(obj, ToolCall):
|
|
98
|
+
tool_invocation = obj
|
|
99
|
+
metric_attrs = _get_metric_attributes(
|
|
100
|
+
tool_invocation.name,
|
|
101
|
+
None,
|
|
102
|
+
GenAI.GenAiOperationNameValues.EXECUTE_TOOL.value,
|
|
103
|
+
tool_invocation.provider,
|
|
104
|
+
tool_invocation.framework,
|
|
105
|
+
)
|
|
106
|
+
# Add agent context if available
|
|
107
|
+
if tool_invocation.agent_name:
|
|
108
|
+
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
|
|
109
|
+
tool_invocation.agent_name
|
|
110
|
+
)
|
|
111
|
+
if tool_invocation.agent_id:
|
|
112
|
+
metric_attrs[GenAI.GEN_AI_AGENT_ID] = tool_invocation.agent_id
|
|
113
|
+
|
|
114
|
+
_record_duration(
|
|
115
|
+
self._duration_histogram,
|
|
116
|
+
tool_invocation,
|
|
117
|
+
metric_attrs,
|
|
118
|
+
span=getattr(tool_invocation, "span", None),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if isinstance(obj, EmbeddingInvocation):
|
|
122
|
+
embedding_invocation = obj
|
|
123
|
+
metric_attrs = _get_metric_attributes(
|
|
124
|
+
embedding_invocation.request_model,
|
|
125
|
+
None,
|
|
126
|
+
embedding_invocation.operation_name,
|
|
127
|
+
embedding_invocation.provider,
|
|
128
|
+
embedding_invocation.framework,
|
|
129
|
+
server_address=embedding_invocation.server_address,
|
|
130
|
+
server_port=embedding_invocation.server_port,
|
|
131
|
+
)
|
|
132
|
+
# Add agent context if available
|
|
133
|
+
if embedding_invocation.agent_name:
|
|
134
|
+
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
|
|
135
|
+
embedding_invocation.agent_name
|
|
136
|
+
)
|
|
137
|
+
if embedding_invocation.agent_id:
|
|
138
|
+
metric_attrs[GenAI.GEN_AI_AGENT_ID] = (
|
|
139
|
+
embedding_invocation.agent_id
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
_record_duration(
|
|
143
|
+
self._duration_histogram,
|
|
144
|
+
embedding_invocation,
|
|
145
|
+
metric_attrs,
|
|
146
|
+
span=getattr(embedding_invocation, "span", None),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def on_error(self, error: Error, obj: Any) -> None:
|
|
150
|
+
# Handle new agentic types
|
|
151
|
+
if isinstance(obj, Workflow):
|
|
152
|
+
self._record_workflow_metrics(obj)
|
|
153
|
+
return
|
|
154
|
+
if isinstance(obj, AgentInvocation):
|
|
155
|
+
self._record_agent_metrics(obj)
|
|
156
|
+
return
|
|
157
|
+
# Step metrics removed
|
|
158
|
+
|
|
159
|
+
# Handle existing types with agent context
|
|
160
|
+
if isinstance(obj, LLMInvocation):
|
|
161
|
+
llm_invocation = obj
|
|
162
|
+
metric_attrs = _get_metric_attributes(
|
|
163
|
+
llm_invocation.request_model,
|
|
164
|
+
llm_invocation.response_model_name,
|
|
165
|
+
llm_invocation.operation,
|
|
166
|
+
llm_invocation.provider,
|
|
167
|
+
llm_invocation.framework,
|
|
168
|
+
)
|
|
169
|
+
# Add agent context if available
|
|
170
|
+
if llm_invocation.agent_name:
|
|
171
|
+
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
|
|
172
|
+
llm_invocation.agent_name
|
|
173
|
+
)
|
|
174
|
+
if llm_invocation.agent_id:
|
|
175
|
+
metric_attrs[GenAI.GEN_AI_AGENT_ID] = llm_invocation.agent_id
|
|
176
|
+
if getattr(error, "type", None) is not None:
|
|
177
|
+
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
|
|
178
|
+
error.type.__qualname__
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
_record_duration(
|
|
182
|
+
self._duration_histogram, llm_invocation, metric_attrs
|
|
183
|
+
)
|
|
184
|
+
return
|
|
185
|
+
if isinstance(obj, ToolCall):
|
|
186
|
+
tool_invocation = obj
|
|
187
|
+
metric_attrs = _get_metric_attributes(
|
|
188
|
+
tool_invocation.name,
|
|
189
|
+
None,
|
|
190
|
+
GenAI.GenAiOperationNameValues.EXECUTE_TOOL.value,
|
|
191
|
+
tool_invocation.provider,
|
|
192
|
+
tool_invocation.framework,
|
|
193
|
+
)
|
|
194
|
+
# Add agent context if available
|
|
195
|
+
if tool_invocation.agent_name:
|
|
196
|
+
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
|
|
197
|
+
tool_invocation.agent_name
|
|
198
|
+
)
|
|
199
|
+
if tool_invocation.agent_id:
|
|
200
|
+
metric_attrs[GenAI.GEN_AI_AGENT_ID] = tool_invocation.agent_id
|
|
201
|
+
if getattr(error, "type", None) is not None:
|
|
202
|
+
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
|
|
203
|
+
error.type.__qualname__
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
_record_duration(
|
|
207
|
+
self._duration_histogram,
|
|
208
|
+
tool_invocation,
|
|
209
|
+
metric_attrs,
|
|
210
|
+
span=getattr(tool_invocation, "span", None),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if isinstance(obj, EmbeddingInvocation):
|
|
214
|
+
embedding_invocation = obj
|
|
215
|
+
metric_attrs = _get_metric_attributes(
|
|
216
|
+
embedding_invocation.request_model,
|
|
217
|
+
None,
|
|
218
|
+
embedding_invocation.operation_name,
|
|
219
|
+
embedding_invocation.provider,
|
|
220
|
+
embedding_invocation.framework,
|
|
221
|
+
server_address=embedding_invocation.server_address,
|
|
222
|
+
server_port=embedding_invocation.server_port,
|
|
223
|
+
)
|
|
224
|
+
# Add agent context if available
|
|
225
|
+
if embedding_invocation.agent_name:
|
|
226
|
+
metric_attrs[GenAI.GEN_AI_AGENT_NAME] = (
|
|
227
|
+
embedding_invocation.agent_name
|
|
228
|
+
)
|
|
229
|
+
if embedding_invocation.agent_id:
|
|
230
|
+
metric_attrs[GenAI.GEN_AI_AGENT_ID] = (
|
|
231
|
+
embedding_invocation.agent_id
|
|
232
|
+
)
|
|
233
|
+
if getattr(error, "type", None) is not None:
|
|
234
|
+
metric_attrs[ErrorAttributes.ERROR_TYPE] = (
|
|
235
|
+
error.type.__qualname__
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
_record_duration(
|
|
239
|
+
self._duration_histogram,
|
|
240
|
+
embedding_invocation,
|
|
241
|
+
metric_attrs,
|
|
242
|
+
span=getattr(embedding_invocation, "span", None),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def handles(self, obj: Any) -> bool:
|
|
246
|
+
return isinstance(
|
|
247
|
+
obj,
|
|
248
|
+
(
|
|
249
|
+
LLMInvocation,
|
|
250
|
+
ToolCall,
|
|
251
|
+
Workflow,
|
|
252
|
+
AgentInvocation,
|
|
253
|
+
EmbeddingInvocation,
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Helper methods for new agentic types
|
|
258
|
+
def _record_workflow_metrics(self, workflow: Workflow) -> None:
|
|
259
|
+
"""Record metrics for a workflow."""
|
|
260
|
+
if workflow.end_time is None:
|
|
261
|
+
return
|
|
262
|
+
duration = workflow.end_time - workflow.start_time
|
|
263
|
+
metric_attrs = {
|
|
264
|
+
"gen_ai.workflow.name": workflow.name,
|
|
265
|
+
}
|
|
266
|
+
if workflow.workflow_type:
|
|
267
|
+
metric_attrs["gen_ai.workflow.type"] = workflow.workflow_type
|
|
268
|
+
if workflow.framework:
|
|
269
|
+
metric_attrs["gen_ai.framework"] = workflow.framework
|
|
270
|
+
|
|
271
|
+
context = None
|
|
272
|
+
span = getattr(workflow, "span", None)
|
|
273
|
+
if span is not None:
|
|
274
|
+
try:
|
|
275
|
+
context = trace.set_span_in_context(span)
|
|
276
|
+
except (ValueError, RuntimeError): # pragma: no cover - defensive
|
|
277
|
+
context = None
|
|
278
|
+
|
|
279
|
+
self._workflow_duration_histogram.record(
|
|
280
|
+
duration, attributes=metric_attrs, context=context
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def _record_agent_metrics(self, agent: AgentInvocation) -> None:
|
|
284
|
+
"""Record metrics for an agent operation."""
|
|
285
|
+
if agent.end_time is None:
|
|
286
|
+
return
|
|
287
|
+
duration = agent.end_time - agent.start_time
|
|
288
|
+
metric_attrs = {
|
|
289
|
+
GenAI.GEN_AI_OPERATION_NAME: agent.operation,
|
|
290
|
+
GenAI.GEN_AI_AGENT_NAME: agent.name,
|
|
291
|
+
GenAI.GEN_AI_AGENT_ID: str(agent.run_id),
|
|
292
|
+
}
|
|
293
|
+
if agent.agent_type:
|
|
294
|
+
metric_attrs["gen_ai.agent.type"] = agent.agent_type
|
|
295
|
+
if agent.framework:
|
|
296
|
+
metric_attrs["gen_ai.framework"] = agent.framework
|
|
297
|
+
|
|
298
|
+
context = None
|
|
299
|
+
span = getattr(agent, "span", None)
|
|
300
|
+
if span is not None:
|
|
301
|
+
try:
|
|
302
|
+
context = trace.set_span_in_context(span)
|
|
303
|
+
except (ValueError, RuntimeError): # pragma: no cover - defensive
|
|
304
|
+
context = None
|
|
305
|
+
|
|
306
|
+
self._agent_duration_histogram.record(
|
|
307
|
+
duration, attributes=metric_attrs, context=context
|
|
308
|
+
)
|