mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-27 05:30:39 +00:00
fix(tools): make the search coercion guards catch OverflowError (#5957)
* fix(tools): make the search coercion guards catch OverflowError
The community search tools coerce max_results (and Sofya's content
limit) from YAML config or tool parameters through guards that catch
only TypeError and ValueError. `int(float('inf'))` raises OverflowError
instead, so a YAML `.inf` (or a JSON `Infinity`) value still crashes
the coercion - SearXNG's search even fails wholesale with
'cannot convert float infinity to integer'.
Catch OverflowError alongside them so an infinite value falls back to
the default like every other invalid input. NaN is already covered by
ValueError.
Signed-off-by: yetuge <2219677952@qq.com>
* test(tools): move the GroundRoute inf test to module scope
The regression test was appended inside TestWebSearchTool, ending the
class early and turning its last two methods into uncollected nested
functions. Move it after TestWebFetchTool so pytest collects all 20
tests again.
Signed-off-by: yetuge <2219677952@qq.com>
---------
Signed-off-by: yetuge <2219677952@qq.com>
This commit is contained in:
parent
1e26f00664
commit
5092a45943
@ -56,7 +56,7 @@ def _coerce_max_results(
|
||||
) -> int:
|
||||
try:
|
||||
coerced = int(value)
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
logger.warning(
|
||||
"Invalid Brave Search max_results=%r; using default %s",
|
||||
value,
|
||||
|
||||
@ -55,7 +55,7 @@ def _get_api_key(tool_name: str) -> str | None:
|
||||
def _coerce_max_results(value: object, *, default: int = _DEFAULT_MAX_RESULTS) -> int:
|
||||
try:
|
||||
coerced = int(value)
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
logger.warning("Invalid GroundRoute max_results=%r; using default %s", value, default)
|
||||
coerced = default
|
||||
return max(1, min(coerced, _MAX_RESULTS_CAP))
|
||||
|
||||
@ -46,7 +46,7 @@ async def web_search_tool(query: str, time_range: SearchTimeRange | None = None)
|
||||
else:
|
||||
try:
|
||||
max_results = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
logger.warning(
|
||||
"Invalid SearXNG max_results=%r; using default %s",
|
||||
raw,
|
||||
|
||||
@ -40,7 +40,7 @@ def _coerce_max_results(value: object, default: int = 5, max_allowed: int = _SER
|
||||
"""Coerce config/parameter input into a bounded positive result count."""
|
||||
try:
|
||||
count = int(value)
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
return default
|
||||
if count <= 0:
|
||||
return default
|
||||
|
||||
@ -58,7 +58,7 @@ def _coerce_max_results(
|
||||
) -> int:
|
||||
try:
|
||||
coerced = int(value)
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
logger.warning("Invalid Serply max_results=%r; using default %s", value, default)
|
||||
coerced = default
|
||||
if coerced < 1:
|
||||
|
||||
@ -46,7 +46,7 @@ def _coerce_max_results(value: object, default: int = 5, max_allowed: int = _SOF
|
||||
"""Coerce config/parameter input into a bounded positive result count."""
|
||||
try:
|
||||
count = int(value) # type: ignore[call-overload]
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
return default
|
||||
if count <= 0:
|
||||
return default
|
||||
@ -57,7 +57,7 @@ def _coerce_content_limit(value: object, default: int = _DEFAULT_CONTENTS_MAX_CH
|
||||
"""Coerce the per-result content limit. 0 means no limit; anything invalid falls back to the default."""
|
||||
try:
|
||||
limit = int(value) # type: ignore[call-overload]
|
||||
except (TypeError, ValueError):
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
return default
|
||||
return limit if limit >= 0 else default
|
||||
|
||||
|
||||
@ -731,3 +731,10 @@ def test_package_exports_image_search_tool():
|
||||
from deerflow.community.brave.tools import image_search_tool as direct_image_search_tool
|
||||
|
||||
assert image_search_tool is direct_image_search_tool
|
||||
|
||||
|
||||
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.brave.tools as brave_mod
|
||||
|
||||
assert brave_mod._coerce_max_results(float("inf")) == brave_mod._DEFAULT_MAX_RESULTS
|
||||
|
||||
@ -328,3 +328,10 @@ class TestWebFetchTool:
|
||||
patcher.stop()
|
||||
|
||||
assert result == "Error: No results found"
|
||||
|
||||
|
||||
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.groundroute.tools as groundroute_mod
|
||||
|
||||
assert groundroute_mod._coerce_max_results(float("inf")) == groundroute_mod._DEFAULT_MAX_RESULTS
|
||||
|
||||
@ -298,3 +298,24 @@ class TestSearxngTools:
|
||||
await tools.web_search_tool.ainvoke({"query": "latest release", "time_range": "week"})
|
||||
|
||||
mock_client.search.assert_called_once_with("latest release", max_results=5, time_range="week")
|
||||
|
||||
@patch("deerflow.community.searxng.tools._get_searxng_client")
|
||||
async def test_web_search_tool_inf_max_results_falls_back_to_default(self, mock_get_client):
|
||||
"""A YAML `.inf` max_results must fall back to the default, not error out."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.search = AsyncMock(
|
||||
return_value=[
|
||||
{"title": "Result 1", "url": "https://example.com/1", "content": "Desc 1"},
|
||||
]
|
||||
)
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
with patch(
|
||||
"deerflow.community.searxng.tools._get_tool_config",
|
||||
return_value={"max_results": float("inf")},
|
||||
):
|
||||
result = await tools.web_search_tool.ainvoke("test query")
|
||||
|
||||
data = json.loads(result)
|
||||
assert "error" not in data
|
||||
assert mock_client.search.call_args.kwargs["max_results"] == 5
|
||||
|
||||
@ -1212,3 +1212,10 @@ def test_package_exports_image_search_tool():
|
||||
from deerflow.community.serper.tools import image_search_tool as direct_image_search_tool
|
||||
|
||||
assert image_search_tool is direct_image_search_tool
|
||||
|
||||
|
||||
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.serper.tools as serper_mod
|
||||
|
||||
assert serper_mod._coerce_max_results(float("inf")) == 5
|
||||
|
||||
@ -316,3 +316,10 @@ 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
|
||||
|
||||
@ -591,3 +591,15 @@ class TestWebFetchTool:
|
||||
result = web_fetch_tool.invoke({"url": "https://example.com"})
|
||||
|
||||
assert result == "Error: SOFYA_API_KEY is not configured"
|
||||
|
||||
|
||||
class TestCoerceInfFallsBackToDefault:
|
||||
def test_coerce_max_results_inf_returns_default(self):
|
||||
from deerflow.community.sofya.tools import _coerce_max_results
|
||||
|
||||
assert _coerce_max_results(float("inf")) == 5
|
||||
|
||||
def test_coerce_content_limit_inf_returns_default(self):
|
||||
from deerflow.community.sofya.tools import _coerce_content_limit
|
||||
|
||||
assert _coerce_content_limit(float("inf")) == 2000
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user