OpenAI Tool Calling — Configuración
Módulo de tool calling listo para integrar en Next.js (App Router) usando el SDK de OpenAI. Ejecuta el loop IA ↔ ejecución de tools:
AI → Tool → Database/API → Result → AI
Es un módulo separado e independiente: no importa nada de los módulos Text Generation ni Chat ni depende de ellos. Es deliberadamente agnóstico sobre de dónde viene el historial de mensajes: vos le pasás los mensajes que maneje tu propia implementación de chat, y recibís la respuesta final del modelo más un registro de cada tool que llamó.
Dependencias
openai— SDK oficial de OpenAIajv— validador de JSON Schema, usado para validar los argumentos de las tools que devuelve el modelo antes de ejecutarlas
Variables de entorno
Mirá .env.local:
OPENAI_API_KEY— obligatoria.
Estructura de archivos
lib/openai-tools/
├── types.ts # Types, default model/token config, rate limit config
├── tools.ts # Customize here: define your tools with `defineTool`
├── service.ts # Internal — SDK calls, the AI ↔ tool loop, ajv validation, error normalization
├── actions.ts # "use server" — single public entrypoint (runWithToolsAction)
└── .env.local
Pasos de configuración
- Copiá la carpeta
lib/openai-tools/a tu proyecto. - Definí
OPENAI_API_KEYen el.env.localde tu proyecto. - Definí tus tools (mirá más abajo) y pasalas a
runWithToolsActiondesde donde viva el backend de tu chat.
Uso
Definir tools
Usá defineTool de tools.ts para la inferencia de tipos de parámetros y resultados. Cada
tool necesita un JSON Schema para sus parámetros y una función execute que
haga el trabajo real (llamar a tu base de datos/API):
import { defineTool } from "@/lib/openai-tools/tools";
const getOrderStatus = defineTool({
name: "get_order_status",
description: "Look up the current status of a customer's order by order id",
parameters: {
type: "object",
properties: {
orderId: { type: "string" },
},
required: ["orderId"],
additionalProperties: false,
},
execute: async ({ orderId }: { orderId: string }) => {
// Replace with your real DB/API call.
return { orderId, status: "shipped" };
},
});
Ejecutar el loop
import { runWithToolsAction } from "@/lib/openai-tools/actions";
const result = await runWithToolsAction({
messages: [
{ role: "system", content: "You are a helpful customer support assistant." },
{ role: "user", content: "Where's my order #12345?" },
],
tools: [getOrderStatus],
});
if (result.ok) {
console.log(result.data.text); // model's final reply
console.log(result.data.toolCalls); // every tool call executed, with args + result
} else {
console.error(result.error.code, result.error.message, result.error.toolName);
}
Integración con un chat externo
messages es intencionalmente solo { role, content }[]: convertí el historial
de mensajes de tu chat (por ejemplo, desde una base de datos o desde el módulo OpenAI Chat)
a ese formato antes de llamar a runWithToolsAction, y agregá result.data.text
como respuesta del asistente usando la forma en que tu chat persiste los mensajes.
Manejo de errores
Mismo formato normalizado que los otros módulos, con tres códigos específicos de tools:
interface AIActionError {
code:
| "RATE_LIMITED"
| "INVALID_REQUEST"
| "AUTH_ERROR"
| "TIMEOUT"
| "TOOL_NOT_FOUND"
| "TOOL_VALIDATION_ERROR"
| "TOOL_EXECUTION_ERROR"
| "UNKNOWN";
message: string;
retryable: boolean;
retryAfter?: number; // seconds, only set for RATE_LIMITED
toolName?: string; // set for TOOL_* codes
}
runWithToolsActionnunca lanza errores: devuelve{ ok: false, error }.- Si una tool falla (tool desconocida, argumentos inválidos, o la función
executelanza un error), el loop se detiene de inmediato: el error se devuelve a tu app y no se le pasa al modelo para que intente recuperarse por su cuenta. Si querés que el modelo vea y reaccione a los fallos de las tools, tenés que cambiar el loop enservice.tspara que agregue el error como mensaje de resultado de la tool en lugar de lanzarlo.
Rate limiting
Mismo enfoque que los otros módulos: reintento automático ante un 429 con
backoff exponencial (mirá RATE_LIMIT_CONFIG en types.ts). Si se agotan los
reintentos, el error se devuelve como RATE_LIMITED con un retryAfter en
segundos.
Límite de round trips
maxToolRoundtrips (por defecto: DEFAULT_MAX_TOOL_ROUNDTRIPS en types.ts)
limita cuántas veces puede el modelo llamar a tools antes de que este módulo se rinda
y devuelva el texto de respuesta que tenga hasta ese momento; esto evita un loop infinito si
el modelo sigue pidiendo tools indefinidamente.