diff --git a/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md b/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md index c67d33561..2351ca3db 100644 --- a/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md +++ b/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 instrumentation-only logic, with explicit rejection of deferred generator lifecycles. +### Fixed + +- Keep multimodal tests compatible with downstream Python 3.8 consumers. + ## Version 0.7.0 (2026-07-03) ### Added diff --git a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_fs_uploader_usage_metrics.py b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_fs_uploader_usage_metrics.py index 35b1f68dc..a9638bbf0 100644 --- a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_fs_uploader_usage_metrics.py +++ b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_fs_uploader_usage_metrics.py @@ -18,7 +18,7 @@ import threading import time from dataclasses import dataclass, field -from typing import List, Optional +from typing import Dict, List, Optional, Tuple from unittest.mock import MagicMock, patch import httpx @@ -33,8 +33,8 @@ @dataclass class RecordingUsageRecorder: - successes: List[tuple[str, int]] = field(default_factory=list) - errors: List[tuple[str, str]] = field(default_factory=list) + successes: List[Tuple[str, int]] = field(default_factory=list) + errors: List[Tuple[str, str]] = field(default_factory=list) success_event: threading.Event = field(default_factory=threading.Event) raise_on_success: bool = False raise_on_error: bool = False @@ -352,7 +352,7 @@ def counting_write( path: str, content: bytes, content_type: Optional[str], - meta: Optional[dict[str, str]], + meta: Optional[Dict[str, str]], ) -> bool: write_calls["count"] += 1 return original_write(path, content, content_type, meta) diff --git a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_multimodal_upload_hook.py b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_multimodal_upload_hook.py index 3c9e6b438..4029255a5 100644 --- a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_multimodal_upload_hook.py +++ b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_multimodal_upload_hook.py @@ -372,19 +372,17 @@ def load_pair_and_bump_generation( ) return FakeUploader(), FakePreUploader() - with ( - patch.object( - module, - "_load_pair_from_snapshot", - side_effect=load_pair_and_bump_generation, - ), - patch.object( + with patch.object( + module, + "_load_pair_from_snapshot", + side_effect=load_pair_and_bump_generation, + ): + with patch.object( module, "_schedule_retired_pair_shutdown", side_effect=track_shutdown, - ), - ): - uploader, pre_uploader = module.get_or_rebuild_uploader_pair() + ): + uploader, pre_uploader = module.get_or_rebuild_uploader_pair() self.assertIsNone(uploader) self.assertIsNone(pre_uploader) diff --git a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader.py b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader.py index 2e65e4a19..8a42da2d8 100644 --- a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader.py +++ b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader.py @@ -1153,35 +1153,35 @@ def test_local_file_processing_relative_path(pre_uploader_factory): relative_path = test_file.name # Aliyun Python Agent Extension: for compatibility with Python 3.8 - with ( - patch.dict( - os.environ, - { - "OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_LOCAL_FILE_ENABLED": "true", - "OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_ALLOWED_ROOT_PATHS": str( - test_dir - ), - }, - ), - patch("os.getcwd", return_value=str(test_dir)), + with patch.dict( + os.environ, + { + "OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_LOCAL_FILE_ENABLED": "true", + "OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_ALLOWED_ROOT_PATHS": str( + test_dir + ), + }, ): - reset_multimodal_runtime_state_for_test() - pre_uploader = pre_uploader_factory() - # Test with simple filename (relative path) - part = Uri( - modality="image", mime_type="image/png", uri=relative_path - ) - message = InputMessage(role="user", parts=[part]) + with patch("os.getcwd", return_value=str(test_dir)): + reset_multimodal_runtime_state_for_test() + pre_uploader = pre_uploader_factory() + # Test with simple filename (relative path) + part = Uri( + modality="image", + mime_type="image/png", + uri=relative_path, + ) + message = InputMessage(role="user", parts=[part]) - uploads = pre_uploader.pre_upload( - span_context=None, - start_time_utc_nano=1000000000000, - input_messages=[message], - output_messages=[], - ) + uploads = pre_uploader.pre_upload( + span_context=None, + start_time_utc_nano=1000000000000, + input_messages=[message], + output_messages=[], + ) - assert len(uploads) == 1 - assert uploads[0].data == test_file.read_bytes() + assert len(uploads) == 1 + assert uploads[0].data == test_file.read_bytes() @staticmethod def test_local_file_processing_disabled_by_default(pre_uploader_factory): diff --git a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader_audio.py b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader_audio.py index 39bb6dcc5..bcd84d14f 100644 --- a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader_audio.py +++ b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_pre_uploader_audio.py @@ -235,40 +235,40 @@ def test_pcm16_conversion_missing_audio_libs_logs_single_warning( caplog, ): """Missing optional audio libs should only log the actual conversion skip.""" - with ( - patch( - "opentelemetry.util.genai._multimodal_upload.pre_uploader._audio_libs_available", - False, - ), - patch( + with patch( + "opentelemetry.util.genai._multimodal_upload.pre_uploader._audio_libs_available", + False, + ): + with patch( "opentelemetry.util.genai._multimodal_upload.pre_uploader.np", None, - ), - patch( - "opentelemetry.util.genai._multimodal_upload.pre_uploader.sf", - None, - ), - ): - pre_uploader = MultimodalPreUploader(base_path="/tmp/test_upload") - part = Blob( - content=b"\x00\x01" * 1000, - mime_type="audio/pcm16", - modality="audio", - ) - input_messages = [InputMessage(role="user", parts=[part])] - - with caplog.at_level( - "WARNING", - logger=( - "opentelemetry.util.genai._multimodal_upload.pre_uploader" - ), ): - uploads = pre_uploader.pre_upload( - span_context=None, - start_time_utc_nano=1000000000000000000, - input_messages=input_messages, - output_messages=None, - ) + with patch( + "opentelemetry.util.genai._multimodal_upload.pre_uploader.sf", + None, + ): + pre_uploader = MultimodalPreUploader( + base_path="/tmp/test_upload" + ) + part = Blob( + content=b"\x00\x01" * 1000, + mime_type="audio/pcm16", + modality="audio", + ) + input_messages = [InputMessage(role="user", parts=[part])] + + with caplog.at_level( + "WARNING", + logger=( + "opentelemetry.util.genai._multimodal_upload.pre_uploader" + ), + ): + uploads = pre_uploader.pre_upload( + span_context=None, + start_time_utc_nano=1000000000000000000, + input_messages=input_messages, + output_messages=None, + ) assert len(uploads) == 1 assert uploads[0].content_type == "audio/pcm16" diff --git a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_usage_recorder.py b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_usage_recorder.py index e4acfeb74..83aa90100 100644 --- a/util/opentelemetry-util-genai/tests/_multimodal_upload/test_usage_recorder.py +++ b/util/opentelemetry-util-genai/tests/_multimodal_upload/test_usage_recorder.py @@ -16,7 +16,7 @@ import threading from dataclasses import dataclass, field -from typing import Iterator, List +from typing import Iterator, List, Tuple import pytest @@ -29,8 +29,8 @@ @dataclass class RecordingUsageRecorder: - successes: List[tuple[str, int]] = field(default_factory=list) - errors: List[tuple[str, str]] = field(default_factory=list) + successes: List[Tuple[str, int]] = field(default_factory=list) + errors: List[Tuple[str, str]] = field(default_factory=list) def record_upload_success( self, *, provider: str, content_bytes: int diff --git a/util/opentelemetry-util-genai/tests/test_extended_handler.py b/util/opentelemetry-util-genai/tests/test_extended_handler.py index 64ccf0976..27e85587c 100644 --- a/util/opentelemetry-util-genai/tests/test_extended_handler.py +++ b/util/opentelemetry-util-genai/tests/test_extended_handler.py @@ -1863,40 +1863,40 @@ def _record_llm_metrics(self, *_args: Any, **_kwargs: Any) -> None: async_inv.span = mock_span async_inv.input_messages = inv.input_messages - with ( - patch.object(h1, "_upload_and_set_metadata") as mock_upload, - patch( + with patch.object(h1, "_upload_and_set_metadata") as mock_upload: + with patch( "opentelemetry.util.genai._multimodal_processing._apply_llm_finish_attributes" - ), - patch( - "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" - ), - ): - h1._async_stop_llm( - _MultimodalAsyncTask( - invocation=async_inv, method="stop_llm", handler=h1 - ) - ) - mock_upload.assert_called_once() - mock_span.end.assert_called_once() + ): + with patch( + "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" + ): + h1._async_stop_llm( + _MultimodalAsyncTask( + invocation=async_inv, + method="stop_llm", + handler=h1, + ) + ) + mock_upload.assert_called_once() + mock_span.end.assert_called_once() mock_span.reset_mock() - with ( - patch.object(h2, "_upload_and_set_metadata") as mock_upload2, - patch( + with patch.object(h2, "_upload_and_set_metadata") as mock_upload2: + with patch( "opentelemetry.util.genai._multimodal_processing._apply_llm_finish_attributes" - ), - patch( - "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" - ), - ): - h2._async_stop_llm( - _MultimodalAsyncTask( - invocation=async_inv, method="stop_llm", handler=h2 - ) - ) - mock_upload2.assert_not_called() - mock_span.end.assert_called_once() + ): + with patch( + "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" + ): + h2._async_stop_llm( + _MultimodalAsyncTask( + invocation=async_inv, + method="stop_llm", + handler=h2, + ) + ) + mock_upload2.assert_not_called() + mock_span.end.assert_called_once() # ==================== process_multimodal_stop/fail Tests ====================