Вызов функций: Responses API и Chat Completions
Сравнение на уровне протокола: определения функций, идентификаторы вызовов, результаты, потоковая передача аргументов, авторизация, идемпотентность и маршруты.

Responses и Chat Completions могут попросить приложение выполнить функцию, но представляют цикл вызова инструментов по-разному. Chat Completions определяет функции в tools[].function, возвращает message.tool_calls и принимает результаты как сообщения role: "tool". Responses использует плоские определения, типизированные элементы вывода function_call и function_call_output, связанные через call_id.
Модель не выполняет функцию. Код приложения должен проверить аргументы, авторизовать операцию, безопасно выполнить её, вернуть результат и предотвратить повторные побочные эффекты при повторной попытке.
Четыре шага цикла инструментов
1. Объявить разрешённую функцию и схему её аргументов
2. Получить один или несколько предложенных моделью вызовов функции
3. Проверить, авторизовать и выполнить каждый вызов в приложении
4. Вернуть каждый результат с идентификатором корреляции вызова
Только после четвёртого шага модель может сформировать ответ на основе результата. Следующий инструмент запускает новую итерацию с новым идентификатором вызова. Руководство OpenAI по Function Calling также описывает многошаговый обмен.
Сравнить транспортные контракты
| Аспект | Responses API | Chat Completions |
|---|---|---|
| Определение | Плоский элемент tools[] |
Поля в tools[].function |
| Предложенный вызов | Элемент вывода function_call |
tool_calls[] в сообщении ассистента |
| Корреляция | call_id |
id, результат как tool_call_id |
| Имя | function_call.name |
tool_calls[].function.name |
| Аргументы | JSON-строка function_call.arguments |
JSON-строка tool_calls[].function.arguments |
| Результат | function_call_output |
Сообщение role: "tool" |
| Потоковая передача | Типизированные события аргументов | Фрагменты delta.tool_calls[] |
| Итоговый текст | Элементы вывода / метод SDK | choices[0].message.content |
Нельзя связывать вызовы по позиции в массиве. Параллельные вызовы и фрагменты могут завершиться в другом порядке. Явный идентификатор вызова — стабильный ключ связи.
Определить строгую схему функции
{
"type": "object",
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORDER-[0-9]{4}$"
}
},
"required": ["order_id"],
"additionalProperties": false
}
strict: true требует от модели соблюдать схему, но приложение всё равно разбирает и проверяет аргументы. Поддержка pattern различается, поэтому применяется та же проверка совместимости, что и для Structured Outputs.
Реализовать цикл через Responses API
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.MODELFLARE_API_KEY,
baseURL: "https://modelflare.dev/v1",
});
const model = process.env.MODEL_ID;
if (!model) throw new Error("Требуется MODEL_ID");
const orders = new Map([
["ORDER-1001", { status: "in_transit", eta: "2026-08-06" }],
]);
const tools = [
{
type: "function",
name: "get_delivery_status",
description: "Возвращает текущий статус доставки заказа.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
];
function executeTool(name, rawArguments) {
if (name !== "get_delivery_status") {
throw new Error(`Инструмент не разрешён: ${name}`);
}
const args = JSON.parse(rawArguments);
if (!/^ORDER-[0-9]{4}$/.test(args.order_id)) {
throw new Error("Некорректный order_id");
}
return orders.get(args.order_id) ?? { status: "not_found" };
}
const first = await client.responses.create({
model,
input: "Где находится заказ ORDER-1001?",
tools,
parallel_tool_calls: false,
});
const calls = first.output.filter((item) => item.type === "function_call");
if (calls.length === 0) {
console.log(first.output_text);
process.exit(0);
}
const toolOutputs = calls.map((call) => ({
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(executeTool(call.name, call.arguments)),
}));
const final = await client.responses.create({
model,
input: [...first.output, ...toolOutputs],
tools,
parallel_tool_calls: false,
});
console.log(final.output_text);
call.call_id и call_id результата должны совпадать. В варианте без сохранения состояния предыдущие элементы вывода возвращаются вместе с результатами. Нельзя предполагать, что любой маршрут, совместимый с OpenAI, хранит состояние ответа.
Реализовать цикл через Chat Completions
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.MODELFLARE_API_KEY,
baseURL: "https://modelflare.dev/v1",
});
const model = process.env.MODEL_ID;
if (!model) throw new Error("Требуется MODEL_ID");
const orders = new Map([
["ORDER-1001", { status: "in_transit", eta: "2026-08-06" }],
]);
const tools = [
{
type: "function",
function: {
name: "get_delivery_status",
description: "Возвращает текущий статус доставки заказа.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
},
];
function executeTool(name, rawArguments) {
if (name !== "get_delivery_status") {
throw new Error(`Инструмент не разрешён: ${name}`);
}
const args = JSON.parse(rawArguments);
if (!/^ORDER-[0-9]{4}$/.test(args.order_id)) {
throw new Error("Некорректный order_id");
}
return orders.get(args.order_id) ?? { status: "not_found" };
}
const messages = [
{ role: "user", content: "Где находится заказ ORDER-1001?" },
];
const first = await client.chat.completions.create({
model,
messages,
tools,
parallel_tool_calls: false,
});
const assistant = first.choices[0].message;
const calls = assistant.tool_calls ?? [];
if (calls.length === 0) {
console.log(assistant.content);
process.exit(0);
}
messages.push(assistant);
for (const call of calls) {
const result = executeTool(call.function.name, call.function.arguments);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
const final = await client.chat.completions.create({
model,
messages,
tools,
parallel_tool_calls: false,
});
console.log(final.choices[0].message.content);
Сообщение ассистента с вызовами добавляется в messages до результатов. Его отсутствие или неправильный tool_call_id создаёт некорректную историю.
Считать потоковые аргументы фрагментами
{"order — неполный, а не невалидный JSON. Для каждого идентификатора вызова нужен отдельный буфер: добавляйте приращения, дождитесь завершения аргументов или ответа, один раз разберите полную строку и выполняйте функцию только после валидации. Responses использует типизированные события, Chat Completions — choices[].delta.tool_calls[]. Нельзя запускать функцию на первом фрагменте. См. руководство по потоковым ответам AI API.
Сделать выполнение безопасным и идемпотентным
- разрешать только зарегистрированные имена функций;
- ограничивать размер и проверять полную схему;
- авторизовать пользователя или рабочую нагрузку для доступа к ресурсу;
- разделять инструменты только для чтения и инструменты с побочными эффектами;
- удалять секреты из результата;
- задавать сроки и ограничивать повторные обращения к зависимым сервисам;
- сохранять безопасную ссылку на операцию.
Для побочного эффекта создаётся ключ идемпотентности из стабильного запроса приложения и идентификатора вызова. Результат сохраняется до ответа модели. Повторная попытка возвращает сохранённый результат вместо второго возврата средств, сообщения, развёртывания или изменения базы данных.
Имя функции и аргументы модели — недоверенные входные данные. Валидная схема не выдаёт прав и не заменяет транзакцию.
Определить правила для нескольких вызовов
parallel_tool_calls: false упрощает автомат состояний. При параллельном режиме связывайте результаты по идентификатору вызова, ограничивайте число вызовов и параллелизм, определяйте влияние ошибки, возвращайте результат для каждого принятого вызова и сериализуйте зависимые побочные эффекты. Параллельность ускоряет независимое чтение, но усложняет авторизацию, повторные попытки и обработку частичных сбоев.
Понимать границу совместимости Modelflare
Modelflare сохраняет строгие определения функций и преобразует поддерживаемые формы между Responses и Chat Completions на допустимых маршрутах OpenAI/Codex. Это преобразование намеренно охватывает меньше, чем полный набор инструментов Responses.
Функции, определённые приложением, представлены в обоих форматах. Размещённые у поставщика инструменты — поиск, выполнение кода и другие — не эквивалентны и не должны считаться автоматически конвертируемыми. Для других семейств, совместимых с OpenAI, Chat Completions передаётся без преобразования до отдельной проверки; контракт поставщика определяет поддержку tools, strict, параллельных вызовов, потоковых аргументов и tool_choice.
| Тест | Необходимая информация |
|---|---|
| Вызов только для чтения | Имя, аргументы, идентификатор вызова, связь результата, итоговый текст |
| Невалидные аргументы | Явная ошибка без выполнения |
| Неизвестная функция | Отказ по списку разрешённых функций |
| Инструмент не нужен | Обычный текст без выдуманного вызова |
| Потоковая передача | Полные аргументы и корректный идентификатор |
| Повторный запрос | Один побочный эффект или сохранённый результат |
| Несколько вызовов | Связь независимо от порядка |
| Резервный маршрут | Те же модель, протокол, схема и контракт |
Надёжная маршрутизация AI API помогает сделать смену маршрута явной, а безопасность ключей AI API — разделить рабочие нагрузки и права.
Выбрать формат по контракту приложения
Responses подходит для типизированных элементов вывода, модели событий Responses, продолжения с сохранением состояния и других проверенных функций. Chat Completions подходит при стабильной истории сообщений и проверенном контракте инструментов поставщика. См. Responses API и Chat Completions.
Стандарт одинаков: явный список разрешённых инструментов, строгая схема аргументов, валидация в приложении, авторизация до выполнения, связь по идентификатору вызова, идемпотентность побочных эффектов и полный результат для модели. Надёжность дают эти меры контроля, а не название конечной точки.