Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Verify Moltbot Hybrid Engine API is working. | |
| Tests: health, /v1/models, chat completions (string + array content), optional streaming. | |
| Results: | |
| - By default: printed to stdout only. | |
| - With --output PATH: JSON report written to PATH (e.g. scripts/verify_results.json). | |
| - Path is printed at the end so you know where results are. | |
| Usage: | |
| python3 scripts/verify_api.py --api-key YOUR_KEY | |
| python3 scripts/verify_api.py --output scripts/verify_results.json --api-key YOUR_KEY | |
| python3 scripts/verify_api.py --base-url http://localhost:7860 | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| import time | |
| try: | |
| import requests | |
| except ImportError: | |
| print("Install requests: pip install requests") | |
| sys.exit(1) | |
| DEFAULT_BASE = os.environ.get("MOLTBOT_VERIFY_BASE_URL", "https://deebee7-moltbot-hybrid-engine.hf.space") | |
| DEFAULT_KEY = os.environ.get("MOLTBOT_VERIFY_API_KEY", "") | |
| # Default path for results when using --output without a path | |
| DEFAULT_RESULTS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "verify_results.json") | |
| def run(name: str, ok: bool, detail: str = "", results: list = None) -> None: | |
| status = "PASS" if ok else "FAIL" | |
| print(f" [{status}] {name}" + (f" — {detail}" if detail else "")) | |
| if results is not None: | |
| results.append({"name": name, "passed": ok, "detail": detail or None}) | |
| def main() -> int: | |
| p = argparse.ArgumentParser(description="Verify Moltbot Hybrid Engine API") | |
| p.add_argument("--base-url", default=DEFAULT_BASE, help="API base URL (no trailing slash)") | |
| p.add_argument("--api-key", default=DEFAULT_KEY, help="API key for /v1/chat/completions (Bearer). Default: MOLTBOT_VERIFY_API_KEY") | |
| p.add_argument("--no-stream-test", action="store_true", help="Skip streaming chat test") | |
| p.add_argument("--timeout", type=int, default=60, help="Request timeout seconds") | |
| p.add_argument("--output", "-o", default="", help="Write JSON results to this path (e.g. scripts/verify_results.json)") | |
| args = p.parse_args() | |
| results = [] # list of {name, passed, detail} | |
| def r(name: str, ok: bool, detail: str = "") -> None: | |
| run(name, ok, detail, results) | |
| base = args.base_url.rstrip("/") | |
| headers = { | |
| "Authorization": f"Bearer {args.api_key}", | |
| "Content-Type": "application/json", | |
| } | |
| timeout = args.timeout | |
| fails = 0 | |
| print("Moltbot Hybrid Engine — API verification") | |
| print(f"Base URL: {base}") | |
| print() | |
| # --- GET / --- | |
| print("1. GET / (health)") | |
| try: | |
| resp = requests.get(f"{base}/", timeout=10) | |
| r("GET /", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| else: | |
| try: | |
| d = resp.json() | |
| print(f" status={d.get('status')}, version={d.get('version')}") | |
| except Exception: | |
| pass | |
| except requests.RequestException as e: | |
| r("GET /", False, str(e)) | |
| fails += 1 | |
| print() | |
| # --- GET /health --- | |
| print("2. GET /health") | |
| try: | |
| resp = requests.get(f"{base}/health", timeout=10) | |
| r("GET /health", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| else: | |
| try: | |
| d = resp.json() | |
| llm = d.get("llm_backends", {}) | |
| print(f" ollama={llm.get('ollama', {}).get('running')}, hf_available={llm.get('hf_inference_api', {}).get('available')}") | |
| except Exception: | |
| pass | |
| except requests.RequestException as e: | |
| r("GET /health", False, str(e)) | |
| fails += 1 | |
| print() | |
| # --- GET /v1/models --- | |
| print("3. GET /v1/models") | |
| try: | |
| resp = requests.get(f"{base}/v1/models", timeout=10) | |
| r("GET /v1/models", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| else: | |
| try: | |
| d = resp.json() | |
| ids = [m.get("id") for m in d.get("data", [])] | |
| print(f" models={ids}") | |
| except Exception: | |
| pass | |
| except requests.RequestException as e: | |
| r("GET /v1/models", False, str(e)) | |
| fails += 1 | |
| print() | |
| # --- POST /v1/chat/completions (string content) --- | |
| print("4. POST /v1/chat/completions (string content, non-streaming)") | |
| try: | |
| body = { | |
| "model": "moltbot-legal", | |
| "messages": [{"role": "user", "content": "Reply with exactly: OK"}], | |
| "stream": False, | |
| "max_tokens": 50, | |
| } | |
| resp = requests.post(f"{base}/v1/chat/completions", headers=headers, json=body, timeout=timeout) | |
| r("Chat (string)", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| if resp.text: | |
| print(f" body: {resp.text[:300]}") | |
| else: | |
| try: | |
| d = resp.json() | |
| content = (d.get("choices") or [{}])[0].get("message", {}).get("content", "") | |
| print(f" response length={len(content)}, has 'OK'={('OK' in content)}") | |
| except Exception as e: | |
| print(f" parse: {e}") | |
| except requests.RequestException as e: | |
| r("Chat (string)", False, str(e)) | |
| fails += 1 | |
| print() | |
| # --- POST /v1/chat/completions (array content — 422 fix) --- | |
| print("5. POST /v1/chat/completions (array content, R1/vision-style)") | |
| try: | |
| body = { | |
| "model": "moltbot-legal", | |
| "messages": [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": "Reply with exactly: ARRAY_OK"}, | |
| ], | |
| } | |
| ], | |
| "stream": False, | |
| "max_tokens": 50, | |
| } | |
| resp = requests.post(f"{base}/v1/chat/completions", headers=headers, json=body, timeout=timeout) | |
| r("Chat (array content)", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| if resp.text: | |
| print(f" body: {resp.text[:300]}") | |
| else: | |
| try: | |
| d = resp.json() | |
| content = (d.get("choices") or [{}])[0].get("message", {}).get("content", "") | |
| print(f" response length={len(content)}, has 'ARRAY_OK'={('ARRAY_OK' in content)}") | |
| except Exception as e: | |
| print(f" parse: {e}") | |
| except requests.RequestException as e: | |
| r("Chat (array content)", False, str(e)) | |
| fails += 1 | |
| print() | |
| # --- POST /v1/chat/completions (array with text + image placeholder) --- | |
| print("6. POST /v1/chat/completions (array: text + image part)") | |
| try: | |
| body = { | |
| "model": "moltbot-legal", | |
| "messages": [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": "Say only: IMAGE_OK"}, | |
| {"type": "image_url", "image_url": {"url": "https://example.com/fake.png"}}, | |
| ], | |
| } | |
| ], | |
| "stream": False, | |
| "max_tokens": 50, | |
| } | |
| resp = requests.post(f"{base}/v1/chat/completions", headers=headers, json=body, timeout=timeout) | |
| r("Chat (text+image parts)", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| if resp.text: | |
| print(f" body: {resp.text[:200]}") | |
| else: | |
| try: | |
| d = resp.json() | |
| content = (d.get("choices") or [{}])[0].get("message", {}).get("content", "") | |
| print(f" response length={len(content)}") | |
| except Exception as e: | |
| print(f" parse: {e}") | |
| except requests.RequestException as e: | |
| r("Chat (text+image parts)", False, str(e)) | |
| fails += 1 | |
| print() | |
| # --- Streaming (optional) --- | |
| if not args.no_stream_test: | |
| print("7. POST /v1/chat/completions (streaming)") | |
| try: | |
| body = { | |
| "model": "moltbot-legal", | |
| "messages": [{"role": "user", "content": "Say hello in one word."}], | |
| "stream": True, | |
| "max_tokens": 20, | |
| } | |
| resp = requests.post(f"{base}/v1/chat/completions", headers=headers, json=body, timeout=timeout, stream=True) | |
| r("Chat (streaming)", resp.status_code == 200, f"status={resp.status_code}" if resp.status_code != 200 else "") | |
| if resp.status_code != 200: | |
| fails += 1 | |
| else: | |
| chunks = [line for line in resp.iter_lines(decode_unicode=True) if line and line.startswith("data: ") and line != "data: [DONE]"] | |
| print(f" chunks received={len(chunks)}") | |
| except requests.RequestException as e: | |
| r("Chat (streaming)", False, str(e)) | |
| fails += 1 | |
| print() | |
| else: | |
| print("7. POST /v1/chat/completions (streaming) — skipped (--no-stream-test)") | |
| print() | |
| # --- Summary --- | |
| print("---") | |
| if fails == 0: | |
| print("All checks passed.") | |
| else: | |
| print(f"Failed: {fails} check(s).") | |
| # --- Write results to file if requested --- | |
| out_path = (args.output or "").strip() | |
| if out_path: | |
| report = { | |
| "base_url": base, | |
| "timestamp_iso": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), | |
| "all_passed": fails == 0, | |
| "failed_count": fails, | |
| "checks": results, | |
| } | |
| try: | |
| with open(out_path, "w") as f: | |
| json.dump(report, f, indent=2) | |
| print(f"Results written to: {os.path.abspath(out_path)}") | |
| except Exception as e: | |
| print(f"Could not write results to {out_path}: {e}") | |
| return 0 if fails == 0 else 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |