Skip to content

Commit b597868

Browse files
committed
fix: satisfy precommit and typecheck for multimodal hot-reload
1 parent 50c52a4 commit b597868

14 files changed

Lines changed: 125 additions & 87 deletions

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
- Add multimodal runtime config snapshot and generation-aware uploader hot-reload.
1011
- Avoid import-time warnings when optional audio dependencies for PCM16-to-WAV conversion are not installed.
1112

1213
## Version 0.3b0 (2026-02-20)

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_processing.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def _should_async_process(self, invocation: _MultimodalInvocation) -> bool:
361361
if not self._multimodal_enabled:
362362
return False
363363

364-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
364+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
365365
get_multimodal_config_snapshot,
366366
)
367367

@@ -405,7 +405,7 @@ def _check_messages(
405405
@staticmethod
406406
def _quick_has_multimodal(invocation: _MultimodalInvocation) -> bool:
407407
"""Quick detection of multimodal data (O(n), no network)"""
408-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
408+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
409409
get_multimodal_config_snapshot,
410410
)
411411

@@ -766,7 +766,7 @@ def _get_uploader_and_pre_uploader( # pylint: disable=no-self-use
766766
from opentelemetry.util.genai._multimodal_upload import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
767767
get_or_rebuild_uploader_pair,
768768
)
769-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
769+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
770770
get_multimodal_config_snapshot,
771771
)
772772

@@ -783,7 +783,7 @@ def _upload_and_set_metadata(
783783
pre_uploader: "PreUploader",
784784
) -> None:
785785
"""Upload multimodal data and set metadata attributes on span"""
786-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
786+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
787787
get_multimodal_config_snapshot,
788788
)
789789

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
Uploader,
2323
UploadItem,
2424
)
25+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=no-name-in-module
26+
get_multimodal_config_snapshot,
27+
update_multimodal_runtime_config,
28+
)
2529
from opentelemetry.util.genai._multimodal_upload.multimodal_upload_hook import ( # pylint: disable=no-name-in-module
2630
get_or_load_pre_uploader,
2731
get_or_load_uploader,
@@ -33,10 +37,6 @@
3337
load_pre_uploader_hook,
3438
load_uploader_hook,
3539
)
36-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=no-name-in-module
37-
get_multimodal_config_snapshot,
38-
update_multimodal_runtime_config,
39-
)
4040

4141
try:
4242
from opentelemetry.util.genai._multimodal_upload.fs_uploader import (

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/config.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
_APSARA_SLS_LOGSTORE_ENV = "APSARA_APM_COLLECTOR_MULTIMODAL_SLS_LOGSTORE"
4747
_APSARA_SLS_ENDPOINT_ENV = "APSARA_APM_COLLECTOR_MULTIMODAL_SLS_ENDPOINT"
4848
_APSARA_SLS_AUTH_TYPE_ENV = "APSARA_APM_COLLECTOR_MULTIMODAL_SLS_AUTH_TYPE"
49-
_APSARA_SLS_ACCESS_KEY_ID_ENV = "APSARA_APM_COLLECTOR_MULTIMODAL_SLS_ACCESS_KEY_ID"
49+
_APSARA_SLS_ACCESS_KEY_ID_ENV = (
50+
"APSARA_APM_COLLECTOR_MULTIMODAL_SLS_ACCESS_KEY_ID"
51+
)
5052
_APSARA_SLS_ACCESS_KEY_SECRET_ENV = (
5153
"APSARA_APM_COLLECTOR_MULTIMODAL_SLS_ACCESS_KEY_SECRET"
5254
)
@@ -145,7 +147,9 @@ def _normalize_upload_mode(value: Optional[str]) -> str:
145147
return "none"
146148
mode = str(value).strip().lower()
147149
if mode not in _VALID_UPLOAD_MODES:
148-
_logger.warning("Invalid multimodal upload_mode %r, fallback to none", value)
150+
_logger.warning(
151+
"Invalid multimodal upload_mode %r, fallback to none", value
152+
)
149153
return "none"
150154
return mode
151155

@@ -204,18 +208,24 @@ def _snapshot_from_env(*, version: int = 0) -> MultimodalConfigSnapshot:
204208
storage_base_path = os.getenv(
205209
OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_STORAGE_BASE_PATH
206210
)
207-
uploader_hook_name = normalize_multimodal_hook_name(
208-
os.getenv(
209-
OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_UPLOADER,
210-
DEFAULT_MULTIMODAL_UPLOADER_HOOK,
211+
uploader_hook_name = (
212+
normalize_multimodal_hook_name(
213+
os.getenv(
214+
OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_UPLOADER,
215+
DEFAULT_MULTIMODAL_UPLOADER_HOOK,
216+
)
211217
)
212-
) or DEFAULT_MULTIMODAL_UPLOADER_HOOK
213-
pre_uploader_hook_name = normalize_multimodal_hook_name(
214-
os.getenv(
215-
OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_PRE_UPLOADER,
216-
DEFAULT_MULTIMODAL_PRE_UPLOADER_HOOK,
218+
or DEFAULT_MULTIMODAL_UPLOADER_HOOK
219+
)
220+
pre_uploader_hook_name = (
221+
normalize_multimodal_hook_name(
222+
os.getenv(
223+
OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_PRE_UPLOADER,
224+
DEFAULT_MULTIMODAL_PRE_UPLOADER_HOOK,
225+
)
217226
)
218-
) or DEFAULT_MULTIMODAL_PRE_UPLOADER_HOOK
227+
or DEFAULT_MULTIMODAL_PRE_UPLOADER_HOOK
228+
)
219229

220230
return MultimodalConfigSnapshot(
221231
upload_mode=upload_mode,
@@ -373,5 +383,7 @@ def get_multimodal_config_snapshot() -> MultimodalConfigSnapshot:
373383
return _runtime_config.get_snapshot()
374384

375385

376-
def update_multimodal_runtime_config(**fields: Any) -> MultimodalConfigSnapshot:
386+
def update_multimodal_runtime_config(
387+
**fields: Any,
388+
) -> MultimodalConfigSnapshot:
377389
return _runtime_config.update(**fields)

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/fs_uploader.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,15 @@ def fs_uploader_hook(
6565
) -> Optional[Uploader]:
6666
"""Create default FsUploader from runtime snapshot."""
6767
if snapshot is None:
68-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
68+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
6969
get_multimodal_config_snapshot,
7070
)
7171

7272
snapshot = get_multimodal_config_snapshot()
7373

74-
base_path = snapshot.effective_storage_base_path or snapshot.storage_base_path
74+
base_path = (
75+
snapshot.effective_storage_base_path or snapshot.storage_base_path
76+
)
7577
if not base_path:
7678
_logger.warning(
7779
"%s is required but not set, multimodal uploader disabled",

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/multimodal_upload_hook.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def _shutdown() -> None:
121121
if pre_uploader is not None:
122122
pre_uploader.shutdown()
123123
except Exception: # pylint: disable=broad-except
124-
_logger.debug("Failed to shutdown retired pre-uploader", exc_info=True)
124+
_logger.debug(
125+
"Failed to shutdown retired pre-uploader", exc_info=True
126+
)
125127
try:
126128
if uploader is not None:
127129
uploader.shutdown()

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/pre_uploader.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666

6767
# Try importing audio processing libraries (optional dependencies)
6868
try:
69-
import numpy as np
70-
import soundfile as sf
69+
import numpy as np # pyright: ignore[reportMissingImports]
70+
import soundfile as sf # pyright: ignore[reportMissingImports]
7171

7272
_audio_libs_available = True
7373
except ImportError:
@@ -591,7 +591,7 @@ def _convert_pcm16_to_wav(
591591

592592
try:
593593
# Convert PCM16 byte data to numpy int16 array
594-
audio_np = np.frombuffer(pcm_data, dtype=np.int16) # pyright: ignore[reportUnknownMemberType]
594+
audio_np = np.frombuffer(pcm_data, dtype=np.int16) # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
595595

596596
# Write WAV data to memory buffer
597597
buffer = io.BytesIO()
@@ -1190,28 +1190,21 @@ def _collect_http_uris(
11901190

11911191
@staticmethod
11921192
def _read_applied_strategy_version() -> int:
1193-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
1193+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
11941194
get_multimodal_config_snapshot,
11951195
)
11961196

11971197
return get_multimodal_config_snapshot().strategy_version
11981198

11991199
def _sync_strategy_from_snapshot(self, config_snapshot: Any) -> bool:
12001200
"""Apply strategy fields when snapshot strategy_version changes."""
1201-
if (
1202-
config_snapshot.strategy_version
1203-
!= self._applied_strategy_version
1204-
):
1201+
if config_snapshot.strategy_version != self._applied_strategy_version:
12051202
self._process_input = config_snapshot.process_input
12061203
self._process_output = config_snapshot.process_output
12071204
self._download_enabled = config_snapshot.download_enabled
12081205
self._local_file_enabled = config_snapshot.local_file_enabled
1209-
self._allowed_root_paths = list(
1210-
config_snapshot.allowed_root_paths
1211-
)
1212-
self._applied_strategy_version = (
1213-
config_snapshot.strategy_version
1214-
)
1206+
self._allowed_root_paths = list(config_snapshot.allowed_root_paths)
1207+
self._applied_strategy_version = config_snapshot.strategy_version
12151208
return self._process_input or self._process_output
12161209

12171210
def pre_upload( # pylint: disable=too-many-branches
@@ -1241,7 +1234,7 @@ def pre_upload( # pylint: disable=too-many-branches
12411234
uploads: List[PreUploadItem] = []
12421235

12431236
if config_snapshot is None:
1244-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
1237+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
12451238
get_multimodal_config_snapshot,
12461239
)
12471240

@@ -1266,7 +1259,6 @@ def _pre_upload_with_current_config(
12661259
output_messages: Optional[List[Any]],
12671260
uploads: List[PreUploadItem],
12681261
) -> List[PreUploadItem]:
1269-
12701262
trace_id: Optional[str] = None
12711263
span_id: Optional[str] = None
12721264
try:
@@ -1330,13 +1322,15 @@ def fs_pre_uploader_hook(
13301322
) -> Optional[PreUploader]:
13311323
"""Create file-system pre-uploader from runtime snapshot."""
13321324
if snapshot is None:
1333-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
1325+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
13341326
get_multimodal_config_snapshot,
13351327
)
13361328

13371329
snapshot = get_multimodal_config_snapshot()
13381330

1339-
base_path = snapshot.effective_storage_base_path or snapshot.storage_base_path
1331+
base_path = (
1332+
snapshot.effective_storage_base_path or snapshot.storage_base_path
1333+
)
13401334
if not base_path:
13411335
_logger.warning(
13421336
"%s is required but not set, multimodal pre-uploader disabled",

util/opentelemetry-util-genai/src/opentelemetry/util/genai/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _parse_env_bool(value: Optional[str], default: bool) -> bool:
136136

137137

138138
def _get_multimodal_snapshot():
139-
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module
139+
from opentelemetry.util.genai._multimodal_upload.config import ( # pylint: disable=import-outside-toplevel,no-name-in-module # noqa: PLC0415
140140
get_multimodal_config_snapshot,
141141
)
142142

util/opentelemetry-util-genai/tests/_multimodal_upload/test_default_hooks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
MultimodalPreUploader,
2525
fs_pre_uploader_hook,
2626
)
27-
from .multimodal_test_helpers import reset_multimodal_runtime_state_for_test
2827
from opentelemetry.util.genai.extended_environment_variables import (
2928
OTEL_INSTRUMENTATION_GENAI_MULTIMODAL_STORAGE_BASE_PATH,
3029
)
3130

31+
from .multimodal_test_helpers import reset_multimodal_runtime_state_for_test
32+
3233

3334
class TestDefaultHooks(TestCase):
3435
@patch.dict("os.environ", {}, clear=True)

util/opentelemetry-util-genai/tests/_multimodal_upload/test_multimodal_runtime_config.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ def test_env_fallback_defaults(self) -> None:
6868
snapshot.uploader_hook_name, get_default_uploader_hook_name()
6969
)
7070
self.assertEqual(
71-
snapshot.pre_uploader_hook_name, get_default_pre_uploader_hook_name()
71+
snapshot.pre_uploader_hook_name,
72+
get_default_pre_uploader_hook_name(),
7273
)
7374
self.assertIsNone(snapshot.effective_storage_base_path)
7475
self.assertFalse(snapshot.process_input)
@@ -131,9 +132,13 @@ def test_uploader_generation_fields_bump_generation(self) -> None:
131132
uploader_hook_name="fs",
132133
pre_uploader_hook_name="fs",
133134
)
134-
self.assertGreater(after.uploader_generation, before.uploader_generation)
135+
self.assertGreater(
136+
after.uploader_generation, before.uploader_generation
137+
)
135138
self.assertEqual(after.storage_base_path, "oss://bucket/prefix")
136-
self.assertEqual(after.effective_storage_base_path, "oss://bucket/prefix")
139+
self.assertEqual(
140+
after.effective_storage_base_path, "oss://bucket/prefix"
141+
)
137142

138143
def test_sls_project_change_bumps_uploader_generation(self) -> None:
139144
before = get_multimodal_config_snapshot()
@@ -143,7 +148,9 @@ def test_sls_project_change_bumps_uploader_generation(self) -> None:
143148
sls_project="project-a",
144149
sls_logstore="logstore-a",
145150
)
146-
self.assertGreater(after.uploader_generation, before.uploader_generation)
151+
self.assertGreater(
152+
after.uploader_generation, before.uploader_generation
153+
)
147154
self.assertEqual(after.uploader_hook_name, "arms")
148155
self.assertEqual(
149156
after.effective_storage_base_path,
@@ -159,7 +166,9 @@ def test_logstore_change_bumps_uploader_generation(self) -> None:
159166
)
160167
before = get_multimodal_config_snapshot()
161168
after = update_multimodal_runtime_config(sls_logstore="l2")
162-
self.assertGreater(after.uploader_generation, before.uploader_generation)
169+
self.assertGreater(
170+
after.uploader_generation, before.uploader_generation
171+
)
163172
self.assertIn("sls_logstore", UPLOADER_GENERATION_FIELDS)
164173
self.assertEqual(after.effective_storage_base_path, "sls://p/l2")
165174

@@ -185,7 +194,9 @@ def test_empty_upload_mode_fallback_to_none(self) -> None:
185194

186195
def test_no_op_update_keeps_snapshot(self) -> None:
187196
before = get_multimodal_config_snapshot()
188-
after = update_multimodal_runtime_config(upload_mode=before.upload_mode)
197+
after = update_multimodal_runtime_config(
198+
upload_mode=before.upload_mode
199+
)
189200
self.assertEqual(after, before)
190201

191202
def test_blank_sls_project_is_coalesced_to_none(self) -> None:

0 commit comments

Comments
 (0)