From 35edb49af72a7f38b83d3a2fa9d8142517cbe10b Mon Sep 17 00:00:00 2001 From: adityamehra Date: Mon, 13 Oct 2025 11:40:33 -0700 Subject: [PATCH 1/2] make utils compatible with py3.9 Use Union --- .../util/genai/_fsspec_upload/fsspec_hook.py | 6 +-- .../src/opentelemetry/util/genai/config.py | 4 +- .../util/genai/emitters/composite.py | 20 +++++----- .../util/genai/emitters/evaluation.py | 10 ++--- .../opentelemetry/util/genai/emitters/span.py | 10 ++--- .../opentelemetry/util/genai/emitters/spec.py | 4 +- .../util/genai/emitters/utils.py | 6 +-- .../util/genai/evaluators/base.py | 10 ++--- .../util/genai/evaluators/manager.py | 17 +++++---- .../util/genai/evaluators/registry.py | 16 ++++---- .../opentelemetry/util/genai/interfaces.py | 6 +-- .../src/opentelemetry/util/genai/plugins.py | 4 +- .../src/opentelemetry/util/genai/types.py | 38 ++++++++++--------- .../opentelemetry/util/genai/upload_hook.py | 6 +-- 14 files changed, 81 insertions(+), 76 deletions(-) diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py index 9bfbc864f0..c9241b4fea 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py @@ -22,7 +22,7 @@ from concurrent.futures import Future, ThreadPoolExecutor from dataclasses import asdict, dataclass from functools import partial -from typing import Any, Callable, Literal, TextIO, cast +from typing import Any, Callable, Literal, TextIO, cast, Union from uuid import uuid4 import fsspec @@ -147,8 +147,8 @@ def upload( inputs: list[types.InputMessage], outputs: list[types.OutputMessage], system_instruction: list[types.MessagePart], - span: Span | None = None, - log_record: LogRecord | None = None, + span: Union[Span, None] = None, + log_record: Union[LogRecord, None] = None, **kwargs: Any, ) -> None: completion = Completion( diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/config.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/config.py index 27dd99c5dc..3cf4f03588 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/config.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/config.py @@ -3,7 +3,7 @@ import logging import os from dataclasses import dataclass -from typing import Dict +from typing import Dict, Union from .emitters.spec import CategoryOverride from .environment_variables import ( @@ -123,7 +123,7 @@ def parse_env() -> Settings: def _parse_category_override( category: str, raw: str -) -> CategoryOverride | None: # pragma: no cover - thin parsing +) -> Union[CategoryOverride, None]: # pragma: no cover - thin parsing if not raw: return None text = raw.strip() diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/composite.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/composite.py index 3ea3954416..43bbfae98e 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/composite.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/composite.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Any, Iterable, Iterator, Mapping, Sequence +from typing import Any, Iterable, Iterator, Mapping, Sequence, Union from ..interfaces import EmitterMeta, EmitterProtocol from ..types import Error, EvaluationResult @@ -37,10 +37,10 @@ class CompositeEmitter(EmitterMeta): def __init__( self, *, - span_emitters: Iterable[EmitterProtocol] | None = None, - metrics_emitters: Iterable[EmitterProtocol] | None = None, - content_event_emitters: Iterable[EmitterProtocol] | None = None, - evaluation_emitters: Iterable[EmitterProtocol] | None = None, + span_emitters: Union[Iterable[EmitterProtocol], None] = None, + metrics_emitters: Union[Iterable[EmitterProtocol], None] = None, + content_event_emitters: Union[Iterable[EmitterProtocol], None] = None, + evaluation_emitters: Union[Iterable[EmitterProtocol], None] = None, ) -> None: self._categories: dict[str, list[EmitterProtocol]] = { "span": list(span_emitters or []), @@ -64,7 +64,7 @@ def on_error(self, error: Error, obj: Any) -> None: # type: ignore[override] def on_evaluation_results( self, results: Sequence[EvaluationResult], - obj: Any | None = None, + obj: Union[Any, None] = None, ) -> None: # type: ignore[override] if not results: return @@ -79,7 +79,7 @@ def on_evaluation_results( # Introspection helpers used during configuration refresh def iter_emitters( - self, categories: Sequence[str] | None = None + self, categories: Union[Sequence[str], None] = None ) -> Iterator[EmitterProtocol]: names = categories or ( "span", @@ -108,9 +108,9 @@ def _dispatch( categories: Sequence[str], method_name: str, *, - obj: Any | None = None, - error: Error | None = None, - results: Sequence[EvaluationResult] | None = None, + obj: Union[Any, None] = None, + error: Union[Error, None] = None, + results: Union[Sequence[EvaluationResult], None] = None, ) -> None: for category in categories: emitters = self._categories.get(category) diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/evaluation.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/evaluation.py index 335597373e..c63ee8a782 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/evaluation.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/evaluation.py @@ -4,7 +4,7 @@ import logging import os -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Optional, Sequence, Union from opentelemetry import _events as _otel_events @@ -23,13 +23,13 @@ from ..types import EvaluationResult, GenAI -def _get_request_model(invocation: GenAI) -> str | None: +def _get_request_model(invocation: GenAI) -> Union[str, None]: return getattr(invocation, "request_model", None) or getattr( invocation, "model", None ) -def _get_response_id(invocation: GenAI) -> str | None: # best-effort +def _get_response_id(invocation: GenAI) -> Union[str, None]: # best-effort return getattr(invocation, "response_id", None) @@ -128,7 +128,7 @@ def _direct_factory(_name: str): # ignore metric name, single hist def on_evaluation_results( # type: ignore[override] self, results: Sequence[EvaluationResult], - obj: Any | None = None, + obj: Union[Any, None] = None, ) -> None: invocation = obj if isinstance(obj, GenAI) else None if invocation is None: @@ -337,7 +337,7 @@ def __init__( def on_evaluation_results( # type: ignore[override] self, results: Sequence[EvaluationResult], - obj: Any | None = None, + obj: Union[Any, None] = None, ) -> None: if self._event_logger is None: return diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/span.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/span.py index 4c4fbb3f09..7cc73d1be3 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/span.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/span.py @@ -3,7 +3,7 @@ import json # noqa: F401 (kept for backward compatibility if external code relies on this module re-exporting json) from dataclasses import asdict # noqa: F401 -from typing import Any, Optional +from typing import Any, Optional, Union from opentelemetry import trace from opentelemetry.semconv._incubating.attributes import ( @@ -202,7 +202,7 @@ def _apply_start_attrs(self, invocation: GenAIType): # Agent context (already covered by semconv metadata on base fields) def _apply_finish_attrs( - self, invocation: LLMInvocation | EmbeddingInvocation + self, invocation: Union[LLMInvocation, EmbeddingInvocation] ): span = getattr(invocation, "span", None) if span is None: @@ -255,7 +255,7 @@ def _apply_finish_attrs( # ---- lifecycle ------------------------------------------------------- def on_start( - self, invocation: LLMInvocation | EmbeddingInvocation + self, invocation: Union[LLMInvocation, EmbeddingInvocation] ) -> None: # type: ignore[override] # Handle new agentic types if isinstance(invocation, Workflow): @@ -289,7 +289,7 @@ def on_start( invocation.context_token = cm # type: ignore[assignment] self._apply_start_attrs(invocation) - def on_end(self, invocation: LLMInvocation | EmbeddingInvocation) -> None: # type: ignore[override] + def on_end(self, invocation: Union[LLMInvocation, EmbeddingInvocation]) -> None: # type: ignore[override] if isinstance(invocation, Workflow): self._finish_workflow(invocation) elif isinstance(invocation, AgentInvocation): @@ -312,7 +312,7 @@ def on_end(self, invocation: LLMInvocation | EmbeddingInvocation) -> None: # ty span.end() def on_error( - self, error: Error, invocation: LLMInvocation | EmbeddingInvocation + self, error: Error, invocation: Union[LLMInvocation, EmbeddingInvocation] ) -> None: # type: ignore[override] if isinstance(invocation, Workflow): self._error_workflow(error, invocation) diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/spec.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/spec.py index e2a16caed7..d9989af2fb 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/spec.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/spec.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, Callable, Mapping, Sequence +from typing import Any, Callable, Mapping, Sequence, Union from ..interfaces import EmitterProtocol @@ -30,7 +30,7 @@ class EmitterSpec: mode: str = "append" after: Sequence[str] = field(default_factory=tuple) before: Sequence[str] = field(default_factory=tuple) - invocation_types: Sequence[str] | None = None + invocation_types: Union[Sequence[str], None] = None @dataclass(frozen=True) diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/utils.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/utils.py index 3c7f074bcc..0b4cca7900 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/utils.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/emitters/utils.py @@ -3,7 +3,7 @@ import json from dataclasses import asdict -from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence +from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Union from opentelemetry import trace from opentelemetry._logs import ( @@ -56,7 +56,7 @@ def filter_semconv_gen_ai_attributes( - attributes: Mapping[str, Any] | None, + attributes: Union[Mapping[str, Any], None], *, extras: Iterable[str] = (), ) -> dict[str, Any]: @@ -564,7 +564,7 @@ def _record_token_metrics( def _record_duration( duration_histogram: Histogram, - invocation: LLMInvocation | EmbeddingInvocation | ToolCall, + invocation: Union[LLMInvocation, EmbeddingInvocation, ToolCall], metric_attributes: Dict[str, AttributeValue], *, span: Optional[Span] = None, diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/base.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/base.py index 44f954324b..bd833ec812 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/base.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/base.py @@ -15,7 +15,7 @@ from __future__ import annotations from abc import ABC -from typing import Iterable, Mapping, Sequence +from typing import Iterable, Mapping, Sequence, Union from opentelemetry.util.genai.types import ( AgentInvocation, @@ -35,10 +35,10 @@ class Evaluator(ABC): def __init__( self, - metrics: Iterable[str] | None = None, + metrics: Union[Iterable[str], None] = None, *, - invocation_type: str | None = None, - options: Mapping[str, str] | None = None, + invocation_type: Union[str, None] = None, + options: Union[Mapping[str, str], None] = None, ) -> None: default_metrics = ( self.default_metrics_for(invocation_type) @@ -65,7 +65,7 @@ def default_metrics(self) -> Sequence[str]: # pragma: no cover - trivial return () def default_metrics_for( - self, invocation_type: str | None + self, invocation_type: Union[str, None] ) -> Sequence[str]: mapping = self.default_metrics_by_type() if invocation_type and invocation_type in mapping: diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/manager.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/manager.py index 031b990b1a..aa8935d9e7 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/manager.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/manager.py @@ -5,7 +5,7 @@ import threading import time from dataclasses import dataclass -from typing import TYPE_CHECKING, Mapping, Protocol, Sequence +from typing import TYPE_CHECKING, Mapping, Protocol, Sequence, Union from ..callbacks import CompletionCallback from ..environment_variables import ( @@ -75,8 +75,8 @@ def __init__( self, handler: "TelemetryHandler", *, - interval: float | None = None, - aggregate_results: bool | None = None, + interval: Union[float, None] = None, + aggregate_results: Union[bool, None] = None, ) -> None: self._handler = handler evaluation_sample_rate = _read_evaluation_sample_rate() @@ -91,7 +91,7 @@ def __init__( self._evaluators = self._instantiate_evaluators(self._plans) self._queue: queue.Queue[GenAI] = queue.Queue() self._shutdown = threading.Event() - self._worker: threading.Thread | None = None + self._worker: Union[threading.Thread, None] = None if self.has_evaluators: self._worker = threading.Thread( target=self._worker_loop, @@ -138,7 +138,7 @@ def offer(self, invocation: GenAI) -> None: "Failed to enqueue invocation for evaluation", exc_info=True ) - def wait_for_all(self, timeout: float | None = None) -> None: + def wait_for_all(self, timeout: Union[float, None] = None) -> None: if not self.has_evaluators: return if timeout is None: @@ -239,11 +239,14 @@ def _emit_results( return flattened def _flag_invocation(self, invocation: GenAI) -> None: + # print(f"_flag_invocation:") if not self.has_evaluators: return attributes = getattr(invocation, "attributes", None) + # print(f"attributes inside _flag_invocation: {attributes}") if isinstance(attributes, dict): attributes.setdefault("gen_ai.evaluation.executed", True) + # print(f"attributes inside _flag_invocation: {attributes['gen_ai.evaluation.executed']}") # Configuration ------------------------------------------------------ def _load_plans(self) -> Sequence[EvaluatorPlan]: @@ -383,7 +386,7 @@ def _generate_default_plans(self) -> Sequence[EvaluatorPlan]: # Environment parsing helpers -def _read_raw_evaluator_config() -> str | None: +def _read_raw_evaluator_config() -> Union[str, None]: return _get_env(OTEL_INSTRUMENTATION_GENAI_EVALS_EVALUATORS) @@ -422,7 +425,7 @@ def _read_evaluation_sample_rate() -> float: return value -def _get_env(name: str) -> str | None: +def _get_env(name: str) -> Union[str, None]: import os return os.environ.get(name) diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/registry.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/registry.py index d87350c990..1674dfc7cc 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/registry.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/evaluators/registry.py @@ -17,7 +17,7 @@ import inspect import logging from dataclasses import dataclass -from typing import Callable, Dict, Mapping, Sequence +from typing import Callable, Dict, Mapping, Sequence, Union from opentelemetry.util._importlib_metadata import ( entry_points, @@ -45,9 +45,9 @@ class EvaluatorRegistration: def _call_with_optional_params( target: EvaluatorFactory, *, - metrics: Sequence[str] | None = None, - invocation_type: str | None = None, - options: Mapping[str, str] | None = None, + metrics: Union[Sequence[str], None] = None, + invocation_type: Union[str, None] = None, + options: Union[Mapping[str, str], None] = None, ) -> Evaluator: """Call a factory/constructor handling optional ``metrics`` gracefully.""" @@ -169,7 +169,7 @@ def _load_entry_points() -> None: "Failed to load evaluator entry point '%s': %s", ep.name, exc ) continue - registration: EvaluatorRegistration | None = None + registration: Union[EvaluatorRegistration, None] = None if isinstance(target, EvaluatorRegistration): registration = target elif hasattr(target, "factory") and hasattr(target, "default_metrics"): @@ -204,10 +204,10 @@ def _load_entry_points() -> None: def get_evaluator( name: str, - metrics: Sequence[str] | None = None, + metrics: Union[Sequence[str], None] = None, *, - invocation_type: str | None = None, - options: Mapping[str, str] | None = None, + invocation_type: Union[str, None] = None, + options: Union[Mapping[str, str], None] = None, ) -> Evaluator: _load_entry_points() key = name.lower() diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/interfaces.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/interfaces.py index 4a66cd76a3..ec347bc437 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/interfaces.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/interfaces.py @@ -2,7 +2,7 @@ # composite generator + plugin system can rely on a stable narrow contract. from __future__ import annotations -from typing import Any, Protocol, Sequence, runtime_checkable +from typing import Any, Protocol, Sequence, runtime_checkable, Union from .types import Error, EvaluationResult, LLMInvocation @@ -27,7 +27,7 @@ def on_error( ... def on_evaluation_results( - self, results: Sequence[EvaluationResult], obj: Any | None = None + self, results: Sequence[EvaluationResult], obj: Union[Any, None] = None ) -> None: # pragma: no cover - structural ... @@ -53,6 +53,6 @@ def handles(self, obj: Any) -> bool: # pragma: no cover (trivial) return True def on_evaluation_results( - self, results: Sequence[EvaluationResult], obj: Any | None = None + self, results: Sequence[EvaluationResult], obj: Union[Any, None] = None ) -> None: # pragma: no cover - default no-op return None diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/plugins.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/plugins.py index 9ca31ae5a3..05c3b759b6 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/plugins.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/plugins.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Iterable, Mapping, Sequence +from typing import Iterable, Mapping, Sequence, Union from opentelemetry.util._importlib_metadata import ( entry_points, # pyright: ignore[reportUnknownVariableType] @@ -13,7 +13,7 @@ def load_emitter_specs( - names: Sequence[str] | None = None, + names: Union[Sequence[str], None] = None, ) -> list[EmitterSpec]: """Load emitter specs declared under the ``opentelemetry_util_genai_emitters`` entry point. diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/types.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/types.py index 54c40b6de0..3b371721af 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/types.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/types.py @@ -57,7 +57,7 @@ def _new_str_any_dict() -> dict[str, Any]: return {} -@dataclass(kw_only=True) +@dataclass class GenAI: """Base type for all GenAI telemetry entities.""" @@ -115,16 +115,16 @@ def semantic_convention_attributes(self) -> dict[str, Any]: class ToolCall(GenAI): """Represents a single tool call invocation (Phase 4).""" - arguments: Any - name: str - id: Optional[str] + arguments: Any = field(default=None) + name: str = field(default="") + id: Optional[str] = field(default=None) type: Literal["tool_call"] = "tool_call" @dataclass() class ToolCallResponse: - response: Any - id: Optional[str] + response: Any = field(default=None) + id: Optional[str] = field(default=None) type: Literal["tool_call_response"] = "tool_call_response" @@ -135,7 +135,7 @@ class ToolCallResponse: @dataclass() class Text: - content: str + content: str = field(default="") type: Literal["text"] = "text" @@ -144,15 +144,15 @@ class Text: @dataclass() class InputMessage: - role: str - parts: list[MessagePart] + role: str = field(default="") + parts: list[MessagePart] = field(default_factory=list) @dataclass() class OutputMessage: - role: str - parts: list[MessagePart] - finish_reason: Union[str, FinishReason] + role: str = field(default="") + parts: list[MessagePart] = field(default_factory=list) + finish_reason: Union[str, FinishReason] = field(default="") @dataclass @@ -165,6 +165,7 @@ class LLMInvocation(GenAI): """ request_model: str = field( + default="", metadata={"semconv": GenAIAttributes.GEN_AI_REQUEST_MODEL} ) input_messages: List[InputMessage] = field( @@ -266,8 +267,8 @@ class LLMInvocation(GenAI): @dataclass class Error: - message: str - type: Type[BaseException] + message: str = field(default="") + type: Type[BaseException] = field(default=Exception) @dataclass @@ -278,7 +279,7 @@ class EvaluationResult: breaking callers that rely only on the current contract. """ - metric_name: str + metric_name: str = field(default="") score: Optional[float] = None label: Optional[str] = None explanation: Optional[str] = None @@ -337,7 +338,7 @@ class Workflow(GenAI): parent_run_id: Optional parent workflow/trace identifier """ - name: str + name: str = field(default="") workflow_type: Optional[str] = None # sequential, parallel, graph, dynamic description: Optional[str] = None initial_input: Optional[str] = None # User's initial query/request @@ -353,8 +354,9 @@ class AgentInvocation(GenAI): and agent invocation (execution) phases. """ - name: str + name: str = field(default="") operation: Literal["create_agent", "invoke_agent"] = field( + default="create_agent", metadata={"semconv": GenAIAttributes.GEN_AI_OPERATION_NAME} ) agent_type: Optional[str] = ( @@ -383,7 +385,7 @@ class Task(GenAI): scenarios through flexible parent relationships. """ - name: str + name: str = field(default="") objective: Optional[str] = None # what the task aims to achieve task_type: Optional[str] = ( None # planning, execution, reflection, tool_use, etc. diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/upload_hook.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/upload_hook.py index 9180b98eb8..10f9df4d30 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/upload_hook.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/upload_hook.py @@ -25,7 +25,7 @@ import logging from os import environ -from typing import Any, Protocol, cast, runtime_checkable +from typing import Any, Protocol, cast, runtime_checkable, Union from opentelemetry._logs import LogRecord from opentelemetry.trace import Span @@ -72,8 +72,8 @@ def upload( inputs: list[types.InputMessage], outputs: list[types.OutputMessage], system_instruction: list[types.MessagePart], - span: Span | None = None, - log_record: LogRecord | None = None, + span: Union[Span, None] = None, + log_record: Union[LogRecord, None] = None, ) -> None: ... From 054d857c15ac90d955432eb6739f576ef8925a50 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Mon, 20 Oct 2025 10:23:37 -0700 Subject: [PATCH 2/2] make tests compatible with python 3.9 --- .../src/opentelemetry/util/genai/handler.py | 2 +- .../src/opentelemetry/util/genai/utils.py | 3 ++- util/opentelemetry-util-genai-dev/tests/test_evaluators.py | 3 ++- util/opentelemetry-util-genai-dev/tests/test_utils.py | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/handler.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/handler.py index e51f86689c..d0263cfc01 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/handler.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/handler.py @@ -96,7 +96,7 @@ def genai_debug_log(*_args: Any, **_kwargs: Any) -> None: # type: ignore _TRUTHY_VALUES = {"1", "true", "yes", "on"} -def _is_truthy_env(value: str | None) -> bool: +def _is_truthy_env(value: Optional[str]) -> bool: if value is None: return False return value.strip().lower() in _TRUTHY_VALUES diff --git a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/utils.py b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/utils.py index 2ed1f1b90e..b7363b3590 100644 --- a/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/utils.py +++ b/util/opentelemetry-util-genai-dev/src/opentelemetry/util/genai/utils.py @@ -14,6 +14,7 @@ import logging import os +from typing import Optional from opentelemetry.util.genai.environment_variables import ( OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, @@ -31,7 +32,7 @@ def is_experimental_mode() -> bool: # backward stub (always false) _TRUTHY_VALUES = {"1", "true", "yes", "on"} -def _is_truthy(value: str | None) -> bool: +def _is_truthy(value: Optional[str]) -> bool: if value is None: return False return value.strip().lower() in _TRUTHY_VALUES diff --git a/util/opentelemetry-util-genai-dev/tests/test_evaluators.py b/util/opentelemetry-util-genai-dev/tests/test_evaluators.py index 9b12fbbe82..1abd351372 100644 --- a/util/opentelemetry-util-genai-dev/tests/test_evaluators.py +++ b/util/opentelemetry-util-genai-dev/tests/test_evaluators.py @@ -1,6 +1,7 @@ import importlib import os import unittest +from typing import Optional from unittest.mock import patch from opentelemetry.util.genai.environment_variables import ( @@ -44,7 +45,7 @@ def __init__( self, metrics=None, *, - invocation_type: str | None = None, + invocation_type: Optional[str] = None, options=None, ) -> None: super().__init__( diff --git a/util/opentelemetry-util-genai-dev/tests/test_utils.py b/util/opentelemetry-util-genai-dev/tests/test_utils.py index 644e7e3478..7524ebcd41 100644 --- a/util/opentelemetry-util-genai-dev/tests/test_utils.py +++ b/util/opentelemetry-util-genai-dev/tests/test_utils.py @@ -15,7 +15,7 @@ import json import os import unittest -from typing import Any, Callable, TypeVar +from typing import Any, Callable, Optional, TypeVar from unittest.mock import patch from opentelemetry import trace @@ -41,7 +41,7 @@ _F = TypeVar("_F", bound=Callable[..., Any]) -def patch_capture_mode(value: str | None) -> Callable[[_F], _F]: +def patch_capture_mode(value: Optional[str]) -> Callable[[_F], _F]: def decorator(test_case: _F) -> _F: # type: ignore[misc] def wrapper(*args: Any, **kwargs: Any): # type: ignore[override] with patch.dict(os.environ, {}, clear=False):