Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

- name: Install
run: pip install build -e ".[dev]"

- name: Ruff
run: |
ruff check .
ruff format --check .

- name: Mypy
run: mypy

- name: Pytest
run: pytest

- name: Build
run: python -m build
16 changes: 16 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: PR title

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
conventional:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63 changes: 63 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Release

on:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: release
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.ref_name }}
fetch-depth: 0

- run: git reset --hard ${{ github.sha }}

- uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Install
run: pip install build -e ".[dev]"

- name: Ruff
run: |
ruff check .
ruff format --check .

- name: Mypy
run: mypy

- name: Pytest
run: pytest

- id: release
uses: python-semantic-release/python-semantic-release@v10.7.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
commit: "false"
changelog: "false"

- name: Build
if: steps.release.outputs.released == 'true'
run: python -m build

- name: Publish
if: steps.release.outputs.released == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
dist/
*.egg-info/
.eggs/
*.egg

# Virtual environments
.venv/
venv/
env/

# Type checking / linting caches
.mypy_cache/
.ruff_cache/

# Testing
.pytest_cache/
.coverage
htmlcov/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Environment
.env

# OS
.DS_Store
25 changes: 25 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Contributing

## Development

```bash
python3.12 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/ruff check . && .venv/bin/ruff format --check .
.venv/bin/mypy
.venv/bin/pytest
```

The live test calls the real API and only runs when `DECODO_LIVE_API_KEY` is set:

```bash
DECODO_LIVE_API_KEY="<api_key>" .venv/bin/pytest -m live
```

## Releases

Releases are automated. Merging to `main` publishes a new version based on the
PR title, which must follow [Conventional Commits](https://www.conventionalcommits.org):
`fix:` → patch, `feat:` → minor, `feat!:` or a `BREAKING CHANGE:` footer → major.
`chore:`, `docs:`, `ci:`, `refactor:`, `test:` don't release. Don't edit the
version in `pyproject.toml`.
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Decodo tools for CrewAI

![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-green)
![License](https://img.shields.io/badge/license-MIT-blue)

[CrewAI](https://www.crewai.com) tools for the [Decodo](https://decodo.com) Web Scraping API. Give your agents live web pages and search results.

| Tool | What it does |
| --- | --- |
| `DecodoWebScraperTool` | Scrapes any public URL and returns the page as Markdown. |
| `DecodoSearchTool` | Searches Google or Amazon and returns the parsed results as JSON. |

## Requirements

- Python 3.12 or 3.13
- A Decodo Web Scraping API key

## Installation

```bash
pip install decodo-crewai
```

## Credentials

Copy your API key from your Web Scraping API subscription on the [Decodo dashboard](https://dashboard.decodo.com/web-data/playground) and set it as an environment variable:

```bash
export DECODO_API_KEY="<api_key>"
```

Or pass it to each tool:

```python
DecodoWebScraperTool(api_key="<api_key>")
```

## Usage

```python
from crewai import Agent, Crew, Task

from decodo_crewai import DecodoSearchTool, DecodoWebScraperTool

researcher = Agent(
role="Researcher",
goal="Find and summarize up-to-date information from the web",
backstory="You search the web, read the most relevant pages and report what they say.",
tools=[DecodoSearchTool(), DecodoWebScraperTool()],
)

task = Task(
description="Find the three most recent announcements about {topic} and summarize each one.",
expected_output="Three bullet points, each with a one-sentence summary and the source URL.",
agent=researcher,
)

crew = Crew(agents=[researcher], tasks=[task])
print(crew.kickoff(inputs={"topic": "web scraping APIs"}))
```

The tools can also be called directly:

```python
from decodo_crewai import DecodoSearchTool, DecodoWebScraperTool

print(DecodoWebScraperTool().run(url="https://ip.decodo.com"))
print(DecodoSearchTool().run(query="mechanical keyboard", source="amazon"))
```

## Tools

### DecodoWebScraperTool

Arguments the agent fills in:

| Argument | Type | Description |
| --- | --- | --- |
| `url` | `str` | Full URL of the page to scrape. |
| `geo` | `str`, optional | Country to send the request from, e.g. `United States`. |
| `js_render` | `bool`, optional | Open the page in a headless browser. Only needed for pages that load content with JavaScript. |

Constructor options:

| Option | Default | Description |
| --- | --- | --- |
| `api_key` | `DECODO_API_KEY` | Decodo Web Scraping API key. |
| `max_chars` | `45000` | Markdown longer than this is truncated to keep the page within the agent's context. |

### DecodoSearchTool

Arguments the agent fills in:

| Argument | Type | Description |
| --- | --- | --- |
| `query` | `str` | Search query. |
| `source` | `"google"` or `"amazon"`, optional | `google` (default) for web results, `amazon` for product listings. |
| `geo` | `str`, optional | Google: a country name, e.g. `United States`. Amazon: a US ZIP code, e.g. `10001`, or a two-letter country code for delivery outside the US, e.g. `DE`. |

## Errors

When a request fails, for example because of an invalid API key, a rate limit or a page that returns `404`, the tools return a CrewAI `ToolFailure` instead of raising. The agent sees what went wrong and can retry or change course, and CrewAI records the failure according to the agent's `tool_failure_policy`.

## License

MIT
65 changes: 65 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "decodo-crewai"
version = "0.0.0"
description = "Decodo Web Scraping API tools for CrewAI agents."
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.12,<3.14"
authors = [{ name = "Decodo", email = "support@decodo.com" }]
keywords = ["decodo", "crewai", "web-scraping", "agents", "tools"]
classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
# ToolFailure first shipped in 1.15.9.
"crewai>=1.15.9",
"decodo-sdk>=2.4.1",
]

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"ruff>=0.4.0",
"mypy>=1.10.0",
]

[project.urls]
Homepage = "https://decodo.com"
Repository = "https://github.com/Decodo/crewAI"
Documentation = "https://help.decodo.com/docs/introduction"

[tool.hatch.build.targets.wheel]
packages = ["src/decodo_crewai"]

[tool.ruff]
line-length = 120
src = ["src"]

[tool.ruff.lint]
select = ["E", "F", "I", "UP"]

[tool.mypy]
strict = true
packages = ["decodo_crewai"]
mypy_path = "src"

[tool.pytest.ini_options]
testpaths = ["tests"]
markers = ["live: calls the real Decodo API; needs DECODO_LIVE_API_KEY"]

[tool.semantic_release]
version_toml = ["pyproject.toml:project.version"]
commit_parser = "conventional"

[tool.semantic_release.branches.main]
match = "main"
5 changes: 5 additions & 0 deletions src/decodo_crewai/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ._base import DecodoBaseTool
from .search_tool import DecodoSearchTool
from .web_scraper_tool import DecodoWebScraperTool

__all__ = ["DecodoBaseTool", "DecodoSearchTool", "DecodoWebScraperTool"]
Loading
Loading