Un problema, tres dialectos
OpenAI, Anthropic y Google describen la misma intención: «quiero ejecutar esta función con estos argumentos». Lo que cambia es la envoltura. OpenAI devuelve un item function_call, Anthropic un bloque tool_use y Google una parte functionCall.
Un harness no debería propagar esas tres formas por todo el producto. Conviene traducirlas en la frontera del proveedor a una orden común para el ejecutor. Pero normalizar no significa borrar: el identificador nativo de la llamada debe sobrevivir para que el resultado vuelva a la petición correcta.
function_call
Los argumentos llegan como una cadena JSON. call_id enlaza la salida de la función.
tool_use
El input ya es un objeto. El resultado referencia tool_use_id.
functionCall
Los argumentos suelen ser un objeto y id es opcional, pero debe devolverse si existe.
El JSON que devuelve cada API
Estos ejemplos reducen capturas reales a los campos que intervienen en la llamada. Se han sustituido los identificadores opacos por nombres legibles. OpenAI y Google se capturaron con una petición mínima forzada; el ejemplo de Anthropic procede de una fixture previamente grabada y se contrastó con su documentación oficial porque la credencial de captura había caducado.
{
"type": "function_call",
"id": "fc_openai_1",
"call_id": "call_openai_1",
"name": "lookup_demo_value",
"arguments": "{\"key\":\"height\"}",
"status": "completed"
}{
"type": "tool_use",
"id": "toolu_anthropic_1",
"name": "lookup_demo_value",
"input": {
"key": "height"
}
}{
"functionCall": {
"name": "lookup_demo_value",
"args": {
"key": "height"
},
"id": "call_google_1"
}
}| Concepto | OpenAI | Anthropic | |
|---|---|---|---|
| Nombre | name | name | functionCall.name |
| Argumentos | arguments, string JSON | input, objeto | args, objeto o string tolerado |
| Correlación | call_id | id → tool_use_id | id opcional en llamada y respuesta |
| Resultado | function_call_output | tool_result | functionResponse |
Una forma común sin perder contexto
La parte que ejecuta código solo necesita name y args. La parte que construye el siguiente turno necesita además el proveedor y su identificador nativo. Separar ambos conceptos evita que un detalle de OpenAI llegue al handler, pero también evita responder «por nombre» cuando hay dos llamadas iguales.
{
"execution": {
"name": "lookup_demo_value",
"args": {
"key": "height"
}
},
"correlation": {
"provider": "openai | anthropic | google",
"callId": "identificador nativo de la llamada"
}
}normaliza los datos que consume tu código; conserva los metadatos que consume el protocolo.
Parsear el stream, no los paquetes
En streaming, un fragmento de red no equivale a un evento SSE ni a un JSON completo. La red puede cortar justo en mitad de "arguments", juntar cinco eventos en el mismo fragmento o dejar el último evento sin la línea en blanco final.
- Acumula texto en un buffer.
- Extrae únicamente eventos SSE completos y conserva el resto.
- Interpreta el tipo de evento y agrega deltas por su índice o identificador.
- Al cerrar el stream, procesa una última vez el resto si forma un evento válido.
OpenAI puede separar la creación del item y los deltas de argumentos; Anthropic transmite input_json_delta; Google suele entregar una parte de función completa. El parser debe ocultar esa diferencia y producir el mismo resultado con cualquier partición de bytes.
Múltiples llamadas y datos rotos
Conserva todas las llamadas
Un turno puede solicitar varias tools. Mantén el orden del proveedor y devuelve un resultado por llamada.
El nombre no basta
Dos llamadas a lookup_demo_value pueden tener inputs distintos. Correlaciónalas por id, no solo por nombre.
No fabriques una llamada
Un evento truncado o un JSON exterior inválido se ignora. Un error explícito del proveedor se convierte en un error controlado.
Los argumentos inválidos pueden normalizarse a un objeto vacío para mantener estable el parser, pero eso no es validación. Antes de ejecutar una acción con efectos, el harness todavía debe comprobar el JSON Schema, permisos y reglas de negocio.
El contrato de pruebas
Fixtures por proveedor
Una llamada, ninguna llamada y varias llamadas con ids y argumentos concretos.
Continuación nativa
Comprueba call_id, tool_use_id y el id opcional de Google.
Property-based
Genera particiones arbitrarias del stream y exige el mismo resultado que al recibirlo de una vez.
Añade regresiones para JSON truncado, errores explícitos y argumentos con forma inesperada. Estas pruebas no demuestran que el modelo elegirá bien una tool; demuestran que el harness interpretará de forma determinista lo que reciba.
Fuentes oficiales
Aprender a construir un agente completo
Este artículo pertenece a una serie sobre cómo desarrollar un agente o harness de principio a fin. Gymnasia sirve como ejemplo práctico, pero el parser por proveedor, la forma común y las pruebas de fragmentación son patrones trasladables a otros productos.
Ver el índice completo de la serie sobre el agente de Gymnasia.