uttapen

بهترین مدل هوش مصنوعی برای برنامه‌نویسی در ۱۴۰۵ — تست روی ۵ وظیفهٔ واقعی

به‌جای جدول امتیاز آماده، یک harness باز می‌گیری: ۵ وظیفهٔ واقعی، تست خودکار، هزینهٔ تومانی هر مدل. خودت اجرا کن و نتیجهٔ کدِ خودت را ببین.

۱۶ شهریور ۱۴۰۵ · ۱۱ دقیقه مطالعه · تیم یوتاپن

هر ماه یک جدول جدید منتشر می‌شود که می‌گوید «مدل X در SWE-bench چند درصد گرفت». این جدول‌ها بد نیستند، ولی یک مشکل جدی دارند: روی کدِ تو، با زبان تو، با سبک تیم تو تست نشده‌اند. مدلی که در حل مسئله‌های LeetCode درخشان است، شاید در نوشتن تابع تبدیل تاریخ جلالی یا slug ساختن از عنوان فارسی گیر کند. برای همین در این مقاله به تو عدد نمی‌دهیم؛ ابزار می‌دهیم.

چیزی که در ادامه می‌بینی یک harness کامل و قابل تکرار است: پنج وظیفهٔ واقعی که در پروژه‌های ایرانی زیاد پیش می‌آید، تست خودکار برای هر کدام، و اسکریپتی که همهٔ مدل‌های کاندید را از طریق یک API صدا می‌زند و علاوه بر درست/غلط، هزینهٔ تومانی و زمان هر پاسخ را هم ثبت می‌کند. تو فقط کلیدت را می‌گذاری، اجرا می‌کنی و جدول نهایی مال خودت است.

اعدادی که در این متن می‌بینی فقط قیمت و ظرفیت context هستند که از کاتالوگ واقعی مدل‌ها آمده‌اند. هیچ امتیاز کیفیتی اینجا ادعا نمی‌کنیم، چون امتیاز به وظیفه، زبان و prompt تو بستگی دارد. harness را اجرا کن؛ نتیجهٔ آن معتبرتر از هر جدولی است که ما بنویسیم.

چرا benchmark عمومی برای تصمیم تو کافی نیست

سه دلیل مشخص دارد. اول اینکه benchmarkهای عمومی تقریباً همیشه انگلیسی‌اند؛ وقتی prompt تو فارسی است یا داده‌ات ارقام فارسی و نیم‌فاصله دارد، رفتار مدل می‌تواند فرق کند. دوم اینکه مدل‌ها روی همان benchmarkها بهینه می‌شوند و فاصلهٔ بین «امتیاز خوب» و «کد قابل merge» روزبه‌روز بیشتر شده. سوم و مهم‌تر از همه، benchmark هزینه را نمی‌سنجد. اگر مدل A سه درصد بهتر از مدل B است ولی ده برابر گران‌تر، برای یک ابزار داخلی که روزی هزار بار صدا زده می‌شود، B انتخاب درست‌تری است.

پس معیار ما سه‌بعدی است: درستی (کد تست‌ها را پاس می‌کند یا نه)، هزینه (چند تومان برای پنج وظیفه)، و زمان پاسخ. جدول نهایی هر سه را کنار هم می‌گذارد و تصمیم با توست.

پنج وظیفهٔ واقعی

وظیفه‌ها را طوری انتخاب کردیم که هر کدام یک مهارت متفاوت را بسنجد و در عین حال چیزی باشد که واقعاً در پروژه‌های فارسی نوشته می‌شود:

  1. slug فارسی — تابعی که از عنوان فارسی یک slug برای URL بسازد؛ نیم‌فاصله و فاصله‌های تکراری را درست هندل کند و حروف لاتین را کوچک کند. مهارت: کار با Unicode و لبه‌های زبان فارسی.
  2. تبدیل تاریخ میلادی به جلالی — بدون کتابخانهٔ خارجی. مهارت: الگوریتم دقیق و دانستن تقویم؛ خیلی از مدل‌ها اینجا یک روز خطا دارند.
  3. رفع باگ — تابعی که قرار است دومین عدد بزرگِ متمایز را برگرداند و برای لیست خالی یا تک‌عضوی خراب است. مهارت: خواندن کد موجود و اصلاح حداقلی بدون تغییر امضا.
  4. تجمیع داده مثل SQL — از یک لیست دیکشنری، n مشتری با بیشترین مجموع پرداختِ «paid» را با شکستن تساوی به‌ترتیب الفبا برگرداند. مهارت: منطق تجاری با چند شرط هم‌زمان.
  5. parse مبلغ فارسی — رشته‌هایی مثل «۱۲٬۵۰۰ تومان» یا «۱۲.۵ هزار تومان» را به عدد صحیح تومان تبدیل کند، با پشتیبانی از ارقام فارسی و عربی. مهارت: تمیزکاری ورودی واقعی کاربر.

برای هر وظیفه دو یا سه assert نوشته‌ایم. کد تولیدشده در یک فایل موقت کنار assertها گذاشته و با python -I اجرا می‌شود؛ اگر خروجی OK چاپ شد یعنی پاس. عمداً تست‌ها کم و سخت‌گیرند: می‌خواهیم مدل‌ها را از هم جدا کنند، نه اینکه همه پاس کنند.

قاعدهٔ امتیازدهی

  • هر وظیفه یک امتیاز دارد؛ یا همهٔ assertها پاس می‌شوند یا صفر. امتیاز جزئی نمی‌دهیم، چون در عمل کدی که نصف تست‌ها را پاس می‌کند به درد merge نمی‌خورد.
  • temperature=0 تا نتیجه تا حد ممکن تکرارپذیر باشد. با این حال بعضی مدل‌ها همچنان کمی تصادفی‌اند؛ اگر می‌خواهی دقیق باشی، harness را سه بار اجرا کن و میانگین بگیر.
  • system prompt همه یکی است و از مدل می‌خواهد فقط یک بلوک کد پایتون برگرداند. اگر مدلی توضیح اضافه داد، regex اولین بلوک را برمی‌دارد.
  • هزینه از هدر X-Uttapen-Cost-Toman هر پاسخ خوانده می‌شود، نه از محاسبهٔ دستی توکن. این همان عددی است که از کیف پولت کم شده.
  • خطاهای API (مثل 402 موجودی ناکافی یا 429 نرخ) امتیاز صفر می‌گیرند ولی اجرا را متوقف نمی‌کنند؛ در ستون detail ثبت می‌شوند.

قیمت و context کاندیدها

فهرست کاندید ما شش مدل است که در کاتالوگ برچسب کدنویسی دارند یا در تیم‌های برنامه‌نویسی زیاد استفاده می‌شوند. قیمت‌ها را از GET /v1/models گرفتیم؛ این endpoint بدون کلید هم جواب می‌دهد و بلوک uttapen_pricing هر مدل قیمت نهایی تومانی به‌ازای یک میلیون توکن را می‌دهد. قیمت‌های کاتالوگ روزانه به‌روز می‌شوند؛ عددها را وقتی خودت اجرا می‌کنی به‌روز خواهی دید.

مدلcontextورودی (تومان / ۱M)خروجی (تومان / ۱M)
openai/gpt-5.3-codex۴۰۰٬۰۰۰۲۳۰٬۷۸۱۱٬۸۴۶٬۲۵۰
anthropic/claude-sonnet-4.6۱٬۰۰۰٬۰۰۰۳۹۵٬۶۲۵۱٬۹۷۸٬۱۲۵
google/gemini-3.1-pro-preview۱٬۰۴۸٬۵۷۶۲۶۳٬۷۵۰۱٬۵۸۲٬۵۰۰
deepseek/deepseek-v4-pro۱٬۰۴۸٬۵۷۶۸۶٬۶۶۸۱۷۳٬۳۳۶
qwen/qwen3-coder-plus۱٬۰۰۰٬۰۰۰۸۵٬۷۱۹۴۲۸٬۵۹۴
moonshotai/kimi-k2.7-code۲۶۲٬۱۴۴۸۷٬۰۳۸۴۴۸٬۳۷۵

همهٔ این شش مدل tools را پشتیبانی می‌کنند، پس اگر بعداً بخواهی همین harness را با tool calling (مثلاً اجازهٔ اجرای تست به خودِ مدل) گسترش بدهی، محدودیتی نداری.

یک حساب سرانگشتی برای هزینهٔ یک دور کامل: هر وظیفه حدود ۳۰۰ توکن ورودی و تا ۶۰۰ توکن خروجی دارد. برای گران‌ترین مدل جدول، پنج وظیفه چیزی حدود ۶٬۵۰۰ تومان می‌شود و برای ارزان‌ترین کمتر از ۷۰۰ تومان. یعنی اجرای کل harness روی هر شش مدل، حتی سه بار پشت سر هم، زیر ۵۰ هزار تومان تمام می‌شود — ارزان‌تر از یک ساعت وقت تیمی که بر سر انتخاب مدل بحث می‌کند.

اسکریپت harness

پیش‌نیاز: پایتون ۳٫۱۰ به بالا و بستهٔ openai. کلیدت را در متغیر محیطی UTTAPEN_API_KEY بگذار. اگر هنوز کلید نداری، از داشبورد بساز؛ برای اجرای این تست چند ده هزار تومان شارژ کافی است.

# bench.py — harness مقایسهٔ مدل‌های کدنویسی روی ۵ وظیفهٔ واقعی
# اجرا: UTTAPEN_API_KEY=sk-up-... python bench.py
import os, re, json, subprocess, tempfile, time, sys
from openai import OpenAI

client = OpenAI(
    base_url=os.getenv("UTTAPEN_BASE_URL", "https://api.uttapen.ir/v1"),
    api_key=os.environ["UTTAPEN_API_KEY"],
)

CANDIDATES = [
    "openai/gpt-5.3-codex",
    "anthropic/claude-sonnet-4.6",
    "google/gemini-3.1-pro-preview",
    "deepseek/deepseek-v4-pro",
    "qwen/qwen3-coder-plus",
    "moonshotai/kimi-k2.7-code",
]

SYSTEM = (
    "You are a senior Python engineer. Reply with exactly one ```python code block "
    "and nothing else. No explanations, no prints, no example usage."
)

# هر وظیفه: prompt + تست‌های واحد که روی کد تولیدشده اجرا می‌شود
TASKS = {
    "t1_persian_slug": {
        "prompt": "Write `slugify(title: str) -> str` for Persian titles: keep Persian/Latin letters and digits, "
                  "replace spaces and ZWNJ (\\u200c) with '-', collapse repeated '-', strip edges, lowercase Latin.",
        "tests": "assert slugify('آموزش  Python برای مبتدی‌ها') == 'آموزش-python-برای-مبتدی-ها'\n"
                 "assert slugify('--سلام--') == 'سلام'\n",
    },
    "t2_jalali": {
        "prompt": "Write `to_jalali(y: int, m: int, d: int) -> tuple[int,int,int]` converting a Gregorian date to Jalali "
                  "(Solar Hijri) without third-party libraries.",
        "tests": "assert to_jalali(2026, 9, 7) == (1405, 6, 16)\nassert to_jalali(2024, 3, 20) == (1403, 1, 1)\n",
    },
    "t3_bugfix": {
        "prompt": "This function should return the second largest distinct number or None. Fix it, keep the name.\n"
                  "```python\ndef second_largest(xs):\n    xs = sorted(xs)\n    return xs[-2]\n```",
        "tests": "assert second_largest([3, 1, 3, 2]) == 2\nassert second_largest([5]) is None\nassert second_largest([]) is None\n",
    },
    "t4_sql": {
        "prompt": "Write `top_customers(rows: list[dict], n: int) -> list[str]`. Each row has keys customer, amount, status. "
                  "Return names of the n customers with the highest total amount of rows where status == 'paid', "
                  "ties broken alphabetically.",
        "tests": "rows=[{'customer':'b','amount':10,'status':'paid'},{'customer':'a','amount':10,'status':'paid'},"
                 "{'customer':'c','amount':99,'status':'refunded'}]\nassert top_customers(rows, 2) == ['a','b']\n",
    },
    "t5_refactor": {
        "prompt": "Write `parse_amount(s: str) -> int` that parses Persian money strings like '۱۲٬۵۰۰ تومان', "
                  "'12,500', '۱۲.۵ هزار تومان' into integer toman. Support Persian and Arabic-Indic digits.",
        "tests": "assert parse_amount('۱۲٬۵۰۰ تومان') == 12500\nassert parse_amount('12,500') == 12500\n"
                 "assert parse_amount('۱۲.۵ هزار تومان') == 12500\n",
    },
}

def extract_code(text: str) -> str:
    m = re.search(r"```(?:python)?\n(.*?)```", text, re.S)
    return m.group(1) if m else text

def run_tests(code: str, tests: str, timeout=10) -> tuple[bool, str]:
    with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as f:
        f.write(code + "\n\n" + tests + "\nprint('OK')\n")
        path = f.name
    try:
        p = subprocess.run([sys.executable, "-I", path], capture_output=True, text=True, timeout=timeout)
        return p.returncode == 0 and "OK" in p.stdout, (p.stderr or p.stdout)[-300:]
    except subprocess.TimeoutExpired:
        return False, "timeout"

def ask(model: str, prompt: str):
    t0 = time.perf_counter()
    raw = client.chat.completions.with_raw_response.create(
        model=model,
        messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}],
        max_tokens=1500,
        temperature=0,
    )
    resp = raw.parse()
    return {
        "text": resp.choices[0].message.content or "",
        "cost_toman": float(raw.headers.get("X-Uttapen-Cost-Toman", "0")),
        "out_tokens": resp.usage.completion_tokens if resp.usage else None,
        "latency_s": round(time.perf_counter() - t0, 2),
    }

results = {}
for model in CANDIDATES:
    row = {"pass": 0, "cost_toman": 0.0, "latency_s": 0.0, "detail": {}}
    for name, task in TASKS.items():
        try:
            r = ask(model, task["prompt"])
        except Exception as e:  # ۴۰۲، ۴۲۹، ۵۰۲ … — امتیاز صفر، ادامه بده
            row["detail"][name] = f"error: {e.__class__.__name__}"
            continue
        ok, log = run_tests(extract_code(r["text"]), task["tests"])
        row["pass"] += int(ok)
        row["cost_toman"] += r["cost_toman"]
        row["latency_s"] += r["latency_s"]
        row["detail"][name] = "pass" if ok else f"fail: {log.strip()[-80:]}"
    results[model] = row

print(f"{'model':32} {'pass/5':>6} {'toman':>10} {'sec':>6}")
for model, row in sorted(results.items(), key=lambda kv: (-kv[1]['pass'], kv[1]['cost_toman'])):
    print(f"{model:32} {row['pass']:>6} {row['cost_toman']:>10.1f} {row['latency_s']:>6.1f}")
json.dump(results, open("results.json", "w"), ensure_ascii=False, indent=2)

خروجی یک جدول متنی است که بر اساس تعداد پاس (نزولی) و بعد هزینه (صعودی) مرتب شده، و فایل results.json که برای هر مدل و هر وظیفه می‌گوید چرا fail شد — آخرین ۸۰ کاراکتر stderr. همین بخش detail معمولاً از خودِ جدول آموزنده‌تر است: می‌بینی مدلی که در تبدیل تاریخ خطا داده، دقیقاً کدام روز را اشتباه حساب کرده.

نکته‌هایی که موقع خواندن نتیجه یادت باشد

اجرای کد مدل روی ماشین خودت خطر دارد. کدی که مدل تولید کرده مثل کد یک غریبه است. harness آن را با -I اجرا می‌کند که فقط متغیرهای محیطی و sys.path را ایزوله می‌کند، نه بیشتر. برای اجرای جدی، داخل یک container بدون شبکه اجرا کن یا حداقل کاربر جداگانه بساز.

تست کم یعنی نویز زیاد. با دو assert در هر وظیفه، یک مدل می‌تواند شانسی پاس کند یا سر یک لبهٔ خاص بیفتد. اگر بین دو مدل تنها یک امتیاز فاصله بود، آن را تفاوت معنادار نگیر. تست بیشتر اضافه کن یا سه دور اجرا کن.

قیمت‌ها وسط ماه عوض می‌شوند. ارائه‌دهنده‌ها قیمت مدل‌ها را تغییر می‌دهند و قیمت تومانی کاتالوگ روزانه به‌روز می‌شود. عددهای جدول بالا برای همان روز درست‌اند؛ harness همیشه هزینهٔ واقعی لحظهٔ اجرا را از هدر می‌خواند، پس نگران کهنه شدن جدول نباش.

مدل‌های استدلالی را با max_tokens بیشتر تست کن. بعضی از این مدل‌ها قبل از جواب «فکر می‌کنند» و توکن‌های استدلال با قیمت خروجی شارژ می‌شود. اگر max_tokens=1500 کم بود و جواب نصفه ماند، آن را بالا ببر ولی حواست به ستون هزینه باشد.

مدل‌های رایگان را هم می‌توانی اضافه کنی. در کاتالوگ چند مدل با پسوند :free هست که با سقف روزانه بدون شارژ کار می‌کنند. برای این harness جالب‌اند، چون می‌بینی فاصلهٔ کیفیت رایگان و پولی در کدنویسی واقعاً چقدر است. فقط یادت باشد ظرفیت رایگان مشترک است و ممکن است 429 بگیری.

چطور harness را برای پروژهٔ خودت شخصی کنی

بهترین benchmark، کد خودِ توست. سه کار ساده:

  • به‌جای پنج وظیفهٔ ما، پنج issue واقعی از ریپوی خودت را بردار: prompt همان توضیح issue باشد و تست همان unit test ای که بعد از merge نوشته شد.
  • اگر زبانت پایتون نیست، run_tests را عوض کن: برای Node یک فایل .mjs بساز و با node --test اجرا کن؛ برای PHP همان کار با phpunit. بقیهٔ harness زبان‌بی‌طرف است.
  • برای وظیفه‌های چندفایلی، به‌جای یک تابع، یک diff بخواه و با git apply --check تست کن که حداقل اعمال می‌شود؛ بعد تست‌ها را اجرا کن.

اگر همین حالا می‌خواهی امتحان کنی: فایل بالا را ذخیره کن، pip install openai، کلیدت را بگذار و اجرا کن. راهنمای پارامترهای هر مدل (مثل reasoning یا response_format) در مستندات شروع سریع هست و جزئیات نحوهٔ محاسبهٔ هزینه در صفحهٔ قیمت‌ها.

جمع‌بندی

«بهترین مدل برای برنامه‌نویسی» یک جواب ثابت ندارد؛ جواب تابعی از وظیفه، زبان، بودجه و حتی روز اجراست. کاری که می‌توانی بکنی این است که تصمیم را از حدس به اندازه‌گیری تبدیل کنی — با همین harness، در کمتر از ده دقیقه و با چند هزار تومان. نتیجه‌ای که می‌گیری مال خودت است و می‌توانی هر ماه، با آمدن مدل جدید، دوباره اجرایش کنی.

اگر با harness نتیجهٔ جالبی گرفتی، مخصوصاً روی وظیفه‌های فارسی، خوشحال می‌شویم برایمان بفرستی؛ نمونه‌های واقعی به بقیهٔ برنامه‌نویس‌ها بیشتر از هر جدولی کمک می‌کند.

مقاله‌های مرتبط

می‌خواهی همین کد را اجرا کنی؟

با شماره موبایل ثبت‌نام کن، کیف پول را شارژ کن و کلید بگیر. ساخت کلید API ←