From ebb86018393f84e7de4865bdcc81f99f823bd88a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Le Borgne Date: Thu, 29 Oct 2026 22:11:47 +0000 Subject: [PATCH 1/2] docs(genai-util): fix the docstring that breaks the docs build #294 added extended_types to the API docs. Napoleon reads the first line of an attribute docstring written as "Name: text" as "type: description", so the "Streaming:" prefix of InvokeAgentInvocation.monotonic_first_token_s became a py:class reference to "Streaming". Nitpicky mode reports it and -W makes it an error, so the docs job fails on every pull request since #294. Reword the docstring. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01GwUvhmzgfkiZ6vo4uGyesN --- util/opentelemetry-util-genai/CHANGELOG-loongsuite.md | 3 +++ .../src/opentelemetry/util/genai/extended_types.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md b/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md index 6a392610a..bb48072eb 100644 --- a/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md +++ b/util/opentelemetry-util-genai/CHANGELOG-loongsuite.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 event coverage, Provider setup, context lifetimes, and version troubleshooting. Present GenAI Utils as a standalone library configured through the OpenTelemetry SDK, with optional storage backends separate from the setup guide. +- Fix the API docs build: Sphinx read the "Streaming:" prefix of the + `InvokeAgentInvocation.monotonic_first_token_s` docstring as a type and + failed on the missing `Streaming` class. ## Version 0.9.0 (2026-09-07) diff --git a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/extended_types.py b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/extended_types.py index d74131cf9..b488dd997 100644 --- a/util/opentelemetry-util-genai/src/opentelemetry/util/genai/extended_types.py +++ b/util/opentelemetry-util-genai/src/opentelemetry/util/genai/extended_types.py @@ -206,7 +206,9 @@ class InvokeAgentInvocation: monotonic_start_s: float | None = None monotonic_end_s: float | None = None monotonic_first_token_s: float | None = None - """Streaming: first-token time; TTFT attribute derived like ``LLMInvocation``.""" + """Monotonic time when the first token of a streaming response arrived. + The time-to-first-token attribute is derived from it as for + ``LLMInvocation``.""" @dataclass From dfbc58d8a123d216d57ddca1916ebf6fa3167cda Mon Sep 17 00:00:00 2001 From: Pierre-Yves Le Borgne Date: Thu, 29 Oct 2026 22:11:47 +0000 Subject: [PATCH 2/2] test(google-genai): fix the Interactions mocks for google-genai 2.34 google-genai 2.34.0 (2026-10-29) adds automatic continuation to interactions.create. The public create now merges the hops it sends, even a single one, into a copy of the interaction made with model_copy(update=...), for the interaction and for its usage. The tests return MagicMock interactions from the generated create, and MagicMock().model_copy() is a new mock without the configured fields, so test_instrumentation_does_not_break_core_functionality got a mock id. The instrumentation is unaffected: it wraps the generated create and reads each hop's response, as before. With real objects over a mocked HTTP API, a single-hop interaction gives the same object and span on 2.33.0 and 2.34.0, and an interaction continued over two hops gives one span per request. Mock the interaction and its usage as pydantic-like models whose model_copy keeps their fields. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01GwUvhmzgfkiZ6vo4uGyesN --- .../CHANGELOG.md | 6 +++ .../tests/interactions/util.py | 44 +++++++++++++------ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/CHANGELOG.md b/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/CHANGELOG.md index eaf43047e..161172dd7 100644 --- a/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/CHANGELOG.md +++ b/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- Keep the Interactions test mocks working with google-genai 2.34, which + copies the interaction that `interactions.create` returns with + `model_copy` to merge automatic continuation hops. + ## Version 0.9.0 (2026-09-07) ### Added diff --git a/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/tests/interactions/util.py b/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/tests/interactions/util.py index 29a66f8da..17d93879b 100644 --- a/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/tests/interactions/util.py +++ b/instrumentation-loongsuite/loongsuite-instrumentation-google-genai/tests/interactions/util.py @@ -21,6 +21,24 @@ from typing import Any +def create_mock_model(**fields: Any) -> Any: + """Mock a pydantic model of the Interactions API with these fields. + + Since google-genai 2.34, ``interactions.create`` copies the interaction + it returns, and its usage, with ``model_copy(update=...)`` (to merge + automatic continuation hops, even for a single hop). On a plain + ``MagicMock`` that copy would be a new mock without these fields. + """ + mock = unittest.mock.MagicMock() + mock.configure_mock(**fields) + + def model_copy(update: Any = None, **kwargs: Any) -> Any: + return create_mock_model(**{**fields, **(update or {})}) + + mock.model_copy.side_effect = model_copy + return mock + + def create_mock_interaction( interaction_id: str = "test-id", model_name: str = "test-model", @@ -29,11 +47,12 @@ def create_mock_interaction( input_tokens: int = 10, output_tokens: int = 20, ) -> Any: - mock_usage = unittest.mock.MagicMock() - mock_usage.total_input_tokens = input_tokens - mock_usage.total_output_tokens = output_tokens - mock_usage.total_thought_tokens = 0 - mock_usage.total_cached_tokens = 0 + mock_usage = create_mock_model( + total_input_tokens=input_tokens, + total_output_tokens=output_tokens, + total_thought_tokens=0, + total_cached_tokens=0, + ) mock_user_step = unittest.mock.MagicMock() mock_user_step.type = "user_input" @@ -47,14 +66,13 @@ def create_mock_interaction( mock_model_part.text = output_text mock_model_step.content = [mock_model_part] - mock_interaction = unittest.mock.MagicMock() - mock_interaction.id = interaction_id - mock_interaction.model = model_name - mock_interaction.usage = mock_usage - mock_interaction.steps = [mock_user_step, mock_model_step] - mock_interaction.output_text = output_text - - return mock_interaction + return create_mock_model( + id=interaction_id, + model=model_name, + usage=mock_usage, + steps=[mock_user_step, mock_model_step], + output_text=output_text, + ) def create_mock_completed_event(interaction: Any) -> Any: