google-adk-scavio 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.
- google_adk_scavio-0.1.0/.github/workflows/publish.yml +44 -0
- google_adk_scavio-0.1.0/.gitignore +5 -0
- google_adk_scavio-0.1.0/LICENSE +21 -0
- google_adk_scavio-0.1.0/PKG-INFO +102 -0
- google_adk_scavio-0.1.0/README.md +84 -0
- google_adk_scavio-0.1.0/google_adk_scavio/__init__.py +6 -0
- google_adk_scavio-0.1.0/google_adk_scavio/tools.py +179 -0
- google_adk_scavio-0.1.0/pyproject.toml +28 -0
- google_adk_scavio-0.1.0/tests/test_tools.py +145 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pypi:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
|
|
18
|
+
- name: Check if version is new
|
|
19
|
+
id: check
|
|
20
|
+
run: |
|
|
21
|
+
NAME=$(grep -m1 '^name' pyproject.toml | sed -E 's/.*"(.*)".*/\1/')
|
|
22
|
+
VERSION=$(grep -m1 '^version' pyproject.toml | sed -E 's/.*"(.*)".*/\1/')
|
|
23
|
+
echo "version=$VERSION"
|
|
24
|
+
if curl -fsSL "https://pypi.org/pypi/$NAME/$VERSION/json" > /dev/null 2>&1; then
|
|
25
|
+
echo "new=false" >> "$GITHUB_OUTPUT"
|
|
26
|
+
echo "$NAME $VERSION already on PyPI, skipping"
|
|
27
|
+
else
|
|
28
|
+
echo "new=true" >> "$GITHUB_OUTPUT"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
- name: Install build tools
|
|
32
|
+
if: steps.check.outputs.new == 'true'
|
|
33
|
+
run: pip install build twine
|
|
34
|
+
|
|
35
|
+
- name: Build
|
|
36
|
+
if: steps.check.outputs.new == 'true'
|
|
37
|
+
run: python -m build
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
if: steps.check.outputs.new == 'true'
|
|
41
|
+
env:
|
|
42
|
+
TWINE_USERNAME: __token__
|
|
43
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
44
|
+
run: twine upload dist/*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scavio
|
|
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.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: google-adk-scavio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scavio real-time search tools (Google, YouTube, Amazon, Walmart, Reddit) for Google ADK (Agent Development Kit)
|
|
5
|
+
Project-URL: Homepage, https://scavio.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/scavio-ai/google-adk-scavio
|
|
7
|
+
Project-URL: Documentation, https://scavio.dev/docs/google-adk
|
|
8
|
+
Author-email: Scavio <support@scavio.dev>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: adk,agent-development-kit,agents,google-adk,scavio,search,serp,tavily-alternative
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: google-adk>=1.0
|
|
14
|
+
Requires-Dist: scavio>=0.5.0
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# google-adk-scavio
|
|
20
|
+
|
|
21
|
+
[Scavio](https://scavio.dev) real-time search tools for [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) — Google, YouTube, Amazon, Walmart, and Reddit, with one API key.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install google-adk-scavio
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Setup
|
|
30
|
+
|
|
31
|
+
Get a Scavio API key from the [Scavio Dashboard](https://dashboard.scavio.dev) (new accounts get 50 free signup credits, no credit card). Set `SCAVIO_API_KEY` or pass `api_key=` to the factory.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from google.adk.agents import Agent
|
|
37
|
+
from google_adk_scavio import create_scavio_tools
|
|
38
|
+
|
|
39
|
+
root_agent = Agent(
|
|
40
|
+
name="search_assistant",
|
|
41
|
+
model="gemini-2.5-flash",
|
|
42
|
+
instruction="Search the web, news, shopping sites, and Reddit with the Scavio tools.",
|
|
43
|
+
tools=create_scavio_tools(), # reads SCAVIO_API_KEY
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Expose only the providers you need:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
tools = create_scavio_tools(
|
|
51
|
+
enable_google=True,
|
|
52
|
+
enable_reddit=True,
|
|
53
|
+
enable_youtube=False,
|
|
54
|
+
enable_amazon=False,
|
|
55
|
+
enable_walmart=False,
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Tools
|
|
60
|
+
|
|
61
|
+
`create_scavio_tools()` returns plain typed Python functions; ADK wraps each one in a `FunctionTool` automatically. Each returns the structured Scavio JSON response as a dict; errors come back as `{"error": "..."}` rather than raising, so a failed call never crashes the run.
|
|
62
|
+
|
|
63
|
+
| Tool | Description | Credits |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `scavio_google_search` | Google web results (organic, knowledge graph, AI overview) | 1 |
|
|
66
|
+
| `scavio_google_news` | Google News articles | 1 |
|
|
67
|
+
| `scavio_youtube_search` | YouTube videos, channels, playlists | 1 |
|
|
68
|
+
| `scavio_youtube_metadata` | Full metadata for a video by id | 1 |
|
|
69
|
+
| `scavio_amazon_search` | Amazon product search | 1 |
|
|
70
|
+
| `scavio_amazon_product` | Amazon product details by ASIN | 1 |
|
|
71
|
+
| `scavio_walmart_search` | Walmart product search | 1 |
|
|
72
|
+
| `scavio_walmart_product` | Walmart product details by product id | 1 |
|
|
73
|
+
| `scavio_reddit_search` | Reddit posts or comments search | 2 |
|
|
74
|
+
| `scavio_reddit_post` | Reddit post with threaded comments by URL | 2 |
|
|
75
|
+
|
|
76
|
+
## Use every endpoint via MCP
|
|
77
|
+
|
|
78
|
+
For the full Scavio API (TikTok, Instagram, Google Maps, Shopping, Trends, and more), point ADK at the hosted MCP server. Requires ADK's MCP extra: `pip install "google-adk[mcp]"`.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from google.adk.agents import Agent
|
|
82
|
+
from google.adk.tools.mcp_tool import McpToolset, StreamableHTTPConnectionParams
|
|
83
|
+
|
|
84
|
+
scavio = McpToolset(
|
|
85
|
+
connection_params=StreamableHTTPConnectionParams(
|
|
86
|
+
url="https://mcp.scavio.dev/mcp",
|
|
87
|
+
headers={"x-api-key": "sk_live_..."},
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
root_agent = Agent(name="search_assistant", model="gemini-2.5-flash", tools=[scavio])
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Links
|
|
95
|
+
|
|
96
|
+
- Scavio: https://scavio.dev
|
|
97
|
+
- Docs: https://scavio.dev/docs/google-adk
|
|
98
|
+
- Dashboard: https://dashboard.scavio.dev
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# google-adk-scavio
|
|
2
|
+
|
|
3
|
+
[Scavio](https://scavio.dev) real-time search tools for [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) — Google, YouTube, Amazon, Walmart, and Reddit, with one API key.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install google-adk-scavio
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Get a Scavio API key from the [Scavio Dashboard](https://dashboard.scavio.dev) (new accounts get 50 free signup credits, no credit card). Set `SCAVIO_API_KEY` or pass `api_key=` to the factory.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from google.adk.agents import Agent
|
|
19
|
+
from google_adk_scavio import create_scavio_tools
|
|
20
|
+
|
|
21
|
+
root_agent = Agent(
|
|
22
|
+
name="search_assistant",
|
|
23
|
+
model="gemini-2.5-flash",
|
|
24
|
+
instruction="Search the web, news, shopping sites, and Reddit with the Scavio tools.",
|
|
25
|
+
tools=create_scavio_tools(), # reads SCAVIO_API_KEY
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Expose only the providers you need:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
tools = create_scavio_tools(
|
|
33
|
+
enable_google=True,
|
|
34
|
+
enable_reddit=True,
|
|
35
|
+
enable_youtube=False,
|
|
36
|
+
enable_amazon=False,
|
|
37
|
+
enable_walmart=False,
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
`create_scavio_tools()` returns plain typed Python functions; ADK wraps each one in a `FunctionTool` automatically. Each returns the structured Scavio JSON response as a dict; errors come back as `{"error": "..."}` rather than raising, so a failed call never crashes the run.
|
|
44
|
+
|
|
45
|
+
| Tool | Description | Credits |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `scavio_google_search` | Google web results (organic, knowledge graph, AI overview) | 1 |
|
|
48
|
+
| `scavio_google_news` | Google News articles | 1 |
|
|
49
|
+
| `scavio_youtube_search` | YouTube videos, channels, playlists | 1 |
|
|
50
|
+
| `scavio_youtube_metadata` | Full metadata for a video by id | 1 |
|
|
51
|
+
| `scavio_amazon_search` | Amazon product search | 1 |
|
|
52
|
+
| `scavio_amazon_product` | Amazon product details by ASIN | 1 |
|
|
53
|
+
| `scavio_walmart_search` | Walmart product search | 1 |
|
|
54
|
+
| `scavio_walmart_product` | Walmart product details by product id | 1 |
|
|
55
|
+
| `scavio_reddit_search` | Reddit posts or comments search | 2 |
|
|
56
|
+
| `scavio_reddit_post` | Reddit post with threaded comments by URL | 2 |
|
|
57
|
+
|
|
58
|
+
## Use every endpoint via MCP
|
|
59
|
+
|
|
60
|
+
For the full Scavio API (TikTok, Instagram, Google Maps, Shopping, Trends, and more), point ADK at the hosted MCP server. Requires ADK's MCP extra: `pip install "google-adk[mcp]"`.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from google.adk.agents import Agent
|
|
64
|
+
from google.adk.tools.mcp_tool import McpToolset, StreamableHTTPConnectionParams
|
|
65
|
+
|
|
66
|
+
scavio = McpToolset(
|
|
67
|
+
connection_params=StreamableHTTPConnectionParams(
|
|
68
|
+
url="https://mcp.scavio.dev/mcp",
|
|
69
|
+
headers={"x-api-key": "sk_live_..."},
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
root_agent = Agent(name="search_assistant", model="gemini-2.5-flash", tools=[scavio])
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Links
|
|
77
|
+
|
|
78
|
+
- Scavio: https://scavio.dev
|
|
79
|
+
- Docs: https://scavio.dev/docs/google-adk
|
|
80
|
+
- Dashboard: https://dashboard.scavio.dev
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Scavio search tools for Google ADK (Agent Development Kit).
|
|
2
|
+
|
|
3
|
+
Build the tools with `create_scavio_tools()` and pass them to an ADK Agent:
|
|
4
|
+
|
|
5
|
+
from google.adk.agents import Agent
|
|
6
|
+
from google_adk_scavio import create_scavio_tools
|
|
7
|
+
|
|
8
|
+
agent = Agent(name="search_assistant", model="gemini-2.5-flash", tools=create_scavio_tools())
|
|
9
|
+
|
|
10
|
+
The factory returns plain Python functions with typed parameters and docstrings;
|
|
11
|
+
ADK wraps each one in a FunctionTool automatically. Each provider is gated by an
|
|
12
|
+
``enable_*`` flag. Tools return the Scavio JSON response as a dict.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any, Callable, Dict, Optional
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from scavio import ScavioClient
|
|
20
|
+
except ImportError as exc: # pragma: no cover
|
|
21
|
+
raise ImportError("`scavio` not installed. Please install using `pip install scavio`") from exc
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _run(call: Callable[[], Dict[str, Any]]) -> Dict[str, Any]:
|
|
25
|
+
"""Run a Scavio SDK call, returning its JSON dict or an {"error": ...} dict."""
|
|
26
|
+
try:
|
|
27
|
+
return call()
|
|
28
|
+
except Exception as exc: # noqa: BLE001 - surface errors to the agent
|
|
29
|
+
return {"error": str(exc)}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def create_scavio_tools(
|
|
33
|
+
api_key: Optional[str] = None,
|
|
34
|
+
*,
|
|
35
|
+
enable_google: bool = True,
|
|
36
|
+
enable_youtube: bool = True,
|
|
37
|
+
enable_amazon: bool = True,
|
|
38
|
+
enable_walmart: bool = True,
|
|
39
|
+
enable_reddit: bool = True,
|
|
40
|
+
) -> list:
|
|
41
|
+
"""Build Scavio search tools for a Google ADK Agent.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
api_key: Scavio API key. Falls back to the SCAVIO_API_KEY env var.
|
|
45
|
+
enable_google: Register the Google tools. Defaults to True.
|
|
46
|
+
enable_youtube: Register the YouTube tools. Defaults to True.
|
|
47
|
+
enable_amazon: Register the Amazon tools. Defaults to True.
|
|
48
|
+
enable_walmart: Register the Walmart tools. Defaults to True.
|
|
49
|
+
enable_reddit: Register the Reddit tools. Defaults to True.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A list of plain functions ready to pass as ``tools=`` to an ADK Agent.
|
|
53
|
+
"""
|
|
54
|
+
client = ScavioClient(api_key=api_key or os.getenv("SCAVIO_API_KEY"))
|
|
55
|
+
tools: list = []
|
|
56
|
+
|
|
57
|
+
if enable_google:
|
|
58
|
+
def scavio_google_search(query: str, gl: Optional[str] = None, hl: Optional[str] = None) -> dict:
|
|
59
|
+
"""Search Google for real-time web results (organic results, knowledge graph, AI overview). Costs 1 credit.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
query: The search query.
|
|
63
|
+
gl: Two-letter ISO country code for the search, e.g. us.
|
|
64
|
+
hl: Two-letter language code for the results, e.g. en.
|
|
65
|
+
"""
|
|
66
|
+
_p = {"gl": gl, "hl": hl}
|
|
67
|
+
return _run(lambda: client.google.search(query, **{k: v for k, v in _p.items() if v is not None}))
|
|
68
|
+
tools.append(scavio_google_search)
|
|
69
|
+
|
|
70
|
+
def scavio_google_news(query: str, gl: Optional[str] = None, hl: Optional[str] = None) -> dict:
|
|
71
|
+
"""Search Google News for current news articles on a topic. Costs 1 credit.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
query: The news search query.
|
|
75
|
+
gl: Two-letter ISO country code for the search, e.g. us.
|
|
76
|
+
hl: Two-letter language code for the results, e.g. en.
|
|
77
|
+
"""
|
|
78
|
+
_p = {"query": query, "gl": gl, "hl": hl}
|
|
79
|
+
return _run(lambda: client.google.news(**{k: v for k, v in _p.items() if v is not None}))
|
|
80
|
+
tools.append(scavio_google_news)
|
|
81
|
+
|
|
82
|
+
if enable_youtube:
|
|
83
|
+
def scavio_youtube_search(query: str, upload_date: Optional[str] = None, type: Optional[str] = None, duration: Optional[str] = None, sort_by: Optional[str] = None) -> dict:
|
|
84
|
+
"""Search YouTube for videos, channels, or playlists. Costs 1 credit.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
query: The video search query.
|
|
88
|
+
upload_date: Upload date filter: last_hour, today, this_week, this_month, this_year.
|
|
89
|
+
type: Result type: video, channel, playlist.
|
|
90
|
+
duration: Duration filter: short, medium, long.
|
|
91
|
+
sort_by: Sort order: relevance, date, view_count, rating.
|
|
92
|
+
"""
|
|
93
|
+
_p = {"upload_date": upload_date, "type": type, "duration": duration, "sort_by": sort_by}
|
|
94
|
+
return _run(lambda: client.youtube.search(query, **{k: v for k, v in _p.items() if v is not None}))
|
|
95
|
+
tools.append(scavio_youtube_search)
|
|
96
|
+
|
|
97
|
+
def scavio_youtube_metadata(video_id: str) -> dict:
|
|
98
|
+
"""Fetch full metadata for a YouTube video by id. Costs 1 credit.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
video_id: YouTube video id, e.g. dQw4w9WgXcQ.
|
|
102
|
+
"""
|
|
103
|
+
return _run(lambda: client.youtube.metadata(video_id))
|
|
104
|
+
tools.append(scavio_youtube_metadata)
|
|
105
|
+
|
|
106
|
+
if enable_amazon:
|
|
107
|
+
def scavio_amazon_search(query: str, domain: Optional[str] = None, country: Optional[str] = None, sort_by: Optional[str] = None, start_page: Optional[int] = None) -> dict:
|
|
108
|
+
"""Search Amazon for products matching a query. Costs 1 credit.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
query: The product search query.
|
|
112
|
+
domain: Amazon domain suffix, e.g. com or co.uk.
|
|
113
|
+
country: Two-letter country code for localization.
|
|
114
|
+
sort_by: Sort order: featured, price_low_to_high, price_high_to_low, average_review, most_recent, bestsellers.
|
|
115
|
+
start_page: First result page to return (1-based).
|
|
116
|
+
"""
|
|
117
|
+
_p = {"domain": domain, "country": country, "sort_by": sort_by, "start_page": start_page}
|
|
118
|
+
return _run(lambda: client.amazon.search(query, **{k: v for k, v in _p.items() if v is not None}))
|
|
119
|
+
tools.append(scavio_amazon_search)
|
|
120
|
+
|
|
121
|
+
def scavio_amazon_product(asin: str, domain: Optional[str] = None, country: Optional[str] = None) -> dict:
|
|
122
|
+
"""Fetch full Amazon product details by ASIN. Costs 1 credit.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
asin: Amazon Standard Identification Number, e.g. B09XS7JWHH.
|
|
126
|
+
domain: Amazon domain suffix, e.g. com or co.uk.
|
|
127
|
+
country: Two-letter country code for localization.
|
|
128
|
+
"""
|
|
129
|
+
_p = {"domain": domain, "country": country}
|
|
130
|
+
return _run(lambda: client.amazon.product(asin, **{k: v for k, v in _p.items() if v is not None}))
|
|
131
|
+
tools.append(scavio_amazon_product)
|
|
132
|
+
|
|
133
|
+
if enable_walmart:
|
|
134
|
+
def scavio_walmart_search(query: str, sort_by: Optional[str] = None, min_price: Optional[int] = None, max_price: Optional[int] = None, start_page: Optional[int] = None) -> dict:
|
|
135
|
+
"""Search Walmart for products matching a query. Costs 1 credit.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
query: The product search query.
|
|
139
|
+
sort_by: Sort order: best_match, price_low, price_high, best_seller.
|
|
140
|
+
min_price: Minimum price filter in USD.
|
|
141
|
+
max_price: Maximum price filter in USD.
|
|
142
|
+
start_page: First result page to return (1-based).
|
|
143
|
+
"""
|
|
144
|
+
_p = {"sort_by": sort_by, "min_price": min_price, "max_price": max_price, "start_page": start_page}
|
|
145
|
+
return _run(lambda: client.walmart.search(query, **{k: v for k, v in _p.items() if v is not None}))
|
|
146
|
+
tools.append(scavio_walmart_search)
|
|
147
|
+
|
|
148
|
+
def scavio_walmart_product(product_id: str) -> dict:
|
|
149
|
+
"""Fetch full Walmart product details by product id. Costs 1 credit.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
product_id: Walmart product id.
|
|
153
|
+
"""
|
|
154
|
+
return _run(lambda: client.walmart.product(product_id))
|
|
155
|
+
tools.append(scavio_walmart_product)
|
|
156
|
+
|
|
157
|
+
if enable_reddit:
|
|
158
|
+
def scavio_reddit_search(query: str, type: Optional[str] = None, sort: Optional[str] = None) -> dict:
|
|
159
|
+
"""Search Reddit posts or comments. Costs 2 credits.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
query: The Reddit search query.
|
|
163
|
+
type: Result type: posts or comments.
|
|
164
|
+
sort: Sort order: relevance, new, hot, top, comments.
|
|
165
|
+
"""
|
|
166
|
+
_p = {"type": type, "sort": sort}
|
|
167
|
+
return _run(lambda: client.reddit.search(query, **{k: v for k, v in _p.items() if v is not None}))
|
|
168
|
+
tools.append(scavio_reddit_search)
|
|
169
|
+
|
|
170
|
+
def scavio_reddit_post(url: str) -> dict:
|
|
171
|
+
"""Fetch a Reddit post and its threaded comments by URL. Costs 2 credits.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
url: Full URL of the Reddit post.
|
|
175
|
+
"""
|
|
176
|
+
return _run(lambda: client.reddit.post(url))
|
|
177
|
+
tools.append(scavio_reddit_post)
|
|
178
|
+
|
|
179
|
+
return tools
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "google-adk-scavio"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Scavio real-time search tools (Google, YouTube, Amazon, Walmart, Reddit) for Google ADK (Agent Development Kit)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Scavio", email = "support@scavio.dev" }]
|
|
13
|
+
keywords = ["google-adk", "adk", "agent-development-kit", "agents", "scavio", "search", "serp", "tavily-alternative"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"google-adk>=1.0",
|
|
16
|
+
"scavio>=0.5.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://scavio.dev"
|
|
21
|
+
Repository = "https://github.com/scavio-ai/google-adk-scavio"
|
|
22
|
+
Documentation = "https://scavio.dev/docs/google-adk"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = ["pytest>=7.0"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["google_adk_scavio"]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Tests for google-adk-scavio. The Scavio SDK client is mocked; no key or network used,
|
|
2
|
+
except the env-gated live test at the bottom."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
import google_adk_scavio.tools as tools_mod
|
|
9
|
+
from google_adk_scavio import create_scavio_tools
|
|
10
|
+
|
|
11
|
+
ALL_TOOL_NAMES = {
|
|
12
|
+
"scavio_google_search",
|
|
13
|
+
"scavio_google_news",
|
|
14
|
+
"scavio_youtube_search",
|
|
15
|
+
"scavio_youtube_metadata",
|
|
16
|
+
"scavio_amazon_search",
|
|
17
|
+
"scavio_amazon_product",
|
|
18
|
+
"scavio_walmart_search",
|
|
19
|
+
"scavio_walmart_product",
|
|
20
|
+
"scavio_reddit_search",
|
|
21
|
+
"scavio_reddit_post",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _Recorder:
|
|
26
|
+
def __init__(self, calls):
|
|
27
|
+
self._calls = calls
|
|
28
|
+
|
|
29
|
+
def __getattr__(self, method):
|
|
30
|
+
def _call(*args, **kwargs):
|
|
31
|
+
self._calls.append((method, args, kwargs))
|
|
32
|
+
return {"ok": True, "method": method, "args": args, "kwargs": kwargs}
|
|
33
|
+
|
|
34
|
+
return _call
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _FakeClient:
|
|
38
|
+
def __init__(self, *args, **kwargs):
|
|
39
|
+
self.calls = []
|
|
40
|
+
for ns in ("google", "youtube", "amazon", "walmart", "reddit"):
|
|
41
|
+
setattr(self, ns, _Recorder(self.calls))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _RaisingClient:
|
|
45
|
+
def __init__(self, *args, **kwargs):
|
|
46
|
+
class _Boom:
|
|
47
|
+
def __getattr__(self, method):
|
|
48
|
+
def _call(*a, **kw):
|
|
49
|
+
raise RuntimeError("scavio exploded")
|
|
50
|
+
|
|
51
|
+
return _call
|
|
52
|
+
|
|
53
|
+
for ns in ("google", "youtube", "amazon", "walmart", "reddit"):
|
|
54
|
+
setattr(self, ns, _Boom())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _build(monkeypatch, **kwargs):
|
|
58
|
+
monkeypatch.setattr(tools_mod, "ScavioClient", _FakeClient)
|
|
59
|
+
return create_scavio_tools(api_key="test", **kwargs)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get(tools, name):
|
|
63
|
+
return next(t for t in tools if t.__name__ == name)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_all_tools_register(monkeypatch):
|
|
67
|
+
tools = _build(monkeypatch)
|
|
68
|
+
names = [t.__name__ for t in tools]
|
|
69
|
+
assert len(names) == len(set(names)) == 10
|
|
70
|
+
assert set(names) == ALL_TOOL_NAMES
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_provider_gating(monkeypatch):
|
|
74
|
+
tools = _build(
|
|
75
|
+
monkeypatch,
|
|
76
|
+
enable_google=False,
|
|
77
|
+
enable_youtube=False,
|
|
78
|
+
enable_amazon=False,
|
|
79
|
+
enable_walmart=False,
|
|
80
|
+
enable_reddit=True,
|
|
81
|
+
)
|
|
82
|
+
assert {t.__name__ for t in tools} == {"scavio_reddit_search", "scavio_reddit_post"}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_google_search_sends_gl_hl(monkeypatch):
|
|
86
|
+
g = _get(_build(monkeypatch), "scavio_google_search")
|
|
87
|
+
out = g("ai agents", gl="fr", hl="fr")
|
|
88
|
+
assert isinstance(out, dict)
|
|
89
|
+
assert out["method"] == "search"
|
|
90
|
+
assert out["args"] == ("ai agents",)
|
|
91
|
+
assert out["kwargs"] == {"gl": "fr", "hl": "fr"}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_google_search_drops_none(monkeypatch):
|
|
95
|
+
g = _get(_build(monkeypatch), "scavio_google_search")
|
|
96
|
+
out = g("ai agents")
|
|
97
|
+
assert out["args"] == ("ai agents",)
|
|
98
|
+
assert out["kwargs"] == {}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_google_news_sends_query_kwarg(monkeypatch):
|
|
102
|
+
n = _get(_build(monkeypatch), "scavio_google_news")
|
|
103
|
+
out = n("openai", gl="us")
|
|
104
|
+
assert out["method"] == "news"
|
|
105
|
+
assert out["args"] == ()
|
|
106
|
+
assert out["kwargs"] == {"query": "openai", "gl": "us"}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_amazon_product_uses_asin(monkeypatch):
|
|
110
|
+
p = _get(_build(monkeypatch), "scavio_amazon_product")
|
|
111
|
+
out = p("B000000000")
|
|
112
|
+
assert out["method"] == "product"
|
|
113
|
+
assert out["args"] == ("B000000000",)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_errors_wrapped_not_raised(monkeypatch):
|
|
117
|
+
monkeypatch.setattr(tools_mod, "ScavioClient", _RaisingClient)
|
|
118
|
+
tools = create_scavio_tools(api_key="test")
|
|
119
|
+
g = _get(tools, "scavio_google_search")
|
|
120
|
+
assert g("boom") == {"error": "scavio exploded"}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_adk_function_tool_wraps(monkeypatch):
|
|
124
|
+
"""ADK must be able to wrap every tool function as a FunctionTool."""
|
|
125
|
+
from google.adk.tools import FunctionTool
|
|
126
|
+
|
|
127
|
+
tools = _build(monkeypatch)
|
|
128
|
+
wrapped = [FunctionTool(t) for t in tools]
|
|
129
|
+
assert {w.name for w in wrapped} == ALL_TOOL_NAMES
|
|
130
|
+
assert all(w.description for w in wrapped)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.mark.skipif("SCAVIO_API_KEY" not in os.environ, reason="requires live API key")
|
|
134
|
+
def test_live_google_search():
|
|
135
|
+
"""One live call against api.scavio.dev (set SCAVIO_API_KEY to run)."""
|
|
136
|
+
tools = create_scavio_tools(
|
|
137
|
+
enable_youtube=False, enable_amazon=False, enable_walmart=False, enable_reddit=False
|
|
138
|
+
)
|
|
139
|
+
g = _get(tools, "scavio_google_search")
|
|
140
|
+
out = g("scavio search api for ai agents", gl="us", hl="en")
|
|
141
|
+
assert "error" not in out, out.get("error")
|
|
142
|
+
assert out.get("organic_results"), out
|
|
143
|
+
first = out["organic_results"][0]
|
|
144
|
+
assert first["link"].startswith("http")
|
|
145
|
+
assert first["title"]
|