diff --git a/tests/tools/test_web_tools_config.py b/tests/tools/test_web_tools_config.py index 7fcf700d55..25ef647f7c 100644 --- a/tests/tools/test_web_tools_config.py +++ b/tests/tools/test_web_tools_config.py @@ -448,6 +448,54 @@ class TestParallelClientConfig: assert client1 is client2 +class TestWebSearchSchema: + """Test suite for web_search tool schema and handler wiring.""" + + def test_schema_exposes_optional_limit(self): + import tools.web_tools + + limit_schema = tools.web_tools.WEB_SEARCH_SCHEMA["parameters"]["properties"]["limit"] + + assert limit_schema["type"] == "integer" + assert limit_schema["minimum"] == 1 + assert limit_schema["maximum"] == 100 + assert limit_schema["default"] == 5 + assert "limit" not in tools.web_tools.WEB_SEARCH_SCHEMA["parameters"]["required"] + + def test_registered_handler_passes_limit(self): + import tools.web_tools + + entry = tools.web_tools.registry.get_entry("web_search") + with patch("tools.web_tools.web_search_tool", return_value='{"success": true}') as mock_search: + result = entry.handler({"query": "site:example.com docs", "limit": 12}) + + assert result == '{"success": true}' + mock_search.assert_called_once_with("site:example.com docs", limit=12) + + def test_registered_handler_defaults_limit_to_five(self): + import tools.web_tools + + entry = tools.web_tools.registry.get_entry("web_search") + with patch("tools.web_tools.web_search_tool", return_value='{"success": true}') as mock_search: + result = entry.handler({"query": "docs"}) + + assert result == '{"success": true}' + mock_search.assert_called_once_with("docs", limit=5) + + def test_web_search_clamps_limit_before_backend_call(self): + import tools.web_tools + + with patch("tools.web_tools._get_backend", return_value="parallel"), \ + patch("tools.web_tools._parallel_search", return_value={"success": True, "data": {"web": []}}) as mock_search, \ + patch("tools.interrupt.is_interrupted", return_value=False), \ + patch.object(tools.web_tools._debug, "log_call"), \ + patch.object(tools.web_tools._debug, "save"): + result = json.loads(tools.web_tools.web_search_tool("docs", limit=500)) + + assert result == {"success": True, "data": {"web": []}} + mock_search.assert_called_once_with("docs", 100) + + class TestWebSearchErrorHandling: """Test suite for web_search_tool() error responses.""" diff --git a/tools/web_tools.py b/tools/web_tools.py index 9e5d878da0..bc4b8703f0 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -1066,6 +1066,12 @@ def web_search_tool(query: str, limit: int = 5) -> str: Raises: Exception: If search fails or API key is not set """ + try: + limit = int(limit) + except (TypeError, ValueError): + limit = 5 + limit = min(max(limit, 1), 100) + debug_call_data = { "parameters": { "query": query, @@ -2047,13 +2053,20 @@ from tools.registry import registry, tool_error WEB_SEARCH_SCHEMA = { "name": "web_search", - "description": "Search the web for information on any topic. Returns up to 5 relevant results with titles, URLs, and descriptions.", + "description": "Search the web for information. Returns up to 5 results by default with titles, URLs, and descriptions. The query is passed through to the configured backend, so operators such as site:domain, filetype:pdf, intitle:word, -term, and \"exact phrase\" may work when the backend supports them.", "parameters": { "type": "object", "properties": { "query": { "type": "string", - "description": "The search query to look up on the web" + "description": "The search query to look up on the web. You may include backend-supported operators such as site:example.com, filetype:pdf, intitle:word, -term, or \"exact phrase\"." + }, + "limit": { + "type": "integer", + "description": "Maximum number of results to return. Defaults to 5.", + "minimum": 1, + "maximum": 100, + "default": 5 } }, "required": ["query"] @@ -2081,7 +2094,7 @@ registry.register( name="web_search", toolset="web", schema=WEB_SEARCH_SCHEMA, - handler=lambda args, **kw: web_search_tool(args.get("query", ""), limit=5), + handler=lambda args, **kw: web_search_tool(args.get("query", ""), limit=args.get("limit", 5)), check_fn=check_web_api_key, requires_env=_web_requires_env(), emoji="🔍",