diff --git a/main.py b/main.py index 4767ae2..bfd4e9d 100644 --- a/main.py +++ b/main.py @@ -1,17 +1,14 @@ -"""ModelHub XC 适配智能体 · 只读探针骨架 (probe-only) v1.0.1 +"""ModelHub XC 适配智能体 · 只读探针 + 容器内提交测试 v1.0.2 -用途: - 1) 验证平台「建仓 -> Kaniko 构建 -> 部署 -> /health -> 运行/停止」整条链路。 - 2) 零副作用诊断平台注入的凭证: - - dump 所有环境变量的「名字 + 值长度」(不打值),看真 token 是否藏在别的变量名里。 - - 打印 EXTERNAL_SERVICE_TOKEN 的值(短值必是废值、直接看;长值打码,避免泄露真 token)。 - - 对每个「长得像 token」(len>=16) 的环境变量,试当 Xc-Token 调一次只读接口,看哪个能通(code=0)。 - - 仍用 EXTERNAL_SERVICE_TOKEN 三种头各探一次,作对照。 +关键实验:在容器里用注入的 EXTERNAL_SERVICE_TOKEN(=tmp) + STRATEGY_ID 真调一次 task/add, +验证平台是否靠「请求来自容器 pod」放行智能体提交(浏览器/脚本提交会被 60014 挡)。 -严格约束: - - 只服务 GET /health 与 GET /,绝不提交任何验证任务 (不调 task/add, 不调 build-config)。 - - 只读 GET;只记录 http/code/计数,不打印任何接口返回体内容(防 PII);不打印任何长凭证的完整值。 - - 纯 stdlib 零依赖;正确处理 SIGTERM 优雅停机。 +诊断输出: + - dump 所有 env 的名字+长度(不打值)。 + - EXTERNAL_SERVICE_TOKEN 值(短则原样、长则打码)。 + - 容器内 build-config + task/add(真实模型) 的返回 code/msg。 + +约束:token 值不打(短占位除外);只提交这一次测试(真实模型可能进队列);SIGTERM 优雅停机。 """ import json import os @@ -27,6 +24,20 @@ TOKEN = os.getenv("EXTERNAL_SERVICE_TOKEN", "") STRATEGY_ID = os.getenv("STRATEGY_ID", "") PORT = int(os.getenv("PORT", "8080")) +TEST_MODEL = os.getenv("TEST_MODEL", "https://www.modelscope.cn/models/mradermacher/TrialSpace-1225-GGUF") +GPU, FW, TT = os.getenv("GPU", "MetaX_c-500"), os.getenv("FW", "vllm"), os.getenv("TT", "text-generation") + +HARDCODED_CFG = ( + "framework: vllm\napi: completion\nlang: en\nmax_model_len: 4096\nmax_tokens: 1024\n" + "temperature: 0.7\nrepetition_penalty: 1.1\ntop_p: 0.9\nsut_config:\n gpu_num: 1\n values:\n" + " command:\n - /opt/conda/bin/vllm\n - serve\n - /model\n - --port\n - '20644'\n" + " - --served-model-name\n - llm\n - --max-model-len\n - '4096'\n" + " - --gpu-memory-utilization\n - '0.9'\n - -tp\n - '1'\n - --enforce-eager\n" + " - --trust-remote-code\nref_config:\n gpu_num: 1\n values:\n command:\n - vllm\n" + " - serve\n - /model\n - --port\n - '80'\n - --served-model-name\n - llm\n" + " - --max-model-len\n - '4096'\n - -tp\n - '1'\n - --enforce-eager\n - --trust-remote-code\n" +) + shutdown = threading.Event() PROBE = {"done": False, "results": []} @@ -36,69 +47,50 @@ def log(msg: str) -> None: def _mask(v: str) -> str: - """短值(必是废值)原样看;长值只留头尾,避免泄露可能的真 token。""" return v if len(v) <= 8 else (v[:2] + "…" + v[-2:]) -def _readonly_get(path: str, headers: dict, timeout: int = 20) -> dict: - req = urllib.request.Request(MAIN + path, method="GET", headers=headers) +def _req(method: str, path: str, headers: dict, body=None, timeout: int = 30) -> dict: + data = body.encode() if isinstance(body, str) else body + req = urllib.request.Request(MAIN + path, data=data, method=method, headers=headers) try: with urllib.request.urlopen(req, timeout=timeout) as r: - body = r.read().decode("utf-8", "ignore") + txt = r.read().decode("utf-8", "ignore") status = r.status except urllib.error.HTTPError as e: - body = e.read().decode("utf-8", "ignore") + txt = e.read().decode("utf-8", "ignore") status = e.code except Exception as e: # noqa: BLE001 - return {"http": -1, "err": str(e)[:120]} - code = count = None + return {"http": -1, "err": str(e)[:100]} try: - j = json.loads(body) - code = j.get("code") - d = j.get("data") - if isinstance(d, dict): - count = d.get("total") - if count is None and isinstance(d.get("records"), list): - count = len(d["records"]) - elif isinstance(d, list): - count = len(d) + j = json.loads(txt) + return {"http": status, "code": j.get("code"), "msg": (j.get("message") or "")[:100], + "data": j.get("data")} except Exception: # noqa: BLE001 - pass - return {"http": status, "code": code, "count": count} + return {"http": status, "raw": txt[:100]} def probe() -> None: - # 1) dump 所有 env 的 key + 值长度(不打值),找隐藏的真 token env_lens = {k: len(str(os.environ.get(k, ""))) for k in sorted(os.environ)} log("env keys+lens: " + json.dumps(env_lens, ensure_ascii=False)) + log(f"strategy_id={STRATEGY_ID or 'MISSING'} | EXTERNAL_SERVICE_TOKEN value={_mask(TOKEN)!r} len={len(TOKEN)}") - # 2) EXTERNAL_SERVICE_TOKEN 的值(短则全打,长则打码)+ STRATEGY_ID - log(f"strategy_id={STRATEGY_ID or 'MISSING'} | " - f"EXTERNAL_SERVICE_TOKEN value={_mask(TOKEN)!r} len={len(TOKEN)}") - - path = "/api/adapt/task/page?current=1&pageSize=1" - - # 3) 对每个「长得像 token」(len>=16) 的 env 值,试当 Xc-Token,看哪个能通 - candidates = [k for k in sorted(os.environ) if len(str(os.environ.get(k, ""))) >= 16] - log("token-like env candidates (len>=16): " + json.dumps(candidates, ensure_ascii=False)) - for k in candidates: - res = _readonly_get(path, {"Xc-Token": os.environ[k]}) - PROBE["results"].append({"try_env": k, "as": "Xc-Token", **res}) - log(f"try Xc-Token from ${k} -> {json.dumps(res, ensure_ascii=False)}") - - # 4) EXTERNAL_SERVICE_TOKEN 三种头对照 - trials = [ - ("Xc-Token", {"Xc-Token": TOKEN}), - ("Authorization-Bearer", {"Authorization": "Bearer " + TOKEN}), - ("Authorization-raw", {"Authorization": TOKEN}), - ] - for name, headers in trials: - res = {"skip": "no-token"} if not TOKEN else _readonly_get(path, headers) - PROBE["results"].append({"est_token_hdr": name, **res}) - log(f"EST-token {name} -> {json.dumps(res, ensure_ascii=False)}") + # 只读:token 有效性 + page = _req("GET", "/api/adapt/task/page?current=1&pageSize=1", {"Xc-Token": TOKEN}) + log("EST-token task/page -> " + json.dumps({k: page.get(k) for k in ("http", "code")}, ensure_ascii=False)) + # 关键:容器内 build-config + task/add(真实模型) + bc = _req("POST", f"/api/adapt/task/build-config?gpuType={GPU}&framework={FW}&taskType={TT}", {"Xc-Token": TOKEN}) + log("build-config(EST-token) -> " + json.dumps({k: bc.get(k) for k in ("http", "code", "msg")}, ensure_ascii=False)) + cfg = bc.get("data") if isinstance(bc.get("data"), str) and bc.get("data") else HARDCODED_CFG + body = json.dumps({"modelAddress": TEST_MODEL, "taskType": TT, "targetGpu": GPU, + "framework": FW, "strategyId": STRATEGY_ID, "configParams": cfg}) + ta = _req("POST", "/api/adapt/task/add", {"Xc-Token": TOKEN, "Content-Type": "application/json"}, body) + log("SUBMIT-TEST task/add(EST-token+strategyId, real model) -> " + json.dumps(ta, ensure_ascii=False)) + PROBE["results"] = [{"task_page": page.get("code")}, {"build_config": bc.get("code")}, + {"task_add": ta.get("code"), "msg": ta.get("msg")}] PROBE["done"] = True - log("probe done | 判读: 任一 code=0 => 该来源凭证有效; 全 401/40100 => 无有效凭证") + log("probe done | task/add code=0 => 容器提交通; 60014 => 容器来源也被判脚本; 401 => token 无效") class Handler(BaseHTTPRequestHandler): @@ -115,9 +107,7 @@ class Handler(BaseHTTPRequestHandler): self._json({"status": "ok"}) return if self.path == "/": - self._json({"name": "huni-probe-agent", "mode": "probe-only-v1.0.1", - "strategy_id_present": bool(STRATEGY_ID), - "token_present": bool(TOKEN), "probe": PROBE}) + self._json({"name": "huni-probe-agent", "mode": "submit-test-v1.0.2", "probe": PROBE}) return self._json({"error": "not found"}, 404) @@ -137,7 +127,7 @@ def main() -> None: server = ThreadingHTTPServer(("0.0.0.0", PORT), Handler) server.timeout = 1 - log(f"probe-only agent v1.0.1 listening on 0.0.0.0:{PORT}") + log(f"probe agent v1.0.2 listening on 0.0.0.0:{PORT}") while not shutdown.is_set(): server.handle_request() server.server_close()