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.
- serpapi_hermes_plugin-0.1.0/LICENSE +22 -0
- serpapi_hermes_plugin-0.1.0/PKG-INFO +188 -0
- serpapi_hermes_plugin-0.1.0/README.md +164 -0
- serpapi_hermes_plugin-0.1.0/pyproject.toml +62 -0
- serpapi_hermes_plugin-0.1.0/setup.cfg +4 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/__init__.py +30 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/client.py +98 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/plugin.yaml +16 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/provider.py +94 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/py.typed +1 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/schemas.py +168 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin/tools.py +255 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin.egg-info/PKG-INFO +188 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin.egg-info/SOURCES.txt +20 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin.egg-info/dependency_links.txt +1 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin.egg-info/entry_points.txt +2 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin.egg-info/requires.txt +1 -0
- serpapi_hermes_plugin-0.1.0/src/serpapi_hermes_plugin.egg-info/top_level.txt +1 -0
- serpapi_hermes_plugin-0.1.0/tests/test_live.py +115 -0
- serpapi_hermes_plugin-0.1.0/tests/test_project_metadata.py +27 -0
- serpapi_hermes_plugin-0.1.0/tests/test_provider.py +185 -0
- serpapi_hermes_plugin-0.1.0/tests/test_tools.py +216 -0
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|