Skip to content

Commit a7c6e63

Browse files
committed
fix(genai-utils): move get_content_attributes to _invocation.py, add test coverage
1 parent fd08416 commit a7c6e63

5 files changed

Lines changed: 217 additions & 116 deletions

File tree

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
)
2323
from opentelemetry.semconv.attributes import server_attributes
2424
from opentelemetry.trace import SpanKind, Tracer
25-
from opentelemetry.util.genai._content import _get_content_attributes
26-
from opentelemetry.util.genai._invocation import Error, GenAIInvocation
25+
from opentelemetry.util.genai._invocation import (
26+
Error,
27+
GenAIInvocation,
28+
get_content_attributes,
29+
)
2730
from opentelemetry.util.genai.metrics import InvocationMetricsRecorder
2831
from opentelemetry.util.genai.types import (
2932
InputMessage,
@@ -32,6 +35,17 @@
3235
ToolDefinition,
3336
)
3437

38+
_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS: str = getattr(
39+
GenAI,
40+
"GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS",
41+
"gen_ai.usage.cache_creation.input_tokens",
42+
)
43+
_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS: str = getattr(
44+
GenAI,
45+
"GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS",
46+
"gen_ai.usage.cache_read.input_tokens",
47+
)
48+
3549

3650
class AgentInvocation(GenAIInvocation):
3751
"""Represents a single agent invocation (invoke_agent span).
@@ -143,18 +157,18 @@ def _get_usage_attributes(self) -> dict[str, Any]:
143157
(GenAI.GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens),
144158
(GenAI.GEN_AI_USAGE_OUTPUT_TOKENS, self.output_tokens),
145159
(
146-
GenAI.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
160+
_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
147161
self.cache_creation_input_tokens,
148162
),
149163
(
150-
GenAI.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
164+
_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
151165
self.cache_read_input_tokens,
152166
),
153167
)
154168
return {k: v for k, v in optional_attrs if v is not None}
155169

156170
def _get_content_attributes_for_span(self) -> dict[str, Any]:
157-
return _get_content_attributes(
171+
return get_content_attributes(
158172
input_messages=self.input_messages,
159173
output_messages=self.output_messages,
160174
system_instruction=self.system_instruction,

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

Lines changed: 0 additions & 102 deletions
This file was deleted.

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
)
2424
from opentelemetry.semconv.attributes import server_attributes
2525
from opentelemetry.trace import INVALID_SPAN, Span, SpanKind, Tracer
26-
from opentelemetry.util.genai._content import _get_content_attributes
27-
from opentelemetry.util.genai._invocation import Error, GenAIInvocation
26+
from opentelemetry.util.genai._invocation import (
27+
Error,
28+
GenAIInvocation,
29+
get_content_attributes,
30+
)
2831
from opentelemetry.util.genai.metrics import InvocationMetricsRecorder
2932
from opentelemetry.util.genai.types import (
3033
InputMessage,
@@ -37,6 +40,17 @@
3740
should_emit_event,
3841
)
3942

43+
_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS: str = getattr(
44+
GenAI,
45+
"GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS",
46+
"gen_ai.usage.cache_creation.input_tokens",
47+
)
48+
_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS: str = getattr(
49+
GenAI,
50+
"GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS",
51+
"gen_ai.usage.cache_read.input_tokens",
52+
)
53+
4054

4155
class InferenceInvocation(GenAIInvocation):
4256
"""Represents a single LLM chat/completion call.
@@ -118,7 +132,7 @@ def __init__( # pylint: disable=too-many-locals
118132
self._start()
119133

120134
def _get_message_attributes(self, *, for_span: bool) -> dict[str, Any]:
121-
return _get_content_attributes(
135+
return get_content_attributes(
122136
input_messages=self.input_messages,
123137
output_messages=self.output_messages,
124138
system_instruction=self.system_instruction,
@@ -166,11 +180,11 @@ def _get_attributes(self) -> dict[str, Any]:
166180
(GenAI.GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens),
167181
(GenAI.GEN_AI_USAGE_OUTPUT_TOKENS, self.output_tokens),
168182
(
169-
GenAI.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
183+
_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
170184
self.cache_creation_input_tokens,
171185
),
172186
(
173-
GenAI.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
187+
_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
174188
self.cache_read_input_tokens,
175189
),
176190
)

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

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,33 @@
1818
from abc import ABC, abstractmethod
1919
from contextlib import contextmanager
2020
from contextvars import Token
21-
from typing import TYPE_CHECKING, Any, Iterator
21+
from dataclasses import asdict
22+
from typing import TYPE_CHECKING, Any, Iterator, Sequence
2223

2324
from typing_extensions import Self, TypeAlias
2425

2526
from opentelemetry._logs import Logger
2627
from opentelemetry.context import Context, attach, detach
28+
from opentelemetry.semconv._incubating.attributes import (
29+
gen_ai_attributes as GenAI,
30+
)
2731
from opentelemetry.semconv.attributes import error_attributes
2832
from opentelemetry.trace import INVALID_SPAN as _INVALID_SPAN
2933
from opentelemetry.trace import Span, SpanKind, Tracer, set_span_in_context
3034
from opentelemetry.trace.status import Status, StatusCode
31-
from opentelemetry.util.genai.types import Error
35+
from opentelemetry.util.genai.types import (
36+
Error,
37+
InputMessage,
38+
MessagePart,
39+
OutputMessage,
40+
ToolDefinition,
41+
)
42+
from opentelemetry.util.genai.utils import (
43+
ContentCapturingMode,
44+
gen_ai_json_dumps,
45+
get_content_capturing_mode,
46+
is_experimental_mode,
47+
)
3248

3349
if TYPE_CHECKING:
3450
from opentelemetry.util.genai.metrics import InvocationMetricsRecorder
@@ -138,3 +154,64 @@ def _managed(self) -> Iterator[Self]:
138154
self.fail(exc)
139155
raise
140156
self.stop()
157+
158+
159+
def get_content_attributes(
160+
*,
161+
input_messages: Sequence[InputMessage],
162+
output_messages: Sequence[OutputMessage],
163+
system_instruction: Sequence[MessagePart],
164+
tool_definitions: Sequence[ToolDefinition] | None,
165+
for_span: bool,
166+
) -> dict[str, Any]:
167+
"""Serialize messages, system instructions, and tool definitions into attributes.
168+
169+
Args:
170+
input_messages: Input messages to serialize.
171+
output_messages: Output messages to serialize.
172+
system_instruction: System instructions to serialize.
173+
tool_definitions: Tool definitions to serialize (may be None).
174+
for_span: If True, serialize for span attributes (JSON string);
175+
if False, serialize for event attributes (list of dicts).
176+
"""
177+
if not is_experimental_mode():
178+
return {}
179+
180+
mode = get_content_capturing_mode()
181+
allowed_modes = (
182+
(
183+
ContentCapturingMode.SPAN_ONLY,
184+
ContentCapturingMode.SPAN_AND_EVENT,
185+
)
186+
if for_span
187+
else (
188+
ContentCapturingMode.EVENT_ONLY,
189+
ContentCapturingMode.SPAN_AND_EVENT,
190+
)
191+
)
192+
if mode not in allowed_modes:
193+
return {}
194+
195+
def serialize(items: Sequence[Any]) -> Any:
196+
dicts = [asdict(item) for item in items]
197+
return gen_ai_json_dumps(dicts) if for_span else dicts
198+
199+
optional_attrs = (
200+
(
201+
GenAI.GEN_AI_INPUT_MESSAGES,
202+
serialize(input_messages) if input_messages else None,
203+
),
204+
(
205+
GenAI.GEN_AI_OUTPUT_MESSAGES,
206+
serialize(output_messages) if output_messages else None,
207+
),
208+
(
209+
GenAI.GEN_AI_SYSTEM_INSTRUCTIONS,
210+
serialize(system_instruction) if system_instruction else None,
211+
),
212+
(
213+
GenAI.GEN_AI_TOOL_DEFINITIONS,
214+
serialize(tool_definitions) if tool_definitions else None,
215+
),
216+
)
217+
return {key: value for key, value in optional_attrs if value is not None}

0 commit comments

Comments
 (0)