From 9875ffbabc4cc4dd4ba86f12d75cefc8c81b44c9 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Mon, 8 Dec 2025 09:44:43 -0500 Subject: [PATCH] feat(core): support google maps grounding in genai block translator (#34244) https://github.com/langchain-ai/langchain-google/pull/1330 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../block_translators/google_genai.py | 35 ++- .../block_translators/test_google_genai.py | 218 ++++++++++++++++++ 2 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 libs/core/tests/unit_tests/messages/block_translators/test_google_genai.py 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 2a82f035c23..5e879b91287 100644 --- a/libs/core/langchain_core/messages/block_translators/google_genai.py +++ b/libs/core/langchain_core/messages/block_translators/google_genai.py @@ -76,21 +76,36 @@ def translate_grounding_metadata_to_citations( for chunk_index in chunk_indices: if chunk_index < len(grounding_chunks): chunk = grounding_chunks[chunk_index] - web_info = chunk.get("web", {}) + + # Handle web and maps grounding + web_info = chunk.get("web") or {} + maps_info = chunk.get("maps") or {} + + # Extract citation info depending on source + url = maps_info.get("uri") or web_info.get("uri") + title = maps_info.get("title") or web_info.get("title") + + # Note: confidence_scores is a legacy field from Gemini 2.0 and earlier + # that indicated confidence (0.0-1.0) for each grounding chunk. + # + # In Gemini 2.5+, this field is always None/empty and should be ignored. + extras_metadata = { + "web_search_queries": web_search_queries, + "grounding_chunk_index": chunk_index, + "confidence_scores": support.get("confidence_scores") or [], + } + + # Add maps-specific metadata if present + if maps_info.get("placeId"): + extras_metadata["place_id"] = maps_info["placeId"] citation = create_citation( - url=web_info.get("uri"), - title=web_info.get("title"), + url=url, + title=title, start_index=start_index, end_index=end_index, cited_text=cited_text, - extras={ - "google_ai_metadata": { - "web_search_queries": web_search_queries, - "grounding_chunk_index": chunk_index, - "confidence_scores": support.get("confidence_scores", []), - } - }, + google_ai_metadata=extras_metadata, ) citations.append(citation) 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 new file mode 100644 index 00000000000..5679d1fd9e6 --- /dev/null +++ b/libs/core/tests/unit_tests/messages/block_translators/test_google_genai.py @@ -0,0 +1,218 @@ +"""Tests for Google GenAI block translator.""" + +from langchain_core.messages.block_translators.google_genai import ( + translate_grounding_metadata_to_citations, +) + + +def test_translate_grounding_metadata_web() -> None: + """Test translation of web grounding metadata to citations.""" + grounding_metadata = { + "grounding_chunks": [ + { + "web": { + "uri": "https://example.com", + "title": "Example Site", + }, + "maps": None, + } + ], + "grounding_supports": [ + { + "segment": { + "start_index": 0, + "end_index": 13, + "text": "Test response", + }, + "grounding_chunk_indices": [0], + "confidence_scores": [], + } + ], + "web_search_queries": ["test query"], + } + + citations = translate_grounding_metadata_to_citations(grounding_metadata) + + assert len(citations) == 1 + citation = citations[0] + assert citation["type"] == "citation" + assert citation.get("url") == "https://example.com" + assert citation.get("title") == "Example Site" + assert citation.get("start_index") == 0 + assert citation.get("end_index") == 13 + assert citation.get("cited_text") == "Test response" + + extras = citation.get("extras", {})["google_ai_metadata"] + assert extras["web_search_queries"] == ["test query"] + assert extras["grounding_chunk_index"] == 0 + assert "place_id" not in extras + + +def test_translate_grounding_metadata_maps() -> None: + """Test translation of maps grounding metadata to citations.""" + grounding_metadata = { + "grounding_chunks": [ + { + "web": None, + "maps": { + "uri": "https://maps.google.com/?cid=13100894621228039586", + "title": "Heaven on 7th Marketplace", + "placeId": "places/ChIJ0-zA1vBZwokRon0fGj-6z7U", + }, + } + ], + "grounding_supports": [ + { + "segment": { + "start_index": 0, + "end_index": 25, + "text": "Great Italian restaurant", + }, + "grounding_chunk_indices": [0], + "confidence_scores": [0.95], + } + ], + "web_search_queries": [], + } + + citations = translate_grounding_metadata_to_citations(grounding_metadata) + + assert len(citations) == 1 + citation = citations[0] + assert citation["type"] == "citation" + assert citation.get("url") == "https://maps.google.com/?cid=13100894621228039586" + assert citation.get("title") == "Heaven on 7th Marketplace" + assert citation.get("start_index") == 0 + assert citation.get("end_index") == 25 + assert citation.get("cited_text") == "Great Italian restaurant" + + extras = citation.get("extras", {})["google_ai_metadata"] + assert extras["web_search_queries"] == [] + assert extras["grounding_chunk_index"] == 0 + assert extras["confidence_scores"] == [0.95] + assert extras["place_id"] == "places/ChIJ0-zA1vBZwokRon0fGj-6z7U" + + +def test_translate_grounding_metadata_none() -> None: + """Test translation when both web and maps are None.""" + grounding_metadata = { + "grounding_chunks": [ + { + "web": None, + "maps": None, + } + ], + "grounding_supports": [ + { + "segment": { + "start_index": 0, + "end_index": 10, + "text": "test text", + }, + "grounding_chunk_indices": [0], + "confidence_scores": [], + } + ], + "web_search_queries": [], + } + + citations = translate_grounding_metadata_to_citations(grounding_metadata) + + # Should still create citation but without url/title fields when None + assert len(citations) == 1 + citation = citations[0] + assert citation["type"] == "citation" + # url and title are omitted when None + assert "url" not in citation + assert "title" not in citation + assert citation.get("start_index") == 0 + assert citation.get("end_index") == 10 + assert citation.get("cited_text") == "test text" + + +def test_translate_grounding_metadata_confidence_scores_none() -> None: + """Test translation when confidence_scores is None (API returns this).""" + grounding_metadata = { + "grounding_chunks": [ + { + "web": None, + "maps": { + "uri": "https://maps.google.com/?cid=123", + "title": "Test Restaurant", + "placeId": "places/ChIJ123", + }, + } + ], + "grounding_supports": [ + { + "segment": { + "start_index": 0, + "end_index": 10, + "text": "test text", + }, + "grounding_chunk_indices": [0], + "confidence_scores": None, # API returns None, not [] + } + ], + "web_search_queries": ["test query"], + } + + citations = translate_grounding_metadata_to_citations(grounding_metadata) + + assert len(citations) == 1 + extras = citations[0].get("extras", {})["google_ai_metadata"] + # Should convert None to empty list + assert extras["confidence_scores"] == [] + assert isinstance(extras["confidence_scores"], list) + + +def test_translate_grounding_metadata_multiple_chunks() -> None: + """Test translation with multiple grounding chunks.""" + grounding_metadata = { + "grounding_chunks": [ + { + "web": { + "uri": "https://example1.com", + "title": "Example 1", + }, + "maps": None, + }, + { + "web": None, + "maps": { + "uri": "https://maps.google.com/?cid=123", + "title": "Place 1", + "placeId": "places/123", + }, + }, + ], + "grounding_supports": [ + { + "segment": { + "start_index": 0, + "end_index": 10, + "text": "First part", + }, + "grounding_chunk_indices": [0, 1], + "confidence_scores": [], + } + ], + "web_search_queries": [], + } + + citations = translate_grounding_metadata_to_citations(grounding_metadata) + + # Should create two citations, one for each chunk + assert len(citations) == 2 + + # First citation from web chunk + assert citations[0].get("url") == "https://example1.com" + assert citations[0].get("title") == "Example 1" + assert "place_id" not in citations[0].get("extras", {})["google_ai_metadata"] + + # Second citation from maps chunk + assert citations[1].get("url") == "https://maps.google.com/?cid=123" + assert citations[1].get("title") == "Place 1" + assert ( + citations[1].get("extras", {})["google_ai_metadata"]["place_id"] == "places/123" + )