uttapen

SDK پایتون — نصب، استریم و tool calling

پکیج رسمی openai در پایتون با یوتاپن: نصب، ساخت کلاینت، chat، استریم، tool calling، embeddings و مدیریت خطا با کد قابل اجرا.

به‌روزرسانی: ۱۶ شهریور ۱۴۰۵

پکیج رسمی openai (نسخهٔ ۱ به بالا) بدون هیچ patch یا wrapper با یوتاپن کار می‌کند. همهٔ نمونه‌های این صفحه علیه gateway اجرا و تأیید شده‌اند.

نصب و کلاینت

pip install openai
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.uttapen.ir/v1",
    api_key=os.environ["UTTAPEN_API_KEY"],   # sk-up-...
)

اگر ترجیح می‌دهی کد را دست نزنی، فقط متغیرهای محیطی OPENAI_BASE_URL و OPENAI_API_KEY را ست کن؛ OpenAI() بدون آرگومان آن‌ها را می‌خواند. گزینه‌های مفید سازنده: timeout (پیش‌فرض ۱۰ دقیقه)، max_retries (پیش‌فرض ۲؛ روی 429 و 5xx)، و default_headers برای فرستادن همیشگی X-Uttapen-Include-Meta.

Chat

resp = client.chat.completions.create(
    model="openai/gpt-5-mini",
    messages=[
        {"role": "system", "content": "پاسخ کوتاه و فارسی بده."},
        {"role": "user", "content": "تفاوت index و unique index در Postgres چیست؟"},
    ],
    max_tokens=300,
    temperature=0.3,
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

برای هدرهای هزینه، نسخهٔ raw را بگیر:

raw = client.chat.completions.with_raw_response.create(
    model="openai/gpt-5-mini",
    messages=[{"role": "user", "content": "سلام"}],
)
print(raw.headers["x-uttapen-cost-toman"], raw.headers["x-uttapen-balance-toman"])
resp = raw.parse()   # همان آبجکت ChatCompletion

استریم

stream = client.chat.completions.create(
    model="openai/gpt-5-mini",
    messages=[{"role": "user", "content": "یک تابع Python برای اعتبارسنجی کد ملی بنویس."}],
    stream=True,
    extra_headers={"X-Uttapen-Include-Meta": "1"},
)
for chunk in stream:
    if chunk.object == "uttapen.meta":
        print(f"\n[هزینه: {chunk.model_extra['cost_toman']} تومان، موجودی: {chunk.model_extra['balance_toman']}]")
        continue
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

رویداد uttapen.meta بدون choices است؛ همیشه قبل از خواندن chunk.choices[0] وجودش را چک کن. برای لغو، stream.close() را صدا بزن یا از with client.chat.completions.create(...) as stream: استفاده کن تا اتصال با خروج از بلوک بسته شود. جزئیات فرمت در استریم.

Tool calling

import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "دمای فعلی یک شهر",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
    },
}]

def get_weather(city: str) -> dict:
    return {"city": city, "temp_c": 31}

messages = [{"role": "user", "content": "هوای تهران چطور است؟"}]
resp = client.chat.completions.create(model="openai/gpt-5-mini", messages=messages, tools=tools)
msg = resp.choices[0].message
messages.append(msg)
for call in msg.tool_calls or []:
    result = get_weather(**json.loads(call.function.arguments))
    messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False)})
final = client.chat.completions.create(model="openai/gpt-5-mini", messages=messages, tools=tools)
print(final.choices[0].message.content)

حلقهٔ کامل با چند ابزار و استریم در Tool calling.

Embeddings

emb = client.embeddings.create(
    model="openai/text-embedding-3-small",
    input=["قرارداد اجاره", "اجاره‌نامهٔ ملک مسکونی", "دستور پخت قورمه‌سبزی"],
)
vectors = [d.embedding for d in emb.data]
print(len(vectors), len(vectors[0]), emb.usage.prompt_tokens)

input می‌تواند رشته یا فهرست رشته باشد؛ برای batch بزرگ، فهرست بفرست تا یک درخواست شمرده شود. شناسهٔ مدل embeddings را از صفحهٔ مدل‌ها بردار.

Async

import asyncio
from openai import AsyncOpenAI

aclient = AsyncOpenAI(base_url="https://api.uttapen.ir/v1", api_key=os.environ["UTTAPEN_API_KEY"])

async def ask(q: str) -> str:
    r = await aclient.chat.completions.create(model="openai/gpt-5-nano", messages=[{"role": "user", "content": q}], max_tokens=100)
    return r.choices[0].message.content

async def main():
    answers = await asyncio.gather(*(ask(q) for q in ["۱+۱؟", "پایتخت فرانسه؟", "رنگ آسمان؟"]))
    print(answers)

asyncio.run(main())

با gather روی صدها درخواست، سقف دقیقه‌ای کلید را می‌زنی؛ با asyncio.Semaphore هم‌زمانی را محدود کن (محدودیت نرخ).

مدیریت خطا

import time
import openai

try:
    resp = client.chat.completions.create(model="openai/gpt-5-mini", messages=messages)
except openai.AuthenticationError:
    raise SystemExit("کلید uttapen نامعتبر یا لغوشده است")
except openai.NotFoundError as e:
    print("مدل یافت نشد:", e.message)
except openai.RateLimitError as e:
    time.sleep(int(e.response.headers.get("retry-after", "5")))
except openai.APIStatusError as e:
    if e.status_code == 402:
        info = e.body["error"].get("uttapen", {})
        print("شارژ لازم:", info.get("required_toman"), "تومان", info.get("topup_url"))
    else:
        print(e.status_code, e.code, e.message)
except openai.APIConnectionError:
    print("اتصال برقرار نشد؛ شبکه را چک کن")

e.code همان error.code است و e.response.headers["x-uttapen-request-id"] شناسه‌ای است که در گزارش خطا به ما می‌دهی. جدول کامل در خطاها.

فیلدهای اضافی

هر فیلدی که SDK نمی‌شناسد (مثل reasoning، provider، models) را با extra_body بفرست:

resp = client.chat.completions.create(
    model="deepseek/deepseek-r1",
    messages=messages,
    extra_body={"reasoning": {"effort": "low"}, "models": ["openai/o3-mini"]},
)

استفاده در سرور (FastAPI و Django)

کلاینت را یک بار در سطح ماژول بساز و در همهٔ درخواست‌ها همان را استفاده کن؛ OpenAI() یک connection pool از httpx دارد و ساختنش به‌ازای هر درخواست، هم کند است هم اتصال‌های نیمه‌باز می‌گذارد. در FastAPI از AsyncOpenAI استفاده کن تا حلقهٔ رویداد بلوکه نشود؛ در Django همزمان (OpenAI) کافی است مگر با ASGI کار کنی. برای درخواست‌هایی که چند دقیقه طول می‌کشند (مدل‌های استدلالی، سند بلند)، کار را به یک worker (Celery، RQ، Dramatiq) بده و نتیجه را با شناسهٔ درخواست ذخیره کن؛ نگه داشتن اتصال HTTP کاربر برای دو دقیقه ایدهٔ خوبی نیست.

# core/llm.py — یک نمونه برای کل پروژه
import httpx
from openai import OpenAI

client = OpenAI(
    base_url="https://api.uttapen.ir/v1",
    api_key=os.environ["UTTAPEN_API_KEY"],
    timeout=httpx.Timeout(120.0, connect=10.0),
    max_retries=1,
)

X-Uttapen-Request-Id را کنار لاگ خودت بنویس؛ با with_raw_response یا از e.response.headers در خطاها در دسترس است. اگر سرور پشت proxy خروجی است، http_client=httpx.Client(proxy="http://...") را به سازنده بده.

اشتباه‌های رایج

  • OPENAI_API_KEY قدیمی در محیط. اگر api_key را صریح ندهی و متغیر محیطی کلید OpenAI را داشته باشد، SDK آن را برمی‌دارد و 401 می‌گیری. همیشه api_key را صریح از UTTAPEN_API_KEY بده.
  • تکرار دوبرابر. SDK خودش 429 و 5xx را تکرار می‌کند. اگر لایهٔ تکرار خودت را داری، max_retries=0 بگذار وگرنه یک خطای گذرا ۹ بار زده می‌شود.
  • شمارش توکن با tiktoken. برای مدل‌های غیر-OpenAI فقط تقریب است؛ عدد واقعی در usage پاسخ است.
  • نسخهٔ پکیج. openai>=1.0 لازم است؛ نسخهٔ ۰٫x قرارداد دیگری داشت. در requirements.txt نسخه را pin کن.
  • خروجی فارسی در ویندوز. اگر کنسول متن را خراب نشان می‌دهد، PYTHONIOENCODING=utf-8 بگذار؛ به API ربطی ندارد.

Responses API

client.responses.create(...) فعلاً 404 می‌گیرد. از chat.completions استفاده کن؛ همهٔ قابلیت‌ها (tools، تصویر، JSON، reasoning) آن‌جا هست (migration).