diff --git a/toolkits/search/arcade_search/constants.py b/toolkits/search/arcade_search/constants.py index c525145d..cca971fb 100644 --- a/toolkits/search/arcade_search/constants.py +++ b/toolkits/search/arcade_search/constants.py @@ -37,3 +37,13 @@ DEFAULT_GOOGLE_MAPS_TRAVEL_MODE = GoogleMapsTravelMode( YOUTUBE_MAX_DESCRIPTION_LENGTH = 500 DEFAULT_YOUTUBE_SEARCH_LANGUAGE = os.getenv("ARCADE_YOUTUBE_SEARCH_LANGUAGE") DEFAULT_YOUTUBE_SEARCH_COUNTRY = os.getenv("ARCADE_YOUTUBE_SEARCH_COUNTRY") + +# ------------------------------------------------------------------------------------------------ +# Google Shopping default constants +# ------------------------------------------------------------------------------------------------ +DEFAULT_GOOGLE_SHOPPING_LANGUAGE = os.getenv( + "ARCADE_GOOGLE_SHOPPING_LANGUAGE", DEFAULT_GOOGLE_LANGUAGE +) +DEFAULT_GOOGLE_SHOPPING_COUNTRY = os.getenv( + "ARCADE_GOOGLE_SHOPPING_COUNTRY", DEFAULT_GOOGLE_COUNTRY +) diff --git a/toolkits/search/arcade_search/google_data.py b/toolkits/search/arcade_search/google_data.py index 789e3183..0c44fc11 100644 --- a/toolkits/search/arcade_search/google_data.py +++ b/toolkits/search/arcade_search/google_data.py @@ -279,3 +279,191 @@ LANGUAGE_CODES = { "zh-cn": "Chinese (Simplified)", "zh-tw": "Chinese (Traditional)", } + + +GOOGLE_DOMAIN_BY_COUNTRY_CODE = { + "ad": "google.ad", + "ae": "google.ae", + "al": "google.al", + "am": "google.am", + "as": "google.as", + "at": "google.at", + "az": "google.az", + "ba": "google.ba", + "be": "google.be", + "bf": "google.bf", + "bg": "google.bg", + "bi": "google.bi", + "bj": "google.bj", + "bs": "google.bs", + "bt": "google.bt", + "by": "google.by", + "ca": "google.ca", + "cg": "google.cg", + "cf": "google.cf", + "ch": "google.ch", + "ci": "google.ci", + "cl": "google.cl", + "cm": "google.cm", + "ao": "google.co.ao", + "bw": "google.co.bw", + "ck": "google.co.ck", + "cr": "google.co.cr", + "id": "google.co.id", + "il": "google.co.il", + "in": "google.co.in", + "jp": "google.co.jp", + "ke": "google.co.ke", + "kr": "google.co.kr", + "ls": "google.co.ls", + "ma": "google.co.ma", + "mz": "google.co.mz", + "nz": "google.co.nz", + "th": "google.co.th", + "tz": "google.co.tz", + "ug": "google.co.ug", + "uk": "google.co.uk", + "uz": "google.co.uz", + "ve": "google.co.ve", + "vi": "google.co.vi", + "za": "google.co.za", + "zm": "google.co.zm", + "zw": "google.co.zw", + "us": "google.com", + "af": "google.com.af", + "ag": "google.com.ag", + "ai": "google.com.ai", + "ar": "google.com.ar", + "au": "google.com.au", + "bd": "google.com.bd", + "bh": "google.com.bh", + "bn": "google.com.bn", + "bo": "google.com.bo", + "br": "google.com.br", + "bz": "google.com.bz", + "co": "google.com.co", + "cu": "google.com.cu", + "cy": "google.com.cy", + "do": "google.com.do", + "ec": "google.com.ec", + "eg": "google.com.eg", + "et": "google.com.et", + "fj": "google.com.fj", + "gh": "google.com.gh", + "gi": "google.com.gi", + "gt": "google.com.gt", + "hk": "google.com.hk", + "jm": "google.com.jm", + "kh": "google.com.kh", + "kw": "google.com.kw", + "lb": "google.com.lb", + "ly": "google.com.ly", + "mm": "google.com.mm", + "mt": "google.com.mt", + "mx": "google.com.mx", + "my": "google.com.my", + "na": "google.com.na", + "ng": "google.com.ng", + "ni": "google.com.ni", + "np": "google.com.np", + "om": "google.com.om", + "pa": "google.com.pa", + "pe": "google.com.pe", + "pg": "google.com.pg", + "ph": "google.com.ph", + "pk": "google.com.pk", + "pr": "google.com.pr", + "py": "google.com.py", + "qa": "google.com.qa", + "sa": "google.com.sa", + "sb": "google.com.sb", + "sg": "google.com.sg", + "sl": "google.com.sl", + "sv": "google.com.sv", + "tj": "google.com.tj", + "tr": "google.com.tr", + "tw": "google.com.tw", + "ua": "google.com.ua", + "uy": "google.com.uy", + "vc": "google.com.vc", + "vn": "google.com.vn", + "cv": "google.cv", + "cz": "google.cz", + "de": "google.de", + "dj": "google.dj", + "dk": "google.dk", + "dm": "google.dm", + "dz": "google.dz", + "ee": "google.ee", + "es": "google.es", + "fi": "google.fi", + "fm": "google.fm", + "fr": "google.fr", + "ga": "google.ga", + "ge": "google.ge", + "gl": "google.gl", + "gm": "google.gm", + "gp": "google.gp", + "gr": "google.gr", + "gy": "google.gy", + "hn": "google.hn", + "hr": "google.hr", + "ht": "google.ht", + "hu": "google.hu", + "ie": "google.ie", + "iq": "google.iq", + "is": "google.is", + "it": "google.it", + "je": "google.je", + "jo": "google.jo", + "kg": "google.kg", + "ki": "google.ki", + "kz": "google.kz", + "la": "google.la", + "li": "google.li", + "lk": "google.lk", + "lt": "google.lt", + "lu": "google.lu", + "lv": "google.lv", + "md": "google.md", + "mg": "google.mg", + "mk": "google.mk", + "ml": "google.ml", + "mn": "google.mn", + "ms": "google.ms", + "mu": "google.mu", + "mv": "google.mv", + "mw": "google.mw", + "ne": "google.ne", + "nl": "google.nl", + "no": "google.no", + "nr": "google.nr", + "nu": "google.nu", + "pl": "google.pl", + "ps": "google.ps", + "pt": "google.pt", + "ro": "google.ro", + "rs": "google.rs", + "ru": "google.ru", + "rw": "google.rw", + "sc": "google.sc", + "se": "google.se", + "sh": "google.sh", + "si": "google.si", + "sk": "google.sk", + "sm": "google.sm", + "sn": "google.sn", + "so": "google.so", + "sr": "google.sr", + "td": "google.td", + "tg": "google.tg", + "tk": "google.tk", + "tl": "google.tl", + "tm": "google.tm", + "tn": "google.tn", + "to": "google.to", + "tt": "google.tt", + "vg": "google.vg", + "vu": "google.vu", + "ws": "google.ws", +} diff --git a/toolkits/search/arcade_search/tools/google_shopping.py b/toolkits/search/arcade_search/tools/google_shopping.py new file mode 100644 index 00000000..1195aca2 --- /dev/null +++ b/toolkits/search/arcade_search/tools/google_shopping.py @@ -0,0 +1,66 @@ +from typing import Annotated, Any, Optional + +from arcade.sdk import ToolContext, tool +from arcade.sdk.errors import ToolExecutionError + +from arcade_search.constants import ( + DEFAULT_GOOGLE_SHOPPING_COUNTRY, + DEFAULT_GOOGLE_SHOPPING_LANGUAGE, +) +from arcade_search.google_data import GOOGLE_DOMAIN_BY_COUNTRY_CODE +from arcade_search.utils import ( + call_serpapi, + extract_shopping_results, + prepare_params, + resolve_country_code, + resolve_language_code, +) + + +@tool(requires_secrets=["SERP_API_KEY"]) +async def search_shopping_products( + context: ToolContext, + keywords: Annotated[ + str, + "Keywords to search for products in Google Shopping. E.g. 'Apple iPhone'.", + ], + country_code: Annotated[ + Optional[str], + "2-character country code to search for products in Google Shopping. " + f"E.g. 'us' (United States). Defaults to '{DEFAULT_GOOGLE_SHOPPING_COUNTRY or 'us'}'.", + ] = DEFAULT_GOOGLE_SHOPPING_COUNTRY, + language_code: Annotated[ + Optional[str], + "2-character language code to search for products on Google Shopping. E.g. 'en' (English). " + f"Defaults to '{DEFAULT_GOOGLE_SHOPPING_LANGUAGE or 'en'}'.", + ] = DEFAULT_GOOGLE_SHOPPING_LANGUAGE, +) -> Annotated[dict[str, list[dict[str, Any]]], "Products on Google Shopping."]: + """Search for products on Google Shopping related to a given query.""" + country_code = resolve_country_code(country_code, DEFAULT_GOOGLE_SHOPPING_COUNTRY) + language_code = resolve_language_code(language_code, DEFAULT_GOOGLE_SHOPPING_LANGUAGE) + + if not isinstance(country_code, str): + country_code = "us" + + if not isinstance(language_code, str): + language_code = "en" + + google_domain = GOOGLE_DOMAIN_BY_COUNTRY_CODE.get(country_code, "google.com") + + params = prepare_params( + "google_shopping", + q=keywords, + gl=country_code, + hl=language_code, + google_domain=google_domain, + ) + + response = call_serpapi(context, params) + + if response.get("error"): + error_msg = response.get("error") or "Unknown Google Shopping Error" + raise ToolExecutionError(error_msg) + + return { + "products": extract_shopping_results(response.get("shopping_results", [])), + } diff --git a/toolkits/search/arcade_search/utils.py b/toolkits/search/arcade_search/utils.py index 41901fc9..fe602ac8 100644 --- a/toolkits/search/arcade_search/utils.py +++ b/toolkits/search/arcade_search/utils.py @@ -276,6 +276,27 @@ def extract_news_results( return news_results +# ------------------------------------------------------------------------------------------------ +# Google Shopping utils +# ------------------------------------------------------------------------------------------------ +def extract_shopping_results(results: list[dict[str, Any]]) -> list[dict[str, Any]]: + return [ + { + "title": result.get("title"), + "direct_link": result.get("link"), + "google_link": result.get("product_link"), + "source": result.get("source"), + "price": result.get("price"), + "product_rating": result.get("rating"), + "product_reviews": result.get("reviews"), + "store_rating": result.get("store_rating"), + "store_reviews": result.get("store_reviews"), + "delivery": result.get("delivery"), + } + for result in results + ] + + # ------------------------------------------------------------------------------------------------ # Walmart utils # ------------------------------------------------------------------------------------------------