diff --git a/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md b/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md index b845f9911..7b78fa67f 100644 --- a/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md +++ b/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md @@ -7,5 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Add support for memory operations. ([#83](https://github.com/alibaba/loongsuite-python-agent/pull/83)) + - Add multimodal separation and upload support for GenAI utils. ([#94](https://github.com/alibaba/loongsuite-python-agent/pull/94)) -- Add support for memory operations. ([#83](https://github.com/alibaba/loongsuite-python-agent/pull/83)) \ No newline at end of file + +- Fix compatibility with Python 3.8 hashlib usage. ([#102](https://github.com/alibaba/loongsuite-python-agent/pull/102)) diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/fs_uploader.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/fs_uploader.py index 1b22ab043..bcba19582 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/fs_uploader.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_multimodal_upload/fs_uploader.py @@ -19,7 +19,6 @@ from __future__ import annotations -import hashlib import io import json import logging @@ -36,6 +35,9 @@ import httpx from opentelemetry.instrumentation.utils import suppress_http_instrumentation + +# LoongSuite Extension: For Python 3.8 Compatibility +from opentelemetry.util.genai import compatible_hashlib as hashlib from opentelemetry.util.genai._multimodal_upload._base import ( Uploader, UploadItem, diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_upload/completion_hook.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_upload/completion_hook.py index 4f0b98e6f..a15171b9d 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_upload/completion_hook.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/_upload/completion_hook.py @@ -15,7 +15,6 @@ from __future__ import annotations -import hashlib import logging import posixpath import threading @@ -30,9 +29,15 @@ import fsspec +# LoongSuite Extension: For Python 3.8 Compatibility +from typing_extensions import TypeAlias + from opentelemetry._logs import LogRecord from opentelemetry.semconv._incubating.attributes import gen_ai_attributes from opentelemetry.trace import Span + +# LoongSuite Extension: For Python 3.8 Compatibility +from opentelemetry.util.genai import compatible_hashlib as hashlib from opentelemetry.util.genai import types from opentelemetry.util.genai.completion_hook import CompletionHook from opentelemetry.util.genai.utils import gen_ai_json_dump @@ -72,10 +77,12 @@ class CompletionRefs: system_instruction_ref: str -JsonEncodeable = list[dict[str, Any]] +# LoongSuite Extension: Use TypeAlias with string annotation for Python 3.8 compatibility +JsonEncodeable: TypeAlias = "list[dict[str, Any]]" # mapping of upload path and whether the contents were hashed to the filename to function computing upload data dict -UploadData = dict[tuple[str, bool], Callable[[], JsonEncodeable]] +# LoongSuite Extension: Use TypeAlias with string annotation for Python 3.8 compatibility +UploadData: TypeAlias = "dict[tuple[str, bool], Callable[[], JsonEncodeable]]" def is_system_instructions_hashable( diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/compatible_hashlib.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/compatible_hashlib.py new file mode 100644 index 000000000..9feb9eb5d --- /dev/null +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/compatible_hashlib.py @@ -0,0 +1,51 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import hashlib as _hashlib +import sys +from hashlib import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import # pyright: ignore[reportWildcardImportFromLibrary] + + +def _make_compatible(orig_func): # type: ignore + def compatible_func(content: bytes = b"", *, usedforsecurity: bool = True): # type: ignore + return orig_func(content) # type: ignore + + return compatible_func + + +def _setup_hashlib_compatibility(): + """Setup hashlib compatibility to support Python 3.8""" + if sys.version_info < (3, 9): + # List of hash functions that need usedforsecurity parameter support + functions_to_patch = [ + "md5", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + ] + + for func_name in functions_to_patch: + if hasattr(_hashlib, func_name): + original_func = getattr(_hashlib, func_name) + # Apply patch + compatible_func = _make_compatible(original_func) + setattr(_hashlib, func_name, compatible_func) + + # Also update the reference in the current module + globals()[func_name] = compatible_func + + +_setup_hashlib_compatibility() diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/metrics.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/metrics.py index 68f8d08d3..0e174a9eb 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/metrics.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/metrics.py @@ -90,7 +90,10 @@ def record( for token_count, token_type in token_counts: self._token_histogram.record( token_count, - attributes=attributes | {GenAI.GEN_AI_TOKEN_TYPE: token_type}, + attributes={ + **attributes, + GenAI.GEN_AI_TOKEN_TYPE: token_type, + }, # LoongSuite Extension: For Python 3.8 Compatibility context=span_context, ) diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/types.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/types.py index b8fca6ca2..1c100da35 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/types.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/types.py @@ -14,21 +14,21 @@ from __future__ import annotations -from contextvars import Token +from contextvars import Token # pylint: disable=W0611 from dataclasses import dataclass, field from enum import Enum from typing import Any, Literal, Type, Union from typing_extensions import TypeAlias -from opentelemetry.context import Context +from opentelemetry.context import Context # pylint: disable=W0611 from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAI, ) from opentelemetry.trace import Span # LoongSuite Extension: Add type alias for ContextToken to avoid failure in python 3.8 -ContextToken: TypeAlias = Token[Context] +ContextToken: TypeAlias = "Token[Context]" class ContentCapturingMode(Enum): diff --git a/util/opentelemetry-util-genai/tests/test_extended_handler.py b/util/opentelemetry-util-genai/tests/test_extended_handler.py index 0ba431c3d..0f046b379 100644 --- a/util/opentelemetry-util-genai/tests/test_extended_handler.py +++ b/util/opentelemetry-util-genai/tests/test_extended_handler.py @@ -1388,17 +1388,13 @@ def test_fallback_methods_apply_attributes(self): inv.span = mock_span error = Error(message="err", type=ValueError) - with ( - patch( - "opentelemetry.util.genai._multimodal_processing._apply_llm_finish_attributes" - ) as m1, - patch( - "opentelemetry.util.genai._multimodal_processing._apply_error_attributes" - ) as m2, - patch( - "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" - ), - ): + with patch( + "opentelemetry.util.genai._multimodal_processing._apply_llm_finish_attributes" + ) as m1, patch( + "opentelemetry.util.genai._multimodal_processing._apply_error_attributes" + ) as m2, patch( + "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" + ): # fmt: skip handler._fallback_end_span(inv) m1.assert_called_with(mock_span, inv) mock_span.end.assert_called_once() @@ -1428,17 +1424,13 @@ def test_async_stop_and_fail_llm_process_correctly(self): ) ] - with ( - patch( - "opentelemetry.util.genai._multimodal_processing._apply_llm_finish_attributes" - ) as m1, - patch( - "opentelemetry.util.genai._multimodal_processing._apply_error_attributes" - ) as m2, - patch( - "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" - ), - ): + with patch( + "opentelemetry.util.genai._multimodal_processing._apply_llm_finish_attributes" + ) as m1, patch( + "opentelemetry.util.genai._multimodal_processing._apply_error_attributes" + ) as m2, patch( + "opentelemetry.util.genai._multimodal_processing._maybe_emit_llm_event" + ): # fmt: skip handler._async_stop_llm( _MultimodalAsyncTask( invocation=inv, method="stop", handler=handler