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,440 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import time
|
|
17
|
+
from contextvars import Token
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from dataclasses import fields as dataclass_fields
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Any, Dict, List, Literal, Optional, Type, Union
|
|
22
|
+
from uuid import UUID, uuid4
|
|
23
|
+
|
|
24
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
25
|
+
gen_ai_attributes as GenAIAttributes,
|
|
26
|
+
)
|
|
27
|
+
from opentelemetry.trace import Span, SpanContext
|
|
28
|
+
|
|
29
|
+
# Backward compatibility: older semconv builds may miss new GEN_AI attributes
|
|
30
|
+
if not hasattr(GenAIAttributes, "GEN_AI_PROVIDER_NAME"):
|
|
31
|
+
GenAIAttributes.GEN_AI_PROVIDER_NAME = "gen_ai.provider.name"
|
|
32
|
+
from opentelemetry.util.types import AttributeValue
|
|
33
|
+
|
|
34
|
+
ContextToken = Token # simple alias; avoid TypeAlias warning tools
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContentCapturingMode(Enum):
|
|
38
|
+
# Do not capture content (default).
|
|
39
|
+
NO_CONTENT = 0
|
|
40
|
+
# Only capture content in spans.
|
|
41
|
+
SPAN_ONLY = 1
|
|
42
|
+
# Only capture content in events.
|
|
43
|
+
EVENT_ONLY = 2
|
|
44
|
+
# Capture content in both spans and events.
|
|
45
|
+
SPAN_AND_EVENT = 3
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _new_input_messages() -> list["InputMessage"]: # quotes for forward ref
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _new_output_messages() -> list["OutputMessage"]: # quotes for forward ref
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _new_str_any_dict() -> dict[str, Any]:
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(kw_only=True)
|
|
61
|
+
class GenAI:
|
|
62
|
+
"""Base type for all GenAI telemetry entities."""
|
|
63
|
+
|
|
64
|
+
context_token: Optional[ContextToken] = None
|
|
65
|
+
span: Optional[Span] = None
|
|
66
|
+
span_context: Optional[SpanContext] = None
|
|
67
|
+
trace_id: Optional[int] = None
|
|
68
|
+
span_id: Optional[int] = None
|
|
69
|
+
trace_flags: Optional[int] = None
|
|
70
|
+
start_time: float = field(default_factory=time.time)
|
|
71
|
+
end_time: Optional[float] = None
|
|
72
|
+
provider: Optional[str] = field(
|
|
73
|
+
default=None,
|
|
74
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_PROVIDER_NAME},
|
|
75
|
+
)
|
|
76
|
+
framework: Optional[str] = None
|
|
77
|
+
attributes: Dict[str, Any] = field(default_factory=_new_str_any_dict)
|
|
78
|
+
run_id: UUID = field(default_factory=uuid4)
|
|
79
|
+
parent_run_id: Optional[UUID] = None
|
|
80
|
+
agent_name: Optional[str] = field(
|
|
81
|
+
default=None,
|
|
82
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_AGENT_NAME},
|
|
83
|
+
)
|
|
84
|
+
agent_id: Optional[str] = field(
|
|
85
|
+
default=None,
|
|
86
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_AGENT_ID},
|
|
87
|
+
)
|
|
88
|
+
system: Optional[str] = field(
|
|
89
|
+
default=None,
|
|
90
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_SYSTEM},
|
|
91
|
+
)
|
|
92
|
+
conversation_id: Optional[str] = field(
|
|
93
|
+
default=None,
|
|
94
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_CONVERSATION_ID},
|
|
95
|
+
)
|
|
96
|
+
data_source_id: Optional[str] = field(
|
|
97
|
+
default=None,
|
|
98
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_DATA_SOURCE_ID},
|
|
99
|
+
)
|
|
100
|
+
sample_for_evaluation: Optional[bool] = field(default=True)
|
|
101
|
+
|
|
102
|
+
def semantic_convention_attributes(self) -> dict[str, Any]:
|
|
103
|
+
"""Return semantic convention attributes defined on this dataclass."""
|
|
104
|
+
|
|
105
|
+
result: dict[str, Any] = {}
|
|
106
|
+
for data_field in dataclass_fields(self):
|
|
107
|
+
semconv_key = data_field.metadata.get("semconv")
|
|
108
|
+
if not semconv_key:
|
|
109
|
+
continue
|
|
110
|
+
value = getattr(self, data_field.name)
|
|
111
|
+
if value is None:
|
|
112
|
+
continue
|
|
113
|
+
if isinstance(value, list) and not value:
|
|
114
|
+
continue
|
|
115
|
+
result[semconv_key] = value
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass()
|
|
120
|
+
class ToolCall(GenAI):
|
|
121
|
+
"""Represents a single tool call invocation (Phase 4)."""
|
|
122
|
+
|
|
123
|
+
arguments: Any
|
|
124
|
+
name: str
|
|
125
|
+
id: Optional[str]
|
|
126
|
+
type: Literal["tool_call"] = "tool_call"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass()
|
|
130
|
+
class ToolCallResponse:
|
|
131
|
+
response: Any
|
|
132
|
+
id: Optional[str]
|
|
133
|
+
type: Literal["tool_call_response"] = "tool_call_response"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
FinishReason = Literal[
|
|
137
|
+
"content_filter", "error", "length", "stop", "tool_calls"
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass()
|
|
142
|
+
class Text:
|
|
143
|
+
content: str
|
|
144
|
+
type: Literal["text"] = "text"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
MessagePart = Union[Text, "ToolCall", ToolCallResponse, Any]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass()
|
|
151
|
+
class InputMessage:
|
|
152
|
+
role: str
|
|
153
|
+
parts: list[MessagePart]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass()
|
|
157
|
+
class OutputMessage:
|
|
158
|
+
role: str
|
|
159
|
+
parts: list[MessagePart]
|
|
160
|
+
finish_reason: Union[str, FinishReason]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class LLMInvocation(GenAI):
|
|
165
|
+
"""Represents a single large language model invocation.
|
|
166
|
+
|
|
167
|
+
Only fields tagged with ``metadata["semconv"]`` are emitted as
|
|
168
|
+
semantic-convention attributes by the span emitters. Additional fields are
|
|
169
|
+
util-only helpers or inputs to alternative span flavors (e.g. Traceloop).
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
request_model: str = field(
|
|
173
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_MODEL}
|
|
174
|
+
)
|
|
175
|
+
input_messages: List[InputMessage] = field(
|
|
176
|
+
default_factory=_new_input_messages
|
|
177
|
+
)
|
|
178
|
+
# Traceloop compatibility relies on enumerating these lists into prefixed attributes.
|
|
179
|
+
output_messages: List[OutputMessage] = field(
|
|
180
|
+
default_factory=_new_output_messages
|
|
181
|
+
)
|
|
182
|
+
operation: str = field(
|
|
183
|
+
default=GenAIAttributes.GenAiOperationNameValues.CHAT.value,
|
|
184
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_OPERATION_NAME},
|
|
185
|
+
)
|
|
186
|
+
response_model_name: Optional[str] = field(
|
|
187
|
+
default=None,
|
|
188
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_RESPONSE_MODEL},
|
|
189
|
+
)
|
|
190
|
+
response_id: Optional[str] = field(
|
|
191
|
+
default=None,
|
|
192
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_RESPONSE_ID},
|
|
193
|
+
)
|
|
194
|
+
input_tokens: Optional[AttributeValue] = field(
|
|
195
|
+
default=None,
|
|
196
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS},
|
|
197
|
+
)
|
|
198
|
+
output_tokens: Optional[AttributeValue] = field(
|
|
199
|
+
default=None,
|
|
200
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS},
|
|
201
|
+
)
|
|
202
|
+
# Structured function/tool definitions for semantic convention emission
|
|
203
|
+
request_functions: list[dict[str, Any]] = field(default_factory=list)
|
|
204
|
+
request_temperature: Optional[float] = field(
|
|
205
|
+
default=None,
|
|
206
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE},
|
|
207
|
+
)
|
|
208
|
+
request_top_p: Optional[float] = field(
|
|
209
|
+
default=None,
|
|
210
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_TOP_P},
|
|
211
|
+
)
|
|
212
|
+
request_top_k: Optional[int] = field(
|
|
213
|
+
default=None,
|
|
214
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_TOP_K},
|
|
215
|
+
)
|
|
216
|
+
request_frequency_penalty: Optional[float] = field(
|
|
217
|
+
default=None,
|
|
218
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_FREQUENCY_PENALTY},
|
|
219
|
+
)
|
|
220
|
+
request_presence_penalty: Optional[float] = field(
|
|
221
|
+
default=None,
|
|
222
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_PRESENCE_PENALTY},
|
|
223
|
+
)
|
|
224
|
+
request_stop_sequences: List[str] = field(
|
|
225
|
+
default_factory=list,
|
|
226
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_STOP_SEQUENCES},
|
|
227
|
+
)
|
|
228
|
+
request_max_tokens: Optional[int] = field(
|
|
229
|
+
default=None,
|
|
230
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS},
|
|
231
|
+
)
|
|
232
|
+
request_choice_count: Optional[int] = field(
|
|
233
|
+
default=None,
|
|
234
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_CHOICE_COUNT},
|
|
235
|
+
)
|
|
236
|
+
request_seed: Optional[int] = field(
|
|
237
|
+
default=None,
|
|
238
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_SEED},
|
|
239
|
+
)
|
|
240
|
+
request_encoding_formats: List[str] = field(
|
|
241
|
+
default_factory=list,
|
|
242
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_ENCODING_FORMATS},
|
|
243
|
+
)
|
|
244
|
+
output_type: Optional[str] = field(
|
|
245
|
+
default=None,
|
|
246
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_OUTPUT_TYPE},
|
|
247
|
+
)
|
|
248
|
+
response_finish_reasons: List[str] = field(
|
|
249
|
+
default_factory=list,
|
|
250
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS},
|
|
251
|
+
)
|
|
252
|
+
request_service_tier: Optional[str] = field(
|
|
253
|
+
default=None,
|
|
254
|
+
metadata={
|
|
255
|
+
"semconv": GenAIAttributes.GEN_AI_OPENAI_REQUEST_SERVICE_TIER
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
response_service_tier: Optional[str] = field(
|
|
259
|
+
default=None,
|
|
260
|
+
metadata={
|
|
261
|
+
"semconv": GenAIAttributes.GEN_AI_OPENAI_RESPONSE_SERVICE_TIER
|
|
262
|
+
},
|
|
263
|
+
)
|
|
264
|
+
response_system_fingerprint: Optional[str] = field(
|
|
265
|
+
default=None,
|
|
266
|
+
metadata={
|
|
267
|
+
"semconv": GenAIAttributes.GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class Error:
|
|
274
|
+
message: str
|
|
275
|
+
type: Type[BaseException]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass
|
|
279
|
+
class EvaluationResult:
|
|
280
|
+
"""Represents the outcome of a single evaluation metric.
|
|
281
|
+
|
|
282
|
+
Additional fields (e.g., judge model, threshold) can be added without
|
|
283
|
+
breaking callers that rely only on the current contract.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
metric_name: str
|
|
287
|
+
score: Optional[float] = None
|
|
288
|
+
label: Optional[str] = None
|
|
289
|
+
explanation: Optional[str] = None
|
|
290
|
+
error: Optional[Error] = None
|
|
291
|
+
attributes: Dict[str, Any] = field(default_factory=dict)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass
|
|
295
|
+
class EmbeddingInvocation(GenAI):
|
|
296
|
+
"""Represents a single embedding model invocation."""
|
|
297
|
+
|
|
298
|
+
operation_name: str = field(
|
|
299
|
+
default=GenAIAttributes.GenAiOperationNameValues.EMBEDDINGS.value,
|
|
300
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_OPERATION_NAME},
|
|
301
|
+
)
|
|
302
|
+
request_model: str = field(
|
|
303
|
+
default="",
|
|
304
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_MODEL},
|
|
305
|
+
)
|
|
306
|
+
input_texts: list[str] = field(default_factory=list)
|
|
307
|
+
dimension_count: Optional[int] = None
|
|
308
|
+
server_port: Optional[int] = None
|
|
309
|
+
server_address: Optional[str] = None
|
|
310
|
+
input_tokens: Optional[int] = field(
|
|
311
|
+
default=None,
|
|
312
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS},
|
|
313
|
+
)
|
|
314
|
+
encoding_formats: list[str] = field(
|
|
315
|
+
default_factory=list,
|
|
316
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_ENCODING_FORMATS},
|
|
317
|
+
)
|
|
318
|
+
error_type: Optional[str] = None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass
|
|
322
|
+
class Workflow(GenAI):
|
|
323
|
+
"""Represents a workflow orchestrating multiple agents and steps.
|
|
324
|
+
|
|
325
|
+
A workflow is the top-level orchestration unit in agentic AI systems,
|
|
326
|
+
coordinating agents and steps to achieve a complex goal. Workflows are optional
|
|
327
|
+
and typically used in multi-agent or multi-step scenarios.
|
|
328
|
+
|
|
329
|
+
Attributes:
|
|
330
|
+
name: Identifier for the workflow (e.g., "customer_support_pipeline")
|
|
331
|
+
workflow_type: Type of orchestration (e.g., "sequential", "parallel", "graph", "dynamic")
|
|
332
|
+
description: Human-readable description of the workflow's purpose
|
|
333
|
+
framework: Framework implementing the workflow (e.g., "langgraph", "crewai", "autogen")
|
|
334
|
+
initial_input: User's initial query/request that triggered the workflow
|
|
335
|
+
final_output: Final response/result produced by the workflow
|
|
336
|
+
attributes: Additional custom attributes for workflow-specific metadata
|
|
337
|
+
start_time: Timestamp when workflow started
|
|
338
|
+
end_time: Timestamp when workflow completed
|
|
339
|
+
span: OpenTelemetry span associated with this workflow
|
|
340
|
+
context_token: Context token for span management
|
|
341
|
+
run_id: Unique identifier for this workflow execution
|
|
342
|
+
parent_run_id: Optional parent workflow/trace identifier
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
name: str
|
|
346
|
+
workflow_type: Optional[str] = None # sequential, parallel, graph, dynamic
|
|
347
|
+
description: Optional[str] = None
|
|
348
|
+
initial_input: Optional[str] = None # User's initial query/request
|
|
349
|
+
final_output: Optional[str] = None # Final response/result
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@dataclass
|
|
353
|
+
class _BaseAgent(GenAI):
|
|
354
|
+
"""Shared fields for agent lifecycle phases."""
|
|
355
|
+
|
|
356
|
+
name: str
|
|
357
|
+
agent_type: Optional[str] = (
|
|
358
|
+
None # researcher, planner, executor, critic, etc.
|
|
359
|
+
)
|
|
360
|
+
description: Optional[str] = field(
|
|
361
|
+
default=None,
|
|
362
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_AGENT_DESCRIPTION},
|
|
363
|
+
)
|
|
364
|
+
model: Optional[str] = field(
|
|
365
|
+
default=None,
|
|
366
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_MODEL},
|
|
367
|
+
) # primary model if applicable
|
|
368
|
+
tools: list[str] = field(default_factory=list) # available tool names
|
|
369
|
+
system_instructions: Optional[str] = None # System prompt/instructions
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@dataclass
|
|
373
|
+
class AgentCreation(_BaseAgent):
|
|
374
|
+
"""Represents agent creation/initialisation."""
|
|
375
|
+
|
|
376
|
+
operation: Literal["create_agent"] = field(
|
|
377
|
+
init=False,
|
|
378
|
+
default="create_agent",
|
|
379
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_OPERATION_NAME},
|
|
380
|
+
)
|
|
381
|
+
input_context: Optional[str] = None # optional initial context
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@dataclass
|
|
385
|
+
class AgentInvocation(_BaseAgent):
|
|
386
|
+
"""Represents agent execution (`invoke_agent`)."""
|
|
387
|
+
|
|
388
|
+
operation: Literal["invoke_agent"] = field(
|
|
389
|
+
init=False,
|
|
390
|
+
default="invoke_agent",
|
|
391
|
+
metadata={"semconv": GenAIAttributes.GEN_AI_OPERATION_NAME},
|
|
392
|
+
)
|
|
393
|
+
input_context: Optional[str] = None # Input for invoke operations
|
|
394
|
+
output_result: Optional[str] = None # Output for invoke operations
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@dataclass
|
|
398
|
+
class Step(GenAI):
|
|
399
|
+
"""Represents a discrete unit of work in an agentic AI system.
|
|
400
|
+
|
|
401
|
+
Steps can be orchestrated at the workflow level (assigned to agents) or
|
|
402
|
+
decomposed internally by agents during execution. This design supports both
|
|
403
|
+
scenarios through flexible parent relationships.
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
name: str
|
|
407
|
+
objective: Optional[str] = None # what the step aims to achieve
|
|
408
|
+
step_type: Optional[str] = (
|
|
409
|
+
None # planning, execution, reflection, tool_use, etc.
|
|
410
|
+
)
|
|
411
|
+
source: Optional[Literal["workflow", "agent"]] = (
|
|
412
|
+
None # where step originated
|
|
413
|
+
)
|
|
414
|
+
assigned_agent: Optional[str] = None # for workflow-assigned steps
|
|
415
|
+
status: Optional[str] = None # pending, in_progress, completed, failed
|
|
416
|
+
description: Optional[str] = None
|
|
417
|
+
input_data: Optional[str] = None # Input data/context for the step
|
|
418
|
+
output_data: Optional[str] = None # Output data/result from the step
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
__all__ = [
|
|
422
|
+
# existing exports intentionally implicit before; making explicit for new additions
|
|
423
|
+
"ContentCapturingMode",
|
|
424
|
+
"ToolCall",
|
|
425
|
+
"ToolCallResponse",
|
|
426
|
+
"Text",
|
|
427
|
+
"InputMessage",
|
|
428
|
+
"OutputMessage",
|
|
429
|
+
"GenAI",
|
|
430
|
+
"LLMInvocation",
|
|
431
|
+
"EmbeddingInvocation",
|
|
432
|
+
"Error",
|
|
433
|
+
"EvaluationResult",
|
|
434
|
+
# agentic AI types
|
|
435
|
+
"Workflow",
|
|
436
|
+
"AgentCreation",
|
|
437
|
+
"AgentInvocation",
|
|
438
|
+
"Step",
|
|
439
|
+
# backward compatibility normalization helpers
|
|
440
|
+
]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""This module defines the generic hooks for GenAI content uploading
|
|
16
|
+
|
|
17
|
+
The hooks are specified as part of semconv in `Uploading content to external storage
|
|
18
|
+
<https://github.com/open-telemetry/semantic-conventions/blob/v1.37.0/docs/gen-ai/gen-ai-spans.md#uploading-content-to-external-storage>`__.
|
|
19
|
+
|
|
20
|
+
This module defines the `UploadHook` type that custom implementations should implement, and a
|
|
21
|
+
`load_upload_hook` function to load it from an entry point.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from os import environ
|
|
28
|
+
from typing import Any, Protocol, cast, runtime_checkable
|
|
29
|
+
|
|
30
|
+
from opentelemetry._logs import LogRecord
|
|
31
|
+
from opentelemetry.trace import Span
|
|
32
|
+
from opentelemetry.util._importlib_metadata import (
|
|
33
|
+
entry_points, # pyright: ignore[reportUnknownVariableType]
|
|
34
|
+
)
|
|
35
|
+
from opentelemetry.util.genai import types
|
|
36
|
+
from opentelemetry.util.genai.environment_variables import (
|
|
37
|
+
OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@runtime_checkable
|
|
44
|
+
class UploadHook(Protocol):
|
|
45
|
+
"""A hook to upload GenAI content to an external storage.
|
|
46
|
+
|
|
47
|
+
This is the interface for a hook that can be
|
|
48
|
+
used to upload GenAI content to an external storage. The hook is a
|
|
49
|
+
callable that takes the inputs, outputs, and system instruction of a
|
|
50
|
+
GenAI interaction, as well as the span and log record associated with
|
|
51
|
+
it.
|
|
52
|
+
|
|
53
|
+
The hook can be used to upload the content to any external storage,
|
|
54
|
+
such as a database, a file system, or a cloud storage service.
|
|
55
|
+
|
|
56
|
+
The span and log_record arguments should be provided based on the content capturing mode
|
|
57
|
+
:func:`~opentelemetry.util.genai.utils.get_content_capturing_mode`.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
inputs: The inputs of the GenAI interaction.
|
|
61
|
+
outputs: The outputs of the GenAI interaction.
|
|
62
|
+
system_instruction: The system instruction of the GenAI
|
|
63
|
+
interaction.
|
|
64
|
+
span: The span associated with the GenAI interaction.
|
|
65
|
+
log_record: The event log associated with the GenAI
|
|
66
|
+
interaction.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def upload(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
inputs: list[types.InputMessage],
|
|
73
|
+
outputs: list[types.OutputMessage],
|
|
74
|
+
system_instruction: list[types.MessagePart],
|
|
75
|
+
span: Span | None = None,
|
|
76
|
+
log_record: LogRecord | None = None,
|
|
77
|
+
) -> None: ...
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _NoOpUploadHook(UploadHook):
|
|
81
|
+
def upload(self, **kwargs: Any) -> None:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_upload_hook() -> UploadHook:
|
|
86
|
+
"""Load the upload hook from entry point or return a noop implementation
|
|
87
|
+
|
|
88
|
+
This function loads an upload hook from the entry point group
|
|
89
|
+
``opentelemetry_genai_upload_hook`` with name coming from
|
|
90
|
+
:envvar:`OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK`. If one can't be found, returns a no-op
|
|
91
|
+
implementation.
|
|
92
|
+
"""
|
|
93
|
+
hook_name = environ.get(OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK, None)
|
|
94
|
+
if not hook_name:
|
|
95
|
+
return _NoOpUploadHook()
|
|
96
|
+
|
|
97
|
+
for entry_point in entry_points(group="opentelemetry_genai_upload_hook"): # pyright: ignore[reportUnknownVariableType]
|
|
98
|
+
name = cast(str, entry_point.name) # pyright: ignore[reportUnknownMemberType]
|
|
99
|
+
try:
|
|
100
|
+
if hook_name != name:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
hook = entry_point.load()() # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
|
|
104
|
+
if not isinstance(hook, UploadHook):
|
|
105
|
+
_logger.debug("%s is not a valid UploadHook. Using noop", name)
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
_logger.debug("Using UploadHook %s", name)
|
|
109
|
+
return hook
|
|
110
|
+
|
|
111
|
+
except Exception: # pylint: disable=broad-except
|
|
112
|
+
_logger.exception(
|
|
113
|
+
"UploadHook %s configuration failed. Using noop", name
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return _NoOpUploadHook()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
__all__ = ["UploadHook", "load_upload_hook"]
|