serpapi-hermes-plugin 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SerpApi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: serpapi-hermes-plugin
3
+ Version: 0.1.0
4
+ Summary: SerpApi search plugin for Hermes Agent: web, Maps, News, and Shopping
5
+ Author: SerpApi
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/serpapi/serpapi-hermes-plugin
8
+ Project-URL: Repository, https://github.com/serpapi/serpapi-hermes-plugin
9
+ Project-URL: Issues, https://github.com/serpapi/serpapi-hermes-plugin/issues
10
+ Keywords: hermes-agent,plugin,serpapi,web-search,maps,news,shopping
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Plugins
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
19
+ Requires-Python: <3.15,>=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx<1,>=0.28.1
23
+ Dynamic: license-file
24
+
25
+ # SerpApi for Hermes Agent
26
+
27
+ Give [Hermes Agent](https://hermes-agent.nousresearch.com/) fast, fresh search
28
+ results from the web, Google Maps, Google News, and Google Shopping with
29
+ [SerpApi](https://serpapi.com/).
30
+
31
+ The plugin adds SerpApi to Hermes in two ways:
32
+
33
+ - Hermes's built-in `web_search` uses the fast Google Light engine.
34
+ - Dedicated Maps, News, and Shopping tools let Hermes choose the right SerpApi
35
+ engine for local places, current reporting, and product searches.
36
+
37
+ ## Ask Hermes to install it
38
+
39
+ You can let Hermes install and configure the plugin for you. Paste the message
40
+ below into a Hermes chat, then approve the install or terminal commands if
41
+ Hermes prompts you:
42
+
43
+ ```text
44
+ Install serpapi-hermes-plugin into the same Python environment that runs this
45
+ Hermes Agent. If this is a standard Hermes installation, run:
46
+
47
+ cd ~/.hermes/hermes-agent
48
+ uv pip install --python venv/bin/python serpapi-hermes-plugin
49
+
50
+ Otherwise, use the active Hermes Python environment to run:
51
+
52
+ uv pip install --python /path/to/hermes/python serpapi-hermes-plugin
53
+
54
+ Request my approval for install or terminal commands whenever required; do not
55
+ bypass approvals. After the package is installed, run:
56
+
57
+ hermes plugins enable serpapi
58
+
59
+ If Hermes asks whether to allow this plugin to replace built-in tools, answer
60
+ no; serpapi-hermes-plugin does not need tool-override access.
61
+
62
+ Then ask me for my SerpApi Private API Key, which I can copy from the SerpApi
63
+ dashboard. Do not ask for the key until installation and enablement succeed.
64
+ After I provide it, save it as SERPAPI_API_KEY in ~/.hermes/.env without
65
+ printing, logging, or committing it. Configure SerpApi as the Hermes web search
66
+ backend, tell me whether Hermes must be restarted, and verify that web search,
67
+ Maps, News, and Shopping tools are available. Use this key for future SerpApi
68
+ searches and never expose it in output.
69
+ ```
70
+
71
+ ## Install
72
+
73
+ You need a working
74
+ [Hermes Agent installation](https://hermes-agent.nousresearch.com/docs/getting-started/quickstart/)
75
+ and a SerpApi account.
76
+
77
+ Install the plugin from PyPI in the same Python environment as Hermes. If you
78
+ installed Hermes with its standard installer, use its `venv` interpreter
79
+ explicitly:
80
+
81
+ ```bash
82
+ cd ~/.hermes/hermes-agent
83
+ uv pip install --python venv/bin/python serpapi-hermes-plugin
84
+ ```
85
+
86
+ The explicit `--python` is important: the standard Hermes installer creates a
87
+ directory named `venv`, while bare `uv pip install` automatically looks for a
88
+ directory named `.venv`.
89
+
90
+ For a custom Hermes installation, point uv at the Python interpreter that runs
91
+ Hermes:
92
+
93
+ ```bash
94
+ uv pip install --python /path/to/hermes/python serpapi-hermes-plugin
95
+ ```
96
+
97
+ If that environment already has pip and is activated, the equivalent command
98
+ is `python -m pip install serpapi-hermes-plugin`.
99
+
100
+ Restart any running Hermes session after installation.
101
+
102
+ ## Get your SerpApi API key
103
+
104
+ 1. [Create a SerpApi account](https://serpapi.com/users/sign_up), or sign in to
105
+ your existing account.
106
+ 2. Open the [SerpApi dashboard](https://serpapi.com/dashboard).
107
+ 3. Find your **Private API Key** in the dashboard and copy it.
108
+
109
+ One API key powers every engine in this plugin. Keep it private and never add it
110
+ to source control.
111
+
112
+ ## Connect the API key to Hermes
113
+
114
+ Enable the installed plugin:
115
+
116
+ ```bash
117
+ hermes plugins enable serpapi
118
+ ```
119
+
120
+ If prompted about permission to replace built-in tools, answer **no**. This
121
+ plugin adds new tools and a web-search provider; it does not replace built-in
122
+ tool handlers.
123
+
124
+ Open Hermes's interactive tool configuration:
125
+
126
+ ```bash
127
+ hermes tools
128
+ ```
129
+
130
+ In the **Web Search & Extract** section:
131
+
132
+ 1. Select **SerpApi**.
133
+ 2. Paste the Private API Key copied from your SerpApi dashboard.
134
+ 3. Confirm the selection.
135
+
136
+ Hermes saves the key as `SERPAPI_API_KEY` in its environment configuration and
137
+ selects SerpApi for future `web_search` calls. Restart Hermes if a session was
138
+ already running.
139
+
140
+ ## Manual API-key setup
141
+
142
+ Instead of using `hermes tools`, add the key to `~/.hermes/.env`:
143
+
144
+ ```dotenv
145
+ SERPAPI_API_KEY=your_private_api_key
146
+ ```
147
+
148
+ Then configure `~/.hermes/config.yaml`:
149
+
150
+ ```yaml
151
+ plugins:
152
+ enabled:
153
+ - serpapi
154
+
155
+ web:
156
+ search_backend: serpapi
157
+ ```
158
+
159
+ You may also export `SERPAPI_API_KEY` in the environment that starts Hermes.
160
+ The environment variable takes precedence over the value in `~/.hermes/.env`.
161
+
162
+ ## Search capabilities
163
+
164
+ | What you ask for | Hermes tool | SerpApi engine |
165
+ |---|---|---|
166
+ | General web research | `web_search` | `google_light` |
167
+ | Places and local businesses | `serpapi_maps_search` | `google_maps` |
168
+ | Current and recent news | `serpapi_news_search` | `google_news_light` |
169
+ | Products, prices, and merchants | `serpapi_shopping_search` | `google_shopping_light` |
170
+
171
+ Hermes chooses a tool from your request. Each tool selects and validates its own
172
+ SerpApi engine, so you do not need to specify an engine name.
173
+
174
+ Example prompts:
175
+
176
+ - "Search the web for the latest Python 3.14 release notes."
177
+ - "Find highly rated coffee shops near Times Square."
178
+ - "Show me recent news about reusable rockets."
179
+ - "Find well-reviewed laptops under $1,200 with free shipping."
180
+
181
+ ## Contributing
182
+
183
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, tests, pull request
184
+ guidelines, and the release process.
185
+
186
+ ## License
187
+
188
+ [MIT](LICENSE)
@@ -0,0 +1,164 @@
1
+ # SerpApi for Hermes Agent
2
+
3
+ Give [Hermes Agent](https://hermes-agent.nousresearch.com/) fast, fresh search
4
+ results from the web, Google Maps, Google News, and Google Shopping with
5
+ [SerpApi](https://serpapi.com/).
6
+
7
+ The plugin adds SerpApi to Hermes in two ways:
8
+
9
+ - Hermes's built-in `web_search` uses the fast Google Light engine.
10
+ - Dedicated Maps, News, and Shopping tools let Hermes choose the right SerpApi
11
+ engine for local places, current reporting, and product searches.
12
+
13
+ ## Ask Hermes to install it
14
+
15
+ You can let Hermes install and configure the plugin for you. Paste the message
16
+ below into a Hermes chat, then approve the install or terminal commands if
17
+ Hermes prompts you:
18
+
19
+ ```text
20
+ Install serpapi-hermes-plugin into the same Python environment that runs this
21
+ Hermes Agent. If this is a standard Hermes installation, run:
22
+
23
+ cd ~/.hermes/hermes-agent
24
+ uv pip install --python venv/bin/python serpapi-hermes-plugin
25
+
26
+ Otherwise, use the active Hermes Python environment to run:
27
+
28
+ uv pip install --python /path/to/hermes/python serpapi-hermes-plugin
29
+
30
+ Request my approval for install or terminal commands whenever required; do not
31
+ bypass approvals. After the package is installed, run:
32
+
33
+ hermes plugins enable serpapi
34
+
35
+ If Hermes asks whether to allow this plugin to replace built-in tools, answer
36
+ no; serpapi-hermes-plugin does not need tool-override access.
37
+
38
+ Then ask me for my SerpApi Private API Key, which I can copy from the SerpApi
39
+ dashboard. Do not ask for the key until installation and enablement succeed.
40
+ After I provide it, save it as SERPAPI_API_KEY in ~/.hermes/.env without
41
+ printing, logging, or committing it. Configure SerpApi as the Hermes web search
42
+ backend, tell me whether Hermes must be restarted, and verify that web search,
43
+ Maps, News, and Shopping tools are available. Use this key for future SerpApi
44
+ searches and never expose it in output.
45
+ ```
46
+
47
+ ## Install
48
+
49
+ You need a working
50
+ [Hermes Agent installation](https://hermes-agent.nousresearch.com/docs/getting-started/quickstart/)
51
+ and a SerpApi account.
52
+
53
+ Install the plugin from PyPI in the same Python environment as Hermes. If you
54
+ installed Hermes with its standard installer, use its `venv` interpreter
55
+ explicitly:
56
+
57
+ ```bash
58
+ cd ~/.hermes/hermes-agent
59
+ uv pip install --python venv/bin/python serpapi-hermes-plugin
60
+ ```
61
+
62
+ The explicit `--python` is important: the standard Hermes installer creates a
63
+ directory named `venv`, while bare `uv pip install` automatically looks for a
64
+ directory named `.venv`.
65
+
66
+ For a custom Hermes installation, point uv at the Python interpreter that runs
67
+ Hermes:
68
+
69
+ ```bash
70
+ uv pip install --python /path/to/hermes/python serpapi-hermes-plugin
71
+ ```
72
+
73
+ If that environment already has pip and is activated, the equivalent command
74
+ is `python -m pip install serpapi-hermes-plugin`.
75
+
76
+ Restart any running Hermes session after installation.
77
+
78
+ ## Get your SerpApi API key
79
+
80
+ 1. [Create a SerpApi account](https://serpapi.com/users/sign_up), or sign in to
81
+ your existing account.
82
+ 2. Open the [SerpApi dashboard](https://serpapi.com/dashboard).
83
+ 3. Find your **Private API Key** in the dashboard and copy it.
84
+
85
+ One API key powers every engine in this plugin. Keep it private and never add it
86
+ to source control.
87
+
88
+ ## Connect the API key to Hermes
89
+
90
+ Enable the installed plugin:
91
+
92
+ ```bash
93
+ hermes plugins enable serpapi
94
+ ```
95
+
96
+ If prompted about permission to replace built-in tools, answer **no**. This
97
+ plugin adds new tools and a web-search provider; it does not replace built-in
98
+ tool handlers.
99
+
100
+ Open Hermes's interactive tool configuration:
101
+
102
+ ```bash
103
+ hermes tools
104
+ ```
105
+
106
+ In the **Web Search & Extract** section:
107
+
108
+ 1. Select **SerpApi**.
109
+ 2. Paste the Private API Key copied from your SerpApi dashboard.
110
+ 3. Confirm the selection.
111
+
112
+ Hermes saves the key as `SERPAPI_API_KEY` in its environment configuration and
113
+ selects SerpApi for future `web_search` calls. Restart Hermes if a session was
114
+ already running.
115
+
116
+ ## Manual API-key setup
117
+
118
+ Instead of using `hermes tools`, add the key to `~/.hermes/.env`:
119
+
120
+ ```dotenv
121
+ SERPAPI_API_KEY=your_private_api_key
122
+ ```
123
+
124
+ Then configure `~/.hermes/config.yaml`:
125
+
126
+ ```yaml
127
+ plugins:
128
+ enabled:
129
+ - serpapi
130
+
131
+ web:
132
+ search_backend: serpapi
133
+ ```
134
+
135
+ You may also export `SERPAPI_API_KEY` in the environment that starts Hermes.
136
+ The environment variable takes precedence over the value in `~/.hermes/.env`.
137
+
138
+ ## Search capabilities
139
+
140
+ | What you ask for | Hermes tool | SerpApi engine |
141
+ |---|---|---|
142
+ | General web research | `web_search` | `google_light` |
143
+ | Places and local businesses | `serpapi_maps_search` | `google_maps` |
144
+ | Current and recent news | `serpapi_news_search` | `google_news_light` |
145
+ | Products, prices, and merchants | `serpapi_shopping_search` | `google_shopping_light` |
146
+
147
+ Hermes chooses a tool from your request. Each tool selects and validates its own
148
+ SerpApi engine, so you do not need to specify an engine name.
149
+
150
+ Example prompts:
151
+
152
+ - "Search the web for the latest Python 3.14 release notes."
153
+ - "Find highly rated coffee shops near Times Square."
154
+ - "Show me recent news about reusable rockets."
155
+ - "Find well-reviewed laptops under $1,200 with free shipping."
156
+
157
+ ## Contributing
158
+
159
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, tests, pull request
160
+ guidelines, and the release process.
161
+
162
+ ## License
163
+
164
+ [MIT](LICENSE)
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77,<83"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "serpapi-hermes-plugin"
7
+ version = "0.1.0"
8
+ description = "SerpApi search plugin for Hermes Agent: web, Maps, News, and Shopping"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11,<3.15"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "SerpApi" }]
14
+ keywords = ["hermes-agent", "plugin", "serpapi", "web-search", "maps", "news", "shopping"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Plugins",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
24
+ ]
25
+ dependencies = [
26
+ "httpx>=0.28.1,<1",
27
+ ]
28
+
29
+ [project.entry-points."hermes_agent.plugins"]
30
+ serpapi = "serpapi_hermes_plugin"
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/serpapi/serpapi-hermes-plugin"
34
+ Repository = "https://github.com/serpapi/serpapi-hermes-plugin"
35
+ Issues = "https://github.com/serpapi/serpapi-hermes-plugin/issues"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "pytest>=8.3,<10",
40
+ "ruff>=0.12,<1",
41
+ "twine>=6.1,<7",
42
+ ]
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+
47
+ [tool.setuptools.package-data]
48
+ serpapi_hermes_plugin = ["plugin.yaml", "py.typed"]
49
+
50
+ [tool.pytest.ini_options]
51
+ addopts = "-q"
52
+ testpaths = ["tests"]
53
+ markers = [
54
+ "live: makes real SerpApi requests and requires SERPAPI_API_KEY",
55
+ ]
56
+
57
+ [tool.ruff]
58
+ line-length = 100
59
+ target-version = "py311"
60
+
61
+ [tool.ruff.lint]
62
+ select = ["B", "E", "F", "I", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ """SerpApi web, Maps, News, and Shopping search for Hermes Agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .provider import SerpApiWebSearchProvider
6
+ from .schemas import MAPS_SEARCH_SCHEMA, NEWS_SEARCH_SCHEMA, SHOPPING_SEARCH_SCHEMA
7
+ from .tools import maps_search, news_search, shopping_search
8
+
9
+ __all__ = ["SerpApiWebSearchProvider", "register"]
10
+
11
+
12
+ def register(ctx) -> None:
13
+ """Register SerpApi's web provider and specialized search tools."""
14
+ provider = SerpApiWebSearchProvider()
15
+ ctx.register_web_search_provider(provider)
16
+
17
+ for schema, handler in (
18
+ (MAPS_SEARCH_SCHEMA, maps_search),
19
+ (NEWS_SEARCH_SCHEMA, news_search),
20
+ (SHOPPING_SEARCH_SCHEMA, shopping_search),
21
+ ):
22
+ ctx.register_tool(
23
+ name=schema["name"],
24
+ toolset="serpapi",
25
+ schema=schema,
26
+ handler=handler,
27
+ check_fn=provider.is_available,
28
+ requires_env=["SERPAPI_API_KEY"],
29
+ description=schema["description"],
30
+ )
@@ -0,0 +1,98 @@
1
+ """Shared SerpApi HTTP client for the provider and specialized tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Mapping
7
+ from typing import Any
8
+
9
+ import httpx
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ API_KEY_ENV = "SERPAPI_API_KEY"
14
+ ENDPOINT = "https://serpapi.com/search.json"
15
+ TIMEOUT_SECONDS = 15.0
16
+
17
+
18
+ class SerpApiError(RuntimeError):
19
+ """A safe, user-facing SerpApi request error."""
20
+
21
+
22
+ def get_api_key() -> str:
23
+ """Read the key through Hermes so values in ``~/.hermes/.env`` work."""
24
+ from agent.web_search_provider import get_provider_env
25
+
26
+ return get_provider_env(API_KEY_ENV)
27
+
28
+
29
+ def is_configured() -> bool:
30
+ """Return whether Hermes can resolve a SerpApi API key locally."""
31
+ return bool(get_api_key())
32
+
33
+
34
+ def _safe_api_error(value: Any, api_key: str) -> str:
35
+ """Return a bounded API error without ever echoing the credential."""
36
+ message = str(value or "Unknown SerpApi error").replace(api_key, "[redacted]")
37
+ return message[:500]
38
+
39
+
40
+ def call_serpapi(engine: str, params: Mapping[str, Any]) -> dict[str, Any]:
41
+ """Call one SerpApi engine and return its decoded JSON response."""
42
+ api_key = get_api_key()
43
+ if not api_key:
44
+ raise SerpApiError(f"{API_KEY_ENV} is not set. Run `hermes tools` to configure SerpApi.")
45
+
46
+ request_params = {
47
+ key: value
48
+ for key, value in params.items()
49
+ if key not in {"api_key", "engine"} and value is not None and value != ""
50
+ }
51
+ request_params.update(
52
+ {
53
+ "engine": engine,
54
+ "api_key": api_key,
55
+ "output": "json",
56
+ }
57
+ )
58
+
59
+ try:
60
+ response = httpx.get(
61
+ ENDPOINT,
62
+ params=request_params,
63
+ headers={
64
+ "Accept": "application/json",
65
+ "X-Client-Source": "hermes",
66
+ },
67
+ timeout=TIMEOUT_SECONDS,
68
+ )
69
+ response.raise_for_status()
70
+ except httpx.HTTPStatusError as exc:
71
+ status = exc.response.status_code
72
+ logger.warning("SerpApi returned HTTP %d", status)
73
+ if status == 401:
74
+ message = "SerpApi rejected the API key"
75
+ elif status == 429:
76
+ message = "SerpApi quota exhausted; try again later"
77
+ elif status >= 500:
78
+ message = f"SerpApi upstream error (HTTP {status}); try again shortly"
79
+ else:
80
+ message = f"SerpApi request failed (HTTP {status})"
81
+ raise SerpApiError(message) from None
82
+ except httpx.RequestError as exc:
83
+ logger.warning("SerpApi request failed (%s)", type(exc).__name__)
84
+ raise SerpApiError("Could not reach SerpApi; try again shortly") from None
85
+
86
+ try:
87
+ payload = response.json()
88
+ except ValueError:
89
+ logger.warning("SerpApi returned malformed JSON")
90
+ raise SerpApiError("SerpApi returned malformed JSON") from None
91
+
92
+ if not isinstance(payload, dict):
93
+ raise SerpApiError("SerpApi returned an unexpected response")
94
+
95
+ if payload.get("error"):
96
+ raise SerpApiError(_safe_api_error(payload["error"], api_key))
97
+
98
+ return payload
@@ -0,0 +1,16 @@
1
+ name: serpapi
2
+ version: 0.1.0
3
+ description: "SerpApi search for Hermes Agent: web, Maps, News, and Shopping."
4
+ author: SerpApi
5
+ kind: backend
6
+ provides_web_providers:
7
+ - serpapi
8
+ provides_tools:
9
+ - serpapi_maps_search
10
+ - serpapi_news_search
11
+ - serpapi_shopping_search
12
+ requires_env:
13
+ - name: SERPAPI_API_KEY
14
+ description: "API key for SerpApi web search"
15
+ url: "https://serpapi.com/manage-api-key"
16
+ secret: true
@@ -0,0 +1,94 @@
1
+ """Hermes web-search provider backed by SerpApi's Google Light API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from agent.web_search_provider import WebSearchProvider
9
+
10
+ from .client import API_KEY_ENV, SerpApiError, call_serpapi, is_configured
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ _ENGINE = "google_light"
15
+ _MAX_RESULTS = 20
16
+
17
+
18
+ class SerpApiWebSearchProvider(WebSearchProvider):
19
+ """Search-only Hermes provider using SerpApi's low-latency Google Light engine."""
20
+
21
+ @property
22
+ def name(self) -> str:
23
+ return "serpapi"
24
+
25
+ @property
26
+ def display_name(self) -> str:
27
+ return "SerpApi"
28
+
29
+ def is_available(self) -> bool:
30
+ """Check local configuration without making a network request."""
31
+ return is_configured()
32
+
33
+ def supports_search(self) -> bool:
34
+ return True
35
+
36
+ def supports_extract(self) -> bool:
37
+ return False
38
+
39
+ def search(self, query: str, limit: int = 5) -> dict[str, Any]:
40
+ """Search Google Light and return Hermes's standard web result envelope."""
41
+ query = str(query or "").strip()
42
+ if not query:
43
+ return {"success": False, "error": "Search query must not be empty"}
44
+
45
+ try:
46
+ result_limit = max(1, min(int(limit), _MAX_RESULTS))
47
+ except (TypeError, ValueError):
48
+ result_limit = 5
49
+
50
+ try:
51
+ payload = call_serpapi(
52
+ _ENGINE,
53
+ {
54
+ "q": query,
55
+ "num": result_limit,
56
+ },
57
+ )
58
+ except SerpApiError as exc:
59
+ return {"success": False, "error": str(exc)}
60
+
61
+ organic_results = payload.get("organic_results", [])
62
+ if not isinstance(organic_results, list):
63
+ return {"success": False, "error": "SerpApi returned an unexpected response"}
64
+
65
+ web_results = []
66
+ for result in organic_results[:result_limit]:
67
+ if not isinstance(result, dict):
68
+ continue
69
+ web_results.append(
70
+ {
71
+ "title": str(result.get("title", "")),
72
+ "url": str(result.get("link", "")),
73
+ "description": str(result.get("snippet", "")),
74
+ "position": len(web_results) + 1,
75
+ }
76
+ )
77
+
78
+ logger.info("SerpApi search returned %d result(s)", len(web_results))
79
+ return {"success": True, "data": {"web": web_results}}
80
+
81
+ def get_setup_schema(self) -> dict[str, Any]:
82
+ """Describe SerpApi to Hermes's interactive provider picker."""
83
+ return {
84
+ "name": "SerpApi",
85
+ "badge": "Google Light",
86
+ "tag": "Fast Google Light web search via SerpApi.",
87
+ "env_vars": [
88
+ {
89
+ "key": API_KEY_ENV,
90
+ "prompt": "SerpApi API key",
91
+ "url": "https://serpapi.com/manage-api-key",
92
+ }
93
+ ],
94
+ }