Function calling (también llamado tool calling o tool use) es la forma de dar a un modelo acceso a tu código. Describes funciones como "consultar un pedido" o "calcular el costo de envío". El modelo decide cuál llamar y con qué argumentos; tu código la ejecuta y envía el resultado. Todo agente, desde un bot de soporte de una tienda hasta Claude Code, se basa en este ciclo.
En GetVibe API, function calling funciona mediante el endpoint estándar /v1/chat/completions en el formato de OpenAI y mediante /v1/messages en el formato de Anthropic. A continuación se muestra un ciclo completo que ejecutamos con tres modelos de tres proveedores diferentes.
Cómo funciona el ciclo
- Envías mensajes junto con una lista de
tools. Cada herramienta tiene un nombre, una descripción y un JSON Schema para sus parámetros. - En lugar de texto, el modelo devuelve
tool_calls: qué función llamar y con qué argumentos.finish_reasonestool_calls. - Tu código ejecuta las funciones y agrega un mensaje con el rol
toolpor cada llamada, incluyendo el mismotool_call_id. - Vuelves a enviar el historial. El modelo pide más llamadas o responde al usuario con texto.
El modelo nunca ejecuta nada por sí mismo. Solo solicita una llamada. Tu código decide qué se ejecuta realmente y con qué permisos.
Un ejemplo completo: un asistente de soporte para una tienda
Dos funciones: estado del pedido y cotización de envío. El usuario pregunta por ambas cosas a la vez y el modelo llama a las dos en un solo turno.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://getvibeapi.com/v1", api_key=os.environ["GETVIBE_API_KEY"])
MODEL = "gpt-6-luna"
def get_order_status(order_id: str) -> dict:
# aquí va la consulta a tu base de datos o CRM
return {"order_id": order_id, "status": "out for delivery", "eta": "2026-10-24"}
def get_shipping_quote(zip_code: str) -> dict:
return {"zip_code": zip_code, "price_usd": 7.5, "days": 2}
TOOLS = [
{"type": "function", "function": {
"name": "get_order_status",
"description": "Look up an order by its ID",
"parameters": {"type": "object",
"properties": {"order_id": {"type": "string", "description": "Order ID, e.g. A-1042"}},
"required": ["order_id"]}}},
{"type": "function", "function": {
"name": "get_shipping_quote",
"description": "Courier shipping price and delivery time to a US ZIP code",
"parameters": {"type": "object",
"properties": {"zip_code": {"type": "string"}},
"required": ["zip_code"]}}},
]
FUNCTIONS = {"get_order_status": get_order_status, "get_shipping_quote": get_shipping_quote}
messages = [
{"role": "system", "content": "Eres un asistente de soporte de una tienda. Sé breve."},
{"role": "user", "content": "¿Dónde está mi pedido A-1042? ¿Y cuánto cuesta el envío a 94110?"},
]
for _ in range(5): # limita el número de rondas
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
msg = resp.choices[0].message
if not msg.tool_calls:
print(msg.content)
break
messages.append(msg.model_dump(exclude_none=True))
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = FUNCTIONS[call.function.name](**args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
Ejecutamos este código sin cambios con gpt-6-luna, deepseek-v4.1-flash y claude-haiku-5.5, cambiando únicamente MODEL. En la primera ronda, los tres llamaron a ambas funciones: get_order_status con A-1042 y get_shipping_quote con 94110. En la segunda ronda, los tres respondieron con texto indicando el estado de entrega, la fecha estimada, el precio de 7.50 y el plazo de entrega de dos días.
Lo importante de este código
- Agrega el mensaje completo de llamada a herramientas del modelo al historial. De lo contrario, tu mensaje
toolresponde a una llamada que el modelo no puede ver, y muchos modelos simplemente vuelven a llamar a la misma función, haciendo que el ciclo se repita indefinidamente. - Un mensaje
toolpor llamada, con eltool_call_idcorrespondiente. El modelo puede solicitar varias llamadas a la vez, como ocurrió aquí. - Los argumentos llegan como una cadena.
argumentses JSON dentro de una cadena. Analízalo y valídalo, porque el modelo puede equivocarse en algún valor. - Limita el número de rondas. Sin un límite, un error en una función puede convertir el ciclo en uno interminable y generar una factura interminable de tokens.
tool_choice: cuándo una llamada es obligatoria
De forma predeterminada, el modelo decide si llama a una herramienta o responde con texto ("auto"). tool_choice cambia este comportamiento:
"none"— nunca llamar a herramientas;"required"— llamar al menos a una herramienta;{"type": "function", "function": {"name": "..."}}— llamar a esta función específica.
La última forma es útil para la extracción de datos. El esquema de parámetros de la función define la estructura de la respuesta:
curl https://getvibeapi.com/v1/chat/completions \
-H "Authorization: Bearer $GETVIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [{"role": "user", "content": "Jane Doe, [email protected], quiere una demostración el jueves"}],
"tools": [{
"type": "function",
"function": {
"name": "save_lead",
"description": "Save a sales lead to the CRM",
"parameters": {
"type": "object",
"properties": {"name": {"type": "string"}, "email": {"type": "string"}, "request": {"type": "string"}},
"required": ["name", "email", "request"]
}
}
}],
"tool_choice": {"type": "function", "function": {"name": "save_lead"}}
}'
La respuesta fue una llamada a save_lead con name "Jane Doe", email "[email protected]" y request "Quiere una demostración el jueves".
Una salvedad: algunos modelos de Claude, incluidos Claude Sonnet 5.5 y Claude Opus 5.5, no aceptan una selección forzada de herramienta. GetVibe convierte esa solicitud en auto con las mismas herramientas, así que obtienes una respuesta en lugar de un error, pero el modelo decide por sí mismo si realiza la llamada. Para obtener JSON estricto con esos modelos, usa structured outputs.
El formato de Anthropic: /v1/messages
Con el SDK de Anthropic, las herramientas tienen un aspecto ligeramente diferente: el esquema de parámetros va en input_schema y la llamada regresa como un bloque de contenido tool_use.
curl https://getvibeapi.com/v1/messages \
-H "x-api-key: $GETVIBE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-haiku-5.5",
"max_tokens": 512,
"tools": [{
"name": "get_order_status",
"description": "Look up an order by its ID",
"input_schema": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}
}],
"messages": [{"role": "user", "content": "¿Dónde está el pedido A-1042?"}]
}'
La respuesta incluía stop_reason tool_use y un bloque tool_use que llamaba a get_order_status con order_id A-1042. Envías el resultado de vuelta como un bloque tool_result en el siguiente mensaje del usuario, con el mismo tool_use_id.
Qué modelo usar para herramientas
- Bots de gran volumen y herramientas simples — DeepSeek V4.1 Flash ($0.0112 entrada · $0.0445 salida por 1M de tokens) o Claude Haiku 5.5 ($0.0267 entrada · $0.1334 salida por 1M de tokens).
- Agentes con muchas herramientas y planes largos — GPT-6 Luna ($0.0055 entrada · $0.0274 salida por 1M de tokens) o Claude Sonnet 5.5.
Los ciclos de agentes vuelven a enviar el mismo prefijo largo en cada ronda, por lo que el prompt caching es muy importante en este caso. Encontrarás más detalles sobre los campos de la solicitud en la documentación de tool calling.
