Files
hack/tools/scripts/llm-key-hunter/reverify_iflytek_smart.py
T

150 lines
5.7 KiB
Python

#!/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 (prov<TAB>key 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()