Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions util/opentelemetry-util-genai/CHANGELOG-loongsuite.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import threading
from dataclasses import dataclass, field
from typing import Iterator, List
from typing import Iterator, List, Tuple

import pytest

Expand All @@ -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
Expand Down
60 changes: 30 additions & 30 deletions util/opentelemetry-util-genai/tests/test_extended_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ====================

Expand Down
Loading