اسکیل yingzao نه اسکریپت پایتون دارد که پیش از هر فراخوانی مدل تصویر، عکس را اندازه می‌گیرند. در این نوشته اسکیل را با همان دستور README نصب می‌کنیم، گیت پیش از تولیدش را روی یک عکس واقعی اجرا می‌کنیم و می‌بینیم چرا نصب روی ماشین مجازی قدیمی با خطای X86_V2 می‌شکند و pin کردن نامپی به 2.3.5 آن را درست می‌کند. همه‌ی عددها خروجی واقعی همین اجرا روی همین سرور است.

اسکیل چه می‌کند

مخزن op7418/guizang-yingzao-skill در ۲ سپتامبر ۲۰۲۶ ساخته شد و در لحظه‌ی خواندن این نوشته، ۲۸ سپتامبر، ۴۷۲ ستاره و ۳۸ فورک دارد [۱][۲]. زبان اصلی‌اش پایتون است و کل مخزن با فشردگی حدود ۶۴ مگابایت، بیشترش تصویر مرجع است.

کار سنگین آن دو بخش دارد. بخش اول آن نه اسکریپت قطعی است که تنها از Pillow، numpy، opencv-python-headless و fonttools استفاده می‌کنند [۳]. یک فایل دهم کتابخانه‌ی مشترک است و دستور اجرایی ندارد. بخش دوم فراخوانی مدل تصویر است.

پس گیت پیش از تولید بدون هیچ کلید API اجرا می‌شود و عدد می‌دهد. همین ویژگی موضوع این نوشته است، چون کم‌هزینه‌ترین بخش اسکیل بیشترین اطلاعات را درباره‌ی عکس شما بیرون می‌دهد.

نصب با همان دستور README

دستور نصبی که در خود مخزن آمده، از npx استفاده می‌کند. در یک پوشه‌ی خالی اجرا کردیم:

$ npx --yes skills add https://github.com/op7418/guizang-yingzao-skill --skill yingzao

│  Installed 1 skill ──────────────────────────────
│
│  ✓ .agents/skills/yingzao
│    universal: Amp, Antigravity, Antigravity CLI, Cline, Codex +15 more
│    symlinked: Claude Code
│    skipped: Hermes Agent (project directory not found)

نصب یک اسکیل نیست، یک پوشه‌ی .agents/skills و یک فایل skills-lock.json می‌سازد. برای Claude Code فقط پیوند نمادین می‌سازد. و اگر پوشه‌ی پروژه‌ی ایجنت نباشد، همان ایجنت را بی‌صدا رد می‌کند؛ در اجرای ما Hermes Agent به همین دلیل رد شد.

نیازمندی‌ها چهار بسته‌ی پایتون‌اند و نسخه‌ی هر کدام سقف دارد [۳]:

$ cat yingzao/requirements.txt
Pillow>=10.0,<13
numpy>=1.26,<3
opencv-python-headless>=4.9,<5
fonttools>=4.55,<5

# گیت وابستگی‌های خود اسکیل را می‌سنجد:
$ python3 yingzao/scripts/check_dependencies.py
Missing Python modules: numpy, cv2. Create a caller-workspace .venv and
install the Skill requirements there, or set CAP_PYTHON to a compatible
interpreter.

اسکیل عمداً چیزی نصب نمی‌کند. پیام بالا می‌گوید یک محیط جدا بساز، نه اینکه pip install را روی پایتون سراسری اجرا کنی [۵].

جایی که نصب شکست خورد: X86_V2

نصب چهار بسته در venv تمام شد و بعد از آن import numpy روی این سرور خطا داد:

$ .venv/bin/python -c "import numpy"
RuntimeError: NumPy was built with baseline optimizations:
(X86_V2) but your machine doesn't support:
(X86_V2).

این خطا از کد اسکیل نیست، از خود چرخ numpy است. در یادداشت انتشار ۲٫۴٫۰ نوشته شده که پیش‌فرض cpu-baseline روی x86 به میکروپردازنده‌ی x86-64-v2 بالا برده شده است، یعنی پردازنده‌های از سال ۲۰۰۹ به بعد [۹]. راهنمای ساخت هم می‌گوید این پیش‌فرض برای ماشین‌های مجازی قدیمی مناسب نیست و پیشنهاد می‌کند خط پایه را روی none بگذاری [۱۰]. این سرور یک ماشین مجازی با پردازنده‌ی عمومی است و هیچ‌کدام از پرچم‌های لازم را ندارد، پس نسخه را از جدید به قدیم آزمودیم.

$ for v in 2.3.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6; do
    pip install --quiet "numpy==$v"
    python -c "import numpy" >/dev/null 2>& \
      && echo "numpy $v -> OK" || echo "numpy $v -> FAILS (needs X86_V2)"
  done
numpy 2.3.5 -> OK
numpy 2.4.0 -> FAILS (needs X86_V2)
numpy 2.4.1 -> FAILS (needs X86_V2)
numpy 2.4.2 -> FAILS (needs X86_V2)
numpy 2.4.3 -> FAILS (needs X86_V2)
numpy 2.4.4 -> FAILS (needs X86_V2)
numpy 2.4.5 -> FAILS (needs X86_V2)
numpy 2.4.6 -> FAILS (needs X86_V2)

مرز دقیق است: ۲٫۳٫۵ کار می‌کند و از ۲٫۴٫۰ به بعد هر هشت نسخه شکست می‌خورند. سقف اعلام‌شده‌ی اسکیل numpy<3 است، پس pin کردن به ۲٫۳٫۵ داخل محدوده‌ی مجاز خودش می‌ماند.

$ pip install -r yingzao/requirements.txt
  # خط پایه را روی نسخه‌ای که روی این پردازنده کار می‌کند ببندید
  echo "numpy==2.3.5" >> requirements.local.txt
$ .venv/bin/python yingzao/scripts/check_dependencies.py
OK       Pillow 12.3.0
OK       numpy 2.3.5
OK       opencv-python-headless 4.14.0
OK       fonttools 4.66.0

نکته‌ی عملی این است که نام بسته در گیت اسکیل و آنچه import می‌شود یکی نیست. تست را خود اسکیل با import cv2 می‌نویسد و نام بسته را از روی همان import چاپ می‌کند [۵]. برای همین گیتش می‌گوید opencv-python-headless در حالی که آنچه import می‌شود cv2 است. این همان نکته‌ای است که در پست نصب ECC و هزینه‌ی واقعی هر پروفایل درباره‌ی کانتکست گفتیم: عدد را خودت بسن، به README تکیه نکن.

گیت پیش از تولید روی یک عکس واقعی

حالا که محیط سالم است، گیت را روی یک عکس واقعی اجرا می‌کنیم. عکس نمونه یک نمای نزدیک از دو کارت گرافیک است [۱۱]، نه عکس معماری. گیت به موضوع عکس کاری ندارد و فقط آن را اندازه می‌گیرد.

$ python3 yingzao/scripts/photo_preflight.py shot.webp --json preflight.json
preflight role=hero score=4 flags=none
$ python3 - <<'PY'
import json
d = json.load(open("preflight.json"))
print("triage  ", d["triage"])
print("image   ", d["image"])
print("quality ", d["quality"])
print("tags    ", d["controlled_tag_suggestions"])
print("space   ", d["negative_space"][0]["cell"], d["negative_space"][0]["score"])
PY
triage   {'role': 'hero', 'score': 4, 'flags': [], 'manual_override_allowed': True}
image    {'width': 1600, 'height': 900, 'megapixels': 1.44, 'orientation': 'landscape'}
quality  {'laplacian_variance': 245.868, 'edge_density': 0.0281}
tags     ['横向', '低密度', '夜景', '留白']
space    r3c1 0.9976

امتیاز ۴ از این جمع می‌آید. تابع classify امتیاز را جمع می‌کند [۴]. کوتاه‌ترین ضلع عکس ۹۰۰ است که بین ۸۰۰ تا ۱۲۰۰ می‌افتد و یک امتیاز می‌دهد. واریانس لاپلاسیَن ۲۴۵٫۸ بالای ۱۲۰ است و دو امتیاز می‌دهد. جعبه‌ی توجه پهن است و یک امتیاز دیگر. جمع می‌شود ۱ به‌علاوه‌ی ۲ به‌علاوه‌ی ۱، یعنی ۴، و امتیاز ۳ به بالا نقش hero می‌گیرد. خانه‌ی r3c1 یعنی پایین‌چپ تصویر، با امتیاز ۰٫۹۹۷۶ خلوت‌ترین جای عکس است و برای پوستر ۳ به ۴ معمولاً جای متن می‌شود.

وقتی گیت پرچم می‌زند

همان عکس را به ۲۴۰ در ۱۳۵ کوچک کردیم و دوباره گیت را اجرا کردیم:

$ python3 yingzao/scripts/photo_preflight.py small.webp --json small.json
preflight role=support score=1 flags=low_resolution
triage  {'role': 'support', 'score': 1, 'flags': ['low_resolution'], 'manual_override_allowed': True}
image   {'width': 240, 'height': 135, 'megapixels': 0.032, 'orientation': 'landscape'}

امتیاز از ۴ به ۱ افت کرد و نقش از hero به support تغییر کرد. کوتاه‌ترین ضلع ۱۳۵ زیر آستانه‌ی ۸۰۰ می‌افتد، پس یک امتیاز مثبت به منفی دو تبدیل می‌شود. یعنی امتیاز نهایی یک واحد کم می‌شود و پرچم low_resolution ثبت می‌گردد. چون نتیجه هنوز مثبت است، نقش support می‌گیرد و نه reject.

چرا این تفکیک مهم است؟ چون برچسب manual_override_allowed در هر دو اجرا True است. یعنی گیت می‌گوید عکس کوچک به‌عنوان تصویر اصلی پوستر مناسب نیست، ولی حق تصمیم را از شما نمی‌گیرد. اگر پروژه‌ی شما عمداً از عکس کوچک شروع می‌کند، همین پرچم می‌گوید کجا گمراه‌کننده است.

از برچسب عکس تا دستور طراحی

آخرین حلقه این است که برچسب‌های کنترل‌شده‌ی گیت به یک دستور طراحی واقعی تبدیل می‌شوند. همان چهار برچسبی که گیت روی عکس اصلی تولید کرد را به design_tokens می‌دهیم:

$ python3 yingzao/scripts/design_tokens.py suggest --tag 横向 --tag 低密度 \
      --tag 夜景 --tag 留白 --count 3
cap.recipe.dark-ground-luminous  深底在地发光  family=dark-luminous  score=8
  matched: 低密度, 夜景
  signature: 连续深色底场托住建筑真实暗部,最多三处来自灯具、窗口或檐线反射的亮边
  style: ground=dark, polarity=luminous-on-dark, era=nightlife, texture=low
cap.recipe.side-entry-color-window  偏侧主体功能色窗  family=side-color-window  score=5
  matched: 低密度, 留白
  signature: 建筑从一侧切入大留白,一个取自现场材料的平滑色窗同时托住轮廓和标题
cap.recipe.full-width-title-relief  全幅等宽标题浮雕  family=full-width-title  score=4
  matched: 横向
  signature: 完整中文建筑名以等宽、等高、同字重的宽体字横跨全幅

این تنها جایی است که ادعای شاخه‌ی طراحی را می‌شود سنجید، چون امتیازها قابل مقایسه‌اند. دستور اول هر دو برچسب اصلی را گرفته و امتیاز ۸ گرفته است. دو دستور بعدی هر کدام فقط دو برچسب از چهار برچسب را گرفته‌اند، پس اگر عکس شما همین برچسب‌ها را داشته باشد اولین گزینه هم‌خوان‌تر است.

این ابزار یک فهرست ثابت نیست. بخش suggest فاصله‌ی محورهای سبک را بیشینه می‌کند تا گزینه‌های شبیه به هم ندهد، و اگر --history بدهید، دستورهای انتخابی قبلی را کنار می‌گذارد [۷].

این اسکیل کجا به کار می‌آید و کجا نه

این نوشته عمدا یک گام را کامل رفت، چون گام بعدی بدون کلید مدل تصویر قابل اجرا نبود. آنچه اینجا اندازه گرفتیم، ورودی گیت بود: کیفیت، نوردهی، هندسه، فضای خالی و برچسب. گام بعدی در خود مخزن با prepare_generation.py انجام می‌شود که سه تصویر را برای فراخوانی مدل قفل می‌کند [۶].

اگر هدف شما پوستر تحریریه‌ی یک بنای مشخص با متن چینی است، این ابزار دقیقاً برای همان ساخته شده. اگر عکس شما از یک دوربین امروزی و با کیفیت بالا آمده، گیت پیش از تولید احتمالا برایتان چیزی جز تأیید نخواهد گفت. آنچه این اسکیل را از یک پوشه‌ی اسکریپت متمایز می‌کند، همین گیت قطعی است: جایی که بیشتر اسکیل‌های تصویر فقط راهنمای نوشتاری می‌دهند، اینجا تابع امتیازدهی با آستانه‌های مشخص در کد وجود دارد [۴].

اگر شما هم روی پردازنده‌ی بدون AVX2 یا ماشین مجازی قدیمی کار می‌کنید، همان numpy==2.3.5 را امتحان کنید. اگر نسخه‌ی دیگری روی پردازنده‌ی شما کار می‌کند، آن را بنویسید، چون این عدد به سخت‌افزار شما گره خورده است و نه به اسکیل.

منابع

  1. مخزن op7418/guizang-yingzao-skill در گیت‌هاب — صفحه‌ی اصلی مخزن.
  2. داده‌ی خام مخزن از API گیت‌هاب — آمار ستاره، فورک و تاریخ‌ها در لحظه‌ی خواندن.
  3. فایل requirements.txt اسکیل — چهار بسته و بازه‌ی نسخه‌ی هرکدام.
  4. اسکریپت photo_preflight.py — تابع classify و آستانه‌های امتیازدهی.
  5. اسکریپت check_dependencies.py — نگاشت نام بسته به نام ماژول.
  6. اسکریپت prepare_generation.py — قفل کردن سه تصویر پیش از فراخوانی مدل.
  7. اسکریپت design_tokens.py — بخش suggest و تاریخچه‌ی انتخاب‌ها.
  8. صفحه‌ی بسته‌ی numpy در PyPI — فهرست نسخه‌های منتشرشده.
  9. یادداشت انتشار numpy 2.4.0 — بالا بردن خط پایه‌ی x86 به x86-64-v2.
  10. راهنمای گزینه‌های ساخت CPU در numpy — پیشنهاد خط پایه‌ی none برای ماشین‌های قدیمی.
  11. عکس نمونه در Pexels — عکسی که گیت روی آن اجرا شد.