Skip to content

Commit 4fb00c9

Browse files
authored
Stamp uploaded references refs onto spans and logs (#3763)
* Stamp gen ai refs on spans and logs * remove log body stamping * fix lint
1 parent ee947ce commit 4fb00c9

File tree

3 files changed

+91
-9
lines changed

3 files changed

+91
-9
lines changed

util/opentelemetry-util-genai/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,12 @@ 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.
11+
12+
The hook uses [`fsspec`](https://filesystem-spec.readthedocs.io/en/latest/) to support
13+
various pluggable backends.
14+
([#3752](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3752))
15+
([#3759](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3752))
16+
([#3763](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3763))
1017
- Add a utility to parse the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable.
1118
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/src/opentelemetry/util/genai/_fsspec_upload/fsspec_hook.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,28 @@
2222
from concurrent.futures import Future, ThreadPoolExecutor
2323
from dataclasses import asdict, dataclass
2424
from functools import partial
25-
from typing import Any, Callable, Literal, TextIO, cast
25+
from typing import Any, Callable, Final, Literal, TextIO, cast
2626
from uuid import uuid4
2727

2828
import fsspec
2929

3030
from opentelemetry._logs import LogRecord
31+
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
3132
from opentelemetry.trace import Span
3233
from opentelemetry.util.genai import types
3334
from opentelemetry.util.genai.upload_hook import UploadHook
3435

36+
GEN_AI_INPUT_MESSAGES_REF: Final = (
37+
gen_ai_attributes.GEN_AI_INPUT_MESSAGES + "_ref"
38+
)
39+
GEN_AI_OUTPUT_MESSAGES_REF: Final = (
40+
gen_ai_attributes.GEN_AI_OUTPUT_MESSAGES + "_ref"
41+
)
42+
GEN_AI_SYSTEM_INSTRUCTIONS_REF: Final = (
43+
gen_ai_attributes.GEN_AI_SYSTEM_INSTRUCTIONS + "_ref"
44+
)
45+
46+
3547
_logger = logging.getLogger(__name__)
3648

3749

@@ -177,7 +189,19 @@ def to_dict(
177189
},
178190
)
179191

180-
# TODO: stamp the refs on telemetry
192+
# stamp the refs on telemetry
193+
references = {
194+
GEN_AI_INPUT_MESSAGES_REF: ref_names.inputs_ref,
195+
GEN_AI_OUTPUT_MESSAGES_REF: ref_names.outputs_ref,
196+
GEN_AI_SYSTEM_INSTRUCTIONS_REF: ref_names.system_instruction_ref,
197+
}
198+
if span:
199+
span.set_attributes(references)
200+
if log_record:
201+
log_record.attributes = {
202+
**(log_record.attributes or {}),
203+
**references,
204+
}
181205

182206
def shutdown(self) -> None:
183207
# TODO: support timeout

util/opentelemetry-util-genai/tests/test_fsspec_upload.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
from unittest.mock import MagicMock, patch
2626

2727
import fsspec
28-
from fsspec.implementations.memory import MemoryFileSystem
2928

29+
from opentelemetry._logs import LogRecord
3030
from opentelemetry.test.test_base import TestBase
3131
from opentelemetry.util.genai import types
3232
from opentelemetry.util.genai._fsspec_upload.fsspec_hook import (
@@ -200,9 +200,6 @@ def test_upload(self):
200200

201201

202202
class TestFsspecUploadHookIntegration(TestBase):
203-
def setUp(self):
204-
MemoryFileSystem.store.clear()
205-
206203
def assert_fsspec_equal(self, path: str, value: str) -> None:
207204
with fsspec.open(path, "r") as file:
208205
self.assertEqual(file.read(), value)
@@ -211,13 +208,67 @@ def test_upload_completions(self):
211208
hook = FsspecUploadHook(
212209
base_path=BASE_PATH,
213210
)
211+
tracer = self.tracer_provider.get_tracer(__name__)
212+
log_record = LogRecord()
213+
214+
with tracer.start_as_current_span("chat mymodel") as span:
215+
hook.upload(
216+
inputs=FAKE_INPUTS,
217+
outputs=FAKE_OUTPUTS,
218+
system_instruction=FAKE_SYSTEM_INSTRUCTION,
219+
span=span,
220+
log_record=log_record,
221+
)
222+
hook.shutdown()
223+
224+
finished_spans = self.get_finished_spans()
225+
self.assertEqual(len(finished_spans), 1)
226+
span = finished_spans[0]
227+
228+
# span attributes, log attributes, and log body have refs
229+
for attributes in [
230+
span.attributes,
231+
log_record.attributes,
232+
]:
233+
for ref_key in [
234+
"gen_ai.input.messages_ref",
235+
"gen_ai.output.messages_ref",
236+
"gen_ai.system_instructions_ref",
237+
]:
238+
self.assertIn(ref_key, attributes)
239+
240+
self.assert_fsspec_equal(
241+
span.attributes["gen_ai.input.messages_ref"],
242+
'[{"role":"user","parts":[{"content":"What is the capital of France?","type":"text"}]}]',
243+
)
244+
self.assert_fsspec_equal(
245+
span.attributes["gen_ai.output.messages_ref"],
246+
'[{"role":"assistant","parts":[{"content":"Paris","type":"text"}],"finish_reason":"stop"}]',
247+
)
248+
self.assert_fsspec_equal(
249+
span.attributes["gen_ai.system_instructions_ref"],
250+
'[{"content":"You are a helpful assistant.","type":"text"}]',
251+
)
252+
253+
@staticmethod
254+
def upload_with_log(log_record: LogRecord):
255+
hook = FsspecUploadHook(
256+
base_path=BASE_PATH,
257+
)
258+
214259
hook.upload(
215260
inputs=FAKE_INPUTS,
216261
outputs=FAKE_OUTPUTS,
217262
system_instruction=FAKE_SYSTEM_INSTRUCTION,
263+
log_record=log_record,
218264
)
219265
hook.shutdown()
220266

221-
fs = fsspec.open(BASE_PATH).fs
222-
self.assertEqual(len(fs.ls(BASE_PATH)), 3)
223-
# TODO: test stamped telemetry
267+
def test_stamps_empty_log(self):
268+
log_record = LogRecord()
269+
self.upload_with_log(log_record)
270+
271+
# stamp on both body and attributes
272+
self.assertIn("gen_ai.input.messages_ref", log_record.attributes)
273+
self.assertIn("gen_ai.output.messages_ref", log_record.attributes)
274+
self.assertIn("gen_ai.system_instructions_ref", log_record.attributes)

0 commit comments

Comments
 (0)