-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(google_serper_api): migrate to new tool mode implementation (#…
…5446) * # refactor(google_serper_api): migrate to new tool mode implementation BREAKING CHANGE: Replace legacy LCToolComponent implementation with new Component base class - Migrate from LCToolComponent to Component base class - Add tool_mode flag to MultilineInput - Update output configuration to use DataFrame type - Implement structured error handling with DataFrame responses - Remove legacy tool mode implementation * test(google-serper): add unit tests for GoogleSerperAPIComponent - Add comprehensive test suite for GoogleSerperAPIComponent - Mock HTTP requests to test search functionality - Test component initialization and configuration - Add error handling test cases - Test text search and wrapper building methods - Ensure proper DataFrame output structure This change improves test coverage for the Google Serper API integration, following existing test patterns in the project. * [autofix.ci] apply automated fixes * style(tests): remove unused fixture argument in google-serper test - Remove unused mock_search_results fixture from test_text_search_serper - Fix linting error ARG001 (unused function argument) * revert(tools): restore Google Search API component to original implementation Due to backward compatibility concerns, reverting the Google Search API component to its initial implementation state to maintain stability and prevent breaking changes. * refactor(components): mark GoogleSerperAPI component as deprecated & Legacy * feat(components): add GoogleSerperAPICore component * refactor(google-serper): fix filename typo from 'gloogle' to 'google' * test(google-serper): add unit tests following component test guide * feat(icons): improve Serper icon quality and add to icon list * [autofix.ci] apply automated fixes * Update test_google_serper_api_core.py test update --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
- Loading branch information
1 parent
f65d3aa
commit d650b21
Showing
7 changed files
with
213 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
src/backend/base/langflow/components/tools/google_serper_api_core.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper | ||
|
||
from langflow.custom import Component | ||
from langflow.io import IntInput, MultilineInput, Output, SecretStrInput | ||
from langflow.schema import DataFrame | ||
from langflow.schema.message import Message | ||
|
||
|
||
class GoogleSerperAPICore(Component): | ||
display_name = "Google Serper API" | ||
description = "Call the Serper.dev Google Search API." | ||
icon = "Serper" | ||
|
||
inputs = [ | ||
SecretStrInput( | ||
name="serper_api_key", | ||
display_name="Serper API Key", | ||
required=True, | ||
), | ||
MultilineInput( | ||
name="input_value", | ||
display_name="Input", | ||
tool_mode=True, | ||
), | ||
IntInput( | ||
name="k", | ||
display_name="Number of results", | ||
value=4, | ||
required=True, | ||
), | ||
] | ||
|
||
outputs = [ | ||
Output( | ||
display_name="Results", | ||
name="results", | ||
type_=DataFrame, | ||
method="search_serper", | ||
), | ||
] | ||
|
||
def search_serper(self) -> DataFrame: | ||
try: | ||
wrapper = self._build_wrapper() | ||
results = wrapper.results(query=self.input_value) | ||
list_results = results.get("organic", []) | ||
|
||
# Convert results to DataFrame using list comprehension | ||
df_data = [ | ||
{ | ||
"title": result.get("title", ""), | ||
"link": result.get("link", ""), | ||
"snippet": result.get("snippet", ""), | ||
} | ||
for result in list_results | ||
] | ||
|
||
return DataFrame(df_data) | ||
except (ValueError, KeyError, ConnectionError) as e: | ||
error_message = f"Error occurred while searching: {e!s}" | ||
self.status = error_message | ||
# Return DataFrame with error as a list of dictionaries | ||
return DataFrame([{"error": error_message}]) | ||
|
||
def text_search_serper(self) -> Message: | ||
search_results = self.search_serper() | ||
text_result = search_results.to_string(index=False) if not search_results.empty else "No results found." | ||
return Message(text=text_result) | ||
|
||
def _build_wrapper(self): | ||
return GoogleSerperAPIWrapper(serper_api_key=self.serper_api_key, k=self.k) | ||
|
||
def build(self): | ||
return self.search_serper |
114 changes: 114 additions & 0 deletions
114
src/backend/tests/unit/components/tools/test_google_serper_api_core.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from unittest.mock import MagicMock, patch | ||
|
||
import pytest | ||
from langflow.components.tools import GoogleSerperAPICore | ||
from langflow.schema import DataFrame | ||
|
||
|
||
@pytest.fixture | ||
def google_serper_component(): | ||
return GoogleSerperAPICore() | ||
|
||
|
||
@pytest.fixture | ||
def mock_search_results(): | ||
return { | ||
"organic": [ | ||
{ | ||
"title": "Test Title 1", | ||
"link": "https://test1.com", | ||
"snippet": "Test snippet 1", | ||
}, | ||
{ | ||
"title": "Test Title 2", | ||
"link": "https://test2.com", | ||
"snippet": "Test snippet 2", | ||
}, | ||
] | ||
} | ||
|
||
|
||
def test_component_initialization(google_serper_component): | ||
assert google_serper_component.display_name == "Google Serper API" | ||
assert google_serper_component.icon == "Serper" | ||
|
||
input_names = [input_.name for input_ in google_serper_component.inputs] | ||
assert "serper_api_key" in input_names | ||
assert "input_value" in input_names | ||
assert "k" in input_names | ||
|
||
|
||
@patch("langchain_community.utilities.google_serper.requests.get") | ||
@patch("langchain_community.utilities.google_serper.requests.post") | ||
def test_search_serper_success(mock_post, mock_get, google_serper_component, mock_search_results): | ||
# Configure mocks | ||
mock_response = MagicMock() | ||
mock_response.status_code = 200 | ||
mock_response.json.return_value = mock_search_results | ||
mock_post.return_value = mock_response | ||
mock_get.return_value = mock_response | ||
|
||
# Configure component | ||
google_serper_component.serper_api_key = "test_api_key" | ||
google_serper_component.input_value = "test query" | ||
google_serper_component.k = 2 | ||
|
||
# Execute search | ||
result = google_serper_component.search_serper() | ||
|
||
# Verify results | ||
assert isinstance(result, DataFrame) | ||
assert len(result) == 2 | ||
assert list(result.columns) == ["title", "link", "snippet"] | ||
assert result.iloc[0]["title"] == "Test Title 1" | ||
assert result.iloc[1]["link"] == "https://test2.com" | ||
|
||
|
||
@patch("langchain_community.utilities.google_serper.requests.get") | ||
@patch("langchain_community.utilities.google_serper.requests.post") | ||
def test_search_serper_error_handling(mock_post, mock_get, google_serper_component): | ||
# Configure mocks to simulate error | ||
mock_response = MagicMock() | ||
mock_response.status_code = 403 | ||
mock_response.raise_for_status.side_effect = ConnectionError("API connection failed") | ||
mock_post.return_value = mock_response | ||
mock_get.return_value = mock_response | ||
|
||
# Configure component | ||
google_serper_component.serper_api_key = "test_api_key" | ||
google_serper_component.input_value = "test query" | ||
google_serper_component.k = 2 | ||
|
||
# Execute search | ||
result = google_serper_component.search_serper() | ||
|
||
# Verify error handling | ||
assert isinstance(result, DataFrame) | ||
assert "error" in result.columns | ||
assert "API connection failed" in result.iloc[0]["error"] | ||
|
||
|
||
def test_text_search_serper(google_serper_component): | ||
with patch.object(google_serper_component, "search_serper") as mock_search: | ||
mock_search.return_value = DataFrame( | ||
[{"title": "Test Title", "link": "https://test.com", "snippet": "Test snippet"}] | ||
) | ||
|
||
result = google_serper_component.text_search_serper() | ||
assert result.text is not None | ||
assert "Test Title" in result.text | ||
assert "https://test.com" in result.text | ||
|
||
|
||
def test_build_wrapper(google_serper_component): | ||
google_serper_component.serper_api_key = "test_api_key" | ||
google_serper_component.k = 2 | ||
|
||
wrapper = google_serper_component._build_wrapper() | ||
assert wrapper.serper_api_key == "test_api_key" | ||
assert wrapper.k == 2 | ||
|
||
|
||
def test_build_method(google_serper_component): | ||
build_result = google_serper_component.build() | ||
assert build_result == google_serper_component.search_serper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,26 @@ | ||
const SvgSerper = (props) => ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
xmlSpace="preserve" | ||
viewBox="0 0 70 70" | ||
width="1em" | ||
height="1em" | ||
viewBox="0 0 48 48" | ||
preserveAspectRatio="xMidYMid meet" | ||
fill="none" | ||
{...props} | ||
> | ||
<image | ||
width={48} | ||
height={48} | ||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACGVBMVEUAAACVyvSRzvOPzfSQ zfSRzPSQzfSQzfSQzfSQyPSUyfKRzvOQzfSRzvSQzfOOxvGO0PaQzfSQzfSQzfSTzvWZzP+RzPSQ zfSPzfOA1f+Qy/KPzfSQzfSQzfSPzfSRzfSPzPWPy/iRzfWQzfSRz/GPzfORzvWSzfaPzvaQzPOP zPWQzfWQzfSQzfSQzvSMzPKSzvOPzfSQzfWRzvWQzvWN0PKTzPKQzfOQzfSQzfSAv/+Rz/WQzfWQ zfSPzfOQzfSQzPORzPSPzfSRzPSqqv+QzfWQzfSQzvSQzfSRzfWPzPX///+RzvSQzfSQzPSQzvSL 0f+QzvWSy/WQzfWQzfSQzfSPzfWRzfWRzPWRzPWT0feQzPOQzfOQzPOL0fOPzPORzfSPzfOQzPaQ zfSQzfSPy/KQzfWQzfSQzfSPzPOPz/eQzPSQzvSQzfSQzPSPzvSQzvSQzfWPzvWQzfOQzPSJxOuP zPWQzfSQzvSPz/SV1eqQzPSQzfWPy/OQzfSOzfGQzvSRzfOfv/+PzfSQzfSQzvWQzfWSyO2PzvOQ zfSRzfSRzfSSzvOA//+PzvSOxv+OzfKPzPWQzfSRzPKSzPSQzfSQzfOQzfSOzvWPzfOQzfSSzvSR zfSRzPSW0vCRzPSSzvOQzfOQzvSQzfSIzO6OzfWS2/+RzfSSzPCQzfWQzvWQzfORz/KQzfSRy/WQ zfSQzfSQzfT////309j7AAAAsXRSTlMAGFiQuNnu+Y8XE23KyWwSG/X0jhoFb/KABifQ5aNwSDIi M84lUpY4OZdQZ/z4hxQViWVoxCYox/6KBE/z/WvxbniLdAOVy+jkfxkBXeGmjAuqMa3snntmfash aldVFrC0mTfBqDt64udpIHOl0dRytdxJheMNS9rtMAzPw0DrJL9WCGC8fNsOgkdhpD8CWQk9kuo8 RvvFnzQp+i+7jRFfKsaRdQ9NB/AjTGOYOvZKttg9JvjTAAAAAWJLR0RLaQuFUAAAAAd0SU1FB+cF HgMFK2w+nRoAAANfSURBVEjHhVX5QxJREH6eKGQmiIQheJflgXiRYhalmCRlYWqGZZdHB6WllZra aZaVmZUWppZJduj+h8089mIXcH7Ynfnmm7fvzcybJUQqUdExsXHxCkV8XGxCdCLZRpSqHUyQJO1M jkDfFZPCyEStSQ1D16bpmJCSslsfip++h/UbMoymzKysbJMxw8BCOblyfl5geXX+XjG6ryCwSd1+ Kf9AIeJFxSVSh7lUgZ5CSzBcRtcprwi118oq9FmDIvLo+gerw2RDQ79RIzqvDYDaQ+HzraoFQh1/ cj3NTwQ+IYdprrSsdQQtO4koCcg5ytZXjecV7f+Yqb7BoW4sPy7KgRZPbmuiegyoikre5TzRzJW4 1mXm4YoiAE6ipsTSFPOOUy3irjCc5h12LCt2ogq7hV/JeQZ57tazbe0dWDHHOc5TgjvvBAX7OZ9f 5zxYzZ5As3VdAOMi7+oGqwpQK7wv8ehl2Abfzs4r4EvnrKtYbzOJhlejkA03w1wTLGUPoxZ22wtU D81RhkCBDPSJ0t9/XTg1uQFUDYmFZ5oA3mSYW11hiucF6m1yB54DAjgI5l3LvZABQ+AbJvfh+UAA H47QezGqaRuTTYxM8DwimOxxEZo8IVStY/KxOCAXl5IFEOeTEaHSjR5ZgGRLVNIt9U972JBn0i09 Dz60IC/GpoaxVC8lh5akNUic09CiE5K0YuFeCZzE10FDawo6f4YzXLRw2G0GJ4eZHzGjM6KAN+B9 y33PQVuDNh8/vN6BMS0KmGWY95w+F2g+kgTvAg7Ux8HlEFL5YV60X2zvj0R6gQZgoFg/fab6QgEY i1+40+HNLANFiTepNCgVDNMz2vKVTmJrG+fw4RRYQg3nmmKBj/hmE91p3SQHLy/ibKRqKjJWtHxE f3cRS49f5RtQj0fVRQWM7+j0iXLzY22wtaW13fRTgFaR42UNbQ5aKhJBjMho4Iddbh1OrQgRRhzG 634BqKHjXqMNTdfPonf+lxizYL2ZqoVQ/OUkmt+NYNRCv6GwywZAom+Rrr8hddTUBX6xv/84BdA5 1/2Xwutr8i+P57DZ73V5/2X7/dlDXpeDhRr8JIRUh/uxq0P/2EGafGo53WaPilCg5M4Vq5htXelb ItuIeVKzueVWKNxbmz6P7D9P/gNwmex8k7QhUQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0wNS0z MFQwMzowNTo0MyswMDowMLa1rmEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMDUtMzBUMDM6MDU6 NDMrMDA6MDDH6BbdAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDIzLTA1LTMwVDAzOjA1OjQzKzAw OjAwkP03AgAAAABJRU5ErkJggg==" | ||
<circle | ||
cx="35" | ||
cy="35" | ||
r="32" | ||
stroke="#90CDF4" | ||
strokeWidth="6" | ||
fill="transparent" | ||
/> | ||
<path | ||
d="M35.6992 17.5264C37.571 17.5264 39.3369 17.8438 40.9971 18.4785C42.6735 19.1133 44.1383 19.9922 45.3916 21.1152C46.6449 22.2383 47.597 23.5404 48.248 25.0215C48.3945 25.347 48.4678 25.6807 48.4678 26.0225C48.4678 26.6898 48.2236 27.2676 47.7354 27.7559C47.2633 28.2279 46.6937 28.4639 46.0264 28.4639C45.5869 28.4639 45.1475 28.3255 44.708 28.0488C44.2686 27.7559 43.9593 27.4059 43.7803 26.999C43.1618 25.5993 42.1283 24.4844 40.6797 23.6543C39.2474 22.8242 37.5872 22.4092 35.6992 22.4092C34.7715 22.4092 33.7868 22.5231 32.7451 22.751C31.7197 22.9788 30.7513 23.3288 29.8398 23.8008C28.9284 24.2565 28.1878 24.8343 27.6182 25.5342C27.0485 26.234 26.7637 27.0479 26.7637 27.9756C26.7637 28.7243 27.0078 29.3753 27.4961 29.9287C27.9844 30.4658 28.5296 30.889 29.1318 31.1982C30.5479 31.8981 32.1104 32.3864 33.8193 32.6631C35.5446 32.9398 37.2861 33.2083 39.0439 33.4688C40.8018 33.7292 42.4375 34.1849 43.9512 34.8359C44.9115 35.2428 45.8311 35.8044 46.71 36.5205C47.5889 37.2367 48.305 38.1074 48.8584 39.1328C49.4118 40.1582 49.6885 41.3626 49.6885 42.7461C49.6885 44.5365 49.2572 46.1071 48.3945 47.458C47.5319 48.8089 46.4007 49.932 45.001 50.8271C43.6012 51.7223 42.0794 52.3978 40.4355 52.8535C38.8079 53.293 37.2129 53.5127 35.6504 53.5127C33.5345 53.5127 31.5488 53.179 29.6934 52.5117C27.8379 51.8281 26.2184 50.8678 24.835 49.6309C23.4515 48.3776 22.3936 46.9128 21.6611 45.2363C21.5146 44.9108 21.4414 44.5853 21.4414 44.2598C21.4414 43.5924 21.6774 43.0228 22.1494 42.5508C22.6377 42.0625 23.2155 41.8184 23.8828 41.8184C24.3385 41.8184 24.7861 41.9648 25.2256 42.2578C25.665 42.5345 25.9661 42.8844 26.1289 43.3076C26.8451 44.9515 28.0576 46.2536 29.7666 47.2139C31.4756 48.1579 33.4368 48.6299 35.6504 48.6299C37.0339 48.6299 38.4255 48.4264 39.8252 48.0195C41.2249 47.5964 42.3968 46.9535 43.3408 46.0908C44.3011 45.2119 44.7812 44.097 44.7812 42.7461C44.7812 41.8835 44.4883 41.1755 43.9023 40.6221C43.3327 40.0524 42.7061 39.6211 42.0225 39.3281C40.4762 38.6608 38.8242 38.2051 37.0664 37.9609C35.3086 37.7005 33.5589 37.432 31.8174 37.1553C30.0758 36.8623 28.4482 36.3333 26.9346 35.5684C25.6488 34.9173 24.4769 33.9733 23.4189 32.7363C22.3773 31.4831 21.8564 29.8962 21.8564 27.9756C21.8564 26.2829 22.2633 24.7936 23.0771 23.5078C23.9072 22.2057 24.9977 21.1152 26.3486 20.2363C27.7158 19.3411 29.2132 18.6657 30.8408 18.21C32.4684 17.7542 34.0879 17.5264 35.6992 17.5264Z" | ||
fill="#90CDF4" | ||
/> | ||
</svg> | ||
); | ||
|
||
export default SvgSerper; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters