Skip to content

Commit cf00cf5

Browse files
authored
Rename UploadHook -> CompletionHook (#3780)
1 parent b232b9a commit cf00cf5

File tree

10 files changed

+186
-172
lines changed

10 files changed

+186
-172
lines changed

docs/instrumentation-genai/util.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ OpenTelemetry Python - GenAI Util
2121
:undoc-members:
2222
:show-inheritance:
2323

24-
.. automodule:: opentelemetry.util.genai.upload_hook
24+
.. automodule:: opentelemetry.util.genai.completion_hook
2525
:members:
2626
:undoc-members:
2727
:show-inheritance:

util/opentelemetry-util-genai/CHANGELOG.md

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

88
## Unreleased
99

10-
- Add upload hook to genai utils to implement semconv v1.37.
10+
- Add completion hook to genai utils to implement semconv v1.37.
1111

12-
The hook uses [`fsspec`](https://filesystem-spec.readthedocs.io/en/latest/) to support
13-
various pluggable backends.
12+
Includes a hook implementation using
13+
[`fsspec`](https://filesystem-spec.readthedocs.io/en/latest/) to support uploading to various
14+
pluggable backends.
15+
16+
([#3780](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3780))
1417
([#3752](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3752))
15-
([#3759](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3752))
18+
([#3759](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3759))
1619
([#3763](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3763))
1720
- Add a utility to parse the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable.
1821
Add `gen_ai_latest_experimental` as a new value to the Sem Conv stability flag ([#3716](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3716)).

util/opentelemetry-util-genai/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ dependencies = [
3030
"opentelemetry-api>=1.31.0",
3131
]
3232

33-
[project.entry-points.opentelemetry_genai_upload_hook]
34-
fsspec = "opentelemetry.util.genai._fsspec_upload:fsspec_upload_hook"
33+
[project.entry-points.opentelemetry_genai_completion_hook]
34+
fsspec_upload = "opentelemetry.util.genai._fsspec_upload:fsspec_completion_upload_hook"
3535

3636
[project.optional-dependencies]
3737
test = ["pytest>=7.0.0"]

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,27 @@
1616

1717
from os import environ
1818

19+
from opentelemetry.util.genai.completion_hook import (
20+
CompletionHook,
21+
_NoOpCompletionHook,
22+
)
1923
from opentelemetry.util.genai.environment_variables import (
2024
OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH,
2125
)
22-
from opentelemetry.util.genai.upload_hook import UploadHook, _NoOpUploadHook
2326

2427

25-
def fsspec_upload_hook() -> UploadHook:
28+
def fsspec_completion_upload_hook() -> CompletionHook:
2629
# If fsspec is not installed the hook will be a no-op.
2730
try:
2831
# pylint: disable=import-outside-toplevel
29-
from opentelemetry.util.genai._fsspec_upload.fsspec_hook import (
30-
FsspecUploadHook,
32+
from opentelemetry.util.genai._fsspec_upload.completion_hook import (
33+
FsspecUploadCompletionHook,
3134
)
3235
except ImportError:
33-
return _NoOpUploadHook()
36+
return _NoOpCompletionHook()
3437

3538
base_path = environ.get(OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH)
3639
if not base_path:
37-
return _NoOpUploadHook()
40+
return _NoOpCompletionHook()
3841

39-
return FsspecUploadHook(base_path=base_path)
42+
return FsspecUploadCompletionHook(base_path=base_path)

util/opentelemetry-util-genai/src/opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py renamed to util/opentelemetry-util-genai/src/opentelemetry/util/genai/_fsspec_upload/completion_hook.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
3535
from opentelemetry.trace import Span
3636
from opentelemetry.util.genai import types
37-
from opentelemetry.util.genai.upload_hook import UploadHook
37+
from opentelemetry.util.genai.completion_hook import CompletionHook
3838

3939
GEN_AI_INPUT_MESSAGES_REF: Final = (
4040
gen_ai_attributes.GEN_AI_INPUT_MESSAGES + "_ref"
@@ -75,12 +75,12 @@ def fsspec_open(urlpath: str, mode: Literal["w"]) -> TextIO:
7575
return cast(TextIO, fsspec.open(urlpath, mode)) # pyright: ignore[reportUnknownMemberType]
7676

7777

78-
class FsspecUploadHook(UploadHook):
79-
"""An upload hook using ``fsspec`` to upload to external storage
78+
class FsspecUploadCompletionHook(CompletionHook):
79+
"""An completion hook using ``fsspec`` to upload to external storage
8080
8181
This function can be used as the
82-
:func:`~opentelemetry.util.genai.upload_hook.load_upload_hook` implementation by
83-
setting :envvar:`OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK` to ``fsspec``.
82+
:func:`~opentelemetry.util.genai.completion_hook.load_completion_hook` implementation by
83+
setting :envvar:`OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK` to ``fsspec_upload``.
8484
:envvar:`OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH` must be configured to specify the
8585
base path for uploads.
8686
@@ -128,7 +128,7 @@ def done(future: Future[None]) -> None:
128128
fut.add_done_callback(done)
129129
except RuntimeError:
130130
_logger.info(
131-
"attempting to upload file after FsspecUploadHook.shutdown() was already called"
131+
"attempting to upload file after FsspecUploadCompletionHook.shutdown() was already called"
132132
)
133133
self._semaphore.release()
134134

@@ -161,7 +161,7 @@ def _do_upload(
161161
cls=Base64JsonEncoder,
162162
)
163163

164-
def upload(
164+
def on_completion(
165165
self,
166166
*,
167167
inputs: list[types.InputMessage],

util/opentelemetry-util-genai/src/opentelemetry/util/genai/upload_hook.py renamed to util/opentelemetry-util-genai/src/opentelemetry/util/genai/completion_hook.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""This module defines the generic hooks for GenAI content uploading
15+
"""This module defines the generic hooks for GenAI content completion
1616
1717
The hooks are specified as part of semconv in `Uploading content to external storage
1818
<https://github.com/open-telemetry/semantic-conventions/blob/v1.37.0/docs/gen-ai/gen-ai-spans.md#uploading-content-to-external-storage>`__.
1919
20-
This module defines the `UploadHook` type that custom implementations should implement, and a
21-
`load_upload_hook` function to load it from an entry point.
20+
This module defines the `CompletionHook` type that custom implementations should implement, and a
21+
`load_completion_hook` function to load it from an entry point.
2222
"""
2323

2424
from __future__ import annotations
@@ -34,18 +34,18 @@
3434
)
3535
from opentelemetry.util.genai import types
3636
from opentelemetry.util.genai.environment_variables import (
37-
OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK,
37+
OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK,
3838
)
3939

4040
_logger = logging.getLogger(__name__)
4141

4242

4343
@runtime_checkable
44-
class UploadHook(Protocol):
45-
"""A hook to upload GenAI content to an external storage.
44+
class CompletionHook(Protocol):
45+
"""A hook to be called on completion of a GenAI operation.
4646
4747
This is the interface for a hook that can be
48-
used to upload GenAI content to an external storage. The hook is a
48+
used to capture GenAI content on completion. The hook is a
4949
callable that takes the inputs, outputs, and system instruction of a
5050
GenAI interaction, as well as the span and log record associated with
5151
it.
@@ -66,7 +66,7 @@ class UploadHook(Protocol):
6666
interaction.
6767
"""
6868

69-
def upload(
69+
def on_completion(
7070
self,
7171
*,
7272
inputs: list[types.InputMessage],
@@ -77,43 +77,47 @@ def upload(
7777
) -> None: ...
7878

7979

80-
class _NoOpUploadHook(UploadHook):
81-
def upload(self, **kwargs: Any) -> None:
80+
class _NoOpCompletionHook(CompletionHook):
81+
def on_completion(self, **kwargs: Any) -> None:
8282
return None
8383

8484

85-
def load_upload_hook() -> UploadHook:
86-
"""Load the upload hook from entry point or return a noop implementation
85+
def load_completion_hook() -> CompletionHook:
86+
"""Load the completion hook from entry point or return a noop implementation
8787
88-
This function loads an upload hook from the entry point group
89-
``opentelemetry_genai_upload_hook`` with name coming from
90-
:envvar:`OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK`. If one can't be found, returns a no-op
88+
This function loads an completion hook from the entry point group
89+
``opentelemetry_genai_completion_hook`` with name coming from
90+
:envvar:`OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK`. If one can't be found, returns a no-op
9191
implementation.
9292
"""
93-
hook_name = environ.get(OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK, None)
93+
hook_name = environ.get(OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK, None)
9494
if not hook_name:
95-
return _NoOpUploadHook()
95+
return _NoOpCompletionHook()
9696

97-
for entry_point in entry_points(group="opentelemetry_genai_upload_hook"): # pyright: ignore[reportUnknownVariableType]
97+
for entry_point in entry_points( # pyright: ignore[reportUnknownVariableType]
98+
group="opentelemetry_genai_completion_hook"
99+
):
98100
name = cast(str, entry_point.name) # pyright: ignore[reportUnknownMemberType]
99101
try:
100102
if hook_name != name:
101103
continue
102104

103105
hook = entry_point.load()() # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
104-
if not isinstance(hook, UploadHook):
105-
_logger.debug("%s is not a valid UploadHook. Using noop", name)
106+
if not isinstance(hook, CompletionHook):
107+
_logger.debug(
108+
"%s is not a valid CompletionHook. Using noop", name
109+
)
106110
continue
107111

108-
_logger.debug("Using UploadHook %s", name)
112+
_logger.debug("Using CompletionHook %s", name)
109113
return hook
110114

111115
except Exception: # pylint: disable=broad-except
112116
_logger.exception(
113-
"UploadHook %s configuration failed. Using noop", name
117+
"CompletionHook %s configuration failed. Using noop", name
114118
)
115119

116-
return _NoOpUploadHook()
120+
return _NoOpCompletionHook()
117121

118122

119-
__all__ = ["UploadHook", "load_upload_hook"]
123+
__all__ = ["CompletionHook", "load_completion_hook"]

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
1717
)
1818

19-
OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK = (
20-
"OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK"
19+
OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK = (
20+
"OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK"
2121
)
2222
"""
23-
.. envvar:: OTEL_INSTRUMENTATION_GENAI_UPLOAD_HOOK
23+
.. envvar:: OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK
2424
"""
2525

2626
OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH = (
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
import logging
16+
from dataclasses import dataclass
17+
from typing import Any, Callable
18+
from unittest import TestCase
19+
from unittest.mock import Mock, patch
20+
21+
from opentelemetry.util.genai.completion_hook import (
22+
CompletionHook,
23+
_NoOpCompletionHook,
24+
load_completion_hook,
25+
)
26+
from opentelemetry.util.genai.environment_variables import (
27+
OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK,
28+
)
29+
30+
31+
class FakeCompletionHook(CompletionHook):
32+
def on_completion(self, **kwargs: Any):
33+
pass
34+
35+
36+
class InvalidCompletionHook:
37+
pass
38+
39+
40+
@dataclass
41+
class FakeEntryPoint:
42+
name: str
43+
load: Callable[[], type[CompletionHook]]
44+
45+
46+
class TestCompletionHook(TestCase):
47+
@patch.dict("os.environ", {})
48+
def test_load_completion_hook_noop(self):
49+
self.assertIsInstance(load_completion_hook(), _NoOpCompletionHook)
50+
51+
@patch(
52+
"opentelemetry.util.genai.completion_hook.entry_points",
53+
)
54+
@patch.dict(
55+
"os.environ", {OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK: "my-hook"}
56+
)
57+
def test_load_completion_hook_custom(self, mock_entry_points: Mock):
58+
mock_entry_points.return_value = [
59+
FakeEntryPoint("my-hook", lambda: FakeCompletionHook)
60+
]
61+
62+
self.assertIsInstance(load_completion_hook(), FakeCompletionHook)
63+
64+
@patch("opentelemetry.util.genai.completion_hook.entry_points")
65+
@patch.dict(
66+
"os.environ", {OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK: "my-hook"}
67+
)
68+
def test_load_completion_hook_invalid(self, mock_entry_points: Mock):
69+
mock_entry_points.return_value = [
70+
FakeEntryPoint("my-hook", lambda: InvalidCompletionHook)
71+
]
72+
73+
with self.assertLogs(level=logging.DEBUG) as logs:
74+
self.assertIsInstance(load_completion_hook(), _NoOpCompletionHook)
75+
self.assertEqual(len(logs.output), 1)
76+
self.assertIn(
77+
"is not a valid CompletionHook. Using noop", logs.output[0]
78+
)
79+
80+
@patch("opentelemetry.util.genai.completion_hook.entry_points")
81+
@patch.dict(
82+
"os.environ", {OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK: "my-hook"}
83+
)
84+
def test_load_completion_hook_error(self, mock_entry_points: Mock):
85+
def load():
86+
raise RuntimeError("error")
87+
88+
mock_entry_points.return_value = [FakeEntryPoint("my-hook", load)]
89+
90+
self.assertIsInstance(load_completion_hook(), _NoOpCompletionHook)
91+
92+
@patch("opentelemetry.util.genai.completion_hook.entry_points")
93+
@patch.dict(
94+
"os.environ", {OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK: "my-hook"}
95+
)
96+
def test_load_completion_hook_not_found(self, mock_entry_points: Mock):
97+
mock_entry_points.return_value = [
98+
FakeEntryPoint("other-hook", lambda: FakeCompletionHook)
99+
]
100+
101+
self.assertIsInstance(load_completion_hook(), _NoOpCompletionHook)

0 commit comments

Comments
 (0)