Límites de tasa de la API al trabajar con redes neuronales — y cómo manejarlos
- Publicado el
- • 7 min de lectura•--- vistas
Límites de tasa de la API al trabajar con redes neuronales — y cómo manejarlos
Trabajar con redes neuronales se ha vuelto increíblemente relevante hoy en día. Sin embargo, si usas un servicio remoto para interactuar con agentes de IA (por ejemplo, la API de OpenAI), inevitablemente enfrentarás varias limitaciones, como:
- tamaño máximo de solicitud
- número máximo de solicitudes por unidad de tiempo
- límites de tasa para throughput y concurrencia
Como sabemos, las llamadas a la API de servicios remotos siempre vienen con límites. Y cuando procesas grandes datasets — especialmente dentro de loops — estas restricciones rápidamente se convierten en un problema.
Por qué "simplemente enviar todo a la vez" no funciona
Podrías decir:
"¿Por qué no combinar varias solicitudes en una sola y luego parsear el resultado?"
En la práctica, esto no siempre funciona. OpenAI (y muchos otros modelos) tiene una particularidad — al trabajar con listas o arrays, la red neuronal a menudo descarta, se salta o excluye uno o más elementos.
Por eso la forma más confiable es procesar cada elemento del array individualmente. Puede ser más lento, pero garantiza precisión y consistencia en el resultado.
Qué es el rate limiting — y por qué importa
Para estas situaciones, tenemos un mecanismo llamado rate limiting — una forma de restringir con qué frecuencia puedes enviar solicitudes. Ayuda a prevenir la sobrecarga del servicio y asegura una distribución justa de recursos.
Si ignoras los límites de tasa, rápidamente encontrarás errores como:
Error: 429 Too Many Requests
o incluso la suspensión temporal de la clave de API.
Mi caso: traduciendo grandes datasets

En uno de mis proyectos, necesitaba traducir grandes volúmenes de texto al ruso usando la API de OpenAI. Al principio, simplemente recorría todo en un loop y disparaba solicitudes — pero pronto alcancé los límites.
La solución fue simple: añadir un controlador de tasa de solicitudes. Aquí hay un ejemplo mínimo de un cliente que respeta los límites de solicitudes por minuto.
Este enfoque te permite distribuir la carga de forma fluida y mantenerte dentro de los límites de la API — incluso al procesar miles de elementos.
# 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;
})
}
Versión mejorada con monitoreo de LangFuse añadido
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;
});
}
Comparte tu experiencia
¿Qué enfoques usas tú? Comparte en los comentarios cómo manejas los límites de tasa, procesas colas de solicitudes o distribuyes la carga de la API entre servicios — me encantaría aprender de tus métodos.
Disponible para colaboración por contrato
Estoy disponible para colaborar por contrato. Si tiene una idea de proyecto interesante, reserve una llamada por Calendly.
Agenda una llamada de 30 min