#!/usr/bin/env python3 """Smart iFlytek MaaS verifier — queries /models per key, then tries each granted model until one returns a real chat completion. iFlytek MaaS keys are per-user and each has a different set of granted models (the /models endpoint returns the key's own entitlements). A blanket model list misses them — we must enumerate per key. """ import json, sys, time, urllib.request, urllib.error from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from verify_cache import CachedVerifier BASE = "https://maas-api.cn-huabei-1.xf-yun.com/v1" OUT = Path("results/iflytek_maas_smart") OUT.mkdir(parents=True, exist_ok=True) def http(method, path, key, body=None, timeout=20): url = BASE + path headers = {"Authorization": f"Bearer {key}", "Accept": "application/json"} data = None if body is not None: data = json.dumps(body).encode() headers["Content-Type"] = "application/json" req = urllib.request.Request(url, data=data, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=timeout) as r: return r.getcode(), r.read().decode("utf-8", "replace") except urllib.error.HTTPError as e: try: return e.code, e.read().decode("utf-8", "replace") except Exception: return e.code, "" except Exception as e: return 0, f"net:{type(e).__name__}" def verify(key): # 1. enumerate granted models code, body = http("GET", "/models", key) if code == 401: return "DEAD", f"401 {body[:140]}" models = [] if code == 200: try: data = json.loads(body) models = [m.get("id") for m in data.get("data", []) if m.get("id")] except Exception: pass if not models: # fallback list models = ["xop3qwen1b7", "xopglm5", "xopkimik26", "xsparkx2flash", "generalv3.5", "4.0Ultra", "xqwen257bchat", "xdeepseekv3"] # 2. try chat against each granted model last = "no model tried" for model in models[:12]: cc, cb = http("POST", "/chat/completions", key, { "model": model, "messages": [{"role": "user", "content": "reply ok"}], "max_tokens": 8, "temperature": 0.01, }) bl = (cb or "").lower() if cc == 200 and '"choices"' in bl: # extract content snippet try: j = json.loads(cb) content = (j["choices"][0].get("message", {}).get("content") or "")[:60] except Exception: content = "" return "USABLE", f"{model} 200 {content}" if cc == 401: return "DEAD", f"{model} 401 {cb[:140]}" if cc in (402, 429): return "NO_BALANCE", f"{model} {cc} {cb[:140]}" # 403/400 with "无权使用模型" = model not granted, try next if cc in (400, 403, 404) and ("无权" in cb or "not found" in bl or "model" in bl or "forbidden" in bl): last = f"{model} {cc}: {cb[:120]}" continue if cc == 0: last = f"{model} net: {cb[:120]}" continue last = f"{model} {cc}: {cb[:140]}" return "NO_ACCESS", last def load_keys(): """Load all iflytek_maas candidate keys from candidates.tsv + bucket files.""" keys = {} root = Path("results/all_deep") # from dead/unknown buckets (provkey format, possibly concatenated) for fn in ("dead.txt", "unknown.txt", "no_access.txt", "no_balance.txt", "usable.txt"): p = root / fn if not p.exists(): continue for ln in p.read_text(errors="replace").splitlines(): parts = ln.split("\t") if len(parts) < 3: continue prov, key, url = parts[0], parts[1], parts[2] if not key.startswith("sk-") and prov.startswith("iflytek_maas"): # prov might be "iflytek_maassk-..." (concatenation bug) m = __import__("re").match(r"^(iflytek_maas)(sk-.+)$", prov) if m: prov, key = m.group(1), m.group(2) if prov == "iflytek_maas" and key.startswith("sk-"): keys.setdefault(key, url) return keys def main(): keys = load_keys() print(f"iflytek_maas candidate keys: {len(keys)}", flush=True) buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []} with CachedVerifier("iflytek_maas_smart", verify, force=True) as ver: with ThreadPoolExecutor(max_workers=24) as pool: futs = {pool.submit(ver, k): (k, s) for k, s in keys.items()} done = 0 for fut in as_completed(futs): k, s = futs[fut]; done += 1 try: v, d = fut.result() except Exception as e: v, d = "UNKNOWN", f"exc:{e}" buckets[v].append((k, s, d)) if done % 25 == 0: print(f" {done}/{len(keys)} usable={len(buckets['USABLE'])} " f"dead={len(buckets['DEAD'])}", flush=True) for label, rows in buckets.items(): with (OUT / f"{label.lower()}.txt").open("w") as f: for k, s, d in rows: f.write(f"{k}\t{s}\t{d}\n") print("\n=== IFLYTEK MAAS SMART RESULTS ===") for label in buckets: print(f" {label:11s}: {len(buckets[label])}") for k, s, d in buckets["USABLE"]: print(f" ✅ {k}") print(f" {s}") print(f" {d[:140]}") if __name__ == "__main__": main()