150 lines
5.7 KiB
Python
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()
|