Skip to content

Commit b647d1f

Browse files
fix(google-genai): drop gen_ai.request.stream from fetch_response spans
The attribute is not part of the fetch_response span in the GenAI semantic conventions; a resumed fetch is identified by gen_ai.request.stream_cursor. Matches the same removal made for the OpenAI package in open-telemetry#709. Also name the changelog fragment after the PR and sort imports. Assisted-by: Claude Opus 5
1 parent 53f5de2 commit b647d1f

4 files changed

Lines changed: 30 additions & 23 deletions

File tree

instrumentation/opentelemetry-instrumentation-google-genai/.changelog/PR_NUMBER.added renamed to instrumentation/opentelemetry-instrumentation-google-genai/.changelog/755.added

File renamed without changes.

‎instrumentation/opentelemetry-instrumentation-google-genai/src/opentelemetry/instrumentation/google_genai/interactions.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1172,10 +1172,11 @@ def _start_fetch_invocation(
11721172
else GenAIAttributes.GenAiSystemValues.GEMINI.value
11731173
)
11741174
streaming = bool(kwargs.get("stream"))
1175+
# gen_ai.request.stream is not part of the fetch_response span's attributes;
1176+
# a resumed fetch is identified by gen_ai.request.stream_cursor instead.
11751177
invocation = telemetry_handler.fetch_response(
11761178
provider=provider,
11771179
response_id=interaction_id,
1178-
request_stream=streaming or None,
11791180
server_address=server_address,
11801181
error_type_resolver=resolve_error_type,
11811182
)

‎instrumentation/opentelemetry-instrumentation-google-genai/tests/interactions/fetch_base.py‎

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@
1414
)
1515

1616
from ..common.base import TestCase as CommonTestCaseBase
17-
from .base import _HAS_INTERACTIONS, AsyncInteractionsResource, InteractionsResource
17+
from .base import (
18+
_HAS_INTERACTIONS,
19+
AsyncInteractionsResource,
20+
InteractionsResource,
21+
)
1822
from .util import (
1923
FakeAsyncStream,
2024
FakeStream,
25+
create_mock_content_event,
2126
create_mock_error_event,
27+
create_mock_fetched_interaction,
28+
create_mock_sse_completed_event,
2229
create_mock_step_event,
2330
parse_sse_events,
24-
create_mock_sse_completed_event,
25-
create_mock_content_event,
26-
create_mock_fetched_interaction,
2731
)
2832

2933
_SPAN_NAME = "fetch_response"
@@ -239,9 +243,7 @@ def _result(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
239243
"index": 0,
240244
"step": {
241245
"type": "model_output",
242-
"content": [
243-
{"type": "text", "text": "Hello"}
244-
],
246+
"content": [{"type": "text", "text": "Hello"}],
245247
},
246248
},
247249
{
@@ -357,7 +359,9 @@ def _result(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
357359
interaction.steps = None
358360
interaction.output_text = None
359361
return [
360-
create_mock_step_event("step.start", 0, step_type="thought"),
362+
create_mock_step_event(
363+
"step.start", 0, step_type="thought"
364+
),
361365
create_mock_step_event("step.delta", 0, text=None),
362366
create_mock_step_event(
363367
"step.start", 1, step_type="model_output"
@@ -408,7 +412,9 @@ def run_streaming_fetch_with_caller_error(
408412
) -> None:
409413
raise NotImplementedError()
410414

411-
def run_streaming_fetch_closed_early(self, *args: Any, **kwargs: Any) -> Any:
415+
def run_streaming_fetch_closed_early(
416+
self, *args: Any, **kwargs: Any
417+
) -> Any:
412418
raise NotImplementedError()
413419

414420
def drain_stream(self, *args: Any, **kwargs: Any) -> Any:
@@ -465,7 +471,9 @@ def test_response_model_and_status_recorded(self) -> None:
465471
self.assertEqual(
466472
span.attributes["gen_ai.response.model"], "gemini-2.5-pro"
467473
)
468-
self.assertEqual(span.attributes["gen_ai.response.status"], "completed")
474+
self.assertEqual(
475+
span.attributes["gen_ai.response.status"], "completed"
476+
)
469477
self.assertEqual(
470478
span.attributes["gen_ai.response.finish_reasons"], ("stop",)
471479
)
@@ -645,8 +653,12 @@ def test_streaming_fetch_generates_span(self) -> None:
645653
self.assertEqual(events[1].interaction.id, "stream-id-1")
646654
span = self.otel.get_span_named(_SPAN_NAME)
647655
self.assertEqual(span.attributes["gen_ai.response.id"], "stream-id-1")
648-
self.assertIs(span.attributes["gen_ai.request.stream"], True)
649-
self.assertEqual(span.attributes["gen_ai.response.status"], "completed")
656+
self.assertEqual(
657+
span.attributes["gen_ai.response.status"], "completed"
658+
)
659+
# fetch_response has no gen_ai.request.stream attribute; only a resumed
660+
# fetch is distinguished, by its cursor.
661+
self.assertNotIn("gen_ai.request.stream", span.attributes)
650662
self.assertNotIn("gen_ai.request.stream_cursor", span.attributes)
651663

652664
def test_resumed_streaming_fetch_records_cursor(self) -> None:
@@ -693,9 +705,7 @@ def test_mid_stream_error_records_error(self) -> None:
693705
def test_caller_side_error_records_error_and_closes_stream(self) -> None:
694706
self.configure_valid_fetch()
695707
with self.assertRaises(RuntimeError):
696-
self.run_streaming_fetch_with_caller_error(
697-
"abc-123", stream=True
698-
)
708+
self.run_streaming_fetch_with_caller_error("abc-123", stream=True)
699709
span = self.otel.get_span_named(_SPAN_NAME)
700710
self.assertEqual(span.attributes["error.type"], "RuntimeError")
701711
self.assertTrue(self._last_stream.closed)
@@ -728,9 +738,7 @@ def test_in_band_stream_error_fails_the_span(self) -> None:
728738
events = self.run_streaming_fetch("abc-123", stream=True)
729739
self.assertEqual(len(events), 2)
730740
span = self.otel.get_span_named(_SPAN_NAME)
731-
self.assertEqual(
732-
span.attributes["error.type"], "service_unavailable"
733-
)
741+
self.assertEqual(span.attributes["error.type"], "service_unavailable")
734742
# No completion event arrived, so nothing is known about the response.
735743
self.assertNotIn("gen_ai.response.status", span.attributes)
736744

@@ -952,8 +960,6 @@ def test_early_close_finalizes_span(self) -> None:
952960
self.configure_valid_fetch(interaction_id="closed-early")
953961
self.run_streaming_fetch_closed_early("closed-early", stream=True)
954962
span = self.otel.get_span_named(_SPAN_NAME)
955-
self.assertEqual(
956-
span.attributes["gen_ai.response.id"], "closed-early"
957-
)
963+
self.assertEqual(span.attributes["gen_ai.response.id"], "closed-early")
958964
self.assertNotIn("error.type", span.attributes)
959965
self.assertTrue(self._last_stream.closed)

‎instrumentation/opentelemetry-instrumentation-google-genai/tests/interactions/test_e2e.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ def test_sync_interactions_get_streaming(
455455
interaction = completed[0].interaction
456456

457457
span = _fetch_span(otel_mocker)
458-
assert span.attributes["gen_ai.request.stream"] is True
458+
assert "gen_ai.request.stream" not in span.attributes
459459
assert span.attributes["gen_ai.response.id"] == created.id
460460
# Proves the real SSE completion event is recognised and applied.
461461
assert span.attributes["gen_ai.response.status"] == interaction.status

0 commit comments

Comments
 (0)