factoryflow / src /agents /tools /apify_scraper.py
oabolade23's picture
FactoryFlow demo — initial submission
8ad9950
Raw
History Blame Contribute Delete
4.4 kB
"""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
]
@tool("scrape_suppliers")
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",
})