Skip to content

Commit fc1cfb6

Browse files
Merge branch 'main' into main
2 parents 7f56c85 + 02cbe28 commit fc1cfb6

File tree

10 files changed

+147
-72
lines changed

10 files changed

+147
-72
lines changed

docs/instrumentation-genai/util.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ OpenTelemetry Python - GenAI Util
2626
:undoc-members:
2727
:show-inheritance:
2828

29-
.. automodule:: opentelemetry.util.genai._fsspec_upload
29+
.. automodule:: opentelemetry.util.genai._upload
3030
:members:
3131
:show-inheritance:

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ deps =
10721072
{[testenv]test_deps}
10731073
{toxinidir}/opentelemetry-instrumentation
10741074
{toxinidir}/util/opentelemetry-util-http
1075-
{toxinidir}/util/opentelemetry-util-genai[fsspec]
1075+
{toxinidir}/util/opentelemetry-util-genai[upload]
10761076
{toxinidir}/instrumentation-genai/opentelemetry-instrumentation-vertexai[instruments]
10771077
{toxinidir}/instrumentation-genai/opentelemetry-instrumentation-google-genai[instruments]
10781078
{toxinidir}/instrumentation/opentelemetry-instrumentation-aiokafka[instruments]

typings/fsspec/__init__.pyi

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Handwritten stubs for fsspec usage in opentelemetry-util-genai"""
16+
17+
from __future__ import annotations
18+
19+
import io
20+
from typing import Any, Literal
21+
22+
from fsspec.spec import (
23+
AbstractFileSystem as RealAbstractFileSystem,
24+
)
25+
26+
class AbstractFileSystem(RealAbstractFileSystem):
27+
def open(
28+
self, path: str, mode: Literal["w"], *args: Any, **kwargs: Any
29+
) -> io.TextIOWrapper: ...
30+
31+
def url_to_fs(url: str) -> tuple[AbstractFileSystem, str]: ...

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- Add jsonlines support to fsspec uploader
1111
([https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3791](#3791))
12+
- Rename "fsspec_upload" entry point and classes to more generic "upload"
13+
([https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3798](#3798))
14+
- Record content-type and use canonical paths in fsspec genai uploader
15+
([https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3795](#3795))
1216

1317
## Version 0.1b0 (2025-09-24)
1418

util/opentelemetry-util-genai/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ dependencies = [
3131
]
3232

3333
[project.entry-points.opentelemetry_genai_completion_hook]
34-
fsspec_upload = "opentelemetry.util.genai._fsspec_upload:fsspec_completion_upload_hook"
34+
upload = "opentelemetry.util.genai._upload:upload_completion_hook"
3535

3636
[project.optional-dependencies]
3737
test = ["pytest>=7.0.0"]
38-
fsspec = ["fsspec>=2025.9.0"]
38+
upload = ["fsspec>=2025.9.0"]
3939

4040
[project.urls]
4141
Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/util/opentelemetry-util-genai"
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
)
2626

2727

28-
def fsspec_completion_upload_hook() -> CompletionHook:
28+
def upload_completion_hook() -> CompletionHook:
2929
# If fsspec is not installed the hook will be a no-op.
3030
try:
3131
# pylint: disable=import-outside-toplevel
32-
from opentelemetry.util.genai._fsspec_upload.completion_hook import (
33-
FsspecUploadCompletionHook,
32+
from opentelemetry.util.genai._upload.completion_hook import (
33+
UploadCompletionHook,
3434
)
3535
except ImportError:
3636
return _NoOpCompletionHook()
@@ -39,4 +39,4 @@ def fsspec_completion_upload_hook() -> CompletionHook:
3939
if not base_path:
4040
return _NoOpCompletionHook()
4141

42-
return FsspecUploadCompletionHook(base_path=base_path)
42+
return UploadCompletionHook(base_path=base_path)
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from functools import partial
2727
from os import environ
2828
from time import time
29-
from typing import Any, Callable, Final, Literal, TextIO, cast
29+
from typing import Any, Callable, Final, Literal
3030
from uuid import uuid4
3131

3232
import fsspec
@@ -78,22 +78,17 @@ class CompletionRefs:
7878
UploadData = dict[str, Callable[[], JsonEncodeable]]
7979

8080

81-
def fsspec_open(urlpath: str, mode: Literal["w"]) -> TextIO:
82-
"""typed wrapper around `fsspec.open`"""
83-
return cast(TextIO, fsspec.open(urlpath, mode)) # pyright: ignore[reportUnknownMemberType]
84-
85-
86-
class FsspecUploadCompletionHook(CompletionHook):
81+
class UploadCompletionHook(CompletionHook):
8782
"""An completion hook using ``fsspec`` to upload to external storage
8883
8984
This function can be used as the
9085
:func:`~opentelemetry.util.genai.completion_hook.load_completion_hook` implementation by
91-
setting :envvar:`OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK` to ``fsspec_upload``.
86+
setting :envvar:`OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK` to ``upload``.
9287
:envvar:`OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH` must be configured to specify the
9388
base path for uploads.
9489
9590
Both the ``fsspec`` and ``opentelemetry-sdk`` packages should be installed, or a no-op
96-
implementation will be used instead. You can use ``opentelemetry-util-genai[fsspec]``
91+
implementation will be used instead. You can use ``opentelemetry-util-genai[upload]``
9792
as a requirement to achieve this.
9893
"""
9994

@@ -104,8 +99,9 @@ def __init__(
10499
max_size: int = 20,
105100
upload_format: Format | None = None,
106101
) -> None:
107-
self._base_path = base_path
108102
self._max_size = max_size
103+
self._fs, base_path = fsspec.url_to_fs(base_path)
104+
self._base_path = self._fs.unstrip_protocol(base_path)
109105

110106
if upload_format not in _FORMATS + (None,):
111107
raise ValueError(
@@ -133,15 +129,15 @@ def done(future: Future[None]) -> None:
133129
try:
134130
future.result()
135131
except Exception: # pylint: disable=broad-except
136-
_logger.exception("fsspec uploader failed")
132+
_logger.exception("uploader failed")
137133
finally:
138134
self._semaphore.release()
139135

140136
for path, json_encodeable in upload_data.items():
141137
# could not acquire, drop data
142138
if not self._semaphore.acquire(blocking=False): # pylint: disable=consider-using-with
143139
_logger.warning(
144-
"fsspec upload queue is full, dropping upload %s",
140+
"upload queue is full, dropping upload %s",
145141
path,
146142
)
147143
continue
@@ -153,7 +149,7 @@ def done(future: Future[None]) -> None:
153149
fut.add_done_callback(done)
154150
except RuntimeError:
155151
_logger.info(
156-
"attempting to upload file after FsspecUploadCompletionHook.shutdown() was already called"
152+
"attempting to upload file after UploadCompletionHook.shutdown() was already called"
157153
)
158154
self._semaphore.release()
159155

@@ -188,7 +184,13 @@ def _do_upload(
188184
for message_idx, line in enumerate(message_lines):
189185
line[_MESSAGE_INDEX_KEY] = message_idx
190186

191-
with fsspec_open(path, "w") as file:
187+
content_type = (
188+
"application/json"
189+
if self._format == "json"
190+
else "application/jsonl"
191+
)
192+
193+
with self._fs.open(path, "w", content_type=content_type) as file:
192194
for message in message_lines:
193195
json.dump(
194196
message,

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@
3939
* `Configuration
4040
<https://filesystem-spec.readthedocs.io/en/latest/features.html#configuration>`_ for
4141
configuring a backend with environment variables.
42-
* `URL Chaining
43-
<https://filesystem-spec.readthedocs.io/en/latest/features.html#url-chaining>`_ for advanced
44-
use cases.
4542
"""
4643

4744
OTEL_INSTRUMENTATION_GENAI_UPLOAD_FORMAT = (

0 commit comments

Comments
 (0)