Мы интегрируем 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 дней.







