Function Calling: как LLM учится вызывать инструменты
opensourceaillmfunction-callingtoolsit
Введение: LLM без рук
LLM может генерировать текст, но не может выполнить действие. Function Calling решает эту проблему.
Факт: GPT-4 может вызвать API, выполнить поиск, запустить код — но только если вы правильно оформите function schema.
Что такое Function Calling?
Проблема без Function Calling
Без Function Calling:
User: "Какая погода в Москве?"
LLM: "Я не знаю погоду, я языковая модель"
✗ Нет доступа к внешнему миру
✗ Нет выполнения действий
✗ Только генерация текста
С Function Calling:
User: "Какая погода в Москве?"
LLM: { "function": "get_weather", "args": {"city": "Москва"} }
System: { "result": {"temp": 25, "condition": "sunny"} }
LLM: "В Москве сейчас 25°C, солнечно"
✓ Внешние данные
✓ Выполнение действий
✓ Интеграция с API
Architecture
User Query
|
v
[1] LLM с function schemas
|
v
[2] LLM решает: вызвать функцию?
|
├─ Нет → прямой ответ
└─ Да → JSON с function + args
|
v
[3] System executes function
|
v
[4] System sends result back
|
v
[5] LLM генерирует ответ
с результатом
Уровень 1: Function Schema
Что такое Function Schema?
Function Schema = описание функции для LLM
{
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. 'Moscow'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}
}
Schema Rules
1. name:
- snake_case или camelCase
- Уникальное в системе
- Описательное
2. description:
- Четкое описание что делает
- LLM использует для решения
3. parameters:
- JSON Schema format
- type: object
- properties: { key: { type, description } }
- required: [keys]
4. types:
- string, number, boolean, array, object
- enum для фиксированных значений
Complex Schema
{
"name": "create_ticket",
"description": "Create a support ticket",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Ticket title"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"]
},
"assignee": {
"type": "string",
"description": "Team to assign: 'backend', 'frontend', 'devops'"
},
"tags": {
"type": "array",
"items": { "type": "string" }
},
"metadata": {
"type": "object",
"properties": {
"user_id": { "type": "string" },
"browser": { "type": "string" }
}
}
},
"required": ["title", "priority"]
}
}
Уровень 2: OpenAI Function Calling
API Format
import openai
response = openai.ChatCompletion.create(
model='gpt-4',
messages=[
{'role': 'user', 'content': 'Какая погода в Москве?'}
],
functions=[{
'name': 'get_weather',
'description': 'Get current weather',
'parameters': {
'type': 'object',
'properties': {
'city': {'type': 'string'}
},
'required': ['city']
}
}],
function_call='auto' # или {'name': 'get_weather'}
)
# Response:
# {
# 'role': 'assistant',
# 'content': None,
# 'function_call': {
# 'name': 'get_weather',
# 'arguments': '{"city": "Москва"}'
# }
# }
Multi-turn with Function Result
# Step 1: Get function call
response1 = openai.ChatCompletion.create(
model='gpt-4',
messages=[{'role': 'user', 'content': 'Какая погода в Москве?'}],
functions=[weather_schema],
)
# LLM returns: function_call: get_weather({"city": "Москва"})
# Step 2: Execute function
result = get_weather("Москва")
# {"temp": 25, "condition": "sunny"}
# Step 3: Send result back
response2 = openai.ChatCompletion.create(
model='gpt-4',
messages=[
{'role': 'user', 'content': 'Какая погода в Москве?'},
{
'role': 'assistant',
'function_call': {
'name': 'get_weather',
'arguments': '{"city": "Москва"}'
}
},
{
'role': 'function',
'name': 'get_weather',
'content': json.dumps(result)
}
],
functions=[weather_schema],
)
# LLM returns: "В Москве сейчас 25°C, солнечно"
Уровень 3: Multiple Functions
Function Router
functions = [
{
'name': 'get_weather',
'description': 'Get weather for a city',
'parameters': {
'type': 'object',
'properties': {
'city': {'type': 'string'}
},
'required': ['city']
}
},
{
'name': 'search_web',
'description': 'Search the web for information',
'parameters': {
'type': 'object',
'properties': {
'query': {'type': 'string'},
'max_results': {'type': 'integer', 'default': 5}
},
'required': ['query']
}
},
{
'name': 'calculate',
'description': 'Calculate math expression',
'parameters': {
'type': 'object',
'properties': {
'expression': {'type': 'string'}
},
'required': ['expression']
}
},
{
'name': 'translate',
'description': 'Translate text between languages',
'parameters': {
'type': 'object',
'properties': {
'text': {'type': 'string'},
'source_lang': {'type': 'string'},
'target_lang': {'type': 'string'}
},
'required': ['text', 'target_lang']
}
}
]
# LLM выбирает функцию на основе контекста
Function Selection Logic
User: "Какая погода?"
→ LLM: get_weather({"city": ...})
User: "Найди новости про AI"
→ LLM: search_web({"query": "новости AI"})
User: "Сколько будет 25 * 48?"
→ LLM: calculate({"expression": "25 * 48"})
User: "Переведи 'hello' на французский"
→ LLM: translate({"text": "hello", "target_lang": "fr"})
User: "Привет!"
→ LLM: прямой ответ (без функции)
Уровень 4: Local LLM Function Calling
Ollama Function Calling
import ollama
response = ollama.chat(
model='llama3.1',
messages=[{'role': 'user', 'content': 'Какая погода в Москве?'}],
tools=[{
'type': 'function',
'function': {
'name': 'get_weather',
'description': 'Get current weather',
'parameters': {
'type': 'object',
'properties': {
'city': {'type': 'string'}
},
'required': ['city']
}
}
}]
)
# Response:
# {
# 'message': {
# 'role': 'assistant',
# 'content': '',
# 'tool_calls': [{
# 'function': {
# 'name': 'get_weather',
# 'arguments': {'city': 'Москва'}
# }
# }]
# }
# }
llama.cpp Function Calling
from llama_cpp import Llama
llama = Llama(
model_path="./llama3.gguf",
n_ctx=2048,
logits_all=True
)
output = llama.create_chat_completion(
messages=[{'role': 'user', 'content': 'Какая погода в Москве?'}],
tools=[weather_tool_spec],
stream=False
)
# Response:
# {
# 'choices': [{
# 'message': {
# 'role': 'assistant',
# 'tool_calls': [{
# 'function': {
# 'name': 'get_weather',
# 'arguments': '{"city": "Москва"}'
# }
# }]
# }
# }]
# }
Уровень 5: Tool Use (OpenAI Format)
Новый Tool Use Format
# OpenAI использует новый format (2024+)
response = openai.chat.completions.create(
model='gpt-4',
messages=[
{'role': 'user', 'content': 'Какая погода в Москве?'}
],
tools=[{
'type': 'function',
'function': {
'name': 'get_weather',
'description': 'Get current weather',
'parameters': {
'type': 'object',
'properties': {
'city': {'type': 'string'}
},
'required': ['city']
}
}
}]
)
# Response:
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name) # "get_weather"
print(tool_call.function.arguments) # '{"city": "Москва"}'
Handling Multiple Tool Calls
# LLM может вызвать несколько функций одновременно
response = openai.chat.completions.create(
model='gpt-4',
messages=[
{'role': 'user', 'content': 'Какая погода в Москве и Париже?'}
],
tools=[weather_tool],
)
# Response:
# tool_calls: [
# {function: {name: "get_weather", arguments: '{"city": "Москва"}'}},
# {function: {name: "get_weather", arguments: '{"city": "Париж"}'}}
# ]
# Execute both in parallel
results = await asyncio.gather(
get_weather("Москва"),
get_weather("Париж")
)
# Send results back
messages = [
*response_messages,
{
'role': 'tool',
'tool_call_id': tool_call.id,
'content': json.dumps(result1)
},
{
'role': 'tool',
'tool_call_id': tool_call2.id,
'content': json.dumps(result2)
}
]
response2 = openai.chat.completions.create(
model='gpt-4',
messages=messages,
tools=[weather_tool],
)
Уровень 6: Agent Loop
Simple Agent Loop
def agent_loop(messages, tools, max_turns=5):
for turn in range(max_turns):
# Step 1: Ask LLM
response = llm.chat(
messages=messages,
tools=tools
)
# Step 2: Check if LLM wants to call a tool
if response.tool_calls:
for tool_call in response.tool_calls:
# Execute the tool
result = execute_tool(
tool_call.function.name,
json.loads(tool_call.function.arguments)
)
# Add result to messages
messages.append({
'role': 'tool',
'tool_call_id': tool_call.id,
'content': json.dumps(result)
})
else:
# No more tool calls, return final answer
return response.message.content
return "Max turns reached"
def execute_tool(name, args):
tool_map = {
'get_weather': get_weather,
'search_web': search_web,
'calculate': calculate,
}
return tool_map[name](**args)
ReAct Pattern
ReAct = Reasoning + Acting
Цикл:
Thought → Action → Observation → Thought → Action → Observation → Answer
Пример:
Thought: Мне нужно узнать погоду в Москве
Action: get_weather(city="Москва")
Observation: {"temp": 25, "condition": "sunny"}
Thought: Теперь я могу ответить
Answer: В Москве сейчас 25°C, солнечно
Уровень 7: Structured Output
JSON Mode
# OpenAI JSON mode - всегда возвращает JSON
response = openai.chat.completions.create(
model='gpt-4-turbo',
messages=[
{'role': 'user', 'content': 'Назови 3 столицы Европы'}
],
response_format={
'type': 'json_schema',
'json_schema': {
'name': 'capitals',
'schema': {
'type': 'object',
'properties': {
'capitals': {
'type': 'array',
'items': {'type': 'string'}
}
},
'required': ['capitals']
}
}
}
)
# Результат:
# {"capitals": ["Париж", "Берлин", "Мадрид"]}
Pydantic Integration
from pydantic import BaseModel, Field
from typing import List
class WeatherResponse(BaseModel):
city: str
temperature: float
units: str
conditions: str
# Using litellm or other libraries
response = parse_response(WeatherResponse, llm_response)
# WeatherResponse(
# city="Москва",
# temperature=25.0,
# units="celsius",
# conditions="sunny"
# )
Уровень 8: Best Practices
Schema Design Tips
✅ ДЕЛАЙ:
- Пиши подробные descriptions для каждой функции
- Используй enum для фиксированных значений
- Добавляй examples в descriptions
- Валидируй args на своей стороне
- Обрабатывай ошибки gracefully
❌ НЕ ДЕЛАЙ:
- Не делай слишком много функций (до 20-30)
- Не используй ambiguous names
- Не передавай sensitive data через tools
- Не забывай про error handling
Prompt Engineering for Tools
# Good system prompt
system_prompt = """
Вы помощник с доступом к инструментам.
Доступные инструменты:
- get_weather: Получить погоду для города
- search_web: Поиск в интернете
- calculate: Математические вычисления
Если пользователь спрашивает о погоде, используйте get_weather.
Если вопрос не требует инструмента, ответьте напрямую.
"""
Уровень 9: Evaluation & Testing
Testing Function Calling
import unittest
class TestFunctionCalling(unittest.TestCase):
def test_weather_function(self):
messages = [
{'role': 'user', 'content': 'Какая погода в Москве?'}
]
response = llm.chat(messages=messages, tools=[weather_tool])
self.assertIsNotNone(response.tool_calls)
self.assertEqual(
response.tool_calls[0].function.name,
'get_weather'
)
def test_no_tool_needed(self):
messages = [
{'role': 'user', 'content': 'Привет, как дела?'}
]
response = llm.chat(messages=messages, tools=[weather_tool])
self.assertIsNone(response.tool_calls)
if __name__ == '__main__':
unittest.main()
Benchmarking
Metrics для Function Calling:
1. Tool Accuracy: % правильных вызовов функций
2. Arg Accuracy: % правильных аргументов
3. Latency: время до первого tool call
4. Multi-turn: % успешных multi-turn сценариев
5. Hallucination Rate: % ложных вызовов
Уровень 10: Future of Function Calling
Multimodal Tools
Будущее:
- Image generation tools
- Code execution tools
- Video analysis tools
- Audio processing tools
- Multi-modal agents
Пример:
User: "Создай изображение заката"
LLM: { "function": "generate_image", "args": {"prompt": "sunset"} }
User: "Проанализируй этот скриншот"
LLM: { "function": "analyze_image", "args": {"image": "..."} }
Agentic Workflows
Agent 1: Planner - разбивает задачу на подзадачи
Agent 2: Researcher - ищет информацию
Agent 3: Coder - пишет код
Agent 4: Reviewer - проверяет результат
Координация через:
- Shared memory
- Tool registry
- Communication protocols
Заключение
Function Calling — это мост между LLM и реальным миром.
Ключевые выводы:
- Function Schema — основа всего
- Local LLM (llama3, mistral) поддерживают FC
- Agent Loop — основной паттерн использования
- Structured Output — следующий шаг
- Будущее за multi-agent системами
Ресурсы: