brightdata-ai-agent / brightdata_datasets.py
meirk-brd
change dataset inputs
8ef04af
from smolagents import Tool
import json
import os
import time
import requests
from typing import Dict, Any
from dotenv import load_dotenv
# Load environment variables from .env if present
load_dotenv()
def _build_description(description_lines):
"""Join multiline descriptions defined as lists."""
return "\n".join(description_lines)
# Dataset catalogue mirrored from the MCP implementation (JS version).
# Each entry defines the dataset_id, the required inputs, optional defaults,
# and optional fixed values that are injected automatically.
DATASETS: Dict[str, Dict[str, Any]] = {
"amazon_product": {
"dataset_id": "gd_l7q7dkf244hwjntr0",
"description": _build_description(
[
"Quickly read structured amazon product data.",
"Requires a valid product URL with /dp/ in it.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"amazon_product_reviews": {
"dataset_id": "gd_le8e811kzy4ggddlq",
"description": _build_description(
[
"Quickly read structured amazon product review data.",
"Requires a valid product URL with /dp/ in it.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"amazon_product_search": {
"dataset_id": "gd_lwdb4vjm1ehb499uxs",
"description": _build_description(
[
"Quickly read structured amazon product search data.",
"Requires a valid search keyword and amazon domain URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["keyword", "url"],
"fixed_values": {"pages_to_search": "1"},
},
"walmart_product": {
"dataset_id": "gd_l95fol7l1ru6rlo116",
"description": _build_description(
[
"Quickly read structured walmart product data.",
"Requires a valid product URL with /ip/ in it.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"walmart_seller": {
"dataset_id": "gd_m7ke48w81ocyu4hhz0",
"description": _build_description(
[
"Quickly read structured walmart seller data.",
"Requires a valid walmart seller URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"ebay_product": {
"dataset_id": "gd_ltr9mjt81n0zzdk1fb",
"description": _build_description(
[
"Quickly read structured ebay product data.",
"Requires a valid ebay product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"homedepot_products": {
"dataset_id": "gd_lmusivh019i7g97q2n",
"description": _build_description(
[
"Quickly read structured homedepot product data.",
"Requires a valid homedepot product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"zara_products": {
"dataset_id": "gd_lct4vafw1tgx27d4o0",
"description": _build_description(
[
"Quickly read structured zara product data.",
"Requires a valid zara product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"etsy_products": {
"dataset_id": "gd_ltppk0jdv1jqz25mz",
"description": _build_description(
[
"Quickly read structured etsy product data.",
"Requires a valid etsy product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"bestbuy_products": {
"dataset_id": "gd_ltre1jqe1jfr7cccf",
"description": _build_description(
[
"Quickly read structured bestbuy product data.",
"Requires a valid bestbuy product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"linkedin_person_profile": {
"dataset_id": "gd_l1viktl72bvl7bjuj0",
"description": _build_description(
[
"Quickly read structured linkedin people profile data.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"linkedin_company_profile": {
"dataset_id": "gd_l1vikfnt1wgvvqz95w",
"description": _build_description(
[
"Quickly read structured linkedin company profile data.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"linkedin_job_listings": {
"dataset_id": "gd_lpfll7v5hcqtkxl6l",
"description": _build_description(
[
"Quickly read structured linkedin job listings data.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"linkedin_posts": {
"dataset_id": "gd_lyy3tktm25m4avu764",
"description": _build_description(
[
"Quickly read structured linkedin posts data.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"linkedin_people_search": {
"dataset_id": "gd_m8d03he47z8nwb5xc",
"description": _build_description(
[
"Quickly read structured linkedin people search data.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url", "first_name", "last_name"],
},
"crunchbase_company": {
"dataset_id": "gd_l1vijqt9jfj7olije",
"description": _build_description(
[
"Quickly read structured crunchbase company data.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"zoominfo_company_profile": {
"dataset_id": "gd_m0ci4a4ivx3j5l6nx",
"description": _build_description(
[
"Quickly read structured ZoomInfo company profile data.",
"Requires a valid ZoomInfo company URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"instagram_profiles": {
"dataset_id": "gd_l1vikfch901nx3by4",
"description": _build_description(
[
"Quickly read structured Instagram profile data.",
"Requires a valid Instagram URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"instagram_posts": {
"dataset_id": "gd_lk5ns7kz21pck8jpis",
"description": _build_description(
[
"Quickly read structured Instagram post data.",
"Requires a valid Instagram URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"instagram_reels": {
"dataset_id": "gd_lyclm20il4r5helnj",
"description": _build_description(
[
"Quickly read structured Instagram reel data.",
"Requires a valid Instagram URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"instagram_comments": {
"dataset_id": "gd_ltppn085pokosxh13",
"description": _build_description(
[
"Quickly read structured Instagram comments data.",
"Requires a valid Instagram URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"facebook_posts": {
"dataset_id": "gd_lyclm1571iy3mv57zw",
"description": _build_description(
[
"Quickly read structured Facebook post data.",
"Requires a valid Facebook post URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"facebook_marketplace_listings": {
"dataset_id": "gd_lvt9iwuh6fbcwmx1a",
"description": _build_description(
[
"Quickly read structured Facebook marketplace listing data.",
"Requires a valid Facebook marketplace listing URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"facebook_company_reviews": {
"dataset_id": "gd_m0dtqpiu1mbcyc2g86",
"description": _build_description(
[
"Quickly read structured Facebook company reviews data.",
"Requires a valid Facebook company URL and number of reviews.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url", "num_of_reviews"],
},
"facebook_events": {
"dataset_id": "gd_m14sd0to1jz48ppm51",
"description": _build_description(
[
"Quickly read structured Facebook events data.",
"Requires a valid Facebook event URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"tiktok_profiles": {
"dataset_id": "gd_l1villgoiiidt09ci",
"description": _build_description(
[
"Quickly read structured Tiktok profiles data.",
"Requires a valid Tiktok profile URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"tiktok_posts": {
"dataset_id": "gd_lu702nij2f790tmv9h",
"description": _build_description(
[
"Quickly read structured Tiktok post data.",
"Requires a valid Tiktok post URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"tiktok_shop": {
"dataset_id": "gd_m45m1u911dsa4274pi",
"description": _build_description(
[
"Quickly read structured Tiktok shop data.",
"Requires a valid Tiktok shop product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"tiktok_comments": {
"dataset_id": "gd_lkf2st302ap89utw5k",
"description": _build_description(
[
"Quickly read structured Tiktok comments data.",
"Requires a valid Tiktok video URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"google_maps_reviews": {
"dataset_id": "gd_luzfs1dn2oa0teb81",
"description": _build_description(
[
"Quickly read structured Google maps reviews data.",
"Requires a valid Google maps URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url", "days_limit"],
"defaults": {"days_limit": "3"},
},
"google_shopping": {
"dataset_id": "gd_ltppk50q18kdw67omz",
"description": _build_description(
[
"Quickly read structured Google shopping data.",
"Requires a valid Google shopping product URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"google_play_store": {
"dataset_id": "gd_lsk382l8xei8vzm4u",
"description": _build_description(
[
"Quickly read structured Google play store data.",
"Requires a valid Google play store app URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"apple_app_store": {
"dataset_id": "gd_lsk9ki3u2iishmwrui",
"description": _build_description(
[
"Quickly read structured apple app store data.",
"Requires a valid apple app store app URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"reuter_news": {
"dataset_id": "gd_lyptx9h74wtlvpnfu",
"description": _build_description(
[
"Quickly read structured reuter news data.",
"Requires a valid reuter news report URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"github_repository_file": {
"dataset_id": "gd_lyrexgxc24b3d4imjt",
"description": _build_description(
[
"Quickly read structured github repository data.",
"Requires a valid github repository file URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"yahoo_finance_business": {
"dataset_id": "gd_lmrpz3vxmz972ghd7",
"description": _build_description(
[
"Quickly read structured yahoo finance business data.",
"Requires a valid yahoo finance business URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"x_posts": {
"dataset_id": "gd_lwxkxvnf1cynvib9co",
"description": _build_description(
[
"Quickly read structured X post data.",
"Requires a valid X post URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"zillow_properties_listing": {
"dataset_id": "gd_lfqkr8wm13ixtbd8f5",
"description": _build_description(
[
"Quickly read structured zillow properties listing data.",
"Requires a valid zillow properties listing URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"booking_hotel_listings": {
"dataset_id": "gd_m5mbdl081229ln6t4a",
"description": _build_description(
[
"Quickly read structured booking hotel listings data.",
"Requires a valid booking hotel listing URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"youtube_profiles": {
"dataset_id": "gd_lk538t2k2p1k3oos71",
"description": _build_description(
[
"Quickly read structured youtube profiles data.",
"Requires a valid youtube profile URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"youtube_comments": {
"dataset_id": "gd_lk9q0ew71spt1mxywf",
"description": _build_description(
[
"Quickly read structured youtube comments data.",
"Requires a valid youtube video URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url", "num_of_comments"],
"defaults": {"num_of_comments": "10"},
},
"reddit_posts": {
"dataset_id": "gd_lvz8ah06191smkebj4",
"description": _build_description(
[
"Quickly read structured reddit posts data.",
"Requires a valid reddit post URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
"youtube_videos": {
"dataset_id": "gd_lk56epmy2i5g7lzu0k",
"description": _build_description(
[
"Quickly read structured YouTube videos data.",
"Requires a valid YouTube video URL.",
"This can be a cache lookup, so it can be more reliable than scraping.",
]
),
"inputs": ["url"],
},
}
class BrightDataDatasetTool(Tool):
name = "brightdata_dataset_fetch"
description = (
"Trigger a Bright Data dataset collection and poll until the snapshot is ready. "
"Choose a dataset key (e.g., amazon_product, linkedin_company_profile, google_maps_reviews). "
"For most datasets, you only need to provide the URL parameter. "
"For example: brightdata_dataset_fetch(dataset='linkedin_person_profile', url='https://linkedin.com/in/...')"
)
inputs = {
"dataset": {
"type": "string",
"description": f"Dataset key. Options: {', '.join(sorted(DATASETS.keys()))}",
},
"url": {
"type": "string",
"description": "URL for the dataset (required for most datasets)",
"nullable": True,
},
"keyword": {
"type": "string",
"description": "Search keyword (for search datasets like amazon_product_search)",
"nullable": True,
},
"first_name": {
"type": "string",
"description": "First name (for datasets like linkedin_people_search)",
"nullable": True,
},
"last_name": {
"type": "string",
"description": "Last name (for datasets like linkedin_people_search)",
"nullable": True,
},
"days_limit": {
"type": "string",
"description": "Days limit (for datasets like google_maps_reviews, default: 3)",
"nullable": True,
},
"num_of_reviews": {
"type": "string",
"description": "Number of reviews (for datasets like facebook_company_reviews)",
"nullable": True,
},
"num_of_comments": {
"type": "string",
"description": "Number of comments (for datasets like youtube_comments, default: 10)",
"nullable": True,
},
}
output_type = "string"
def _prepare_payload(self, dataset_key: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""Validate required fields, apply defaults, and merge fixed values."""
config = DATASETS[dataset_key]
payload = {}
defaults = config.get("defaults", {})
fixed_values = config.get("fixed_values", {})
for field in config["inputs"]:
if field in params:
payload[field] = params[field]
elif field in defaults:
payload[field] = defaults[field]
else:
raise ValueError(f"Missing required field '{field}' for dataset '{dataset_key}'")
# Apply fixed values that should always be sent
payload.update(fixed_values)
return payload
def forward(
self,
dataset: str,
url: str = None,
keyword: str = None,
first_name: str = None,
last_name: str = None,
days_limit: str = None,
num_of_reviews: str = None,
num_of_comments: str = None,
) -> str:
"""
Trigger a dataset run and poll until results are ready.
Args:
dataset: The dataset key from DATASETS.
url: URL for the dataset (required for most datasets).
keyword: Search keyword (for search datasets).
first_name: First name (for people search datasets).
last_name: Last name (for people search datasets).
days_limit: Days limit (for time-based datasets).
num_of_reviews: Number of reviews to fetch.
num_of_comments: Number of comments to fetch.
Returns:
JSON string of the snapshot data once ready.
"""
api_token = os.getenv("BRIGHT_DATA_API_TOKEN")
if not api_token:
raise ValueError("BRIGHT_DATA_API_TOKEN not found in environment variables")
if dataset not in DATASETS:
raise ValueError(f"Unknown dataset '{dataset}'. Valid options: {', '.join(sorted(DATASETS.keys()))}")
# Build params dict from provided arguments
params = {}
if url is not None:
params["url"] = url
if keyword is not None:
params["keyword"] = keyword
if first_name is not None:
params["first_name"] = first_name
if last_name is not None:
params["last_name"] = last_name
if days_limit is not None:
params["days_limit"] = days_limit
if num_of_reviews is not None:
params["num_of_reviews"] = num_of_reviews
if num_of_comments is not None:
params["num_of_comments"] = num_of_comments
payload = self._prepare_payload(dataset, params)
dataset_id = DATASETS[dataset]["dataset_id"]
trigger_url = "https://api.brightdata.com/datasets/v3/trigger"
trigger_headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json",
}
trigger_response = requests.post(
trigger_url,
params={"dataset_id": dataset_id, "include_errors": "true"},
json=[payload],
headers=trigger_headers,
timeout=60,
)
trigger_response.raise_for_status()
snapshot_id = trigger_response.json().get("snapshot_id")
if not snapshot_id:
raise RuntimeError("No snapshot ID returned from Bright Data.")
# Poll for completion (up to 10 minutes, matching MCP logic)
snapshot_url = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
max_attempts = 600
attempts = 0
while attempts < max_attempts:
try:
response = requests.get(
snapshot_url,
params={"format": "json"},
headers={"Authorization": f"Bearer {api_token}"},
timeout=30,
)
# If Bright Data returns an error response we don't want to loop forever
if response.status_code == 400:
response.raise_for_status()
data = response.json()
if isinstance(data, list):
return json.dumps(data, indent=2)
status = data.get("status") if isinstance(data, dict) else None
if status not in {"running", "building"}:
return json.dumps(data, indent=2)
attempts += 1
time.sleep(1)
except requests.exceptions.RequestException as exc:
# Mirror JS logic: tolerate transient failures, but break on 400
if getattr(getattr(exc, "response", None), "status_code", None) == 400:
raise
attempts += 1
time.sleep(1)
raise TimeoutError(f"Timeout waiting for snapshot {snapshot_id} after {max_attempts} seconds")