From e92c8a08bf382121cc1e95f7e75ddc8cb9c01ab0 Mon Sep 17 00:00:00 2001 From: ccurme Date: Sat, 29 Aug 2026 22:44:25 -0400 Subject: [PATCH] fix(core): avoid mutation in google-genai standard content (#40023) Co-authored-by: Hotragn Pettugani <103170876+Hotragn@users.noreply.github.com> --- .../block_translators/google_genai.py | 11 +++++-- .../block_translators/test_google_genai.py | 31 ++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/libs/core/langchain_core/messages/block_translators/google_genai.py b/libs/core/langchain_core/messages/block_translators/google_genai.py index d0f5f6b6c1..a80835addc 100644 --- a/libs/core/langchain_core/messages/block_translators/google_genai.py +++ b/libs/core/langchain_core/messages/block_translators/google_genai.py @@ -527,10 +527,15 @@ def _convert_to_v1_from_genai(message: AIMessage) -> list[types.ContentBlock]: if grounding_metadata: citations = translate_grounding_metadata_to_citations(grounding_metadata) - for block in converted_blocks: + for i, block in enumerate(converted_blocks): if block["type"] == "text" and citations: - # Add citations to text blocks (only the first text block) - block["annotations"] = cast("list[types.Annotation]", citations) + # Add citations to text blocks (only the first text block). + # Replaced rather than assigned into: `block` is the caller's own + # dict from `message.content`. + converted_blocks[i] = cast( + "types.TextContentBlock", + {**block, "annotations": citations}, + ) break # Audio is stored on the message.additional_kwargs diff --git a/libs/core/tests/unit_tests/messages/block_translators/test_google_genai.py b/libs/core/tests/unit_tests/messages/block_translators/test_google_genai.py index fcb009a868..f2a698858c 100644 --- a/libs/core/tests/unit_tests/messages/block_translators/test_google_genai.py +++ b/libs/core/tests/unit_tests/messages/block_translators/test_google_genai.py @@ -1,8 +1,9 @@ """Tests for Google GenAI block translator.""" +from copy import deepcopy from typing import Any -from langchain_core.messages import AIMessageChunk +from langchain_core.messages import AIMessage, AIMessageChunk from langchain_core.messages.block_translators.google_genai import ( translate_grounding_metadata_to_citations, ) @@ -331,3 +332,31 @@ def test_content_blocks_index_propagation_is_one_to_one() -> None: assert indices == [0, 1, 2] assert len(indices) == len(set(indices)) + + +_GROUNDING_METADATA = { + "grounding_chunks": [{"web": {"uri": "https://example.com", "title": "Example"}}], + "grounding_supports": [ + { + "segment": {"start_index": 0, "end_index": 5}, + "grounding_chunk_indices": [0], + } + ], +} + + +def test_grounding_citations_do_not_mutate_content() -> None: + """Attaching grounding citations must leave `message.content` unchanged.""" + message = AIMessage( + content=[{"type": "text", "text": "hello"}], + response_metadata={ + "model_provider": "google_genai", + "grounding_metadata": _GROUNDING_METADATA, + }, + ) + original_content = deepcopy(message.content) + + blocks = message.content_blocks + + assert message.content == original_content + assert "annotations" in blocks[0]