API یکپارچه
Chat Completions: یک API برای همهٔ مدلها
endpoint /v1/chat/completions هوشی همان قرارداد OpenAI را دارد، اما با تغییر فیلد model میتوانی بین GPT، Claude، Gemini، Grok، DeepSeek، Kimi، Qwen و دهها مدل دیگر جابهجا شوی؛ هوشی پیامها، ابزارها و تصاویر را به فرمت هر پروایدر ترجمه میکند.
https://api.hooshi.ai/v1/chat/completionsدرخواست پایه
# pip install openai
from openai import OpenAI
client = OpenAI(
base_url="https://api.hooshi.ai/v1",
api_key="hk-...", # کلید هوشی از /console
)
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-5-5", # یا openai/gpt-5.5 ، xai/grok-4.7 ، ...
messages=[
{"role": "system", "content": "تو یک دستیار برنامهنویسی دقیق هستی."},
{"role": "user", "content": "یک تابع پایتون برای تبدیل اعداد به حروف فارسی بنویس."},
],
)
print(resp.choices[0].message.content)
print("هزینه:", resp.model_extra["hooshi"]["cost"], "تومان")انتخاب مدل
فیلد model را میتوانی به دو شکل بفرستی. شناسهٔ کامل provider/model همیشه دقیقترین انتخاب است:
| شکل | مثال | توضیح |
|---|---|---|
| شناسهٔ هوشی | anthropic/claude-opus-5-5 | پیشنهادی؛ یکتا و بدون ابهام. |
| شناسهٔ بالادستی (bare) | gpt-5.5 | شناسهٔ خود پروایدر؛ اگر چند پروایدر یک مدل را داشته باشند، اولین مورد در ترتیب کاتالوگ انتخاب میشود. |
| نام مستعار (alias) | — | اگر برای مدلی نام مستعار تعریف شده باشد، آن هم پذیرفته میشود (بعد از دو حالت بالا بررسی میشود). |
فیلد model در پاسخ همان مقداری است که فرستادهای و شناسهٔ دقیق مدل اجراشده در hooshi.model برمیگردد. فهرست کامل در مدلها و شناسهها.
پارامترهای درخواست
| پارامتر | نوع | توضیح |
|---|---|---|
modelالزامی | string | شناسهٔ مدل، مثلاً openai/gpt-5.5. نوع مدل باید chat باشد. |
messagesالزامی | array | آرایهٔ پیامها با نقشهای system، developer، user، assistant و tool. محتوا میتواند رشته یا آرایهای از partها (text، image_url، file) باشد. |
streamاختیاری | boolean | اگر true باشد پاسخ بهصورت Server-Sent Events استریم میشود. پیشفرض false. |
stream_options.include_usageاختیاری | boolean | در حالت استریم، یک chunk پایانی با usage (و choices خالی) قبل از [DONE] ارسال میشود. |
max_tokens / max_completion_tokensاختیاری | integer | سقف توکن خروجی. هر دو نام پذیرفته میشوند و برای هر پروایدر به نام درست تبدیل میشوند. برای Claude اگر نفرستی، پیشفرض متناسب با مدل گذاشته میشود. |
temperatureاختیاری | number | ۰ تا ۲ (برای Claude حداکثر ۱). مدلهای استدلالی OpenAI و Claudeهای دارای تفکر تطبیقی این پارامتر را نادیده میگیرند. |
top_pاختیاری | number | نمونهبرداری هستهای. مانند temperature برای مدلهای استدلالی OpenAI حذف میشود. |
frequency_penalty / presence_penaltyاختیاری | number | جریمهٔ تکرار؛ برای پروایدرهای سازگار با OpenAI ارسال میشود. |
stopاختیاری | string | string[] | توالیهای توقف. برای Claude به stop_sequences تبدیل میشود. |
seedاختیاری | integer | برای خروجی قابلتکرار (در پروایدرهایی که پشتیبانی میکنند). |
toolsاختیاری | array | تعریف توابع با فرمت OpenAI (type: "function"). برای Claude بهطور خودکار به input_schema تبدیل میشود. |
tool_choiceاختیاری | string | object | auto، none، required یا یک تابع مشخص. برای Claude فقط none اعمال میشود و بقیه به auto برمیگردد. |
parallel_tool_callsاختیاری | boolean | false یعنی حداکثر یک فراخوانی ابزار در هر نوبت (برای Claude هم پشتیبانی میشود). |
response_formatاختیاری | object | {"type": "json_schema", ...} یا json_object. برای Claude فقط json_schema پشتیبانی میشود. |
reasoning_effortاختیاری | string | none، minimal، low، medium، high، xhigh، max؛ فقط روی مدلهای استدلالی اثر دارد. جدول نگاشت. |
web_searchاختیاری | boolean | پارامتر اختصاصی هوشی: برای Claudeهای دارای قابلیت جستجوی وب، ابزار web_search را فعال میکند. منابع در فیلد citations برمیگردند. |
web_search_optionsاختیاری | object | جستجوی وب برای مدلهای search در OpenAI (فرمت OpenAI). |
search_domain_filter / search_recency_filter / return_images / return_related_questionsاختیاری | mixed | پارامترهای اختصاصی Perplexity (Sonar) که مستقیم عبور داده میشوند. |
n, logprobs, top_logprobs, logit_bias, prediction, modalities, audioاختیاری | mixed | برای پروایدرهای سازگار با OpenAI بدون تغییر ارسال میشوند. |
userاختیاری | string | شناسهٔ کاربر نهایی تو برای ردیابی سوءاستفاده؛ اگر نفرستی، شناسهٔ داخلی کلید ارسال میشود. |
ساختار پاسخ
{
"id": "chatcmpl-r1Vq9Lk2P0aZ...",
"object": "chat.completion",
"created": 1791590400,
"model": "openai/gpt-5.5",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "...",
"tool_calls": [ ... ], // فقط وقتی مدل ابزار صدا بزند
"reasoning_content": "..." // فقط وقتی پروایدر خلاصهٔ استدلال برگرداند
},
"finish_reason": "stop", // stop | length | tool_calls | content_filter
"logprobs": null
}],
"usage": {
"prompt_tokens": 1520,
"completion_tokens": 410,
"total_tokens": 1930,
"prompt_tokens_details": { "cached_tokens": 1024 }
},
"citations": [{ "title": "...", "url": "https://..." }], // فقط برای مدلهای جستجو
"hooshi": { "model": "openai/gpt-5.5", "cost": 2374.083, "currency": "IRT" }
}هدر X-Request-Id هم در هر پاسخ برمیگردد (همان مقدار id)؛ در تماس با پشتیبانی آن را بفرست.
شیء hooshi (هزینهٔ تومانی)
| فیلد | توضیح |
|---|---|
hooshi.model | شناسهٔ دقیق مدلی که اجرا شد (حتی اگر شناسهٔ bare یا alias فرستاده باشی). |
hooshi.cost | مبلغی که برای همین درخواست از کیف پول API کسر شد، به تومان با ۴ رقم اعشار. |
hooshi.currency | همیشه IRT (تومان). |
SDKهای رسمی فیلدهای اضافه را حذف نمیکنند: در پایتون از resp.model_extra["hooshi"] و در Node.js از (resp as any).hooshi بخوان. در حالت استریم این شیء ارسال نمیشود؛ هزینه را از گزارش مصرف یا محاسبه با usage بگیر.
استریم (SSE)
با "stream": true پاسخ با Content-Type: text/event-stream و دقیقاً همان قالب chunkهای OpenAI میآید؛ پس هر کتابخانهای که استریم OpenAI را میفهمد، با همهٔ مدلهای هوشی هم کار میکند.
stream = client.chat.completions.create(
model="xai/grok-4.7",
messages=[{"role": "user", "content": "یک داستان خیلی کوتاه بنویس."}],
stream=True,
stream_options={"include_usage": True},
)
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: # آخرین chunk: choices خالی + usage
print("\n", chunk.usage.total_tokens, "tokens")قالب رویدادها
data: {"id":"chatcmpl-a1","object":"chat.completion.chunk","created":1791590400,"model":"xai/grok-4.7","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"logprobs":null}]}
data: {"id":"chatcmpl-a1","object":"chat.completion.chunk","created":1791590400,"model":"xai/grok-4.7","choices":[{"index":0,"delta":{"content":"یکی بود"},"finish_reason":null,"logprobs":null}]}
data: {"id":"chatcmpl-a1","object":"chat.completion.chunk","created":1791590400,"model":"xai/grok-4.7","choices":[{"index":0,"delta":{"content":"، یکی نبود…"},"finish_reason":null,"logprobs":null}]}
data: {"id":"chatcmpl-a1","object":"chat.completion.chunk","created":1791590400,"model":"xai/grok-4.7","choices":[{"index":0,"delta":{},"finish_reason":"stop","logprobs":null}]}
data: {"id":"chatcmpl-a1","object":"chat.completion.chunk","created":1791590400,"model":"xai/grok-4.7","choices":[],"usage":{"prompt_tokens":24,"completion_tokens":311,"total_tokens":335,"prompt_tokens_details":{"cached_tokens":0}}}
data: [DONE]- اولین chunk همیشه
delta.role = "assistant"دارد. - متن در
delta.content، خلاصهٔ استدلال (در مدلهایی که میفرستند) درdelta.reasoning_contentو فراخوانی ابزار درdelta.tool_callsمیآید. اندیس tool_callها همیشه از ۰ شمارهگذاری میشود. - chunk حاوی
usageفقط وقتیstream_options.include_usageرا true کرده باشی ارسال میشود. - اگر وسط استریم خطایی از پروایدر رخ دهد، یک رویداد
{"error": {...}}و سپس[DONE]میآید (جزئیات).
Tool Calling (Function Calling)
ابزارها را با فرمت OpenAI تعریف کن؛ هوشی برای Claude آنها را به tools با input_schema تبدیل میکند، tool_useها را به tool_calls برمیگرداند و پیامهای role: tool را بهtool_result ترجمه میکند. یعنی یک کد واحد برای GPT، Claude، Gemini، Grok و DeepSeek.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.hooshi.ai/v1", api_key="hk-...")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "دمای فعلی یک شهر را برمیگرداند",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "نام شهر، مثلاً Tehran"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "هوای تهران و شیراز الان چطوره؟"}]
resp = client.chat.completions.create(model="anthropic/claude-sonnet-5-5", messages=messages, tools=tools)
msg = resp.choices[0].message
if msg.tool_calls:
messages.append(msg) # پیام دستیار همراه با tool_calls
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = {"city": args["city"], "temp_c": 24} # ← اینجا تابع واقعی خودت را صدا بزن
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
final = client.chat.completions.create(model="anthropic/claude-sonnet-5-5", messages=messages, tools=tools)
print(final.choices[0].message.content)پاسخ وقتی مدل ابزار صدا میزند:
{
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "",
"tool_calls": [{
"id": "toolu_01A9q...",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Tehran\"}" }
}]
},
"finish_reason": "tool_calls"
}]
}تفاوتهای Claude
مدلهای فعلی Claude استفادهٔ اجباری از ابزار را نمیپذیرند؛ بنابراین tool_choice: "required" یا انتخاب یک تابع مشخص نادیده گرفته میشود و مدل خودش تصمیم میگیرد. tool_choice: "none" و parallel_tool_calls: false پشتیبانی میشوند.
Vision: ارسال تصویر
برای مدلهایی که قابلیت vision دارند (در فهرست مدلها مشخص است)، تصویر را با part از نوع image_url بفرست؛ هم URL عمومی و هم data URL (base64) پذیرفته میشود. برای مدلی که تصویر را پشتیبانی نمیکند، تصویر با یک متن جایگزین حذف میشود.
import base64
img = base64.b64encode(open("receipt.jpg", "rb").read()).decode()
resp = client.chat.completions.create(
model="gemini/gemini-3.1-pro-preview",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "مبلغ کل و تاریخ این رسید را استخراج کن."},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img}"}},
],
}],
)
print(resp.choices[0].message.content)فایل PDF هم با part از نوع file (file.file_data بهصورت data:application/pdf;base64,…) برای OpenAI و Claude پشتیبانی میشود.
خروجی ساختاریافته (JSON Schema)
با response_format از نوع json_schema مدل مجبور میشود خروجیای مطابق schema تو تولید کند. این قابلیت روی OpenAI، Claude (از طریق output_config.format) و بیشتر مدلهای سازگار کار میکند.{"type": "json_object"} هم برای پروایدرهای سازگار با OpenAI ارسال میشود.
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-5-5",
messages=[{"role": "user", "content": "از این متن نام، شهر و سن را دربیار: «سارا ۲۸ ساله از اصفهان است.»"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"city": {"type": "string"},
"age": {"type": "integer"},
},
"required": ["name", "city", "age"],
"additionalProperties": False,
},
},
},
)
import json
print(json.loads(resp.choices[0].message.content)) # {'name': 'سارا', 'city': 'اصفهان', 'age': 28}reasoning_effort (میزان تفکر)
برای مدلهای دارای قابلیت reasoning میتوانی عمق تفکر را تنظیم کنی. مقادیر یکسان را بفرست؛ هوشی آنها را به معادل هر پروایدر تبدیل میکند. روی مدلهای غیراستدلالی این پارامتر نادیده گرفته میشود. توکنهای تفکر جزو توکن خروجی محاسبه میشوند.
| مقدار ارسالی | OpenAI | Anthropic (Claude) | سایر پروایدرها |
|---|---|---|---|
none / minimal | minimal | low | low |
low | low | low | low |
medium | medium | medium | medium |
high | high | high | high |
xhigh | xhigh | xhigh | high |
max | xhigh | max | high |
resp = client.chat.completions.create(
model="openai/gpt-6.1-sol",
reasoning_effort="high",
messages=[{"role": "user", "content": "این الگوریتم را از نظر پیچیدگی زمانی تحلیل کن: ..."}],
)
print(resp.choices[0].message.content)
print(getattr(resp.choices[0].message, "reasoning_content", None)) # خلاصهٔ تفکر، اگر موجود باشدClaudeهای استدلالی با «تفکر تطبیقی» اجرا میشوند و خلاصهٔ تفکرشان در reasoning_content برمیگردد. در این حالت temperature اعمال نمیشود.
تزریق هویت مدل و غیرفعالکردن آن
بسیاری از مدلها وقتی از آنها پرسیده شود «تو کدام مدلی؟» جواب نادرست میدهند (مثلاً نسخهٔ قدیمیتر خودشان را نام میبرند). برای اینکه کاربران تو همیشه پاسخ درست بگیرند، هوشی بهطور پیشفرض یک سیستمپرامپت کوتاه به ابتدای گفتگو اضافه میکند که:
- نام دقیق مدل و سازندهاش را مشخص میکند (مثلاً «تو Claude Opus 5.5 ساخت Anthropic هستی»)؛
- تاریخ امروز را به میلادی و شمسی میگوید؛
- از مدل میخواهد به زبان کاربر پاسخ دهد و فارسی را روان و با نیمفاصلهٔ درست بنویسد.
اگر خودت پیام system یا developer داری، متن هویت با جداکنندهٔ --- قبل از پیام تو در همان پیام ادغام میشود (برای سازگاری با پروایدرهایی که فقط یک system میپذیرند). این متن چند صد توکن ورودی اضافه میکند که در usage و هزینه دیده میشود.
غیرفعالکردن
دو راه داری:
- برای یک درخواست: هدر
X-Hooshi-Identity: off(مقادیرfalseو0هم پذیرفته میشوند). - برای یک کلید: گزینهٔ «تزریق هویت مدل» را در کنسول خاموش کن.
client = OpenAI(
base_url="https://api.hooshi.ai/v1",
api_key="hk-...",
default_headers={"X-Hooshi-Identity": "off"}, # برای همهٔ درخواستهای این کلاینت
)
# یا فقط برای یک درخواست:
client.chat.completions.create(model="openai/gpt-5.5", messages=msgs, extra_headers={"X-Hooshi-Identity": "off"})کی خاموشش کنم؟
وقتی خودت یک persona کامل (مثلاً «دستیار فروشگاه X») تعریف کردهای، یا در pipelineهای تست و ارزیابی که میخواهی ورودی دقیقاً همان چیزی باشد که فرستادهای و حتی یک توکن اضافه هم مصرف نشود.
همین رفتار در API بومی هم برقرار است و با همان هدر خاموش میشود.
پایداری و Failover
هوشی برای هر پروایدر چند مسیر بالادستی دارد. اگر یک مسیر با خطای موقت (مثلاً 429 یا 5xx) مواجه شود، درخواست بهطور خودکار و بدون هزینهٔ اضافه روی مسیر بعدی تکرار میشود. در حالت استریم این کار فقط تا قبل از ارسال اولین توکن ممکن است. اگر همهٔ مسیرها ناموفق باشند، خطای upstream_error میگیری؛ راهنمای تلاش مجدد را ببین.