Spaces:
Running
Running
Download scripts/check_grok.py from Shurem/mz_chatbot: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/Shurem/mz_chatbot/resolve/main/scripts/check_grok.py
- Command line
-
hf download hf://spaces/Shurem/mz_chatbot/scripts/check_grok.py
-
curl -L -o check_grok.py https://huggingface.co/spaces/Shurem/mz_chatbot/resolve/main/scripts/check_grok.py
10.8 kB
| """ | |
| check_grok.py β does grounded discovery actually work, and is it actually grounded? | |
| The whole point of this provider is that the model must LOOK THINGS UP instead of | |
| answering from memory. So this script does not just check that a reply comes | |
| back; it counts tool calls and re-checks the websites the agent returned. | |
| uv run python scripts/check_grok.py | |
| uv run python scripts/check_grok.py --provider groq | |
| uv run python scripts/check_grok.py --list-models | |
| uv run python scripts/check_grok.py --icp kitchens --max 3 | |
| uv run python scripts/check_grok.py --icp "your own free-text ICP here" | |
| Costs real tokens on whichever provider runs. No database is touched. | |
| A run that returns companies but made ZERO tool calls is a FAILURE, not a pass β | |
| that is the old ungrounded behaviour wearing a new coat. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import asyncio | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | |
| import requests # noqa: E402 | |
| from app.services import providers # noqa: E402 | |
| from app.services.providers import tools as provider_tools # noqa: E402 | |
| # Named ICPs to test against. Pass a name (--icp golf) or free text. | |
| # | |
| # "golf" is the sharpest test of grounding: it names a narrow vertical AND a | |
| # checkable property of the website, so a fabricated company is obvious and | |
| # fetch_page can confirm or refute the "static photos, no configurator" claim. | |
| ICP_PRESETS = { | |
| "golf": ( | |
| "US custom golf cart and luxury utility vehicle builders doing $1M-$10M revenue " | |
| "whose websites still show static photo galleries instead of an interactive 3D " | |
| "configurator" | |
| ), | |
| "btier": ( | |
| "US companies that are B Tier companies doing around 1M to 10M in profits and would be " | |
| "interested in Interactive 3D Configurators" | |
| ), | |
| "kitchens": ( | |
| "Luxury bespoke kitchen cabinet manufacturers in the UAE or UK without a 3D configurator" | |
| ), | |
| "yachts": ( | |
| "Superyacht and custom boat builders in Europe selling highly configurable vessels " | |
| "with only static photography on their websites" | |
| ), | |
| } | |
| DEFAULT_ICP = ICP_PRESETS["golf"] | |
| def _wrap_tools_with_counter() -> dict: | |
| """Count tool invocations by wrapping each tool's underlying callable.""" | |
| counts: dict[str, int] = {} | |
| for tool in provider_tools.DISCOVERY_TOOLS: | |
| name = getattr(tool, "name", None) or getattr(tool, "__name__", "tool") | |
| counts[name] = 0 | |
| inner = getattr(tool, "on_invoke_tool", None) | |
| if inner is None: | |
| continue | |
| def make(orig, key): | |
| async def wrapped(*a, **kw): | |
| counts[key] += 1 | |
| return await orig(*a, **kw) | |
| return wrapped | |
| tool.on_invoke_tool = make(inner, name) # type: ignore[attr-defined] | |
| return counts | |
| def _list_models(provider) -> list[str]: | |
| """Ask the provider which models it will actually serve this key.""" | |
| try: | |
| r = requests.get( | |
| f"{provider._base_url.rstrip('/')}/models", | |
| headers={"Authorization": f"Bearer {provider._api_key}"}, | |
| timeout=10, | |
| ) | |
| if r.status_code != 200: | |
| return [] | |
| data = r.json() | |
| rows = data.get("data") if isinstance(data, dict) else data | |
| return sorted(str(m.get("id")) for m in rows if isinstance(m, dict) and m.get("id")) | |
| except Exception: | |
| return [] | |
| def _diagnose(provider, error: str) -> None: | |
| """Turn an opaque provider error into something actionable.""" | |
| low = error.lower() | |
| key = provider._api_key or "" | |
| if "incorrect api key" in low or "invalid_api_key" in low or "401" in low: | |
| print("\n The endpoint rejected the key.") | |
| if key.startswith("gsk_") and "x.ai" in provider._base_url: | |
| print(" That key starts with 'gsk_', which is a GROQ key (groq.com).") | |
| print(" You are calling api.x.ai, which is xAI's Grok β a different company.") | |
| print(" Fix: set LLM_PROVIDER=groq, or put an xai- key in XAI_API_KEY.") | |
| elif key.startswith("xai-") and "groq.com" in provider._base_url: | |
| print(" That key starts with 'xai-', which is an xAI key, but this is Groq.") | |
| print(" Fix: set LLM_PROVIDER=grok.") | |
| else: | |
| print(f" Key prefix is {key[:4]!r}. Check it was copied in full and is still active.") | |
| return | |
| if "model" in low and ("not found" in low or "does not exist" in low or "decommission" in low): | |
| print(f"\n The model {provider._model!r} is not available to this key.") | |
| names = _list_models(provider) | |
| if names: | |
| print(" Models this key CAN use:") | |
| for n in names[:25]: | |
| print(f" {n}") | |
| print(f"\n Set one of those in .env, e.g. GROQ_MODEL=... or XAI_MODEL=...") | |
| else: | |
| print(" Could not list available models either β check the key first.") | |
| return | |
| if "tool" in low and "support" in low: | |
| print(f"\n {provider._model!r} appears not to support tool calling, which this") | |
| print(" agent requires. Pick a tool-capable model.") | |
| # A site that refuses an automated request is NOT a dead site. 403/406/429 and | |
| # friends come from bot protection and say nothing about whether the business is | |
| # real -- the same distinction scraper.looks_blocked() and site_matches_lead() | |
| # already make. Only 404/410 and a failure to connect mean the lead is bad. | |
| BLOCKED_STATUSES = {401, 403, 405, 406, 409, 418, 429, 503} | |
| GONE_STATUSES = {404, 410} | |
| # A bare "Mozilla/5.0" is itself enough to trigger some WAFs. Sending what a real | |
| # browser sends removes most false 406s. | |
| _BROWSER_HEADERS = { | |
| "User-Agent": ( | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " | |
| "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" | |
| ), | |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", | |
| "Accept-Language": "en-US,en;q=0.9", | |
| } | |
| def _site_is_live(url: str) -> tuple[str, bool]: | |
| """Return (human description, counts_as_dead).""" | |
| if not url: | |
| return "no website returned", True | |
| try: | |
| r = requests.head(url, timeout=8, allow_redirects=True, headers=_BROWSER_HEADERS) | |
| # Some servers reject HEAD specifically; retry properly before judging. | |
| if r.status_code >= 400: | |
| r = requests.get(url, timeout=10, allow_redirects=True, | |
| headers=_BROWSER_HEADERS, stream=True) | |
| code = r.status_code | |
| except requests.RequestException as exc: | |
| return f"unreachable ({type(exc).__name__}) <-- DEAD", True | |
| if code < 400: | |
| return f"HTTP {code}", False | |
| if code in BLOCKED_STATUSES: | |
| return f"HTTP {code} β bot-blocked, site is up (not a mark against the lead)", False | |
| if code in GONE_STATUSES: | |
| return f"HTTP {code} <-- DEAD", True | |
| return f"HTTP {code} <-- DEAD", True | |
| async def main() -> int: | |
| ap = argparse.ArgumentParser(description=__doc__) | |
| ap.add_argument("--provider", default="", help="grok | groq | gemini (default: auto)") | |
| ap.add_argument("--list-models", action="store_true", | |
| help="list the models this key can use, then exit") | |
| ap.add_argument("--icp", default=DEFAULT_ICP, | |
| help="a preset name (" + " | ".join(ICP_PRESETS) + ") or free text") | |
| ap.add_argument("--max", type=int, default=3) | |
| args = ap.parse_args() | |
| icp = ICP_PRESETS.get(args.icp.strip().lower(), args.icp) | |
| status = providers.provider_status() | |
| print("provider credentials:") | |
| for name, ok in status.items(): | |
| print(f" {name:8} {'configured' if ok else 'NOT configured'}") | |
| provider = providers.get_provider(args.provider) | |
| print(f"\nusing: {provider.name} (model {provider._model})") | |
| if not provider.available(): | |
| print(f"\n{provider.name} has no API key. Set XAI_API_KEY (or GEMINI_API_KEY) in .env.") | |
| return 1 | |
| if args.list_models: | |
| names = _list_models(provider) | |
| if not names: | |
| print("\nCould not list models β the key may be wrong for this endpoint.") | |
| return 1 | |
| print(f"\nmodels available to this key ({len(names)}):") | |
| for n in names: | |
| print(f" {n}") | |
| return 0 | |
| counts = _wrap_tools_with_counter() | |
| print(f"\nICP: {icp}\nasking for {args.max} companies...\n") | |
| result = await provider.discover(icp, args.max) | |
| if result.error: | |
| print(f"FAILED: {result.error}") | |
| _diagnose(provider, result.error) | |
| return 1 | |
| print(f"tool calls made: {sum(counts.values())}") | |
| for name, n in counts.items(): | |
| print(f" {name:22} {n}") | |
| print(f"\ncandidates returned: {len(result.candidates)}") | |
| dead = 0 | |
| for c in result.candidates: | |
| print(f"\n {c.company_name} [{c.country or '??'}] {c.industry_tag}") | |
| print(f" website : {c.company_website}") | |
| live, is_dead = _site_is_live(c.company_website) | |
| print(f" live? : {live}") | |
| dead += is_dead | |
| print(f" why fit : {(c.why_fit or '')[:140]}") | |
| print(f" cites : {len(c.citations)} url(s)") | |
| for u in c.citations[:3]: | |
| print(f" {u}") | |
| # ---- the assertions that matter ------------------------------------- | |
| print("\n" + "=" * 60) | |
| failures = [] | |
| if sum(counts.values()) == 0: | |
| failures.append( | |
| "the agent made NO tool calls β it answered from memory, which is exactly " | |
| "the ungrounded behaviour this provider exists to replace" | |
| ) | |
| if not result.candidates: | |
| failures.append("no candidates returned") | |
| if dead: | |
| failures.append( | |
| f"{dead} of {len(result.candidates)} websites are genuinely dead " | |
| f"(404/410/unreachable β bot-blocks do not count)" | |
| ) | |
| uncited = [c.company_name for c in result.candidates if not c.citations] | |
| if uncited: | |
| failures.append(f"no citations for: {', '.join(uncited)}") | |
| # A model must never volunteer contact details -- Candidate has no field for | |
| # them, but check the prose too in case it smuggled one into why_fit. | |
| leaked = [c.company_name for c in result.candidates if "@" in (c.why_fit or "")] | |
| if leaked: | |
| failures.append(f"an email address appeared in why_fit for: {', '.join(leaked)}") | |
| if failures: | |
| print("FAILED:") | |
| for f in failures: | |
| print(f" - {f}") | |
| return 1 | |
| print(f"PASS β {len(result.candidates)} grounded candidates, " | |
| f"{sum(counts.values())} tool calls, all websites live, all cited.") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(asyncio.run(main())) | |