@dariomvg/create
openaitool-callingnextjsajvintegracion

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 OpenAI
  • ajv — 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

  1. Copiá la carpeta lib/openai-tools/ a tu proyecto.
  2. Definí OPENAI_API_KEY en el .env.local de tu proyecto.
  3. Definí tus tools (mirá más abajo) y pasalas a runWithToolsAction desde 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
}
  • runWithToolsAction nunca lanza errores: devuelve { ok: false, error }.
  • Si una tool falla (tool desconocida, argumentos inválidos, o la función execute lanza 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 en service.ts para 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.