Лимиты API при работе с нейросетями — и как с ними справляться
- Опубликовано
- • 6 мин чтения•--- просмотров
Лимиты API при работе с нейросетями — и как с ними справляться
Работа с нейросетями сегодня невероятно актуальна. Однако если ты используешь удалённый сервис для взаимодействия с ИИ-агентами (например, OpenAI API), ты неизбежно столкнёшься с рядом ограничений, таких как:
- максимальный размер запроса
- максимальное количество запросов в единицу времени
- лимиты на пропускную способность и конкурентность
Как известно, вызовы API к удалённым сервисам всегда сопровождаются лимитами. И когда ты обрабатываешь большие датасеты — особенно внутри циклов — эти ограничения быстро становятся проблемой.
Почему «просто отправить всё разом» не работает
Ты можешь сказать:
«Почему бы не объединить несколько запросов в один, а затем распарсить результат?»
На практике это не всегда работает. У OpenAI (и многих других моделей) есть особенность — при работе со списками или массивами нейросеть часто теряет, пропускает или исключает один или несколько элементов.
Поэтому самый надёжный способ — обрабатывать каждый элемент массива по отдельности. Это может быть медленнее, но гарантирует точность и согласованность результата.
Что такое rate limiting — и почему это важно
Для таких ситуаций существует механизм под названием rate limiting — способ ограничить частоту отправки запросов. Он помогает предотвратить перегрузку сервиса и обеспечивает справедливое распределение ресурсов.
Если игнорировать rate limits, ты быстро столкнёшься с ошибками вроде:
Error: 429 Too Many Requests
или даже временной приостановкой API-ключа.
Мой случай: перевод больших датасетов

В одном из моих проектов мне нужно было переводить большие объёмы текста на русский с помощью OpenAI API. Сначала я просто проходил циклом по всему и отправлял запросы — но быстро упёрся в лимиты.
Решение было простым: добавить контроллер частоты запросов. Вот минимальный пример клиента, который соблюдает лимиты запросов в минуту.
Такой подход позволяет плавно распределять нагрузку и оставаться в пределах лимитов API — даже при обработке тысяч элементов.
# translationClient.py
import Config from "@app/config";
import Bottleneck from "bottleneck";
import { createRequire } from "node:module";
import { z } from "zod";
import zodToJsonSchema, { JsonSchema7Type } from "zod-to-json-schema";
const TranslationSchema = z.object({
genres: z.array(z.string()).describe("Genres"),
features: z.array(z.string()).describe("Features"),
descriptionFull: z.string().describe("Full Description"),
supportedLanguagesText: z
.array(z.string())
.describe("Supported languages text"),
supportedLanguagesVoice: z
.array(z.string())
.describe("Supported languages voice"),
});
export type TranslationInput = z.infer<typeof TranslationSchema>;
const TranslationJsonSchema = zodToJsonSchema(TranslationSchema, {
$refStrategy: "none",
}) as JsonSchema7Type;
type JsonSchemaObject = JsonSchema7Type & {
properties?: Record<string, unknown>;
required?: string[];
};
const schemaObject = TranslationJsonSchema as JsonSchemaObject;
if (schemaObject && schemaObject.properties) {
schemaObject.required = Object.keys(schemaObject.properties);
}
const require = createRequire(import.meta.url);
const openAiModuleName = "openai";
const { default: OpenAI } = require(openAiModuleName) as { default: any };
const client = new OpenAI({ apiKey: Config.openAiKey });
export const openAiLimiter = new Bottleneck({
// Minimum spacing between calls so we stay under the rate limit
// For 500 requests per minute -> ~8.33 per second -> ~120 ms interval
minTime: 120,
// Use the reservoir to cap total calls per window
reservoir: 500, // maximum calls per window
reservoirRefreshInterval: 60 * 1000, // refresh the allowance every minute
reservoirRefreshAmount: 500
});
const limitCall = async <TRes>(call: CallableFunction, maxRetries = 5): Promise<TRes> => {
let attempt = 0
let backoff = 500
while (true) {
try {
return await openAiLimiter.schedule(() => call());
} catch (error: any) {
attempt++
if (attempt > maxRetries) {
throw error
}
if (error.status == 429) {
await new Promise(res => setTimeout(res, backoff))
backoff *= 2
} else {
throw error
}
}
}
}
export async function getGameTranslation(
lang: string,
input: TranslationInput,
) {
return await limitCall<TranslationInput>(async () => {
const completion = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: `You are a professional translator. Preserve meaning and tone. Return ONLY valid JSON`,
},
{
role: "user",
content: [
`Translate this object to ${lang} language: ${JSON.stringify(input)}`,
"Keep formatting where sensible",
"If there are names or bock codes, do not translate them",
"Return every property from the original object even if it should be empty.",
"Input",
].join("\n\n"),
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "Translation",
schema: TranslationJsonSchema,
strict: true,
},
},
});
const raw = completion.choices[0]?.message?.content ?? "{}";
const translation = TranslationSchema.parse(JSON.parse(raw));
return translation;
})
}
Улучшенная версия с добавленным мониторингом LangFuse
import Config from "@app/config";
import Bottleneck from "bottleneck";
import { OpenAI } from "openai/client";
import { z } from "zod";
import zodToJsonSchema, { JsonSchema7Type } from "zod-to-json-schema";
import { observeOpenAI } from "@langfuse/openai";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
const TranslationSchema = z.object({
genres: z.array(z.string()).describe("Genres"),
features: z.array(z.string()).describe("Features"),
descriptionFull: z.string().describe("Full Description"),
supportedLanguagesText: z
.array(z.string())
.describe("Supported languages text"),
supportedLanguagesVoice: z
.array(z.string())
.describe("Supported languages voice"),
});
export type TranslationInput = z.infer<typeof TranslationSchema>;
const TranslationJsonSchema = zodToJsonSchema(TranslationSchema, {
$refStrategy: "none",
}) as JsonSchema7Type;
type JsonSchemaObject = JsonSchema7Type & {
properties?: Record<string, unknown>;
required?: string[];
};
const schemaObject = TranslationJsonSchema as JsonSchemaObject;
if (schemaObject && schemaObject.properties) {
schemaObject.required = Object.keys(schemaObject.properties);
}
const sdk = new NodeSDK({
spanProcessors: [
new LangfuseSpanProcessor({
publicKey: Config.langfusePublicKey,
secretKey: Config.langfuseSecretKey,
baseUrl: Config.langfuseHost,
}),
],
});
sdk.start();
// Wrap the OpenAI client with Langfuse tracing
const tracedOpenAI = observeOpenAI(new OpenAI({ apiKey: Config.openAiKey }), {
// Configure trace-level attributes for all API calls
traceName: "my-openai-trace", // Name for the trace
sessionId: "user-session-123", // Track user session
userId: "user-abc", // Track user identity
tags: ["openai-integration"], // Add searchable tags
});
export const openAiLimiter = new Bottleneck({
// Minimum spacing between calls so we stay under the rate limit
// For 500 requests per minute -> ~8.33 per second -> ~120 ms interval
minTime: 120,
// Use the reservoir to cap total calls per window
reservoir: 500, // maximum calls per window
reservoirRefreshInterval: 60 * 1000, // refresh the allowance every minute
reservoirRefreshAmount: 500,
});
const limitCall = async <TRes>(
call: CallableFunction,
maxRetries = 5,
): Promise<TRes> => {
let attempt = 0;
let backoff = 500;
while (true) {
try {
return await openAiLimiter.schedule(() => call());
} catch (error: any) {
attempt++;
if (attempt > maxRetries) {
throw error;
}
if (error.status == 429) {
await new Promise((res) => setTimeout(res, backoff));
backoff *= 2;
} else {
throw error;
}
}
}
};
export async function getGameTranslation(
lang: string,
input: TranslationInput,
) {
return await limitCall<TranslationInput>(async () => {
const completion = await tracedOpenAI.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: `You are a professional translator. Preserve meaning and tone. Return ONLY valid JSON`,
},
{
role: "user",
content: [
`Translate this object to ${lang} language: ${JSON.stringify(input)}`,
"Keep formatting where sensible",
"If there are names or bock codes, do not translate them",
"Return every property from the original object even if it should be empty.",
"Input",
].join("\n\n"),
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "Translation",
schema: TranslationJsonSchema,
strict: true,
},
},
});
const raw = completion.choices[0]?.message?.content ?? "{}";
const translation = TranslationSchema.parse(JSON.parse(raw));
return translation;
});
}
Поделись своим опытом
Какие подходы используешь ты? Поделись в комментариях, как ты справляешься с rate limits, обрабатываешь очереди запросов или распределяешь нагрузку API между сервисами — было бы интересно узнать о твоих методах.
Открыт для работы по контракту
Я доступен для работы по контракту. Если у вас есть интересная идея проекта — запишитесь на звонок через Calendly.
Записаться на 30-минутный звонок