fix(tencent_wsa): fall back when digit-class max_results cannot parse (#6973)

* 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
This commit is contained in:
DDT 2026-10-18 10:51:13 +08:00 committed by GitHub
parent 75de35628d
commit 453563af5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 4 deletions

View File

@ -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

View File

@ -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: