From 453563af5be43b038432aa4827b96515b7e2b1b5 Mon Sep 17 00:00:00 2001 From: DDT <1786035110@qq.com> Date: Sun, 18 Oct 2026 10:51:13 +0800 Subject: [PATCH] fix(tencent_wsa): fall back when digit-class max_results cannot parse (#6973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(tencent_wsa): fall back when digit-class max_results cannot parse str.isdigit() accepts values like "²" and "1²" that int() rejects, so a configured max_results could crash web_search_tool before the HTTP call. Parse strings with int() inside try/except (matching sibling providers) while still rejecting bools and non-integral floats. Fixes #6921 * docs(tencent_wsa): clarify that all float max_results fall back to default --- .../deerflow/community/tencent_wsa/tools.py | 22 +++++++-- backend/tests/test_tencent_wsa_tools.py | 45 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/backend/packages/harness/deerflow/community/tencent_wsa/tools.py b/backend/packages/harness/deerflow/community/tencent_wsa/tools.py index 6e824c7e4f..9ad61e26b2 100644 --- a/backend/packages/harness/deerflow/community/tencent_wsa/tools.py +++ b/backend/packages/harness/deerflow/community/tencent_wsa/tools.py @@ -41,10 +41,26 @@ def _get_api_key(tool_name: str = "web_search", *, extras: Mapping[str, Any] | N def _coerce_max_results(value: object, *, default: int = _DEFAULT_MAX_RESULTS) -> int: - if isinstance(value, int) and not isinstance(value, bool): + """Coerce configured/tool max_results into a bounded positive int. + + Bools and all floats (including integral ones such as 2.0) fall back to + the default; only ints and integer strings are accepted. + String parsing uses ``int(...)`` inside a try/except so digit-class Unicode + that ``str.isdigit()`` accepts but ``int`` rejects (e.g. ``"²"``, ``"1²"``) + falls back to the default instead of raising, matching Brave/Serply/etc. + Valid decimal Unicode such as ``"٣"`` still parses. + """ + if isinstance(value, bool): + logger.warning("Invalid Tencent Cloud WSA max_results=%r; using default %s", value, default) + return default + if isinstance(value, int): result = value - elif isinstance(value, str) and value.strip().isdigit(): - result = int(value.strip()) + elif isinstance(value, str): + try: + result = int(value.strip()) + except (ValueError, OverflowError): + logger.warning("Invalid Tencent Cloud WSA max_results=%r; using default %s", value, default) + return default else: logger.warning("Invalid Tencent Cloud WSA max_results=%r; using default %s", value, default) return default diff --git a/backend/tests/test_tencent_wsa_tools.py b/backend/tests/test_tencent_wsa_tools.py index 0d27ce5ec3..e12d43b52d 100644 --- a/backend/tests/test_tencent_wsa_tools.py +++ b/backend/tests/test_tencent_wsa_tools.py @@ -331,13 +331,56 @@ class TestTencentWsaSearch: class TestTencentWsaConfiguration: @pytest.mark.parametrize( ("value", "expected"), - [(3, 3), ("7", 7), (True, 5), (2.9, 5), (0, 5), (-1, 5), (500, 50), ("bad", 5)], + [ + (3, 3), + ("7", 7), + (True, 5), + (2.9, 5), + (0, 5), + (-1, 5), + (500, 50), + ("bad", 5), + # str.isdigit() is true for these, but int() raises ValueError (#6921). + ("²", 5), + ("1²", 5), + # Valid non-ASCII decimal digits must still parse. + ("٣", 3), + ], ) def test_coerce_max_results(self, value, expected): from deerflow.community.tencent_wsa.tools import _coerce_max_results assert _coerce_max_results(value) == expected + def test_configured_unparseable_digit_max_results_falls_back(self): + """Config max_results that passes isdigit but fails int must not crash invoke.""" + page = json.dumps( + { + "title": "ok", + "url": "https://example.com", + "content": "snippet", + }, + ensure_ascii=False, + ) + http_response = MagicMock() + http_response.json.return_value = _response([page]) + _client, context_manager = _mock_http_client(http_response) + + with ( + patch("deerflow.community.tencent_wsa.tools.get_app_config") as get_config, + patch("deerflow.community.tencent_wsa.tools.httpx.Client", return_value=context_manager), + ): + get_config.return_value.get_tool_config.return_value = _tool_config({"api_key": "test-key", "max_results": "²"}) + + from deerflow.community.tencent_wsa.tools import web_search_tool + + result = json.loads(web_search_tool.run({"query": "腾讯云"})) + + assert "error" not in result + assert result["query"] == "腾讯云" + assert result["total_results"] == 1 + assert result["results"][0]["title"] == "ok" + @pytest.mark.parametrize("value", (None, "2", True, 2.0, "bad", -1, 3)) def test_invalid_mode_is_omitted(self, value): with patch("deerflow.community.tencent_wsa.tools.get_app_config") as get_config: