Primeros pasos
Guía: agentes con llamadas a herramientas
Crea un bucle de agente con llamadas a herramientas en paralelo, límite de pasos, salida estructurada y control de costes usando el SDK de OpenAI.
Un agente es un bucle: el modelo lee la conversación, decide llamar a una o más de tus herramientas, tú las ejecutas y le devuelves los resultados, y el modelo continúa hasta que puede responder. api14 lo admite con el formato tools de OpenAI en todos los modelos que soportan herramientas, así que el mismo código funciona con OpenAI, Anthropic, Google y otros proveedores cambiando solo el id del modelo.
Esta guía construye un agente pequeño con límite de pasos, ejecución de herramientas en paralelo, una respuesta final estructurada y control de costes. El código usa el paquete oficial openai con la URL base https://api14.com/api/v1.
Define las herramientas
Cada herramienta tiene un nombre, una descripción que lee el modelo y un JSON Schema para sus argumentos. Mantén las descripciones concretas; son la única documentación del modelo.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api14.com/api/v1", api_key=os.environ["API14_API_KEY"])
def search_orders(customer_email: str) -> list:
return [{"id": "A-1001", "status": "shipped"}] # tu consulta a la base de datos
def get_shipment(order_id: str) -> dict:
return {"order_id": order_id, "carrier": "UPS", "eta": "2026-10-09"} # tu llamada a la API
TOOLS = {"search_orders": search_orders, "get_shipment": get_shipment}
tools = [
{"type": "function", "function": {
"name": "search_orders",
"description": "List a customer's orders with their status.",
"parameters": {"type": "object", "properties": {"customer_email": {"type": "string"}},
"required": ["customer_email"]},
}},
{"type": "function", "function": {
"name": "get_shipment",
"description": "Carrier and estimated delivery date for one order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}},
]
El bucle con límite de pasos
Cada iteración es una petición facturada por separado, así que limita siempre el número de pasos. Devuelve los errores de las herramientas al modelo como contenido en lugar de dejar que el programa falle; los modelos suelen recuperarse corrigiendo los argumentos.
MAX_STEPS = 8
def run_tool(call):
try:
args = json.loads(call.function.arguments)
result = TOOLS[call.function.name](**args)
except Exception as e: # JSON incorrecto, herramienta desconocida o fallo de tu función
result = {"error": str(e)}
return {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}
def agent(task, model="openai/gpt-5-mini"):
messages = [
{"role": "system", "content": "You are a support agent. Use tools to look things up. Be brief."},
{"role": "user", "content": task},
]
spent = 0.0
for step in range(MAX_STEPS):
r = client.chat.completions.create(model=model, messages=messages, tools=tools, max_tokens=1000)
spent += r.usage.cost
msg = r.choices[0].message
if not msg.tool_calls:
return msg.content, spent
messages.append(msg)
messages.extend(run_tool(call) for call in msg.tool_calls)
raise RuntimeError(f"No answer after {MAX_STEPS} steps (spent ${spent:.4f})")
print(agent("Where is my order? My email is ada@example.com"))
Envía tools en cada petición del bucle. Muchos proveedores rechazan un mensaje tool cuando la petición no incluye definiciones de herramientas.
Llamadas a herramientas en paralelo
Con parallel_tool_calls (activado por defecto donde se admite), un modelo puede pedir varias llamadas en un mismo turno, por ejemplo get_shipment para tres pedidos. Ejecútalas de forma concurrente y añade un mensaje tool por llamada, en cualquier orden; tool_call_id vincula cada resultado con su llamada. En Node:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api14.com/api/v1", apiKey: process.env.API14_API_KEY });
const MAX_STEPS = 8;
async function agent(messages, tools, impl, model = "openai/gpt-5-mini") {
for (let step = 0; step < MAX_STEPS; step++) {
const res = await client.chat.completions.create({ model, messages, tools, max_tokens: 1000 });
const msg = res.choices[0].message;
if (!msg.tool_calls?.length) return msg.content;
messages.push(msg);
const results = await Promise.all(msg.tool_calls.map(async (call) => {
let content;
try {
content = JSON.stringify(await impl[call.function.name](JSON.parse(call.function.arguments)));
} catch (err) {
content = JSON.stringify({ error: String(err) });
}
return { role: "tool", tool_call_id: call.id, content };
}));
messages.push(...results);
}
throw new Error(`No answer after ${MAX_STEPS} steps`);
}
Si tus herramientas tienen efectos secundarios que deben ocurrir en orden (crear y luego actualizar), establece parallel_tool_calls: false para que el modelo pida una sola llamada por turno.
Salida final estructurada
Cuando la respuesta del agente alimenta otro código, pide un JSON que cumpla un esquema. Un patrón sencillo y portable es una llamada extra después de que el bucle haya reunido los datos (messages aquí es la conversación del bucle; devuélvela desde agent() junto con la respuesta):
schema = {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"status": {"type": "string", "enum": ["processing", "shipped", "delivered", "unknown"]},
"eta": {"type": ["string", "null"]},
"reply": {"type": "string"},
},
"required": ["order_id", "status", "eta", "reply"],
"additionalProperties": False,
}
final = client.chat.completions.create(
model="openai/gpt-5-mini",
messages=messages + [{"role": "user", "content": "Summarize the result for our ticket system."}],
tools=tools,
tool_choice="none",
response_format={"type": "json_schema", "json_schema": {"name": "ticket", "strict": True, "schema": schema}},
)
ticket = json.loads(final.choices[0].message.content)
tool_choice: "none" mantiene tools en la petición (necesario porque el historial contiene mensajes tool) e impide nuevas llamadas. Valida siempre el JSON analizado. Consulta Salidas estructuradas.
Elegir un modelo
- Capacidad: el modelo debe incluir
toolsensupported_parameters. Compruébalo conGET /models, consulta Modelos. Para el paso final, buscastructured_outputsoresponse_format. - Enrutamiento: añade
provider: { "require_parameters": true }(en Python, medianteextra_body) para que las peticiones solo vayan a proveedores que admitan todos los parámetros que envías. Consulta Enrutamiento de proveedores. - Tamaño: los modelos pequeños como
openai/gpt-5-minimanejan con fiabilidad y a bajo coste unas pocas herramientas bien descritas. Pasa a un modelo más grande cuando el agente tenga muchas herramientas, planes largos de varios pasos o elija con frecuencia la herramienta equivocada. - Prueba con tus herramientas: ejecuta las mismas 20-50 tareas reales con dos o tres modelos candidatos y compara la tasa de éxito, los pasos por tarea y el
usage.costtotal.
curl -s "https://api14.com/api/v1/models?category=text" \
| jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id) \(.pricing.prompt)"'
Controlar el coste
Un agente puede hacer muchas peticiones para una sola tarea, y toda la conversación se reenvía en cada paso. Limita el gasto en varios niveles:
- Límite de pasos (
MAX_STEPS) ymax_tokenspor petición, como arriba. - Presupuesto por tarea: suma
usage.costde todos los pasos y detente cuando supere tu umbral. - Resultados de herramientas cortos: devuelve solo los campos que el modelo necesita. Las salidas grandes de herramientas se pagan como tokens de entrada en cada paso posterior.
- Límite diario en USD por clave: crea una clave dedicada al agente en Claves de API y establece un límite diario. Al alcanzarlo, las peticiones devuelven
429 daily_limit_reachedhasta el siguiente día UTC; gestiona ese código deteniendo el agente, no reintentando. - Lista de modelos permitidos: restringe la clave a los modelos que probaste; cualquier otro devuelve
403 model_not_allowed. - Caché de prompts: mantén el prompt de sistema y las definiciones de herramientas idénticos y al principio de cada petición para que se apliquen los descuentos por prefijo en caché. Consulta Caché de prompts.
Consulta Llamadas a herramientas, Límites de tasa y Errores.