ابزارها (function calling)
تعریف ابزار، حلقهٔ فراخوانی، ابزارهای آمادهٔ سمت سرور (جستوجو، خواندن صفحه، تاریخ) و اینکه schema ابزار هم توکن حساب میشود.
ابزارها (function calling) روی هر دو مسیر — chat/completions و messages — و در هر دو حالت stream و غیر-stream کار میکنند.
تعریف ابزار
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "آبوهوای فعلی یک شهر را برمیگرداند",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "هوای تهران چطوره؟"}],
tools=tools,
)حلقهٔ فراخوانی
- درخواست را با tools بفرستید.
- اگر مدل تصمیم به فراخوانی بگیرد،
finish_reasonبرابرtool_callsمیشود وmessage.tool_callsپر میشود. - تابع را خودتان اجرا کنید.
- نتیجه را با نقش
toolو همانtool_call_idبه تاریخچه اضافه و دوباره بفرستید.
messages.append(resp.choices[0].message) # the assistant's tool call messages.append({ "role": "tool", "tool_call_id": resp.choices[0].message.tool_calls[0].id, "content": '{"temp_c": 31}', }) final = client.chat.completions.create(model=..., messages=messages, tools=tools)
schema ابزارها بخشی از پرامپت است و توکن ورودی حساب میشود — روی هر تماسِ همان مکالمه. ده ابزار با توضیحات طولانی میتواند از خودِ سؤال کاربر گرانتر تمام شود. فقط ابزارهایی را بفرستید که در آن مرحله واقعاً ممکن است لازم شوند.
تخمین رزرو کیف پول هم schema ابزارها را حساب میکند، پس رزرو با ابزارهای زیاد بزرگتر میشود.
ابزارهایی که خودمان اجرا میکنیم
سه نام در فضای نام iranrouter: را بهجای اینکه به کد شما برگردانیم، خودمان اجرا میکنیم: جستوجو میکنیم یا صفحه را میخوانیم، نتیجه را به مدل میدهیم و یک پاسخ نهایی به شما میرسد. حلقه را شما نمینویسید.
| نام | ورودی | چه میکند |
|---|---|---|
iranrouter:web_search | {"query": "...", "n": 5} | جستوجوی وب عمومی؛ عنوان، آدرس و خلاصه برمیگرداند. |
iranrouter:web_fetch | {"url": "https://..."} | یک صفحهٔ عمومی http(s) را میخواند و متن خواندنیاش را میدهد. |
iranrouter:datetime | — | تاریخ و ساعت تهران و UTC، با تاریخ جلالی. |
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "امروز چندم است و قیمت دلار چقدر؟"}],
tools=[
{"type": "function", "function": {"name": "iranrouter:datetime"}},
{"type": "function", "function": {"name": "iranrouter:web_search"}},
],
)
print(resp.choices[0].message.content) # one answer; the searching happened on our side- فقط همین نامها مال ماست. ابزار خودتان با نام
web_searchهمچنان مال شماست و مثل قبل به شما برمیگردد. - اگر SDK شما دونقطه را در نام ابزار نمیپذیرد (مثل SDK آنتروپیک)، همان ابزار را با نام
iranrouter__web_searchبنویسید — هر دو یکیاند. - پیشوند
openrouter:و آرایهٔplugins: [{"id": "web"}]هم پذیرفته میشوند: پیشوند برای هر سه ابزار، و plugins فقط به معنی web_search. - اگر کلاینت شما اجازهٔ افزودن ابزار نمیدهد (agentها و اپهای چت)، همان
plugins: [{"id": "web"}]را بهعنوان فیلد اضافهٔ بدنه بفرستید؛ بیشتر کلاینتهای سازگار با OpenAI تنظیم «extra body» دارند (در Hermes: کلید extra_body در ورودی custom_providers). یا بدون هیچ تنظیمی در کلاینت: در داشبورد ← پریستها «جستوجوی وب» را روشن کنید و مدل را @preset/شناسه بدهید. مدل هم باید function calling را پشتیبانی کند. - هر دور حلقه یک درخواست کامل است: رزرو، تسویه و سطر مصرف خودش را دارد. یعنی یک پاسخ ممکن است چند سطر در «مصرف» داشته باشد و مجموعشان صورتحساب آن درخواست است.
- حلقه سقف دارد (پیشفرض ۵ دور، و در هر دور حداکثر ۵ فراخوانی ابزار). در آخرین دور مدل مجبور است با متن پاسخ بدهد، نه با درخواست ابزار تازه. هر دور در سقف RPM کلید شما هم یک درخواست شمرده میشود.
- با stream: true پاسخ نهایی یکجا میرسد، نه توکن به توکن: تا مدل جواب آخر را نداده معلوم نیست آن دور آخر است. در این فاصله اتصال با heartbeat زنده میماند.
- روی این درخواستها
Idempotency-Keyبرای کل حلقه اعمال میشود، نه برای هر دور، و فقط بدون stream: تکرارِ حلقهای که تمام شده پاسخ نهاییاش را از حافظه برمیگرداند و دوباره حساب نمیشود؛ اگر هنوز در حال اجراست ۴۰۹ idempotency_in_flight. با stream: true نادیده گرفته میشود و تکرار دوباره اجرا و حساب میشود. n همیشه ۱ است. - web_fetch فقط آدرسی را باز میکند که عیناً در پیامهای شما یا در نتیجهٔ یک جستوجو یا fetch قبلی آمده باشد. آدرسی که مدل خودش ساخته باشد رد میشود — این جلوی بیرونکشیدن محتوای گفتگو توسط یک صفحهٔ خصمانه را میگیرد.
- اگر سازمان شما guardrail محتوایی دارد، روی نتیجهٔ ابزارها هم اعمال میشود: دادهٔ حساس از متن صفحه حذف میشود و صفحهای که دستور رو به مدل دارد کنار گذاشته میشود — بدون اینکه درخواست شما رد شود.
- متن صفحهای که خوانده میشود و نتیجهٔ جستوجو هیچجا ذخیره نمیشود — مثل بقیهٔ محتوای درخواست.
web_fetch فقط آدرسهای عمومی http و https روی پورت ۸۰ و ۴۴۳ را میخواند، بدون user:pass در آدرس. آدرس شبکهٔ داخلی و localhost رد میشود و هر redirect از نو بررسی میشود. فقط متن و HTML خوانده میشود، تا سقف حجم مشخص.