بسیاری از توسعهدهندگان کار خود را با نوشتن ایجنتها روی API مدلهای OpenAI شروع میکنند؛ اما وقتی نیاز میشود مدل را به کلود (Anthropic Claude) یا جمینای (Google Gemini) تغییر دهند، با انبوهی از تفاوتهای ریز ساختاری مواجه میشوند. اگرچه منطق فراخوانی تابع (Function Calling) در هر سه یکسان است، نام کلیدها، نحوه بازگرداندن آرگومانها (رشته در برابر شیء) و ساختار ارسال نتیجه کاملاً با یکدیگر فرق دارند.
این ناسازگاریها معمولاً ساعتها وقت تیمهای مهندسی را صرف بازنویسی لایههای ورودی و خروجی میکند. در این درس ساختار پِیلودهای هر سه ارائهدهنده را کنار هم میگذاریم و با طراحی یک الگوی مترجم (Translator Pattern)، روشی اصولی برای نوشتن کدهای قابلحمل و مستقل از ارائهدهنده یاد میگیریم.
سه ارائهدهندهی پیشرو در سال 2024 روی یک چرخهی فراخوانی ابزار (tool-call loop) به توافق رسیدند، اما در تمام جزئیات دیگر راه خود را جدا کردند. OpenAI از
toolsوtool_callsاستفاده میکند؛ Anthropic سراغ بلوکهایtool_useوtool_resultرفته است و Gemini ازfunctionDeclarationsو تطبیق با شناسهی یکتا بهره میگیرد. در این درس، تفاوتهای ساختاری این سه مدل را کنار هم بررسی میکنیم تا کدی که برای یک ارائهدهنده نوشتهاید، هنگام پورت کردن روی دیگری از کار نیفتد.
اهداف یادگیری
- بیان سه تفاوت ساختاری میان پِیلودهای فراخوانی تابع (function calling) در OpenAI، Anthropic و Gemini (تعریف، فراخوانی، نتیجه).
- تبدیل تعریف یک ابزار به فرمتهای هر سه ارائهدهنده و پیشبینی تفاوتها در محدودیتهای حالت دقیق (strict mode).
- استفاده از
tool_choiceدر هر ارائهدهنده برای اجبار، منع یا انتخاب خودکار فراخوانی ابزارها. - تسلط بر محدودیتهای قطعی هر ارائهدهنده (تعداد ابزار، عمق اسکیما، طول آرگومانها) و ساختار خطاهایی که هنگام نقض این محدودیتها برمیگردانند.
صورت مسئله
ساختار درخواست فراخوانی تابع در هر ارائهدهنده فرق میکند. به سه نمونهی واقعی از استکهای پروداکشن سال 2026 نگاه کنید:
APIهای Chat Completions / Responses در OpenAI. شما پِیلود tools: [{type: "function", function: {name, description, parameters, strict}}] را ارسال میکنید. پاسخ مدل شامل choices[0].message.tool_calls: [{id, type: "function", function: {name, arguments}}] است که در آن arguments یک رشتهی JSON است و باید آن را پارس کنید. حالت دقیق (strict: true) تطابق با اسکیما (schema compliance) را از طریق دیکود مقید (constrained decoding) تضمین میکند.
API پیامهای Anthropic (یا همان Messages API). شما پِیلود tools: [{name, description, input_schema}] را ارسال میکنید. پاسخ به شکل content: [{type: "text"}, {type: "tool_use", id, name, input}] بازمیگردد. فیلد input از قبل پارس شده است (یک آبجکت است، نه رشته). شما در پاسخ، یک پیام جدید با نقش user میفرستید که شامل بلوک {type: "tool_result", tool_use_id, content} است.
API مدل Google Gemini. شما پِیلود tools: [{functionDeclarations: [{name, description, parameters}]}] را ارسال میکنید (تودرتو داخل functionDeclarations). پاسخ به شکل candidates[0].content.parts: [{functionCall: {name, args, id}}] میرسد که در آن id از Gemini 3 به بعد برای تطبیق فراخوانیهای موازی، مقداری یکتا دارد. شما با پِیلود {functionResponse: {name, id, response}} پاسخ میدهید.
چرخهی کار یکسان است، اما نام فیلدها، ساختار تودرتو، تفاوت رشته در برابر آبجکت و سازوکارهای تطبیق فرق دارند. تیمی که یک ایجنت (agent) هواشناسی بر پایهی OpenAI مینویسد، باید دو روز برای پورت کردن به Anthropic و یک روز دیگر برای Gemini فقط صرف لولهکشی و هماهنگی کدها کند.
در این درس مترجمی میسازیم که این سه فرمت را در یک تعریف ابزار کانونیکال (canonical) یکپارچه میکند و مسیریابی را در لایهی مرزی (edge) انجام میدهد. در فاز 13 · 17 همین الگو را به یک گیتوی مدلهای زبانی (LLM gateway) تعمیم میدهیم.
مفاهیم پایه
ساختار مشترک
هر ارائهدهندهای به پنج بخش نیاز دارد:
- فهرست ابزارها (Tool list): نام، توضیحات و اسکیمای ورودی برای هر ابزار.
- انتخاب ابزار (Tool choice): اجبار به فراخوانی یک ابزار خاص، منع استفاده از ابزارها یا واگذاری تصمیم به مدل.
- صدور فراخوانی (Call emission): خروجی ساختاریافتهای (structured output) که نام ابزار و آرگومانها را مشخص میکند.
- شناسهی فراخوانی (Call id): تطبیق پاسخ با فراخوانی درست (در فراخوانیهای موازی حیاتی است).
- تزریق نتیجه (Result injection): پیام یا بلوکی که نتیجه را به فراخوانی مربوطه پیوند میدهد.
مقایسهی جزئیات ساختار، فیلد به فیلد
| جنبه | OpenAI | Anthropic | Gemini |
|---|---|---|---|
| پوشش تعریف ابزار | {type: "function", function: {...}} |
{name, description, input_schema} |
{functionDeclarations: [{...}]} |
| فیلد اسکیما | parameters |
input_schema |
parameters |
| محفظهی پاسخ | tool_calls[] روی پیام assistant |
content[] از نوع tool_use |
parts[] از نوع functionCall |
| نوع آرگومانها | رشتهی JSON | آبجکت پارسشده | آبجکت پارسشده |
| فرمت شناسه | call_... (تولیدشده توسط OpenAI) |
toolu_... (Anthropic) |
UUID (نسخهی Gemini 3+) |
| بلوک نتیجه | نقش tool با tool_call_id |
نقش user با tool_result و tool_use_id |
functionResponse با id منطبق |
| اجبار به ابزار | tool_choice: {type: "function", function: {name}} |
tool_choice: {type: "tool", name} |
tool_config: {function_calling_config: {mode: "ANY"}} |
| منع ابزارها | tool_choice: "none" |
tool_choice: {type: "none"} |
mode: "NONE" |
| اسکیمای دقیق | strict: true |
strict: true به ازای هر ابزار (اختیاری) |
responseSchema در سطح درخواست |
محدودیتهایی که در عمل با آنها روبهرو میشوید
- OpenAI: حداکثر 128 ابزار در هر درخواست. عمق اسکیما حداکثر 5. طول رشتهی آرگومانها کمتر یا مساوی 8192 بایت. در حالت دقیق، نباید از
$refاستفاده شود، استفاده ازoneOf/anyOf/allOfبا همپوشانی ممنوع است و تمام مشخصهها باید درrequiredفهرست شوند. - Anthropic: حداکثر 64 ابزار در هر درخواست. عمق اسکیما روی کاغذ نامحدود است اما سقف تجربی آن به 10 میرسد. فراخوانی دقیق ابزار، انتخابی است و با فعالسازی
strict: trueروی تعریف ابزار (tool definition) اعمال میشود. بدون آن، اسکیما صرفاً قراردادی است که مدل تمایل دارد رعایت کند، نه تضمینی قطعی. - Gemini: حداکثر 64 تابع در هر درخواست. انواع داده در اسکیما، زیرمجموعهای از OpenAPI 3.0 هستند (کمی با JSON Schema نسخه 2020-12 تفاوت دارد). فراخوانیهای موازی از Gemini 3 شناسهی یکتا دارند.
رفتار tool_choice
هر سه ارائهدهنده از سه حالت اصلی پشتیبانی میکنند، فقط نامگذاریهایشان فرق دارد:
- Auto: مدل بین ابزار یا متن یکی را انتخاب میکند (حالت پیشفرض).
- Required / Any: مدل باید حداقل یک ابزار را فراخوانی کند.
- None: مدل نباید هیچ ابزاری را فراخوانی کند.
علاوه بر این، هر ارائهدهنده یک حالت اختصاصی هم دارد:
- OpenAI: اجبار به فراخوانی یک ابزار مشخص با نام آن.
- Anthropic: اجبار به فراخوانی یک ابزار مشخص با نام آن؛ فلگ
disable_parallel_tool_useفراخوانی تکی را از چندگانه جدا میکند. - Gemini: حالت
mode: "VALIDATED"فارغ از نیت (intent) مدل، همهی پاسخها را از اعتبارسنج اسکیما رد میکند.
فراخوانیهای موازی
تنظیم parallel_tool_calls: true (پیشفرض) در OpenAI چند فراخوانی را در قالب یک پیام assistant برمیگرداند. شما همهی آنها را اجرا میکنید و پاسخی دستهای با نقش tool میفرستید که به ازای هر tool_call_id یک ورودی دارد. Anthropic قبلاً فقط فراخوانی تکی انجام میداد؛ اما با disable_parallel_tool_use: false (پیشفرض از Claude 3.5 به بعد) حالت چندگانه را فعال کرده است. Gemini 2 امکان فراخوانی موازی داشت ولی شناسهی پایداری نمیداد؛ Gemini 3 شناسههای UUID را اضافه کرده تا پاسخهایی که نامرتب میرسند، بدون مشکل تطبیق پیدا کنند.
استریمینگ (Streaming)
هر سه مدل از استریمینگ فراخوانی ابزار پشتیبانی میکنند. فرمت داده روی شبکه تفاوت دارد:
- OpenAI: چانکهای دلتا از
tool_calls[i].function.argumentsبهمرور میرسند. دادهها را تا رسیدن بهfinish_reason: "tool_calls"تجمیع میکنید. - Anthropic: رویدادهای block-start ،block-delta و block-stop ارسال میشوند. چانکهای
input_json_deltaحامل آرگومانهای ناقص هستند. - Gemini: قابلیت
streamFunctionCallArguments(جدید در Gemini 3) چانکها را همراه با یکfunctionCallIdمیفرستد تا چند فراخوانی موازی بتوانند لابهلای هم جریان پیدا کنند.
در فاز 13 · 03 مباحث فراخوانی موازی و سرهمبندی پاسخهای استریمینگ را مفصل باز میکنیم. این درس روی ساختار تعریف ابزار و فراخوانی تکی تمرکز دارد.
خطاها و ترمیم آنها
خطاهای مربوط به آرگومان نامعتبر هم ظاهر متفاوتی دارند:
- OpenAI (حالت عادی): مدل
arguments: "{bad json}"برمیگرداند؛ پارس JSON شما با خطا مواجه میشود؛ یک پیام خطا تزریق میکنید و مدل را دوباره صدا میزنید. - OpenAI (حالت دقیق): اعتبارسنجی حین دیکود انجام میشود؛ تولید JSON نامعتبر غیرممکن است، اما ممکن است پاسخ از نوع امتناع (
refusal) باشد. - Anthropic (حالت عادی): فیلد
inputممکن است حاوی فیلدهای پیشبینینشده باشد؛ اسکیما الزامآور نیست و باید در سمت سرور اعتبارسنجی کنید. باstrict: true، نمونهبرداری به اسکیما مقید میشود. - Gemini: رفتار خاص OpenAPI 3.0: فیلد
enumروی فیلدهای از نوع شیء بدون اعلام خطا نادیده گرفته میشود؛ باید خودتان آن را اعتبارسنجی کنید.
الگوی مترجم (Translator Pattern)
یک تعریف ابزار استاندارد و کانونیکال در کد شما به این شکل است (طراحی ساختار دست خودتان است):
Tool(
name="get_weather",
description="Use when ...",
input_schema={"type": "object", "properties": {...}, "required": [...]},
strict=True,
)
سه تابع کوچک آن را به ساختارهای سهگانهی این ارائهدهندهها ترجمه میکنند. محیط تست در code/main.py دقیقاً همین کار را پیاده میکند و سپس یک فراخوانی ابزار شبیهسازیشده را در ساختار پاسخ هر ارائهدهنده تست و ارزیابی میکند. نیازی به اتصال شبکه نیست؛ این درس ساختارها را آموزش میدهد، نه درخواستهای HTTP را.
تیمهای فنی در محیط پروداکشن، این مترجم را داخل کلاسهایی مثل AbstractToolset (در Pydantic AI)، UniversalToolNode (در LangGraph) یا BaseTool (در LlamaIndex) کپسوله میکنند. در فاز 13 · 17 گیتویی میسازیم که لایهی بیرونی آن رابطی به سبک OpenAI دارد و در پشت صحنه درخواستها را به هر یک از این سه مدل هدایت میکند.
استفاده از ابزارهای واقعی
در فایل code/main.py یک دیتاکلاس کانونیکال به نام Tool و سه مترجم تعریف شدهاند که JSON تعریف ابزار را برای OpenAI، Anthropic و Gemini تولید میکنند. این اسکریپت سپس پاسخ دستی ساختهشدهی هر یک از ارائهدهندهها را به همان آبجکت فراخوانی کانونیکال تبدیل میکند تا نشان دهد منطق کار در پس ظاهر متفاوت فیلدها، کاملاً یکسان است. آن را اجرا کنید و تفاوتهای این سه تعریف را کنار هم بسنجید.
مواردی که باید دقت کنید:
- سه بلوک تعریف ابزار صرفاً در پوشش بیرونی و نام فیلدها با هم فرق دارند.
- سه بلوک پاسخ در محل قرارگیری فراخوانی تفاوت دارند (فیلد سطح بالای
tool_calls، بلوکcontent[]، یا ورودیparts[]). - یک تابع واحد به نام
canonical_call()فیلدهای{id, name, args}را از هر سه فرمت پاسخ استخراج میکند.
خروجی این درس
خروجی این درس فایل outputs/skill-provider-portability-audit.md است. این مهارت با گرفتن کد پیادهسازی فراخوانی ابزار برای یک ارائهدهنده، یک گزارش ارزیابی قابلیت انتقال (portability audit) تولید میکند: کدهای شما به کدام محدودیتهای ارائهدهنده وابسته است، چه فیلدهایی باید تغییر نام دهند، و هنگام انتقال به مدلهای دیگر چه بخشهایی از کار میافتند.
تمرینها
-
اسکریپت
code/main.pyرا اجرا کنید و مطمئن شوید که خروجی JSON تعریف ابزار برای هر سه مدل، از یک آبجکت پایهیToolساخته میشود. ابزار کانونیکال را طوری تغییر دهید که یک پارامتر enum به آن اضافه شود و بررسی کنید که چرا فقط مترجم Gemini نیاز به مدیریت رفتار خاص OpenAPI دارد. -
برای هر ارائهدهنده یک پارسر
ListToolsResponseبنویسید که فهرست ابزارهای بازگرداندهشده از سوی مدل را پس از فراخوانیlist_toolsیا کشف ابزار استخراج کند. OpenAI این قابلیت را بهصورت پیشفرض ندارد؛ به این عدم تقارن دقت کنید. -
تبدیل
tool_choiceرا پیادهسازی کنید: یک ساختار کانونیکال بهصورتToolChoice(mode="force", tool_name="x")را به فرمت هر سه مدل تبدیل کنید. سپس همین کار را برایmode="any"وmode="none"تکرار کنید. از جدول مقایسهای درس کمک بگیرید. -
یکی از سه ارائهدهنده را انتخاب کرده و مستندات فراخوانی تابع آن را از ابتدا تا انتها بخوانید. فیلدی در مشخصات اسکیمای آن پیدا کنید که دو ارائهدهندهی دیگر از آن پشتیبانی نمیکنند. گزینههای پیشنهادی:
strictدر OpenAI، فلگdisable_parallel_tool_useدر Anthropic، یاfunction_calling_config.allowed_function_namesدر Gemini. -
یک ورودی تست بنویسید: فراخوانی ابزاری که آرگومانهای آن، اسکیمای تعریفشده را نقض کند. آن را به اعتبارسنج هر ارائهدهنده بدهید (میتوانید از اعتبارسنج کتابخانهی استاندارد در درس 01 استفاده کنید) و خطاهای صادرشده را ثبت کنید. مستند کنید که برای تضمین دقت و سختگیری در پروداکشن، کدام مدل را انتخاب میکنید.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | معنای دقیق فنی |
|---|---|---|
| Function calling | «فراخوانی ابزار» | API در سطح ارائهدهنده برای تولید ساختاریافتهی فراخوانی ابزار |
| Tool declaration | «مشخصات ابزار» | نام + توضیحات + پِیلود ورودی بر اساس اسکیمای JSON (JSON Schema) |
tool_choice |
«اجبار / منع» | حالتهای auto / required / none / نام خاص |
| Strict mode | «اعمال اسکیما» | فلگی در OpenAI که دیکود را برای انطباق با اسکیما مقید میکند |
tool_use block |
«فرمت فراخوانی در Anthropic» | بلوک محتوایی درونخطی شامل id، name و input |
functionCall part |
«فرمت فراخوانی در Gemini» | یک ورودی در آرایهی parts[] شامل name، args و id |
| Arguments-as-string | «JSON رشتهای» | بازگرداندن آرگومانها به شکل رشتهی JSON توسط OpenAI، نه یک آبجکت |
| Parallel tool calls | «ارسال همزمان در یک نوبت» | چند فراخوانی ابزار درون یک نوبت گفتوگو (turn) در پیام assistant |
| Refusal | «امتناع مدل» | بلوک امتناع فقط در حالت دقیق، بهجای فراخوانی ابزار |
| OpenAPI 3.0 subset | «رفتار خاص اسکیما در Gemini» | استفادهی Gemini از لهجهای شبیه به JSON Schema با تفاوتهای جزئی |
برای مطالعهی بیشتر
- OpenAI — راهنمای فراخوانی تابع — مرجع اصلی شامل حالت دقیق و فراخوانیهای موازی
- Anthropic — مرور کلی فراخوانی ابزار — مفاهیم بلوکهای
tool_useوtool_result - Google — فراخوانی تابع در Gemini — فراخوانیهای موازی، شناسههای یکتا و زیرمجموعهی OpenAPI
- Vertex AI — مرجع فراخوانی تابع — رابط سازمانی Gemini
- OpenAI — خروجیهای ساختاریافته — جزئیات فنی اعمال اسکیما در حالت دقیق
