File size: 6,754 Bytes
ede84b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""

models_lock.py — สร้าง models.yaml จากแคช HF บนเครื่องที่ผ่านชุดทดสอบแล้ว (28 ต.ค.)



    & $py models_lock.py            # ดูอย่างเดียว ไม่เขียนไฟล์

    & $py models_lock.py --write    # เขียน models.yaml (ไฟล์เดิมสำรองเป็น models.yaml.bak-<เวลา>)



หลัก: ไม่เดา — ใช้ commit ที่ refs/main ในแคชชี้อยู่ ซึ่งคือตัวที่ from_pretrained แบบไม่ระบุ revision

โหลดมาใช้ตอนทดสอบ ถ้าโมเดลไหนไม่มีในแคช หรือหา main ไม่เจอ → ไม่ล็อกตัวนั้นและบอกเหตุผล

ล็อกเองได้ด้วย --rev "<model_id>=<commit 40 ตัว>"



ไม่ต่อเน็ต ไม่ดาวน์โหลดอะไร อ่านแคชอย่างเดียว

"""
from __future__ import annotations

import argparse
import os
import re
import shutil
import sys
import time

BASE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE)

# โมเดลที่บอทโหลดจาก Hub (assistant.py · brands/modutech/image_gate.py) — adapter ViT อยู่ในเครื่อง ล็อกด้วย sha256 ใน brand.yaml แล้ว
MODELS = {
    "jinaai/jina-embeddings-v3": "ข้อความ → เวกเตอร์ (ค้นทั้งสองแบรนด์)",
    "google/siglip-base-patch16-224": "SigLIP ของ retriever Movex",
    "google/siglip-base-patch16-384": "ประตูรูป Modutech (เกณฑ์ 0.65 / 0.8254 ปรับกับรุ่นนี้)",
    "google/vit-base-patch16-224-in21k": "ฐานของ ViT LoRA Movex",
}
# โค้ดของ Jina v3 มาจากรีโปแยก (trust_remote_code) — revision= ของ jina ไม่ล็อกตัวนี้ จดไว้ให้รู้อย่างเดียว
CODE_REPOS = {"jinaai/xlm-roberta-flash-implementation": "โค้ดโมเดลของ Jina v3 (จดไว้ ไม่ได้ล็อก)"}
SHA = re.compile(r"^[0-9a-f]{40}$")


def cache_dir() -> str:
    from huggingface_hub import constants
    return constants.HF_HUB_CACHE


def cached_main(repo_id: str, root: str) -> tuple[str | None, str]:
    """อ่าน refs/main ของรีโปเดียวตรง ๆ — ไม่ใช้ scan_cache_dir เพราะมันไล่ทุกรีโปในแคช

    แล้วล้มทั้งหมดเมื่อเจอ symlink เสียของรีโปอื่น (Windows · WinError 1920 · 28 ต.ค.)"""
    d = os.path.join(root, "models--" + repo_id.replace("/", "--"))
    if not os.path.isdir(d):
        return None, "ไม่มีในแคช"
    snaps = os.path.join(d, "snapshots")
    have = sorted(os.listdir(snaps)) if os.path.isdir(snaps) else []
    ref = os.path.join(d, "refs", "main")
    if os.path.isfile(ref):
        with open(ref, encoding="utf-8") as f:
            sha = f.read().strip()
        if not SHA.match(sha):
            return None, f"refs/main อ่านได้ '{sha[:20]}' ไม่ใช่ commit — ระบุเองด้วย --rev"
        if sha not in have:
            return None, f"refs/main ชี้ {sha[:10]} แต่ไม่มี snapshot นั้นในแคช — ระบุเองด้วย --rev"
        return sha, f"refs/main ({len(have)} snapshot ในแคช)"
    others = ", ".join(h[:10] for h in have) or "-"
    return None, f"มีในแคชแต่ไม่มี refs/main (snapshot: {others}) — ระบุเองด้วย --rev"


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--write", action="store_true")
    ap.add_argument("--rev", action="append", default=[], help="<model_id>=<commit> ล็อกเอง (ทับค่าจากแคช)")
    ap.add_argument("-o", default=os.path.join(BASE, "models.yaml"))
    a = ap.parse_args()

    root = cache_dir()
    manual = {}
    for x in a.rev:
        mid, _, sha = x.partition("=")
        if not SHA.match(sha.strip()):
            print(f"❌ --rev {x}: commit ต้องเป็น 40 ตัว")
            return 2
        manual[mid.strip()] = sha.strip()

    locked, missing = {}, []
    print(f"แคช HF: {root}")
    for mid, role in {**MODELS, **CODE_REPOS}.items():
        sha, why = (manual[mid], "--rev") if mid in manual else cached_main(mid, root)
        mark = "✅" if sha else "⚠️"
        print(f"{mark} {mid}: {sha or '-'}  · {why}  · {role}")
        if mid in MODELS:
            (locked.__setitem__(mid, (sha, role)) if sha else missing.append(mid))

    if missing:
        print(f"\n⚠️ ไม่ล็อก {len(missing)} ตัว: {', '.join(missing)} — ตัวนั้นจะโหลด main ตามเดิม")
    if not a.write:
        print("\n(ดูอย่างเดียว — ใส่ --write เพื่อเขียน models.yaml)")
        return 0
    if os.path.exists(a.o):
        bak = f"{a.o}.bak-{time.strftime('%Y%m%d-%H%M%S')}"
        shutil.copy2(a.o, bak)
        print(f"สำรองไฟล์เดิม → {os.path.basename(bak)}")
    lines = [f"# สร้างด้วย models_lock.py {time.strftime('%Y-%m-%d %H:%M')} จากแคช HF บนเครื่องที่ผ่านชุดทดสอบ",
             "# เปลี่ยนโมเดลตัวไหน = ต้องรันชุดทดสอบใหม่ · เปลี่ยน SigLIP-384 = ต้องปรับเกณฑ์ประตูใหม่ด้วย brand_calibration_rows.csv",
             "models:"]
    for mid, (sha, role) in locked.items():
        lines += [f"  {mid}:", f"    revision: {sha}", f"    role: \"{role}\""]
    with open(a.o, "w", encoding="utf-8", newline="\n") as f:          # UTF-8 ไม่มี BOM
        f.write("\n".join(lines) + "\n")
    from core import model_lock
    model_lock.load(a.o)                                                # ตรวจว่าอ่านกลับได้
    print(f"✅ เขียน {os.path.basename(a.o)} · ล็อก {len(locked)} ตัว")
    return 0


if __name__ == "__main__":
    sys.exit(main())