مقاله آموزشی

اتصال به API چت‌جی‌پی‌تی با پایتون؛ از اولین درخواست تا استریم و مدیریت خطا

نویسنده: تیم نِت اَرز ۹ دقیقه مطالعه ۱۴ بازدید

اسکریپت پایتونی که با کتابخانهٔ openai نوشته‌اید، از ایران به نشانی رسمی OpenAI که برسد، خطای ۴۰۳ می‌گیرد، و برای خرید اعتبار هم کارت ارزی لازم است. چرایی‌اش را در وب‌سرویس چت جی‌پی‌تی از ایران باز کرده‌ایم. این‌جا فقط سراغ کار عملی می‌رویم: از یک پوشهٔ خالی تا اسکریپتی که جواب را تکه‌به‌تکه چاپ می‌کند، گفت‌وگو را به خاطر می‌سپارد و وقتی سقف درخواست پر شد، درست صبر می‌کند.

همهٔ کدهای این راهنما از پوشهٔ پایتون مخزن متن‌باز ما در گیت‌هاب آمده‌اند و روی وب‌سرویس هوش مصنوعی نِت اَرز اجرا می‌شوند. این وب‌سرویس رابط OpenAI را پیاده می‌کند، پس کتابخانهٔ رسمی بدون تغییر کار می‌کند و فقط دو مقدار عوض می‌شود: نشانی پایه (base URL) و کلید.

آماده‌سازی: پایتون، محیط مجازی و کلید

پایتون ۳.۹ یا تازه‌تر کافی است. برای هر پروژه یک محیط مجازی (virtualenv) بسازید تا نسخهٔ کتابخانه‌ها با پروژه‌های دیگر قاطی نشود:

mkdir ai-demo && cd ai-demo
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install "openai>=1.40" requests

export NETARZ_API_KEY="sk-ntz-v1-..."   # Windows PowerShell: $env:NETARZ_API_KEY="sk-ntz-v1-..."

کلید را از پنل وب‌سرویس هوش مصنوعی می‌سازید: اول اعتبار را به تومان می‌خرید، بعد یک پروژه و برای آن یک کلید. کلید کامل فقط همان یک بار نمایش داده می‌شود. آن را در متغیر محیطی بگذارید و هرگز داخل کد ننویسید؛ کلیدی که در یک مخزن عمومی جا بماند، با اعتبار شما خرج می‌شود.

اولین درخواست

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://netarz.ir/api/ai/v1",
    api_key=os.environ["NETARZ_API_KEY"],
)

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "شما دستیار فارسی‌زبان یک فروشگاه اینترنتی هستید. کوتاه و دقیق جواب بدهید."},
        {"role": "user", "content": "برای یک فروشگاه لوازم ورزشی یک شعار کوتاه بنویسید."},
    ],
    max_tokens=200,
)

print(response.choices[0].message.content)
print("request id:", response.id, "| tokens:", response.usage.total_tokens)

سه چیز در این کد ارزش دقت دارد. پیام system لحن و نقش را تعیین می‌کند و در هر درخواست دوباره فرستاده می‌شود. max_tokens را همیشه بفرستید: پیش از هر درخواست، درگاه هزینهٔ بلندترین جواب ممکن را از اعتبار شما کنار می‌گذارد و بعد فقط مصرف واقعی را کم می‌کند؛ بدون سقف، این مبلغ رزروشده بزرگ می‌شود و با موجودی کم، خطای insufficient_credit می‌گیرید. و response.id همان شناسه‌ای است که اگر روزی تیکت زدید، پیدا کردن درخواست را آسان می‌کند.

اگر کد قدیمی‌تان از متغیرهای محیطی OPENAI_API_KEY و OPENAI_BASE_URL می‌خواند، کافی است همان دو را با کلید و نشانی نِت اَرز پر کنید و به خود کد دست نزنید. کتابخانهٔ رسمی هر دو را خودش می‌خواند.

مدل را عوض کنید، نه کد را

یک کلید برای مدل‌های چند سازنده کار می‌کند و فرقشان فقط در مقدار model است. چند شناسه که روز نوشتن این مقاله فعال بودند:

سازندهشناسهٔ نمونهنکته
OpenAIgpt-4o-mini، gpt-5-miniمدل‌های Pro و Codex فقط با مسیر responses
Anthropic (Claude)openrouter/anthropic/claude-sonnet-4.5فقط با همین پیشوند؛ کتابخانهٔ anthropic کار نمی‌کند
Google (Gemini)gemini-3.5-flashبا همان کتابخانهٔ openai
DeepSeekdeepseek-flashمدل‌های دیگرش با پیشوند openrouter/deepseek/

فهرست زندهٔ مدل‌ها را بدون کلید بگیرید: requests.get("https://netarz.ir/api/ai/v1/models", params={"type": "chat"}). در هر ردیف، capabilities می‌گوید مدل استریم، خروجی JSON یا فراخوانی ابزار را پشتیبانی می‌کند یا نه. قیمت هر مدل به دلار و تومان در صفحهٔ مدل‌ها است، و اگر می‌خواهید بدانید یک پروژهٔ واقعی چقدر خرج دارد، حساب توکن با مثال ریالی را ببینید.

جواب تکه‌به‌تکه (استریم)

برای جواب‌های بلند، کاربر نباید بیست ثانیه به صفحهٔ خالی نگاه کند. با stream=True متن همان‌طور که ساخته می‌شود می‌رسد:

stream = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "یک شعر کوتاه دربارهٔ پاییز بنویسید."}],
    stream=True,
    max_tokens=200,
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:  # the last chunk carries token usage
        print("\n\ntokens:", chunk.usage.total_tokens)

لازم نیست stream_options بفرستید؛ درگاه مصرف توکن را برای همهٔ سازنده‌ها در تکهٔ آخر می‌گذارد. اگر اتصال وسط استریم قطع شود، فقط همان مقداری که تا آن لحظه ساخته شده حساب می‌شود. برای مدل‌های استدلالی (reasoning) که قبل از جواب مدتی فکر می‌کنند، timeout کلاینت را هم بالاتر ببرید، مثلاً ۱۲۰ ثانیه.

گفت‌وگوی چندمرحله‌ای: حافظه با شماست

مدل چیزی از درخواست قبلی به یاد نمی‌آورد. «حافظه» یعنی خودتان پیام‌های قبلی را در هر درخواست دوباره بفرستید. اگر همه را بفرستید، هر پیام از قبلی گران‌تر می‌شود؛ پس فقط چند نوبت آخر را نگه دارید:

history = [{"role": "system", "content": "شما دستیار پشتیبانی یک فروشگاه هستید. کوتاه جواب بدهید."}]
MAX_TURNS = 6  # keep only the last six exchanges

while True:
    question = input("you: ").strip()
    if not question:
        break
    history.append({"role": "user", "content": question})
    reply = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[history[0]] + history[1:][-MAX_TURNS * 2:],
        max_tokens=300,
    )
    text = reply.choices[0].message.content
    history.append({"role": "assistant", "content": text})
    print("assistant:", text)

خطا و تلاش دوباره، بدون خرج اضافه

همهٔ خطاها یک جور نیستند و تلاش دوباره فقط برای بعضی‌شان معنی دارد. 429 یعنی سقف درخواست در دقیقه پر شده؛ به اندازهٔ سربرگ Retry-After صبر کنید. 502 و 503 یعنی سرویس‌دهندهٔ اصلی جواب نداده؛ این درخواست هزینه‌ای نداشته و یکی دو بار تلاش دوباره منطقی است. 402 یعنی اعتبار یا سقف هزینه تمام شده و تکرار فایده‌ای ندارد. 404 model_not_found هم معمولاً یعنی پیشوند openrouter/ جلوی شناسهٔ Claude جا افتاده.

import os, random, time
from openai import APIStatusError, OpenAI, RateLimitError

client = OpenAI(base_url="https://netarz.ir/api/ai/v1", api_key=os.environ["NETARZ_API_KEY"], max_retries=0)

def ask(messages, model="gpt-4o-mini", attempts=5):
    for i in range(attempts):
        try:
            return client.chat.completions.create(model=model, messages=messages, max_tokens=300)
        except RateLimitError as e:
            time.sleep(float(e.response.headers.get("Retry-After", 2 ** i)) + random.random())
        except APIStatusError as e:
            if e.status_code == 402:
                raise RuntimeError("top up or raise the spend limit") from e
            if e.status_code >= 500:
                time.sleep(2 ** i)
                continue
            raise
    raise RuntimeError("giving up after retries")

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

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

تا این‌جا جواب مدل متنی بود که یک آدم می‌خواند. اما اگر جواب باید وارد پایگاه داده یا یک تابع دیگر شود، متن آزاد دردسر است: یک بار «بله» می‌نویسد، یک بار «بله، حتماً». با response_format از مدل بخواهید فقط یک شیء JSON معتبر برگرداند:

import json

r = client.chat.completions.create(
    model="gpt-4o-mini",
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": "فقط یک شیء JSON معتبر برگردانید."},
        {"role": "user", "content": "برای محصول «کفش دویدن» سه ویژگی کوتاه با کلید features بنویسید."},
    ],
    max_tokens=200,
)
data = json.loads(r.choices[0].message.content)
print(data["features"])

دو نکته این کد را قابل اعتماد می‌کند. کلمهٔ JSON باید جایی در پیام‌ها بیاید، وگرنه بعضی مدل‌ها این حالت را نمی‌پذیرند. و اگر شکل دقیق خروجی برایتان مهم است (کدام کلیدها، چه نوعی)، به‌جای json_object از json_schema با یک مدل OpenAI استفاده کنید؛ نمونهٔ کاملش در فایل structured_output.py مخزن هست.

موجودی را پیش از کار شبانه بپرسید

اسکریپتی که هر شب با کرون صدها متن را خلاصه می‌کند، نباید وسط کار به 402 بخورد و نیمه‌کاره بماند. GET /me با همان کلید، موجودی دلاری حساب و سقف درخواست در دقیقهٔ کلید را برمی‌گرداند. اول همین را بپرسید و اگر موجودی کمتر از خرج یک شب بود، کار را شروع نکنید و به خودتان خبر بدهید:

import requests

me = requests.get(
    "https://netarz.ir/api/ai/v1/me",
    headers={"Authorization": f"Bearer {os.environ['NETARZ_API_KEY']}"},
    timeout=30,
).json()

balance = float(me["balance"]["usd"])
print("balance (USD):", balance, "| key rpm limit:", me["key"]["rpm_limit"])
if balance < 2:
    raise SystemExit("low credit: top up before tonight's batch")

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

بقیهٔ نمونه‌ها در گیت‌هاب

این مقاله فقط شروع کار است. پوشهٔ python/ در مخزن netarz/ai-api-examples برای هر کار یک فایل جدا دارد: structured_output.py برای خروجی JSON و JSON Schema، tool_calling.py برای فراخوانی تابع با چرخهٔ کامل، embeddings.py برای جست‌وجوی معنایی، images.py و audio.py برای تصویر و صوت، و account_and_models.py برای موجودی و فهرست مدل‌ها. هر فایل در چند خط اول می‌گوید چطور اجرا می‌شود و به کدام صفحهٔ مستندات مربوط است.

اگر می‌خواهید بدانید فراخوانی تابع دقیقاً چه می‌کند، آموزش Function Calling را بخوانید. پیش از فرستادن دادهٔ مشتری به مدل هم حریم خصوصی داده در API هوش مصنوعی را ببینید: درگاه متن پیام‌ها را ذخیره نمی‌کند، ولی بدنهٔ درخواست به سرویس‌دهندهٔ اصلی می‌رسد. مرجع کامل پارامترها در مستندات Chat Completions است و همهٔ مخزن‌های ما، از جمله نمونه‌های Node.js، PHP و LangChain، در صفحهٔ کدهای متن‌باز نِت اَرز فهرست شده‌اند.

آموزش‌های کاربردی در گیسو

گیسو برند هوش مصنوعی نِت اَرز است، با همین حساب و همین اعتبار. این راهنماها قدم‌به‌قدم نشان می‌دهند همین کار را چطور با هوش مصنوعی انجام دهید.

پرسش‌های پرتکرار

برای اتصال به API چت‌جی‌پی‌تی با پایتون کتابخانهٔ خاصی لازم است؟

نه. همان کتابخانهٔ رسمی openai کار می‌کند. فقط base_url را https://netarz.ir/api/ai/v1 بگذارید و کلید نِت اَرز را به‌جای کلید OpenAI بدهید. بقیهٔ کد عوض نمی‌شود.

با همین کد می‌شود از Claude یا Gemini هم استفاده کرد؟

بله، فقط مقدار model عوض می‌شود. شناسهٔ Claude با openrouter/anthropic/ شروع می‌شود و کتابخانهٔ anthropic روی این درگاه کار نمی‌کند. فهرست زندهٔ شناسه‌ها را از مسیر models بدون کلید بگیرید.

چرا باید max_tokens را همیشه بفرستم؟

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

درخواستی که با خطا تمام شود هزینه دارد؟

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

محصولات مرتبط با این مقاله

مطالب مشابه

نظر خوانندگان

هنوز نظری ثبت نشده. اگر این مقاله پرسشتان را جواب داد یا جای چیزی در آن خالی ماند، همین‌جا بنویسید.

نظرتان را بنویسید

این مقاله چقدر به کارتان آمد؟ (اختیاری)

نظرها را پیش از انتشار بررسی می‌کنیم. نقد صریح مشکلی ندارد؛ تبلیغ و توهین منتشر نمی‌شود.