Ми інтегруємо Function Calling (Tool Use) в мобільні додатки — це механізм, при якому AI-модель не намагається сама відповісти на питання «яка погода завтра», а повертає структурований JSON з описом того, що потрібно викликати: {"name": "get_weather", "arguments": {"city": "Мінськ", "date": "tomorrow"}}. Додаток виконує виклик, передає результат назад, і модель формує остаточну відповідь. У OpenAI це tools, у Anthropic — tool_use, у Google — function_calling. Наш досвід 5+ років і 30+ успішних проектів гарантує надійну інтеграцію. Замовте консультацію для оцінки вашого сценарію.
Де реально ламається на мобільному
Найчастіша проблема — неправильно описані JSON Schema для інструментів. Модель вибирає інструмент на основі опису та схеми параметрів. Якщо схема розмита («передай що потрібно»), модель або не викликає інструмент взагалі, або передає параметри в неправильному типі. Конкретний кейс: поле amount описано як string замість number — модель передає "150", десеріалізатор очікує Double, додаток падає з JsonDataCorruptedException. Gson і Moshi за замовчуванням не конвертують рядок у число мовчки.
Друге вузьке місце — паралельні виклики інструментів. GPT-4 і Claude 3 можуть повернути кілька tool_calls в одній відповіді. Якщо обробляти їх послідовно, користувач чекає. На Android правильно — async/await через корутини (async { } + awaitAll()), на iOS — async let або TaskGroup. І важливо: всі результати потрібно повернути моделі в одному messages[] кроці з role: "tool" для кожного виклику — OpenAI вимагає саме це, інакше 400 Invalid request.
Третя проблема — нескінченний цикл викликів. Якщо інструмент повернув помилку, модель іноді намагається викликати його знову з тими ж параметрами. Обмежуйте кількість ітерацій (зазвичай 5–10 достатньо) і передавайте помилку явно в content відповіді інструменту — це допомагає моделі переключитися на іншу стратегію.
Як уникнути нескінченного циклу викликів?
Встановіть ліміт ітерацій (наприклад, 8) і передавайте помилку в content відповіді інструменту. Модель, отримавши повідомлення про помилку, змінить стратегію. Також корисно додати прапорець у схему інструменту, щоб модель не викликала його повторно без зміни параметрів.
Що робити при паралельних викликах?
Використовуйте асинхронне виконання. На Android — корутини з async та awaitAll(), на iOS — TaskGroup. Всі результати збирайте в масив і відправляйте одним повідомленням з role: "tool". Це знижує затримку і відповідає вимогам API. Порівняння: правильно реалізоване асинхронне виконання скорочує час відповіді в 3 рази порівняно з послідовною обробкою.
Архітектура ToolDispatcher
// Android — диспетчер інструментів
class ToolDispatcher {
private val tools = mapOf<String, suspend (JsonObject) -> String>(
"get_weather" to ::handleGetWeather,
"search_flights" to ::handleSearchFlights,
"book_hotel" to ::handleBookHotel
)
suspend fun dispatch(toolName: String, args: JsonObject): String {
return tools[toolName]?.invoke(args)
?: """{"error": "unknown tool: $toolName"}"""
}
}
Кожен обробник повертає String (JSON-рядок результату). Модель отримує текст, не об'єкт — це принципово. Не потрібно серіалізовувати складні структури; достатньо зрозумілого JSON з ключовими даними.
Опис інструментів повинен бути максимально конкретним:
{
"name": "search_products",
"description": "Шукає товари в каталозі за назвою або категорією. Використовуй коли користувач запитує про конкретний товар або хоче переглянути асортимент.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Пошуковий запит мовою користувача"},
"category": {"type": "string", "enum": ["electronics", "clothing", "food"]},
"limit": {"type": "integer", "default": 10, "maximum": 50}
},
"required": ["query"]
}
}
Поле description впливає на те, чи викличе модель інструмент. «Пошук» — поганий опис. «Шукає товари в каталозі, коли користувач називає конкретний продукт» — модель розуміє контекст застосування.
Управління станом діалогу на клієнті
Function Calling вимагає зберігати повну історію повідомлень: user → assistant (з tool_calls) → tool (результат) → assistant (остаточна відповідь). На мобільному це означає правильну модель даних для Message:
// iOS
enum MessageRole { case user, assistant, tool }
struct Message: Codable {
let role: MessageRole
let content: String?
let toolCalls: [ToolCall]? // тільки для role == .assistant
let toolCallId: String? // тільки для role == .tool
let name: String? // ім'я інструменту для role == .tool
}
Зберігайте весь ланцюжок у @State / ViewModel. Якщо обрізати історію для економії токенів, ріжте тільки ранні user/assistant пари, але ніколи не ріжте незавершений цикл виклику інструменту — модель отримає помилку контексту.
Порівняння провайдерів Function Calling
| Провайдер | Механізм | Формат опису | Паралельні виклики |
|---|---|---|---|
| OpenAI | tools |
JSON Schema | Так |
| Anthropic | tool_use |
JSON Schema | Так (Claude 3+) |
function_calling |
JSON Schema | Так (Gemini) |
Усі три провайдери використовують JSON Schema ( Wikipedia: JSON Schema ) для опису параметрів. Відмінності в імені поля та форматі відповіді, але архітектура диспетчера універсальна.
Що входить в роботу
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз і опис інструментів | 3–5 днів | JSON Schema для кожного інструменту |
| Реалізація ToolDispatcher | 5–7 днів | Код диспетчера з обробниками |
| Інтеграція в діалоговий цикл | 3–4 дні | Повний ланцюжок викликів |
| Обробка паралельних викликів | 2–3 дні | Асинхронна реалізація |
| Тестування граничних випадків | 5–7 днів | Набір тестів (невідомий інструмент, помилка API, таймаут) |
| Моніторинг та документація | 2–3 дні | Логування та інструкція з експлуатації |
Інтеграція Function Calling для 3–5 інструментів — 2–3 тижні. З розширеною логікою, паралельними викликами та складним управлінням станом — 4–6 тижнів. Замовте консультацію для точної оцінки вашого проекту. Отримайте робочий прототип вже через 10 днів.







