Function Calling چیست و چرا اهمیت دارد؟
اگر با مدلهای زبانی بزرگ (LLM) کار کرده باشید، احتمالاً میدانید که این مدلها بهتنهایی فقط متن تولید میکنند. خب، این خوب است ولی کافی نیست. با قابلیت Function Calling (که بهش Tool Use هم میگن)، این مدلها میتوانند با دنیای واقعی تعامل داشته باشند: از جستجو در پایگاه داده و فراخوانی API گرفته تا ارسال ایمیل، پردازش پرداخت و اجرای هر عملیاتی که تعریف کنید.
یک نکته خیلی مهم: مدل زبانی خودش هیچ تابعی را اجرا نمیکند. در عوض، یک خروجی JSON ساختاریافته تولید میکند که مشخص میکند کدام تابع باید صدا زده بشود و با چه آرگومانهایی. برنامه شما این درخواست را میگیرد، تابع واقعی را اجرا میکند و نتیجه را برمیگرداند.
صادقانه بگویم، این قابلیت یکی از مهمترین پیشرفتها در حوزه LLMهاست. در واقع پل ارتباطی بین درک زبان طبیعی و اجرای عملی وظایف است و پایه اصلی ساخت عاملهای هوش مصنوعی (AI Agents) محسوب میشود.
تفاوت Function Calling و Tool Use
این دو اصطلاح خیلی وقتها بهجای هم استفاده میشوند، ولی تفاوتهای ظریفی دارند:
- Function Calling: اصطلاحی که اولبار OpenAI معرفیاش کرد. در این روش، schema توابع مستقیماً در درخواست API جاسازی میشود و مدل نام تابع و آرگومانها را خروجی میدهد.
- Tool Use: مفهومی گستردهتر که Anthropic و سایر ارائهدهندگان استفاده میکنند. ابزارها میتوانند شامل توابع، endpointهای بازیابی داده، مفسرهای کد، اقدامات مرورگر و خیلی چیزهای دیگر باشند.
در عمل؟ هر دو به یک فرآیند اشاره دارند: مدل تصمیم میگیرد چه ابزاری را فراخوانی کند، پارامترها را مشخص میکند و منتظر نتیجه میماند. پس زیاد نگران تفاوت اسمی نباشید.
معماری و جریان کاری Function Calling
بیایید ببینیم جریان کاری استاندارد Function Calling در سال ۲۰۲۶ چطور کار میکند. این فرآیند شامل مراحل زیر است:
- ارسال درخواست با تعریف ابزارها: درخواست کاربر را همراه با لیست ابزارهای موجود (شامل نام، توضیحات و schema ورودی) به مدل ارسال میکنید.
- تحلیل و تصمیمگیری مدل: مدل درخواست را بررسی میکند و تشخیص میدهد آیا اصلاً به ابزاری نیاز دارد یا نه.
- تولید فراخوانی ابزار: اگر مدل تصمیم به استفاده از ابزار بگیرد، نام ابزار و پارامترهای لازم را در قالب JSON خروجی میدهد.
- اجرای تابع توسط برنامه: کد شما تابع واقعی را اجرا میکند (فراخوانی API، کوئری دیتابیس و غیره).
- بازگرداندن نتیجه: نتیجه اجرا به مدل ارسال میشود.
- پاسخ نهایی: مدل با استفاده از نتیجه، پاسخ نهایی را به زبان طبیعی تولید میکند.
کشف پویای ابزار (Tool Discovery)
یک چیز جالب در سیستمهای پیشرفته ۲۰۲۶ وجود دارد: کشف پویای ابزار. قبل از همه مراحل بالا، برنامه ابتدا از یک Tool Registry (از طریق پروتکل MCP یا یک vector store) کوئری میگیرد تا ابزارهای مرتبط با نیت کاربر را پیدا کند. این کار از اشباع context window جلوگیری میکند و عملکرد سیستم را بهتر نگه میدارد.
پیادهسازی عملی با OpenAI API
خب، وقتشه دست به کد بشویم! بیایید یک مثال کامل از Function Calling با OpenAI API و Python را ببینیم.
تعریف ابزار و ارسال درخواست
from openai import OpenAI
from pydantic import BaseModel
import json
client = OpenAI()
# Define the tool schema
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city. Use this when the user asks about weather conditions.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. Tehran, London"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["city"],
"additionalProperties": False
},
"strict": True
}
}
]
# Send request with tools
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful weather assistant."},
{"role": "user", "content": "What is the weather like in Tehran?"}
],
tools=tools,
parallel_tool_calls=False
)
message = response.choices[0].message
# Check if model wants to call a function
if message.tool_calls:
tool_call = message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
print(f"Function: {tool_call.function.name}")
print(f"Arguments: {args}")
دقت کنید که strict: True در schema استفاده شده. این تضمین میکند که خروجی مدل دقیقاً با schema تعریفشده مطابقت داشته باشد.
اجرای تابع و ارسال نتیجه
# Simulate function execution
def get_weather(city: str, unit: str = "celsius") -> dict:
# In production, call a real weather API
return {
"city": city,
"temperature": 28,
"unit": unit,
"condition": "sunny",
"humidity": 35
}
# Execute the function
result = get_weather(**args)
# Send result back to the model
follow_up = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful weather assistant."},
{"role": "user", "content": "What is the weather like in Tehran?"},
message, # The assistant message with tool_call
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
}
],
tools=tools
)
print(follow_up.choices[0].message.content)
همینقدر ساده! نتیجه تابع را به مدل برمیگردانید و مدل پاسخ نهایی را به زبان طبیعی تولید میکند.
استفاده از Structured Outputs با Pydantic
در سال ۲۰۲۶، رویکرد توصیهشده استفاده از Pydantic برای تعریف schemaهاست. چرا؟ چون از واگرایی بین نوعهای Python و JSON Schema جلوگیری میکند و کدتان تمیزتر میشود:
import openai
from pydantic import BaseModel
class GetDeliveryDate(BaseModel):
"""Get the delivery date for a customer order."""
order_id: str
# Auto-generate tool definition from Pydantic
tools = [openai.pydantic_function_tool(GetDeliveryDate)]
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a customer support assistant."},
{"role": "user", "content": "When will order #12345 arrive?"}
],
tools=tools
)
پیادهسازی عملی با Claude API (Anthropic)
آنتروپیک از اصطلاح Tool Use استفاده میکند. نحوه پیادهسازی کمی متفاوت است ولی مفهوم یکیست. بیایید ببینیم چطور کار میکند.
تعریف و استفاده از ابزار
from anthropic import Anthropic
import json
client = Anthropic()
# Define tools
tools = [
{
"name": "get_stock_price",
"description": "Get the current stock price for a given ticker symbol. Use this when the user asks about stock prices or market data.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "The stock ticker symbol, e.g. AAPL, GOOGL"
}
},
"required": ["ticker"]
}
}
]
# Send request with tools
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What is the current price of Apple stock?"}
]
)
# Process tool use blocks
for block in response.content:
if block.type == "tool_use":
print(f"Tool: {block.name}")
print(f"Input: {block.input}")
tool_use_id = block.id
تفاوت اصلی با OpenAI در ساختار پیامهاست. اینجا بهجای tool_calls از بلوکهای tool_use استفاده میشود و schema ابزار در فیلد input_schema قرار میگیرد.
حلقه ابزار (Tool Loop) برای عاملها
در عمل، یک عامل هوش مصنوعی اغلب نیاز دارد چندین ابزار را پشت سر هم فراخوانی کند. مثلاً اول اطلاعات کاربر را بگیرد، بعد سفارشش را بررسی کند و در نهایت وضعیت ارسال را پیگیری کند. این نیازمند یک حلقه ابزار است:
def run_agent(user_message: str, tools: list, system: str = ""):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=system,
tools=tools,
messages=messages
)
# If model is done (no more tool calls), return
if response.stop_reason == "end_turn":
return response.content[0].text
# Process tool calls
tool_results = []
for block in response.content:
if block.type == "tool_use":
# Execute the tool
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
# Add assistant response and tool results to messages
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
def execute_tool(name: str, input_data: dict) -> dict:
"""Route tool calls to actual implementations."""
tool_map = {
"get_stock_price": get_stock_price,
"get_company_info": get_company_info,
"calculate_returns": calculate_returns,
}
if name in tool_map:
return tool_map[name](**input_data)
return {"error": f"Unknown tool: {name}"}
این الگو (agentic loop) پایه ساخت هر عامل هوش مصنوعیست. مدل تا وقتی که کارش تمام نشده ابزارها را فراخوانی میکند و وقتی پاسخ نهایی آماده شد، حلقه پایان مییابد.
استفاده از دکوراتور beta_tool
اگر نمیخواهید schema را دستی بنویسید (که صادقانه کمی خستهکننده است)، Python SDK آنتروپیک امکان تعریف ابزارها بهصورت توابع خالص Python را فراهم میکند:
from anthropic import Anthropic, beta_tool
import json
client = Anthropic()
@beta_tool
def get_weather(location: str) -> str:
"""Get the weather for a given location.
Args:
location: The city and state, e.g. San Francisco, CA
"""
return json.dumps({
"location": location,
"temperature": "25°C",
"condition": "Sunny",
})
runner = client.beta.messages.tool_runner(
max_tokens=1024,
model="claude-sonnet-4-6",
tools=[get_weather],
messages=[
{"role": "user", "content": "What is the weather in Tehran?"},
],
)
for message in runner:
print(message)
خیلی تمیزتره، نه؟ دکوراتور از docstring و type hintهای تابع، schema ابزار را بهصورت خودکار تولید میکند.
فراخوانی موازی توابع (Parallel Function Calling)
یکی از قابلیتهای جذاب اینه که وقتی مدل تشخیص بدهد چندین ابزار مستقل از هم هستند، میتواند همه را در یک پاسخ واحد فراخوانی کند. مثلاً اگر کاربر بپرسد «آبوهوای تهران و استانبول چطوره؟»، مدل هر دو را همزمان درخواست میدهد:
# Model might return multiple tool calls at once
# e.g., user asks: "Compare weather in Tehran and Istanbul"
for block in response.content:
if block.type == "tool_use":
# Execute each tool call (can be parallelized)
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
این قابلیت تأخیر را بهشدت کاهش میدهد چون بهجای فراخوانی ترتیبی، همه درخواستها موازی اجرا میشوند.
نکته مهم: هنگام استفاده از Structured Outputs در OpenAI با حالت strict: true، باید فراخوانی موازی را غیرفعال کنید (parallel_tool_calls=False) زیرا این دو قابلیت فعلاً با هم سازگار نیستند.
بهترین شیوهها برای محیط تولید (Production)
تا اینجا مفاهیم و کد را دیدیم. حالا بیایید درباره نکاتی صحبت کنیم که در محیط واقعی تولید اهمیت زیادی دارند.
۱. توضیحات دقیق و جامع بنویسید
این شاید مهمترین نکته باشد. کیفیت توضیحات ابزارها مستقیماً تعیین میکند مدل چقدر دقیق ابزار مناسب را انتخاب میکند. در واقع این کار مهندسی پرامپت برای ابزارها است. سناریوهای استفاده، محدودیتها و رفتار پارامترها را حتماً توضیح دهید.
۲. اعتبارسنجی ورودیها با Pydantic
هرگز به خروجی مدل اعتماد کورکورانه نکنید! از Pydantic برای اعتبارسنجی پارامترها قبل از اجرای تابع استفاده کنید:
from pydantic import BaseModel, validator
class StockQuery(BaseModel):
ticker: str
@validator("ticker")
def validate_ticker(cls, v):
if not v.isalpha() or len(v) > 5:
raise ValueError("Invalid ticker symbol")
return v.upper()
# Validate before execution
try:
query = StockQuery(**tool_input)
result = get_stock_price(query.ticker)
except ValidationError as e:
result = {"error": str(e)}
۳. تعداد ابزارها را محدود نگه دارید
تعداد زیاد ابزارها دقت مدل را کاهش میدهد. از تجربه شخصی میگویم: وقتی تعداد ابزارها از ۱۵-۲۰ تا بالاتر میرود، مدل شروع به اشتباه در انتخاب ابزار میکند. ابزارهایی که همیشه پشت سر هم فراخوانی میشوند را ترکیب کنید. مثلاً اگر همیشه بعد از query_location() تابع mark_location() را صدا میزنید، منطق هر دو را در یک تابع واحد ادغام کنید.
۴. آرگومانهای شناختهشده را از مدل نخواهید
اگر قبلاً مقدار یک پارامتر را دارید (مثلاً order_id از مراحل قبلی)، آن را بهصورت برنامهنویسی تنظیم کنید. از مدل نخواهید حدس بزند.
این کار ساده به نظر میرسد ولی خطاها را بهشکل چشمگیری کاهش میدهد.
۵. مدیریت خطا بهصورت حرفهای
وقتی یک ابزار با خطا مواجه میشود، پیام خطای واضحی به مدل برگردانید. در Claude API، فلگ "is_error": true را تنظیم کنید تا مدل بفهمد ابزار شکست خورده:
# Error handling for tool results
try:
result = execute_tool(tool_name, tool_input)
tool_result = {
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result)
}
except Exception as e:
tool_result = {
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": f"Error executing {tool_name}: {str(e)}",
"is_error": True
}
۶. راهنمای استفاده را در System Prompt اضافه کنید
علاوه بر توضیحات هر ابزار، راهنمای صریحی در system prompt اضافه کنید که مشخص کند مدل چه زمانی و چگونه از ابزارها استفاده کند. میدانم، ممکن است تکراری به نظر برسد ولی واقعاً به مدل کمک میکند تصمیمات بهتری بگیرد.
۷. تستهای خودکار برای Function Calling
حتماً مجموعه تستهایی نگهداری کنید که با هر بهروزرسانی سیستم اجرا شوند. ترکیبی از تست واحد، تست عملکردی، معیارهای ارزیابی و تست رگرسیون استفاده کنید. در غیر این صورت، یک تغییر کوچک در prompt یا schema ممکن است رفتار سیستم را بهطور غیرمنتظرهای تغییر دهد.
ارتباط با پروتکل MCP و عاملهای هوش مصنوعی
Function Calling پایه و اساس اکوسیستم عاملهای هوش مصنوعی مدرن است. پروتکل Model Context Protocol (MCP) که Anthropic معرفی کرده، یک استاندارد باز برای ساختاردهی به تعامل برنامههای مبتنی بر LLM با دنیای خارجی است.
MCP در واقع یک لایه استاندارد بالای Function Calling ایجاد میکند. این لایه سه مزیت کلیدی دارد: کشف پویای ابزارها، مدیریت متمرکز دسترسیها و قابلیت استفاده مجدد ابزارها در پروژههای مختلف.
همچنین، سیستمهای چندعامله (Multi-Agent Systems) از Function Calling بهعنوان مکانیزم ارتباطی بین عاملها استفاده میکنند. هر عامل تخصصی مجموعهای از ابزارهای مرتبط با حوزه خود را دارد و یک ارکستراتور مرکزی هماهنگی بین آنها را انجام میدهد. اگر با مفهوم microservices آشنایید، تصور کنید هر عامل مثل یک سرویس مستقل است که از طریق ابزارها با بقیه ارتباط برقرار میکند.
مقایسه پیادهسازی در ارائهدهندگان مختلف
بیایید یک نگاه سریع به تفاوتهای کلیدی بین OpenAI و Claude بیندازیم:
| ویژگی | OpenAI | Anthropic (Claude) |
|---|---|---|
| اصطلاح | Function Calling | Tool Use |
| نوع پیام | tool_calls / tool | tool_use / tool_result |
| Strict Mode | strict: true | ندارد (اعتبارسنجی دستی) |
| فراخوانی موازی | بله (پیشفرض فعال) | بله |
| Pydantic Integration | pydantic_function_tool | دکوراتور beta_tool |
| Structured Outputs | response_format + json_schema | از طریق tool use |
| Agent SDK | OpenAI Agents SDK | Claude Agent SDK |
هر دو ارائهدهنده قابلیتهای مشابهی دارند ولی نحوه پیادهسازی و ساختار APIشان تفاوتهایی دارد. انتخاب بین آنها بیشتر به نیازهای خاص پروژه و ترجیح شخصی برمیگردد.
موارد استفاده رایج
Function Calling در حوزههای مختلفی کاربرد دارد. چند نمونه رایج:
- چتباتهای خدمات مشتری: اتصال به CRM و سیستمهای سفارش برای پاسخدهی بلادرنگ به سؤالات مشتریان
- اتوماسیون وظایف: زمانبندی جلسات، ارسال ایمیل و مدیریت تقویم فقط با یک پیام ساده
- استخراج داده ساختاریافته: تبدیل متون غیرساختاریافته مثل قراردادها و فاکتورها به JSON قابل پردازش
- گردشکارهای چند مرحلهای: مثلاً رزرو سفر که شامل بررسی پرواز، هتل و خودرو با APIهای مختلف است
- دسترسی به اطلاعات بلادرنگ: آبوهوا، قیمت سهام، اخبار و سایر دادههای لحظهای
پرسشهای متداول
آیا مدل زبانی واقعاً تابع را اجرا میکند؟
خیر، و این خیلی مهمه. مدل زبانی هیچوقت تابعی را مستقیماً اجرا نمیکند. فقط نام تابع و آرگومانهای لازم را در قالب JSON مشخص میکند. برنامه شما مسئول اجرای واقعی تابع و بازگرداندن نتیجه است. این جداسازی برای امنیت سیستم حیاتیست.
تفاوت Function Calling با Structured Outputs چیست؟
سؤال خوبیست. Function Calling برای وقتی استفاده میشود که میخواهید مدل را به ابزارها و عملکردهای برنامه متصل کنید (مثلاً کوئری از دیتابیس). اما Structured Outputs از طریق پارامتر response_format برای وقتی مناسب است که میخواهید پاسخ مدل به کاربر ساختاریافته باشد (مثلاً تولید JSON خاص برای رندر UI).
چگونه از هالوسیناسیون مدل در آرگومانهای تابع جلوگیری کنیم؟
سه راهکار اصلی وجود دارد:
- از Strict Mode (در OpenAI) استفاده کنید تا خروجی حتماً با schema مطابقت داشته باشد.
- با Pydantic تمام ورودیها را قبل از اجرا اعتبارسنجی کنید.
- آرگومانهایی را که از قبل میدانید (مانند شناسههای سیستمی) بهصورت برنامهنویسی تنظیم کنید و از مدل نخواهید حدس بزند.
حداکثر چند ابزار میتوان به مدل داد؟
محدودیت سختی وجود ندارد، ولی تجربه نشان داده با افزایش تعداد ابزارها، دقت انتخاب مدل کاهش پیدا میکند. بهترین کار اینه که تعداد ابزارهای اولیه را کم نگه دارید و از روشهایی مانند Tool Discovery پویا (با MCP یا vector store) برای مدیریت مجموعه بزرگ ابزارها استفاده کنید.
آیا مدلهای متنباز هم از Function Calling پشتیبانی میکنند؟
بله! بسیاری از مدلهای متنباز مانند Llama و Mistral از Function Calling پشتیبانی میکنند. در vLLM میتوانید از گزینههای --enable-auto-tool-choice و --tool-call-parser استفاده کنید. Ollama هم پارامتر tools را پشتیبانی میکند. کتابخانههایی مثل Instructor هم امکان Function Calling را با Pydantic فراهم میکنند.