Spaces:
Sleeping
Sleeping
| """CrewAI tool: query suppliers for a part SKU via Apify, with demo fixture. | |
| In ``DEMO_MODE=true`` (or when ``APIFY_API_TOKEN`` is missing) the tool returns | |
| the hardcoded fixture from memory.md so the agent loop runs offline. In live | |
| mode it calls the Apify ``apify/web-scraper`` actor synchronously and parses | |
| the dataset for ``{supplier, unit_price_usd, delivery_days, url}``. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import time | |
| from typing import Any | |
| import structlog | |
| from crewai.tools import tool | |
| log = structlog.get_logger() | |
| APIFY_FIXTURE: dict[str, list[dict[str, Any]]] = { | |
| "SKU-BRG-6205": [ | |
| { | |
| "supplier": "BearingPoint Industrial", | |
| "unit_price_usd": 47.00, | |
| "delivery_days": 2, | |
| "stock_status": "in_stock", | |
| "url": "https://bearingpoint.example.com/6205-2RS", | |
| }, | |
| { | |
| "supplier": "GlobalBearings.com", | |
| "unit_price_usd": 39.50, | |
| "delivery_days": 5, | |
| "stock_status": "in_stock", | |
| "url": "https://globalbearings.example.com/catalog/6205", | |
| }, | |
| { | |
| "supplier": "FastParts Express", | |
| "unit_price_usd": 62.00, | |
| "delivery_days": 1, | |
| "stock_status": "low_stock", | |
| "url": "https://fastparts.example.com/bearings/6205-2RS", | |
| }, | |
| ], | |
| "SKU-GBX-HELICAL-32T": [ | |
| { | |
| "supplier": "GearWorks Direct", | |
| "unit_price_usd": 312.00, | |
| "delivery_days": 4, | |
| "stock_status": "in_stock", | |
| "url": "https://gearworks.example.com/helical-32t", | |
| }, | |
| ], | |
| "SKU-BAL-WEIGHT-KIT": [ | |
| { | |
| "supplier": "VibraTech Supplies", | |
| "unit_price_usd": 89.00, | |
| "delivery_days": 3, | |
| "stock_status": "in_stock", | |
| "url": "https://vibratech.example.com/balance-kit", | |
| }, | |
| ], | |
| } | |
| CACHE_TTL_SECONDS = 60.0 | |
| _cache: dict[str, tuple[float, list[dict[str, Any]]]] = {} | |
| def _is_demo_mode() -> bool: | |
| return os.getenv("DEMO_MODE", "true").lower() in ("1", "true", "yes") | |
| def _from_fixture(sku: str) -> list[dict[str, Any]]: | |
| return APIFY_FIXTURE.get(sku, []) | |
| def _from_apify(sku: str) -> list[dict[str, Any]]: | |
| from apify_client import ApifyClient # imported lazily | |
| token = os.environ["APIFY_API_TOKEN"] | |
| actor_id = os.getenv("APIFY_ACTOR_ID", "apify/web-scraper") | |
| client = ApifyClient(token) | |
| run_input = {"sku": sku, "maxPagesPerCrawl": 5} | |
| run = client.actor(actor_id).call(run_input=run_input, timeout_secs=90) | |
| items = list(client.dataset(run["defaultDatasetId"]).iterate_items()) | |
| return [ | |
| { | |
| "supplier": str(it.get("supplier", "Unknown")), | |
| "unit_price_usd": float(it.get("price", 0.0)), | |
| "delivery_days": int(it.get("delivery_days", 7)), | |
| "stock_status": str(it.get("stock", "unknown")), | |
| "url": str(it.get("url", "")), | |
| } | |
| for it in items | |
| ] | |
| def scrape_suppliers(part_sku: str) -> str: | |
| """Return ranked supplier offers for the given part SKU as JSON. | |
| Uses Apify in live mode, demo fixture when DEMO_MODE=true.""" | |
| sku = part_sku.strip() | |
| cached = _cache.get(sku) | |
| if cached and (time.time() - cached[0]) < CACHE_TTL_SECONDS: | |
| log.info( | |
| "apify_cache_hit", | |
| component="agents.tools.apify_scraper", | |
| sku=sku, | |
| offers=len(cached[1]), | |
| ) | |
| return json.dumps({"ok": True, "sku": sku, "offers": cached[1], "source": "cache"}) | |
| demo_mode = _is_demo_mode() or not os.getenv("APIFY_API_TOKEN") | |
| try: | |
| offers = _from_fixture(sku) if demo_mode else _from_apify(sku) | |
| except Exception as exc: | |
| log.error( | |
| "apify_scrape_failed", | |
| component="agents.tools.apify_scraper", | |
| sku=sku, | |
| error=str(exc), | |
| ) | |
| return json.dumps({"ok": False, "error": str(exc), "sku": sku}) | |
| _cache[sku] = (time.time(), offers) | |
| log.info( | |
| "apify_scrape_ok", | |
| component="agents.tools.apify_scraper", | |
| sku=sku, | |
| offers=len(offers), | |
| source="fixture" if demo_mode else "apify", | |
| ) | |
| return json.dumps({ | |
| "ok": True, | |
| "sku": sku, | |
| "offers": offers, | |
| "source": "fixture" if demo_mode else "apify", | |
| }) | |