Skip to main content
Glama
test_search_grounding.py19.5 kB
""" Tests for search grounding functionality. """ from unittest.mock import MagicMock, patch import pytest from yellhorn_mcp.utils.search_grounding_utils import ( _get_gemini_search_tools, add_citations, add_citations_from_metadata, ) class TestGetGeminiSearchTools: """Tests for _get_gemini_search_tools function.""" @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.GoogleSearch") @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.Tool") def test_gemini_20_model_uses_google_search(self, mock_tool, mock_google_search): """Test that Gemini 2.0+ models use GoogleSearch.""" mock_tool_instance = mock_tool.return_value mock_search_instance = mock_google_search.return_value result = _get_gemini_search_tools("gemini-2.0-flash") assert result == [mock_tool_instance] mock_google_search.assert_called_once() mock_tool.assert_called_once_with(google_search=mock_search_instance) @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.GoogleSearch") @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.Tool") def test_gemini_25_model_uses_google_search(self, mock_tool, mock_google_search): """Test that Gemini 2.5+ models use GoogleSearch.""" mock_tool_instance = mock_tool.return_value mock_search_instance = mock_google_search.return_value result = _get_gemini_search_tools("gemini-2.5-pro") assert result == [mock_tool_instance] mock_google_search.assert_called_once() mock_tool.assert_called_once_with(google_search=mock_search_instance) def test_non_gemini_model_returns_none(self): """Test that non-Gemini models return None.""" result = _get_gemini_search_tools("gpt-4") assert result is None @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.GoogleSearch") def test_tool_creation_exception_returns_none(self, mock_google_search): """Test that exceptions during tool creation return None.""" mock_google_search.side_effect = Exception("Tool creation failed") result = _get_gemini_search_tools("gemini-2.0-flash") assert result is None @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.GoogleSearchRetrieval") @patch("yellhorn_mcp.utils.search_grounding_utils.genai_types.Tool") def test_gemini_15_model_uses_google_search_retrieval(self, mock_tool, mock_search_retrieval): """Test that Gemini 1.5 models use GoogleSearchRetrieval.""" mock_tool_instance = mock_tool.return_value mock_retrieval_instance = mock_search_retrieval.return_value result = _get_gemini_search_tools("gemini-1.5-pro") assert result == [mock_tool_instance] mock_search_retrieval.assert_called_once() mock_tool.assert_called_once_with(google_search_retrieval=mock_retrieval_instance) class TestAddCitations: """Tests for add_citations function.""" def test_add_citations_empty_response(self): """Test add_citations with empty response text.""" # Create a mock response with empty text mock_response = MagicMock() mock_response.text = "" mock_response.candidates = [] result = add_citations(mock_response) assert result == "" def test_add_citations_no_grounding_metadata(self): """Test add_citations with no grounding metadata.""" mock_response = MagicMock() mock_response.text = "This is some text without citations." mock_response.candidates = [MagicMock(grounding_metadata=None)] result = add_citations(mock_response) assert result == "This is some text without citations." def test_add_citations_no_candidates(self): """Test add_citations with no candidates.""" mock_response = MagicMock() mock_response.text = "This is some text." mock_response.candidates = [] result = add_citations(mock_response) assert result == "This is some text." def test_add_citations_with_valid_citations(self): """Test add_citations with valid grounding metadata.""" # Create mock grounding metadata mock_chunk1 = MagicMock() mock_chunk1.web.uri = "https://example.com/page1" mock_chunk2 = MagicMock() mock_chunk2.web.uri = "https://example.com/page2" mock_support = MagicMock() mock_support.segment.end_index = 20 mock_support.grounding_chunk_indices = [0, 1] mock_grounding_metadata = MagicMock() mock_grounding_metadata.grounding_supports = [mock_support] mock_grounding_metadata.grounding_chunks = [mock_chunk1, mock_chunk2] mock_candidate = MagicMock() mock_candidate.grounding_metadata = mock_grounding_metadata mock_response = MagicMock() mock_response.text = "This is some text with citations." mock_response.candidates = [mock_candidate] result = add_citations(mock_response) # Should insert citations at end_index (20) - after "with" expected = "This is some text wi[1](https://example.com/page1), [2](https://example.com/page2)th citations." assert result == expected def test_add_citations_multiple_supports_sorted(self): """Test add_citations with multiple supports sorted by end_index.""" # Create mock chunks mock_chunk1 = MagicMock() mock_chunk1.web.uri = "https://example.com/page1" mock_chunk2 = MagicMock() mock_chunk2.web.uri = "https://example.com/page2" # Create mock supports - intentionally out of order mock_support1 = MagicMock() mock_support1.segment.end_index = 10 mock_support1.grounding_chunk_indices = [0] mock_support2 = MagicMock() mock_support2.segment.end_index = 30 mock_support2.grounding_chunk_indices = [1] mock_grounding_metadata = MagicMock() mock_grounding_metadata.grounding_supports = [mock_support1, mock_support2] # Out of order mock_grounding_metadata.grounding_chunks = [mock_chunk1, mock_chunk2] mock_candidate = MagicMock() mock_candidate.grounding_metadata = mock_grounding_metadata mock_response = MagicMock() mock_response.text = "Short text with two citations here." mock_response.candidates = [mock_candidate] result = add_citations(mock_response) # Should process in reverse order (30 first, then 10) to avoid index shifting # Position 30: after "citations" -> "citations [2](url)here." # Position 10: after "text" -> "text [1](url) with" expected = "Short text[1](https://example.com/page1) with two citations [2](https://example.com/page2)here." assert result == expected def test_add_citations_missing_chunk_uri(self): """Test add_citations with missing chunk URI.""" # Create mock chunk without web.uri mock_chunk = MagicMock() mock_chunk.web = None mock_support = MagicMock() mock_support.segment.end_index = 10 mock_support.grounding_chunk_indices = [0] mock_grounding_metadata = MagicMock() mock_grounding_metadata.grounding_supports = [mock_support] mock_grounding_metadata.grounding_chunks = [mock_chunk] mock_candidate = MagicMock() mock_candidate.grounding_metadata = mock_grounding_metadata mock_response = MagicMock() mock_response.text = "Text with citation." mock_response.candidates = [mock_candidate] result = add_citations(mock_response) # Should insert None as URI at position 10 (after "with") expected = "Text with [1](None)citation." assert result == expected def test_add_citations_invalid_chunk_index(self): """Test add_citations with invalid chunk indices.""" mock_support = MagicMock() mock_support.segment.end_index = 10 mock_support.grounding_chunk_indices = [0, 5] # Index 5 doesn't exist mock_grounding_metadata = MagicMock() mock_grounding_metadata.grounding_supports = [mock_support] mock_grounding_metadata.grounding_chunks = [] # Empty chunks mock_candidate = MagicMock() mock_candidate.grounding_metadata = mock_grounding_metadata mock_response = MagicMock() mock_response.text = "Text with citation." mock_response.candidates = [mock_candidate] result = add_citations(mock_response) # Should skip invalid indices and not add any citations assert result == "Text with citation." class TestAddCitationsFromMetadata: """Tests for add_citations_from_metadata function.""" def test_add_citations_from_metadata_empty_text(self): """Test add_citations_from_metadata with empty text.""" result = add_citations_from_metadata("", None) assert result == "" def test_add_citations_from_metadata_no_metadata(self): """Test add_citations_from_metadata with no metadata.""" text = "This is some text." result = add_citations_from_metadata(text, None) assert result == text def test_add_citations_from_metadata_no_supports(self): """Test add_citations_from_metadata with metadata lacking supports.""" mock_metadata = MagicMock() mock_metadata.grounding_supports = [] mock_metadata.grounding_chunks = [] text = "This is some text." result = add_citations_from_metadata(text, mock_metadata) assert result == text def test_add_citations_from_metadata_no_chunks(self): """Test add_citations_from_metadata with metadata lacking chunks.""" mock_support = MagicMock() mock_support.segment.end_index = 10 mock_support.grounding_chunk_indices = [0] mock_metadata = MagicMock() mock_metadata.grounding_supports = [mock_support] mock_metadata.grounding_chunks = [] text = "This is some text." result = add_citations_from_metadata(text, mock_metadata) assert result == text def test_add_citations_from_metadata_missing_attributes(self): """Test add_citations_from_metadata with metadata missing required attributes.""" # Mock metadata without grounding_supports attribute mock_metadata = MagicMock(spec=[]) text = "This is some text." result = add_citations_from_metadata(text, mock_metadata) assert result == text def test_add_citations_from_metadata_valid_citations(self): """Test add_citations_from_metadata with valid metadata.""" # Create mock chunks mock_chunk1 = MagicMock() mock_chunk1.web.uri = "https://example.com/page1" mock_chunk2 = MagicMock() mock_chunk2.web.uri = "https://example.com/page2" mock_support = MagicMock() mock_support.segment.end_index = 15 mock_support.grounding_chunk_indices = [0, 1] mock_metadata = MagicMock() mock_metadata.grounding_supports = [mock_support] mock_metadata.grounding_chunks = [mock_chunk1, mock_chunk2] text = "Text with citations here." result = add_citations_from_metadata(text, mock_metadata) # Should insert citations at end_index (15) expected = "Text with citat[1](https://example.com/page1), [2](https://example.com/page2)ions here." assert result == expected def test_add_citations_from_metadata_none_end_index(self): """Test add_citations_from_metadata with None end_index.""" mock_chunk = MagicMock() mock_chunk.web.uri = "https://example.com/page1" mock_support = MagicMock() mock_support.segment.end_index = None # None end_index mock_support.grounding_chunk_indices = [0] mock_metadata = MagicMock() mock_metadata.grounding_supports = [mock_support] mock_metadata.grounding_chunks = [mock_chunk] text = "Text with citation." result = add_citations_from_metadata(text, mock_metadata) # Should insert at position 0 (default for None end_index) expected = "[1](https://example.com/page1)Text with citation." assert result == expected def test_add_citations_from_metadata_realistic_grounding_data(self): """Test add_citations_from_metadata with realistic grounding metadata structure.""" # Create mock chunks similar to real Gemini API response mock_chunks = [] chunk_uris = [ "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEhhZced8I5CroQ0NHZXL0X7tNmAK_MOGcxehJzKq3VszaU_KmP9a9x5XdyA26IU2GfNhIvivqNYc4Vq69Sz4NVuXw_6t7aWS1_Os5EH8ks0gnP3cdg11ALb_6jfJV03PrsXr6VQKahIoK99IpcH8g-CQ==", "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFtVYJ8ASRfP5qGnA6VGJ-MU3K1Zoz7M_v3VSrFiiAZsuS8KIe2B_CYvBNS0QDbkOBzKdMURTSftC5L70Bq2VcYfBQQsby814ZTovFdUVW1KGZF6H4R66fsV95gi0O9xtaCp-JQE3sm", "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHC9-XYJNkpUY8tGEA2TXYSRMCTTFxqVIJmRmEAqgj-5aCWD6akMKrVClBj7BjPJ-YGEd9EiBV_1SPCIcdGGRNXBZ4A3NfHA5zslVECFDct42D8VIRYKGZa9O413cgZMl0_z9i9bA-ptwFrZVXq9bMGfY-_vnpvDmw82w==", "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEH1wnUSVxW0FefEEsZBzEJMVP_Tx2hGyslxwf7lEusVPDzr5P1tx8MULu479fj6li2KaYRotJyA3sKMF4EavgmjEAO8XQpVRGl9OHpxDJ65iyC5zdfAyGt", "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQERmT1MMNu5dYlKjuUv6fPsQ05Rm4nsTlzTpLuy0c3H-TrF49Q3Bjn5cEUcwtLbjIBlq7tMqGUTwsSJ9KeitwmbQbAAkq22Ipql0pGD8mkF93XgjtzBpIPp_zkveEM=", "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGt6RNALYlIBVmuGxa_Pgb1a3NDqzZtrZNj_oQ6qcwRdqFuExm2j8CYSXsu7bwhE5bv9jMT-meA4qaKN33O0sXbWdRjp6t09SMekN36Y5Ot9iGzvZ3ROWI1bn-yIVx6ThqhEnBnyeLM4LcJWGk4m1o2QNfZezOEp9FM2aOHamNzXDu0PY3XuOWFYdZHbUO4dGNU98pHQ09yMg==", ] for uri in chunk_uris: mock_chunk = MagicMock() mock_chunk.web = MagicMock() mock_chunk.web.uri = uri mock_chunks.append(mock_chunk) # Create mock supports with realistic segments mock_supports = [ # First support with chunks 0, 1 MagicMock( segment=MagicMock( start_index=347, end_index=639, text="LiteLLM is chosen over OpenRouter due to its self-hosted nature, offering greater control over the inference stack, policy-as-code via GitOps, and deeper integration with existing observability tools, which aligns well with a platform team's need for custom governance and on-prem deployments", ), grounding_chunk_indices=[0, 1], ), # Second support with chunks 2, 3, 4 MagicMock( segment=MagicMock( start_index=796, end_index=1014, text="LiteLLM is an open-source Python SDK and proxy server that provides an OpenAI-compatible API for over 100 LLMs, including Bedrock, Azure, OpenAI, VertexAI, Cohere, Anthropic, Sagemaker, HuggingFace, Replicate, and Groq", ), grounding_chunk_indices=[2, 3, 4], ), # Third support with chunk 5 MagicMock( segment=MagicMock( start_index=1528, end_index=1557, text="LiteLLM requires Python >=3.8" ), grounding_chunk_indices=[5], ), ] # Create mock grounding metadata mock_metadata = MagicMock() mock_metadata.grounding_supports = mock_supports mock_metadata.grounding_chunks = mock_chunks # Test text that matches the segments text = ( "When evaluating LLM proxy solutions, " "LiteLLM is chosen over OpenRouter due to its self-hosted nature, offering greater control over the inference stack, policy-as-code via GitOps, and deeper integration with existing observability tools, which aligns well with a platform team's need for custom governance and on-prem deployments" " for enterprise use cases. Additionally, " "LiteLLM is an open-source Python SDK and proxy server that provides an OpenAI-compatible API for over 100 LLMs, including Bedrock, Azure, OpenAI, VertexAI, Cohere, Anthropic, Sagemaker, HuggingFace, Replicate, and Groq" " which makes it versatile. For installation, " "LiteLLM requires Python >=3.8" " as a minimum requirement." ) result = add_citations_from_metadata(text, mock_metadata) # Verify that citations are inserted at the correct positions # Citations should be inserted in reverse order (highest end_index first) # to avoid position shifting issues # Check that all expected citation patterns are present assert ( "[6](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGt6RNALYlIBVmuGxa_Pgb1a3NDqzZtrZNj_oQ6qcwRdqFuExm2j8CYSXsu7bwhE5bv9jMT-meA4qaKN33O0sXbWdRjp6t09SMekN36Y5Ot9iGzvZ3ROWI1bn-yIVx6ThqhEnBnyeLM4LcJWGk4m1o2QNfZezOEp9FM2aOHamNzXDu0PY3XuOWFYdZHbUO4dGNU98pHQ09yMg==)" in result ) assert ( "[3](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHC9-XYJNkpUY8tGEA2TXYSRMCTTFxqVIJmRmEAqgj-5aCWD6akMKrVClBj7BjPJ-YGEd9EiBV_1SPCIcdGGRNXBZ4A3NfHA5zslVECFDct42D8VIRYKGZa9O413cgZMl0_z9i9bA-ptwFrZVXq9bMGfY-_vnpvDmw82w==)" in result ) assert ( "[1](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEhhZced8I5CroQ0NHZXL0X7tNmAK_MOGcxehJzKq3VszaU_KmP9a9x5XdyA26IU2GfNhIvivqNYc4Vq69Sz4NVuXw_6t7aWS1_Os5EH8ks0gnP3cdg11ALb_6jfJV03PrsXr6VQKahIoK99IpcH8g-CQ==)" in result ) # Verify that the original text content is preserved assert "LiteLLM is chosen over OpenRouter" in result assert "open-source Python SDK" in result assert "requires Python >=3.8" in result # Verify that the result is longer than the original (citations added) assert len(result) > len(text) def test_add_citations_from_metadata_dictionary_format(self): """Test add_citations_from_metadata with dictionary-format metadata.""" # Test with dictionary format instead of object format grounding_metadata = { "grounding_chunks": [ {"web": {"title": "truefoundry.com", "uri": "https://example.com/page1"}}, {"web": {"title": "github.com", "uri": "https://example.com/page2"}}, ], "grounding_supports": [ { "segment": {"start_index": 10, "end_index": 25, "text": "some cited text"}, "grounding_chunk_indices": [0, 1], } ], } text = "This is some cited text with more content." result = add_citations_from_metadata(text, grounding_metadata) # Should insert citations at position 25 expected_citations = "[1](https://example.com/page1), [2](https://example.com/page2)" assert expected_citations in result assert "This is some cited text" in result assert "ith more content." in result # The 'w' is at position 25 where citation is inserted

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/msnidal/yellhorn-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server