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
5 changes: 4 additions & 1 deletion util/opentelemetry-util-genai/CHANGELOG-loongsuite.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

- Fix compatibility with Python 3.8 hashlib usage. ([#102](https://github.com/alibaba/loongsuite-python-agent/pull/102))
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from __future__ import annotations

import hashlib
import io
import json
import logging
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

from __future__ import annotations

import hashlib
import logging
import posixpath
import threading
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
36 changes: 14 additions & 22 deletions util/opentelemetry-util-genai/tests/test_extended_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down