Языковые модели обладают двумя фундаментальными архитектурными ограничениями:
Механизм вызова инструментов (Tool Calling / Function Calling) превращает пассивный генератор текста в активного автономного агента, способного взаимодействовать с внешним миром.
Критически важно понимать физику процесса: модель САМА НЕ ИСПОЛНЯЕТ КОД! Модель не выполняет SQL-запросы и не делает сетевые вызовы. Вместо этого происходит четырехшаговый протокольный диалог:
tool_call с именем функции и аргументами.execute) и получает результат.tool, после чего модель синтезирует человекочитаемый ответ на естественном языке.Взглянем на круговорот данных в стандартном цикле Tool Calling:
+-----------------------------------------------------------------------------------+
| АРХИТЕКТУРНЫЙ ЦИКЛ TOOL CALLING |
+-----------------------------------------------------------------------------------+
| 1. ПОЛЬЗОВАТЕЛЬ: "Какая сейчас погода в Токио и сколько это в долларах?" |
+-----------------------------------------------------------------------------------+
| | |
| v |
| 2. МОДЕЛЬ (Анализ + Генерация Tool Call): |
| "Мне нужна температура." -> { tool: "getWeather", args: { city: "Tokyo" } } |
+-----------------------------------------------------------------------------------+
| | |
| v |
| 3. СЕРВЕРНЫЙ РАНТАЙМ РАЗРАБОТЧИКА (Исполнение): |
| fetch("https://api.weather.com/...") -> { tempC: 18, condition: "rain" } |
+-----------------------------------------------------------------------------------+
| | |
| v |
| 4. МОДЕЛЬ (Финальный синтез с учетом полученных данных): |
| "В Токио сейчас 18°C, идет дождь. Во второй части вопроса вы упомянули..." |
+-----------------------------------------------------------------------------------+
| | |
| v |
| 5. КЛИЕНТ: Получает итоговый ответ с актуальными реальными данными |
+-----------------------------------------------------------------------------------+Разработчик передает объект tools в generateText/streamText. Схемы параметров Zod автоматически компилируются в JSON Schema для провайдера.
Модель понимает, что собственного контекста недостаточно. Она генерирует служебный токен вызова функции и валидирует аргументы по схеме.
SDK перехватывает событие tool-call и вызывает локальный метод execute({ ...args }). Выполняется реальный запрос к PostgreSQL или внешнему API.
Результат выполнения функции сериализуется и дописывается в массив сообщений с ролью tool и ссылкой на toolCallId.
Если maxSteps > 1, модель либо вызывает следующий инструмент, либо завершает генерацию финальным текстом для пользователя.
Когда языковая модель решает вызвать инструмент `getDatabaseUser({ id: 42 })`, где физически исполняется тело функции с запросом к базе данных?
В Vercel AI SDK инструмент объявляется через фабрику tool, принимающую три обязательных поля:
description: подробное семантическое объяснение того, когда и зачем модели следует выбирать этот инструмент.parameters: Zod-схема аргументов, которую модель обязана заполнить.execute: асинхронная TypeScript-функция, исполняющая реальную логику.import { tool } from 'ai';
import { z } from 'zod';
export const weatherTool = tool({
description: 'Получает текущую погоду и температуру для указанного города',
parameters: z.object({
city: z.string().describe('Название города на английском или русском языке (например, Tokyo, Москва)'),
units: z.enum(['celsius', 'fahrenheit']).default('celsius').describe('Единицы измерения температуры'),
}),
execute: async ({ city, units }) => {
// Реальный вызов внешнего REST API
try {
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?q=${encodeURIComponent(city)}`
);
if (!response.ok) {
return { error: `Город '${city}' не найден в метеорологической базе` };
}
const data = await response.json();
return {
temperature: units === 'celsius' ? data.current.temp_c : data.current.temp_f,
condition: data.current.condition.text,
humidity: data.current.humidity,
windKph: data.current.wind_kph,
};
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Сетевой сбой';
// ВАЖНО: возвращаем объект ошибки модели, а не крашим процесс!
return { error: `Не удалось получить данные о погоде: ${msg}` };
}
},
});Чтобы модель могла получить результат работы инструмента и затем сформулировать ответ для пользователя, параметр maxSteps должен быть больше 1. Если maxSteps: 1, модель остановится на шаге генерации tool-call и не успеет прочитать ответ функции.
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { weatherTool } from './tools/weather-tool';
export async function askSupportAgent(userQuestion: string): Promise<string> {
const result = await generateText({
model: openai('gpt-4o'),
system: 'Ты — дружелюбный ассистент службы заботы о клиентах. При необходимости используй инструменты.',
prompt: userQuestion,
tools: {
getWeather: weatherTool,
},
// maxSteps: 5 позволяет модели выполнить цепочку:
// Шаг 1: tool-call (getWeather) -> Сервер исполняет -> Шаг 2: Модель отвечает пользователю
maxSteps: 5,
});
// Логирование шагов агентного цикла
for (const step of result.steps) {
if (step.toolCalls.length > 0) {
console.log(`Модель вызвала инструмент: ${step.toolCalls.map(t => t.toolName).join(', ')}`);
}
}
return result.text;
}В интерфейсах реального времени вызовы инструментов транслируются в поток вместе с текстом:
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { weatherTool } from '@/lib/ai/tools/weather-tool';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeather: weatherTool,
},
maxSteps: 5,
});
return result.toDataStreamResponse();
}Модель читает промпт пользователя и сопоставляет его с описаниями (descriptions) переданных инструментов.
Качественное текстовое описание инструмента определяет вероятность его выбора.
Модель формирует токен останова с finishReason: 'tool-calls' и упаковывает аргументы в JSON.
Генерация аргументов валидируется по схеме Zod.
Vercel AI SDK вызывает функцию execute на доверенном сервере. Выполняются реальные I/O операции.
Модель ожидает завершения асинхронного промиса.
Результат функции сериализуется в JSON и отправляется вторым HTTP-запросом к модели в качестве контекста ответа.
Запрос содержит полную историю: prompt + tool-call + tool-result.
Модель анализирует полученные фактические данные и генерирует естественный ответ для человека.
Если данных недостаточно, модель может инициировать следующий шаг цикла.
Разработчик подключил инструмент к `generateText({ tools: { calc }, prompt: 'Посчитай 2 + 2' })`, но забыл указать `maxSteps` (по умолчанию равен 1). Что вернет вызов?
Если внутри функции execute произойдет исключение (throw new Error('API down')), весь HTTP-запрос вашего бэкенда упадет с ошибкой 500. Модель не получит шанс исправить ситуацию или объяснить проблему пользователю человеческим языком.
Решение: Перехватывать исключения внутри execute и возвращать объект с описанием ошибки: return { error: 'Сервис временно недоступен' }. Модель прочитает этот результат и вежливо ответит: «К сожалению, метеорологический сервер временно не отвечает».
Описание вида: description: 'Инструмент для базы'. Модель выбирает инструменты исключительно по их текстовому описанию. Если назначение размыто, модель либо проигнорирует инструмент, либо будет вызывать его на нерелевантные вопросы.
Решение: Писать подробные, четкие описания, указывающие конкретные триггеры вызова: description: 'Используй этот инструмент ТОЛЬКО когда пользователь явно спрашивает баланс счета или историю транзакций'.
Инструменты вроде deleteDatabaseTable или sendPaymentToUser, подключенные с автоматическим исполнением в execute. Если пользователь применит Prompt Injection, модель может вызвать деструктивную функцию.
Решение: Для критических операций разделять процесс на два этапа: генерация намерения (maxSteps: 1), показ кнопки подтверждения пользователю в UI («Вы действительно хотите перевести 500$?»), и запуск исполнения только после физического клика человека.
Установка maxSteps: 50. Если модель попадет в логическую петлю (вызывает инструмент -> получает ошибку -> снова вызывает тот же инструмент с теми же параметрами), приложение сожжет весь лимит токенов и зависнет.
Решение: Выставлять безопасные лимиты maxSteps: 3...5 и внедрять логику детекции повторяющихся вызовов.