diff --git a/backend/packages/harness/deerflow/community/serply/tools.py b/backend/packages/harness/deerflow/community/serply/tools.py index a766f4064c..83c615dad7 100644 --- a/backend/packages/harness/deerflow/community/serply/tools.py +++ b/backend/packages/harness/deerflow/community/serply/tools.py @@ -17,6 +17,7 @@ import re import httpx from langchain.tools import tool +from deerflow.community.search_max_results import coerce_max_results from deerflow.community.url_safety import safe_public_result_url from deerflow.config import get_app_config @@ -51,21 +52,18 @@ def _get_api_key(tool_name: str = "web_search") -> str | None: return None -def _coerce_max_results( - value: object, - *, - default: int = _DEFAULT_MAX_RESULTS, - max_allowed: int = _SERPLY_MAX_RESULTS, -) -> int: - try: - coerced = int(value) - except (TypeError, ValueError, OverflowError): - logger.warning("Invalid Serply max_results=%r; using default %s", value, default) - coerced = default - if coerced < 1: - logger.warning("Invalid Serply max_results=%r; using default %s", value, default) - coerced = default - return min(coerced, max_allowed) +def _count_for_request(value: object) -> int: + """Coerce ``max_results`` via the shared owner, then mirror Serply's clamp. + + ``deerflow.community.search_max_results.coerce_max_results`` owns what + "invalid" means (booleans, non-integral floats and ``OverflowError`` all + fall back to the default); the 1-100 cap stays here because clamping + policy is deliberately per-provider. + """ + return min( + coerce_max_results(value, provider="Serply", logger=logger, default=_DEFAULT_MAX_RESULTS), + _SERPLY_MAX_RESULTS, + ) def _coerce_vertical(value: object) -> str: @@ -197,7 +195,7 @@ def web_search_tool(query: str, max_results: int = 5) -> str: extra = (config.model_extra or {}) if config is not None else {} if "max_results" in extra: max_results = extra["max_results"] - max_results = _coerce_max_results(max_results) + max_results = _count_for_request(max_results) vertical = _coerce_vertical(extra.get("vertical")) query = _clean_query(query) diff --git a/backend/tests/test_search_max_results_single_owner.py b/backend/tests/test_search_max_results_single_owner.py index 9ade511569..fbbc9079c8 100644 --- a/backend/tests/test_search_max_results_single_owner.py +++ b/backend/tests/test_search_max_results_single_owner.py @@ -32,6 +32,7 @@ from deerflow.community.firecrawl import tools as firecrawl_tools from deerflow.community.image_search import tools as image_search_tools from deerflow.community.search_max_results import DEFAULT_MAX_RESULTS, coerce_max_results from deerflow.community.serper import tools as serper_tools +from deerflow.community.serply import tools as serply_tools from deerflow.community.specstream import tools as specstream_tools from deerflow.community.youcom import tools as youcom_tools @@ -45,6 +46,7 @@ SHARED_OWNER_PROVIDERS = { "ddg_search": ("DDG Search", ddg_search_tools), "image_search": ("DDG image search", image_search_tools), "serper": ("Serper", serper_tools), + "serply": ("Serply", serply_tools), "fastcrw": ("fastCRW", fastcrw_tools), "firecrawl": ("Firecrawl", firecrawl_tools), "youcom": ("You.com", youcom_tools), @@ -62,7 +64,6 @@ DEFERRED_PROVIDERS = { "exa", "groundroute", "searxng", - "serply", "sofya", "tencent_wsa", } diff --git a/backend/tests/test_serply_tools.py b/backend/tests/test_serply_tools.py index 981c51b1af..ecc00cd2bf 100644 --- a/backend/tests/test_serply_tools.py +++ b/backend/tests/test_serply_tools.py @@ -103,25 +103,27 @@ class TestGetApiKey: patcher.stop() -class TestCoerceMaxResults: +class TestCountForRequest: def test_returns_value_when_valid(self): - from deerflow.community.serply.tools import _coerce_max_results + from deerflow.community.serply.tools import _count_for_request - assert _coerce_max_results(3) == 3 - assert _coerce_max_results("7") == 7 + assert _count_for_request(3) == 3 + assert _count_for_request("7") == 7 def test_caps_at_serply_maximum(self): - from deerflow.community.serply.tools import _coerce_max_results + from deerflow.community.serply.tools import _count_for_request - assert _coerce_max_results(999) == 100 + assert _count_for_request(999) == 100 - def test_invalid_values_fall_back_to_default(self): - from deerflow.community.serply.tools import _coerce_max_results + @pytest.mark.parametrize( + "raw", + ["oops", None, 0, -3, True, False, 3.5, float("inf")], + ids=["invalid-string", "none", "zero", "negative", "true", "false", "non-integral-float", "yaml-inf"], + ) + def test_invalid_values_fall_back_to_default(self, raw): + from deerflow.community.serply.tools import _count_for_request - assert _coerce_max_results("oops") == 5 - assert _coerce_max_results(None) == 5 - assert _coerce_max_results(0) == 5 - assert _coerce_max_results(-3) == 5 + assert _count_for_request(raw) == 5 class TestCoerceVertical: @@ -376,10 +378,3 @@ class TestWebSearchTool: assert result["total_results"] == 1 assert mock_get.call_args.kwargs["headers"]["X-Api-Key"] == "env-key" - - -def test_coerce_max_results_inf_falls_back_to_default(): - """A YAML `.inf` max_results must fall back to the default, not crash.""" - import deerflow.community.serply.tools as serply_mod - - assert serply_mod._coerce_max_results(float("inf")) == serply_mod._DEFAULT_MAX_RESULTS