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.
Files changed (31) hide show
  1. opentelemetry/util/genai/__init__.py +17 -0
  2. opentelemetry/util/genai/_fsspec_upload/__init__.py +39 -0
  3. opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py +184 -0
  4. opentelemetry/util/genai/attributes.py +60 -0
  5. opentelemetry/util/genai/callbacks.py +24 -0
  6. opentelemetry/util/genai/config.py +184 -0
  7. opentelemetry/util/genai/debug.py +183 -0
  8. opentelemetry/util/genai/emitters/__init__.py +25 -0
  9. opentelemetry/util/genai/emitters/composite.py +186 -0
  10. opentelemetry/util/genai/emitters/configuration.py +324 -0
  11. opentelemetry/util/genai/emitters/content_events.py +153 -0
  12. opentelemetry/util/genai/emitters/evaluation.py +519 -0
  13. opentelemetry/util/genai/emitters/metrics.py +308 -0
  14. opentelemetry/util/genai/emitters/span.py +774 -0
  15. opentelemetry/util/genai/emitters/spec.py +48 -0
  16. opentelemetry/util/genai/emitters/utils.py +961 -0
  17. opentelemetry/util/genai/environment_variables.py +200 -0
  18. opentelemetry/util/genai/handler.py +1002 -0
  19. opentelemetry/util/genai/instruments.py +44 -0
  20. opentelemetry/util/genai/interfaces.py +58 -0
  21. opentelemetry/util/genai/plugins.py +114 -0
  22. opentelemetry/util/genai/span_context.py +80 -0
  23. opentelemetry/util/genai/types.py +440 -0
  24. opentelemetry/util/genai/upload_hook.py +119 -0
  25. opentelemetry/util/genai/utils.py +182 -0
  26. opentelemetry/util/genai/version.py +15 -0
  27. splunk_otel_util_genai-0.1.3.dist-info/METADATA +70 -0
  28. splunk_otel_util_genai-0.1.3.dist-info/RECORD +31 -0
  29. splunk_otel_util_genai-0.1.3.dist-info/WHEEL +4 -0
  30. splunk_otel_util_genai-0.1.3.dist-info/entry_points.txt +5 -0
  31. 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
+ )