Skip to content

Commit eaca532

Browse files
authored
Remove remaining usage/references to deprecated functions in opentelemetry-util-genai. (#47)
* Migrate google GenAi instrumentation to use the GenAi utils package * Apply fixes * Upade changelog and add assertion to test.. * Fix tests, tool argument / result serialization * Remove some code moved to utils * Add comments explaining serialization * Fix tests and merge main * update changelog * Fix broken CI * Remove all deprecated functions in genai utils. Remove last usages of deprecated functions * Update some docstring comments * Clean up google genai code a bit.. * Add changelog * Remove test deps.. * Add deleted functions back * Respond to comments * Fix broken test.. * Delete duplicated funcs.. * Fix more docstrings
1 parent 563a32a commit eaca532

12 files changed

Lines changed: 82 additions & 103 deletions

File tree

instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/patch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def _create_invocation(
114114
)
115115

116116
server_address, server_port = get_server_address_and_port(instance)
117-
invocation = handler.start_inference(
117+
invocation = handler.inference(
118118
provider=ANTHROPIC,
119119
request_model=request_model,
120120
server_address=server_address,

instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/patch_responses.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def responses_create(handler: TelemetryHandler):
2323

2424
def traced_method(wrapped, instance, args, kwargs):
2525
params = extract_params(**kwargs)
26-
invocation = handler.start_inference(
26+
invocation = handler.inference(
2727
**get_inference_creation_kwargs(params, instance)
2828
)
2929
apply_request_attributes(invocation, params, capture_content)

instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def create_chat_invocation(
352352
# pylint: disable=too-many-branches
353353

354354
address, port = get_server_address_and_port(client_instance)
355-
invocation = handler.start_inference(
355+
invocation = handler.inference(
356356
GenAIAttributes.GenAiProviderNameValues.OPENAI.value,
357357
request_model=kwargs.get("model", ""),
358358
server_address=address if address else None,

instrumentation/opentelemetry-instrumentation-google-genai/src/opentelemetry/instrumentation/google_genai/generate_content.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,7 @@ async def instrumented_generate_content_stream(
11521152
)
11531153
)
11541154
if helper.experimental_sem_convs_enabled:
1155-
invocation = telemetry_handler.start_inference(
1155+
invocation = telemetry_handler.inference(
11561156
provider=helper._genai_system,
11571157
request_model=model,
11581158
operation_name="generate_content",
@@ -1161,13 +1161,14 @@ async def instrumented_generate_content_stream(
11611161
invocation.tool_definitions = (
11621162
await helper._maybe_get_tool_definitions_async(config)
11631163
)
1164-
invocation.input_messages = to_input_messages(
1165-
contents=transformers.t_contents(contents)
1166-
)
1167-
if system_content := _config_to_system_instruction(config):
1168-
invocation.system_instruction = to_system_instructions(
1169-
content=transformers.t_contents(system_content)[0]
1164+
if helper._content_recording_enabled:
1165+
invocation.input_messages = to_input_messages(
1166+
contents=transformers.t_contents(contents)
11701167
)
1168+
if system_content := _config_to_system_instruction(config):
1169+
invocation.system_instruction = to_system_instructions(
1170+
content=transformers.t_contents(system_content)[0]
1171+
)
11711172

11721173
async def _response_async_generator_wrapper():
11731174
candidates = []

instrumentation/opentelemetry-instrumentation-google-genai/src/opentelemetry/instrumentation/google_genai/tool_call_wrapper.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ def _to_otel_value(python_value):
3838
return repr(python_value)
3939

4040

41-
# There is non canonical way to serialize a Python object to a span attribute value.
42-
# Span attribute values currently most be one of the primitive types, or a homogeneous list of primitive types.
43-
# In the future the value will be expanded to include None, a heterogeneous lists of primitive types, and a Map of these types.
41+
# There is no canonical way to serialize a Python object to a span attribute value.
42+
# Span attribute values currently must be one of the primitive types, or a homogeneous list of primitive types.
43+
# In the future the value will be expanded to include None, heterogeneous lists of primitive types, and a Map of these types.
4444
# See https://github.com/open-telemetry/opentelemetry-specification/pull/4485
4545
def _get_function_args(wrapped_function, function_args, function_kwargs):
4646
"""Records the details about a function invocation as span attributes."""
@@ -75,34 +75,42 @@ def _wrap_tool_function(
7575

7676
@functools.wraps(tool_function)
7777
async def wrapped_function(*args, **kwargs):
78+
# Always json.dumps. First we convert args / result to something that we can serialize, then we serialize.
79+
# The return value of _to_otel_value could be a dict, which currently cannot be a span attribute..
80+
# In the future that could change (see https://github.com/open-telemetry/opentelemetry-specification/pull/4485), and we could possibly stop using json.dumps here.
7881
with telemetry_handler.tool(
79-
tool_function.__name__, tool_description=tool_function.__doc__
82+
tool_function.__name__,
83+
tool_description=tool_function.__doc__,
8084
) as tool_invocation:
85+
# Do this before calling the tool in case that crashes.
86+
if tool_invocation.should_capture_content_on_span:
87+
tool_invocation.arguments = json.dumps(
88+
_get_function_args(tool_function, args, kwargs)
89+
)
8190
result = await tool_function(*args, **kwargs)
82-
# Always json.dumps. First we convert args / result to something that we can serialize, then we serialize.
83-
# The return value of _to_otel_value could be a dict, which currently cannot be a span attribute..
84-
# In the future that could change (see https://github.com/open-telemetry/opentelemetry-specification/pull/4485), and we could possibly stop using json.dumps here.
85-
tool_invocation.arguments = json.dumps(
86-
_get_function_args(tool_function, args, kwargs)
87-
)
88-
tool_invocation.tool_result = json.dumps(
89-
_to_otel_value(result)
90-
)
91+
if tool_invocation.should_capture_content_on_span:
92+
tool_invocation.tool_result = json.dumps(
93+
_to_otel_value(result)
94+
)
9195
return result
9296
else:
9397

9498
@functools.wraps(tool_function)
9599
def wrapped_function(*args, **kwargs):
96100
with telemetry_handler.tool(
97-
tool_function.__name__, tool_description=tool_function.__doc__
101+
tool_function.__name__,
102+
tool_description=tool_function.__doc__,
98103
) as tool_invocation:
104+
# Do this before calling the tool in case that crashes.
105+
if tool_invocation.should_capture_content_on_span:
106+
tool_invocation.arguments = json.dumps(
107+
_get_function_args(tool_function, args, kwargs)
108+
)
99109
result = tool_function(*args, **kwargs)
100-
tool_invocation.arguments = json.dumps(
101-
_get_function_args(tool_function, args, kwargs)
102-
)
103-
tool_invocation.tool_result = json.dumps(
104-
_to_otel_value(result)
105-
)
110+
if tool_invocation.should_capture_content_on_span:
111+
tool_invocation.tool_result = json.dumps(
112+
_to_otel_value(result)
113+
)
106114
return result
107115

108116
return wrapped_function

instrumentation/opentelemetry-instrumentation-google-genai/tests/utils/test_tool_call_wrapper.py

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def somefunction():
136136
"An example tool call function.",
137137
)
138138

139+
# Capture content must be enabled to get arguments
139140
@patch.dict(
140141
"os.environ",
141142
{
@@ -165,6 +166,7 @@ def somefunction(
165166
self.assertEqual(
166167
arguments["code.function.parameters.primitive_int.type"], "int"
167168
)
169+
self.assertEqual(span.attributes["gen_ai.tool.name"], "somefunction")
168170
self.assertEqual(
169171
arguments["code.function.parameters.primitive_int.value"], 12345
170172
)
@@ -190,54 +192,32 @@ def somefunction(
190192
[123, "abc"],
191193
)
192194

193-
def test_handle_with_different_capture_content_on_span_config(self):
195+
@patch.dict(
196+
"os.environ",
197+
{
198+
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "false",
199+
"OTEL_SEMCONV_STABILITY_OPT_IN": "default",
200+
},
201+
)
202+
def test_with_capture_content_disabled(self):
203+
_OpenTelemetrySemanticConventionStability._initialized = False
204+
_OpenTelemetrySemanticConventionStability._initialize()
205+
194206
def somefunction(arg=None):
195207
return arg
196208

197-
for capture_content_on_span in ["SPAN_AND_EVENT", "NO_CONTENT"]:
198-
patched_environ = patch.dict(
199-
"os.environ",
200-
{
201-
"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": capture_content_on_span,
202-
"OTEL_SEMCONV_STABILITY_OPT_IN": "gen_ai_latest_experimental",
203-
},
204-
)
205-
with patched_environ:
206-
_OpenTelemetrySemanticConventionStability._initialized = False
207-
_OpenTelemetrySemanticConventionStability._initialize()
208-
self.setUp()
209-
wrapped_somefunction = self.wrap(somefunction)
210-
wrapped_somefunction("a string value")
211-
span = self.otel.get_span_named("execute_tool somefunction")
212-
self.assertEqual(
213-
span.attributes["gen_ai.tool.name"], "somefunction"
214-
)
215-
216-
if capture_content_on_span == "NO_CONTENT":
217-
self.assertNotIn(
218-
"gen_ai.tool.call.arguments",
219-
span.attributes,
220-
)
221-
self.assertNotIn(
222-
"gen_ai.tool.call.result",
223-
span.attributes,
224-
)
225-
else:
226-
self.assertEqual(
227-
span.attributes["gen_ai.tool.call.result"],
228-
'"a string value"',
229-
)
230-
arguments = json.loads(
231-
span.attributes["gen_ai.tool.call.arguments"]
232-
)
233-
self.assertEqual(
234-
arguments["code.function.parameters.arg.type"], "str"
235-
)
236-
self.assertEqual(
237-
arguments["code.function.parameters.arg.value"],
238-
"a string value",
239-
)
240-
self.tearDown()
209+
wrapped_somefunction = self.wrap(somefunction)
210+
wrapped_somefunction("a string value")
211+
span = self.otel.get_span_named("execute_tool somefunction")
212+
213+
self.assertNotIn(
214+
"gen_ai.tool.call.arguments",
215+
span.attributes,
216+
)
217+
self.assertNotIn(
218+
"gen_ai.tool.call.result",
219+
span.attributes,
220+
)
241221

242222
def test_function_that_throws_exception(self):
243223
def somefunction(arg=None):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove remaining usages of and references to deprecated functions

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def _maybe_create_event(self) -> LogRecord | None:
224224
class LLMInvocation:
225225
"""Deprecated. Use InferenceInvocation instead.
226226
227-
Data container for an LLM invocation. Pass to handler.start_llm() to start
227+
Data container for an LLM invocation. Pass to handler.llm() to start
228228
the span, then update fields and call handler.stop_llm() or handler.fail_llm().
229229
"""
230230

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ class GenAIInvocation(AbstractContextManager["GenAIInvocation"]):
4949
Base class for all GenAI invocation types. Manages the lifecycle of a single
5050
GenAI operation (LLM call, embedding, tool execution, workflow, etc.).
5151
52-
Use the factory methods on TelemetryHandler (start_inference, start_embedding,
53-
start_workflow, start_tool) rather than constructing invocations directly.
52+
Use the factory methods on TelemetryHandler (inference, embedding,
53+
workflow, tool) rather than constructing invocations directly.
5454
"""
5555

5656
def __init__(

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

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ToolInvocation(GenAIInvocation):
2424
2525
Not used as a message part — use ToolCallRequest for that purpose.
2626
27-
Use handler.start_tool(name) rather than constructing this directly.
27+
Use handler.tool(name) rather than constructing this directly.
2828
2929
Reference: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md#execute-tool-span
3030
@@ -47,13 +47,11 @@ def __init__(
4747
completion_hook: CompletionHook,
4848
name: str,
4949
*,
50-
arguments: AttributeValue | None = None,
5150
tool_call_id: str | None = None,
5251
tool_type: str | None = None,
5352
tool_description: str | None = None,
54-
tool_result: AttributeValue | None = None,
5553
) -> None:
56-
"""Use handler.start_tool(name) or handler.tool(name) instead of calling this directly."""
54+
"""Use handler.tool(name) instead of calling this directly."""
5755
_operation_name = GenAI.GenAiOperationNameValues.EXECUTE_TOOL.value
5856
super().__init__(
5957
tracer,
@@ -65,8 +63,12 @@ def __init__(
6563
)
6664
self.should_capture_content_on_span = should_capture_content_on_spans()
6765
self.name = name
68-
self.tool_result = tool_result
69-
self.arguments = arguments
66+
self.tool_result: AttributeValue | None = None
67+
# Since arguments and tool_result can be expensive to serialize,
68+
# it's recommended to check the content capture flag in the
69+
# instrumentation library before assigning these attributes
70+
# to the invocation.
71+
self.arguments: AttributeValue | None = None
7072
self.tool_call_id = tool_call_id
7173
self.tool_type = tool_type
7274
self.tool_description = tool_description
@@ -79,18 +81,6 @@ def _get_base_attributes(self) -> dict[str, Any]:
7981
(GenAI.GEN_AI_TOOL_CALL_ID, self.tool_call_id),
8082
(GenAI.GEN_AI_TOOL_TYPE, self.tool_type),
8183
(GenAI.GEN_AI_TOOL_DESCRIPTION, self.tool_description),
82-
(
83-
GenAI.GEN_AI_TOOL_CALL_ARGUMENTS,
84-
self.arguments
85-
if self.should_capture_content_on_span
86-
else None,
87-
),
88-
(
89-
GenAI.GEN_AI_TOOL_CALL_RESULT,
90-
self.tool_result
91-
if self.should_capture_content_on_span
92-
else None,
93-
),
9484
)
9585
return {
9686
GenAI.GEN_AI_OPERATION_NAME: self._operation_name,

0 commit comments

Comments
 (0)