Qué hace realmente el function calling en un LLM
Pregúntale a un modelo “qué tiempo hace ahora en Austin” sin ninguna conexión con el mundo exterior, y tiene dos opciones: decir que no lo sabe, o inventarse algo. El function calling le da una tercera opción: el modelo describe lo que necesita, se lo pasa a tu código y espera una respuesta real antes de contestar.
El nombre es un poco engañoso. El modelo no llama a nada. Emite un bloque de datos estructurados que dice qué tool quiere y con qué argumentos, y tu aplicación decide si lo ejecuta y qué hace con el resultado. “Tool use” es el término más preciso, y es el que usa la documentación de Anthropic; OpenAI y Google suelen decir function calling. El mecanismo es el mismo bajo los dos nombres. El modelo solo produce una petición, nunca el efecto.
Por qué el modelo pide tools pero nunca los ejecuta
Imagina a un cliente en un mostrador que puede señalar platos en el menú y decir cuántos quiere, pero nunca entra detrás del mostrador. El modelo es el cliente: lee el menú que le has dado (las definiciones de los tools), elige un plato y dice cuántos quiere (los argumentos). Tu código es la persona detrás del mostrador que prepara el pedido, y que puede rechazar uno que no tiene sentido.
Esa separación es todo el modelo de seguridad. Una tool call es una sugerencia que tu código puede validar o rechazar, igual que cualquier otra entrada de una fuente no confiable, porque eso es exactamente lo que es. Un modelo al que una página web hostil le ha hecho un prompt injection solo puede pedir una tool call. No puede saltarse tu validación y llegar directamente a la base de datos.
Cómo definir un tool con JSON Schema
Cada proveedor quiere las mismas tres cosas para cada tool: un nombre, una descripción que el modelo usa para decidir cuándo aplica el tool, y un JSON Schema para los argumentos. Aquí tienes un tool de clima para la OpenAI Chat Completions API:
| |
Dedica más tiempo a la descripción que al esquema. El modelo elige un tool comparando la petición del usuario con las descripciones de la lista que le mandaste. Una descripción vaga (“cosas del clima”) se queda fuera frente a un tool mejor descrito que hace otra cosa, o se activa en peticiones que no debería manejar. Escríbela como si tuvieras que explicarle la función a un compañero que va a leer exactamente una frase sobre ella.
Cómo funciona el ciclo de petición-respuesta del function calling
Una tool call necesita al menos dos peticiones. Aquí está el recorrido completo con la API de OpenAI:
| |
La primera respuesta no contesta la pregunta. Llega con finish_reason en
tool_calls y sin contenido de texto, porque el modelo decidió que necesita
get_weather antes de poder decir algo útil. call.function.arguments
llega como una cadena JSON, no como un objeto ya parseado, así que
json.loads no es opcional. get_weather lo ejecutas tú (es una función
Python normal; el modelo no tiene forma de ejecutarla), y luego añades el
resultado como un mensaje con role: "tool" y el tool_call_id
correspondiente.
Solo la segunda petición, que ahora lleva ese resultado, te devuelve una
frase de verdad. Si añades el mensaje del asistente con su tool_calls
pero te olvidas del resultado del tool correspondiente, la API rechaza la
siguiente petición en vez de adivinar.
En qué se diferencia el tool use de Anthropic del function calling de OpenAI
Mismo mecanismo, nombres de campo distintos. Si cambias de proveedor o soportas ambos, aquí es donde van a salir tus bugs:
| Concepto | OpenAI | Anthropic |
|---|---|---|
| Campo con la lista de tools | tools, cada tool envuelto en {"type": "function", "function": {...}} | tools, cada tool plano: {"name", "description", "input_schema"} |
| Clave del esquema de argumentos | parameters | input_schema |
| Petición del modelo | array tool_calls en el mensaje, arguments como cadena JSON | bloque de contenido tool_use, input ya como objeto parseado |
| Envío del resultado | mensaje nuevo, role: "tool", tool_call_id | mensaje de usuario nuevo, bloque de contenido type: "tool_result", tool_use_id |
| Señalar una llamada fallida | sin campo dedicado: pon el error en content | is_error: true en el bloque tool_result |
La trampa está en el formato de los argumentos. Anthropic te da input ya
parseado en un dict de Python o un objeto de JS; OpenAI te da una cadena que
tienes que pasar tú mismo por json.loads. Código escrito para uno y
apuntado al otro se importa sin problemas y falla en la primera tool call.
Si soportas los dos, escribe una única función de normalización por la que
pasen ambos caminos antes de que tus implementaciones de tools vean los
argumentos.
Cómo funcionan las llamadas paralelas y los ciclos de varios pasos
Las dos APIs pueden devolver más de una tool call en una sola respuesta.
Pide el clima de tres ciudades y obtienes tres entradas en tool_calls, no
tres idas y vueltas separadas. Ejecútalas (en paralelo, si son
independientes) y añade los tres resultados antes de la siguiente petición.
Cada ID de llamada que emitió el modelo necesita un resultado; envía uno de
tres y la petición falla.
Algunas tareas necesitan varias rondas: buscar un usuario, luego su historial de pedidos, luego marcar el pedido atrasado. Escríbelo como un ciclo que sigue llamando al modelo y ejecutando lo que pide hasta que llega una respuesta sin tool calls. Luego ponle un límite. Nada en la API impide que un modelo pida el mismo tool nueve veces seguidas cuando los resultados no lo acercan a una respuesta. Un límite fijo, por ejemplo 10 o 20 iteraciones, convierte eso en un error claro en vez de en una factura descontrolada.
Errores comunes del function calling
Tratar los argumentos como entrada de confianza. args["city"] sale de
un modelo, lo que significa que sale de cualquier texto que haya alimentado
la conversación, incluida una página web scrapeada o un documento que el
modelo tenía que resumir. Válidalo antes de que toque una consulta a la base
de datos o un comando de shell. Un tool que construye SQL interpolando los
argumentos del modelo tiene la misma superficie de inyección que uno que
construye SQL a partir de un formulario web.
Confiar en el esquema como si fuera una garantía. El modelo es bueno
siguiendo required y enum, pero no es perfecto. Tarde o temprano pasará
"unit": "F" en vez de "fahrenheit", o se olvidará de un campo
obligatorio. Valida, y devuelve un mensaje de error claro como resultado del
tool en vez de romper. Dile al modelo qué estaba mal y muchas veces puede
reintentarlo con argumentos corregidos en la siguiente ronda.
Dejar que el modelo elija tools destructivos por su cuenta. Si un tool
borra datos o envía un correo, dejar tool_choice en automático es la
opción por defecto equivocada. Mantén los tools destructivos fuera de la
lista hasta que una persona apruebe la acción concreta, o haz que el efecto
sea reversible (un borrador en vez de un correo enviado) y que un paso
separado lo confirme.
Cuándo no usar function calling
Cada tool call añade una ida y vuelta al modelo más la latencia que tenga el tool. Si el “tool” solo va a devolver un dato estático, pon ese dato en el prompt o en el contexto recuperado y ahórrate la ida y vuelta; para eso está el retrieval de RAG. Y si la decisión es en realidad una clasificación con un conjunto pequeño y fijo de resultados (enrutar este ticket a una de cuatro colas), una llamada tipada y no generativa responde más rápido y más barato que pedirle a un modelo de chat genérico que elija un tool. Para eso están pensados los modelos system one.
El function calling compensa su coste cuando la acción depende de datos en tiempo de ejecución que el modelo no puede tener en el prompt, y el conjunto de acciones posibles es demasiado abierto para codificarlo como una máquina de estados. También es el ciclo que hay detrás de cada agente CLI de código: lee un archivo, ejecuta un test, decide qué hacer después, llama a otro tool.
Empieza con un tool, una función de Python fija por detrás, y un ciclo que se detiene después de una sola ida y vuelta. Resuelve bien la validación de argumentos y el camino de error antes de añadir un segundo tool. La mayoría de los bugs en una configuración con varios tools terminan siendo el primer tool manejando mal argumentos que no esperaba.