Skip to content

Commit 3518bda

Browse files
feat: adopt OpenTelemetry semantic conventions for GenAI agents and frameworks
- Fix gen_ai.system value to use 'gcp.vertex_ai' instead of 'gcp.vertex.agent' - Move custom attributes to gcp.vertex.agent.* vendor-specific namespace - Add missing standard GenAI attributes (response.id, response.model, request.temperature) - Add gen_ai.operation.name for all operations (generate_content, execute_tool) - Update gen_ai.response.finish_reasons to use array format - Add utility functions for GenAI events and span naming - Update tests and web server to use new attribute names Aligns with OpenTelemetry GenAI semantic conventions v1.36.0
1 parent ecaa7b4 commit 3518bda

File tree

2 files changed

+79
-11
lines changed

2 files changed

+79
-11
lines changed

src/google/adk/telemetry.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,13 @@ def trace_tool_call(
7070
function_response_event: The event with the function response details.
7171
"""
7272
span = trace.get_current_span()
73-
span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
73+
74+
# Standard OpenTelemetry GenAI attributes as of OTel SemConv v1.36.0 for Agents and Frameworks
75+
span.set_attribute('gen_ai.system', 'gcp.vertex_ai')
7476
span.set_attribute('gen_ai.operation.name', 'execute_tool')
7577
span.set_attribute('gen_ai.tool.name', tool.name)
7678
span.set_attribute('gen_ai.tool.description', tool.description)
79+
7780
tool_call_id = '<not specified>'
7881
tool_response = '<not specified>'
7982
if function_response_event.content.parts:
@@ -86,6 +89,7 @@ def trace_tool_call(
8689

8790
span.set_attribute('gen_ai.tool.call.id', tool_call_id)
8891

92+
# Vendor-specific attributes (moved from gen_ai.* to gcp.vertex.agent.*)
8993
if not isinstance(tool_response, dict):
9094
tool_response = {'result': tool_response}
9195
span.set_attribute(
@@ -121,12 +125,15 @@ def trace_merged_tool_calls(
121125
"""
122126

123127
span = trace.get_current_span()
124-
span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
128+
129+
# Standard OpenTelemetry GenAI attributes
130+
span.set_attribute('gen_ai.system', 'gcp.vertex_ai')
125131
span.set_attribute('gen_ai.operation.name', 'execute_tool')
126132
span.set_attribute('gen_ai.tool.name', '(merged tools)')
127133
span.set_attribute('gen_ai.tool.description', '(merged tools)')
128134
span.set_attribute('gen_ai.tool.call.id', response_event_id)
129135

136+
# Vendor-specific attributes
130137
span.set_attribute('gcp.vertex.agent.tool_call_args', 'N/A')
131138
span.set_attribute('gcp.vertex.agent.event_id', response_event_id)
132139
try:
@@ -167,23 +174,37 @@ def trace_call_llm(
167174
llm_response: The LLM response object.
168175
"""
169176
span = trace.get_current_span()
170-
# Special standard Open Telemetry GenaI attributes that indicate
171-
# that this is a span related to a Generative AI system.
172-
span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
177+
178+
# Standard OpenTelemetry GenAI attributes
179+
span.set_attribute('gen_ai.system', 'gcp.vertex_ai')
173180
span.set_attribute('gen_ai.request.model', llm_request.model)
181+
182+
if hasattr(llm_response, 'id') and llm_response.id:
183+
span.set_attribute('gen_ai.response.id', llm_response.id)
184+
185+
# Set response model if different from request model
186+
if (
187+
hasattr(llm_response, 'model')
188+
and llm_response.model
189+
and llm_response.model != llm_request.model
190+
):
191+
span.set_attribute('gen_ai.response.model', llm_response.model)
192+
174193
span.set_attribute(
175194
'gcp.vertex.agent.invocation_id', invocation_context.invocation_id
176195
)
177196
span.set_attribute(
178197
'gcp.vertex.agent.session_id', invocation_context.session.id
179198
)
180199
span.set_attribute('gcp.vertex.agent.event_id', event_id)
200+
181201
# Consider removing once GenAI SDK provides a way to record this info.
182202
span.set_attribute(
183203
'gcp.vertex.agent.llm_request',
184204
_safe_json_serialize(_build_llm_request_for_trace(llm_request)),
185205
)
186-
# Consider removing once GenAI SDK provides a way to record this info.
206+
207+
# Standard GenAI request attributes
187208
if llm_request.config:
188209
if llm_request.config.top_p:
189210
span.set_attribute(
@@ -195,6 +216,14 @@ def trace_call_llm(
195216
'gen_ai.request.max_tokens',
196217
llm_request.config.max_output_tokens,
197218
)
219+
if (
220+
hasattr(llm_request.config, 'temperature')
221+
and llm_request.config.temperature is not None
222+
):
223+
span.set_attribute(
224+
'gen_ai.request.temperature',
225+
llm_request.config.temperature,
226+
)
198227

199228
try:
200229
llm_response_json = llm_response.model_dump_json(exclude_none=True)
@@ -206,6 +235,7 @@ def trace_call_llm(
206235
llm_response_json,
207236
)
208237

238+
# Standard GenAI usage and response attributes
209239
if llm_response.usage_metadata is not None:
210240
span.set_attribute(
211241
'gen_ai.usage.input_tokens',
@@ -286,3 +316,41 @@ def _build_llm_request_for_trace(llm_request: LlmRequest) -> dict[str, Any]:
286316
)
287317
)
288318
return result
319+
320+
321+
def _create_span_name(operation_name: str, model_name: str) -> str:
322+
"""Creates a span name following OpenTelemetry GenAI conventions.
323+
324+
Args:
325+
operation_name: The GenAI operation name (e.g., 'generate_content', 'execute_tool').
326+
model_name: The model name being used.
327+
328+
Returns:
329+
A span name in the format '{operation_name} {model_name}'.
330+
"""
331+
return f'{operation_name} {model_name}'
332+
333+
334+
def add_genai_prompt_event(span: trace.Span, prompt_content: str):
335+
"""Adds a GenAI prompt event to the span following OpenTelemetry conventions.
336+
337+
Args:
338+
span: The OpenTelemetry span to add the event to.
339+
prompt_content: The prompt content as a JSON string.
340+
"""
341+
span.add_event(
342+
name='gen_ai.content.prompt', attributes={'gen_ai.prompt': prompt_content}
343+
)
344+
345+
346+
def add_genai_completion_event(span: trace.Span, completion_content: str):
347+
"""Adds a GenAI completion event to the span following OpenTelemetry conventions.
348+
349+
Args:
350+
span: The OpenTelemetry span to add the event to.
351+
completion_content: The completion content as a JSON string.
352+
"""
353+
span.add_event(
354+
name='gen_ai.content.completion',
355+
attributes={'gen_ai.completion': completion_content},
356+
)

tests/unittests/test_telemetry.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ async def test_trace_call_llm(monkeypatch, mock_span_fixture):
114114
trace_call_llm(invocation_context, 'test_event_id', llm_request, llm_response)
115115

116116
expected_calls = [
117-
mock.call('gen_ai.system', 'gcp.vertex.agent'),
117+
mock.call('gen_ai.system', 'gcp.vertex_ai'),
118118
mock.call('gen_ai.request.top_p', 0.95),
119119
mock.call('gen_ai.request.max_tokens', 1024),
120120
mock.call('gen_ai.usage.input_tokens', 50),
@@ -173,7 +173,7 @@ async def test_trace_call_llm_with_binary_content(
173173

174174
# Verify basic telemetry attributes are set
175175
expected_calls = [
176-
mock.call('gen_ai.system', 'gcp.vertex.agent'),
176+
mock.call('gen_ai.system', 'gcp.vertex_ai'),
177177
]
178178
assert mock_span_fixture.set_attribute.call_count == 7
179179
mock_span_fixture.set_attribute.assert_has_calls(expected_calls)
@@ -230,7 +230,7 @@ def test_trace_tool_call_with_scalar_response(
230230
# Assert
231231
assert mock_span_fixture.set_attribute.call_count == 10
232232
expected_calls = [
233-
mock.call('gen_ai.system', 'gcp.vertex.agent'),
233+
mock.call('gen_ai.system', 'gcp.vertex_ai'),
234234
mock.call('gen_ai.operation.name', 'execute_tool'),
235235
mock.call('gen_ai.tool.name', mock_tool_fixture.name),
236236
mock.call('gen_ai.tool.description', mock_tool_fixture.description),
@@ -289,7 +289,7 @@ def test_trace_tool_call_with_dict_response(
289289

290290
# Assert
291291
expected_calls = [
292-
mock.call('gen_ai.system', 'gcp.vertex.agent'),
292+
mock.call('gen_ai.system', 'gcp.vertex_ai'),
293293
mock.call('gen_ai.operation.name', 'execute_tool'),
294294
mock.call('gen_ai.tool.name', mock_tool_fixture.name),
295295
mock.call('gen_ai.tool.description', mock_tool_fixture.description),
@@ -328,7 +328,7 @@ def test_trace_merged_tool_calls_sets_correct_attributes(
328328
)
329329

330330
expected_calls = [
331-
mock.call('gen_ai.system', 'gcp.vertex.agent'),
331+
mock.call('gen_ai.system', 'gcp.vertex_ai'),
332332
mock.call('gen_ai.operation.name', 'execute_tool'),
333333
mock.call('gen_ai.tool.name', '(merged tools)'),
334334
mock.call('gen_ai.tool.description', '(merged tools)'),

0 commit comments

Comments
 (0)