English · Español
01 — Formatos de function calling: una convergencia¶
🇪🇸 OpenAI, Anthropic y "raw JSON-Schema" tienen el mismo esqueleto: declarar herramientas, recibir una petición de llamada, ejecutar, devolver el resultado. Las diferencias son cosméticas. Conociendo el patrón, leer cualquier SDK nuevo toma cinco minutos.
Esta página recorre los principales formatos de function calling para que Borja pueda leer la documentación de cualquier proveedor sin sobrecarga de traducción. La tesis: bajo las diferencias cosméticas, son el mismo protocolo.
La forma que comparten¶
Todo sistema de tool calling en 2026 tiene estos cuatro elementos:
- Declaraciones de tools. Una lista de triples
{name, description, input_schema}enviada al modelo junto con el prompt. - Un mensaje de tool call. Cuando el modelo decide llamar a una tool, emite una respuesta estructurada que contiene
{tool_name, arguments}(o los campos equivalentes bajo otros nombres). - Un mensaje de tool result. El host ejecuta la tool y devuelve el resultado al modelo — usualmente como un mensaje especial etiquetado con un role.
- Un paso de continuación. El modelo, viendo ahora el resultado de la tool, produce su siguiente respuesta (que puede ser otro tool call o una respuesta final).
Las diferencias entre proveedores están enteramente en los nombres de los campos y las convenciones de role de los mensajes.
Formato OpenAI¶
// In the request:
{
"model": "gpt-4o",
"messages": [...],
"tools": [
{
"type": "function",
"function": {
"name": "conjugate",
"description": "Return the conjugated form of an English verb.",
"parameters": { "type": "object", "properties": {...}, "required": [...] }
}
}
]
}
// Model's response when calling a tool:
{
"choices": [{
"message": {
"role": "assistant",
"tool_calls": [{
"id": "call_abc",
"type": "function",
"function": { "name": "conjugate", "arguments": "{\"verb\":\"eat\",...}" }
}]
},
"finish_reason": "tool_calls"
}]
}
// Follow-up message from host:
{
"role": "tool",
"tool_call_id": "call_abc",
"content": "ate"
}
Nota: function.arguments es un string (codificado en JSON), no un objeto. Esto es una verruga — el modelo emite JSON, luego el formato del wire lo envuelve en un string. La Fase 31 lo desenvuelve y lo valida.
Formato Anthropic¶
// In the request:
{
"model": "claude-opus-4-7",
"messages": [...],
"tools": [
{
"name": "conjugate",
"description": "Return the conjugated form of an English verb.",
"input_schema": { "type": "object", "properties": {...}, "required": [...] }
}
]
}
// Model's response when calling a tool:
{
"stop_reason": "tool_use",
"content": [
{ "type": "text", "text": "Let me check that conjugation." },
{
"type": "tool_use",
"id": "toolu_abc",
"name": "conjugate",
"input": { "verb": "eat", "tense": "past_simple", "person": "3sg" }
}
]
}
// Follow-up message from host:
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_abc",
"content": "ate"
}]
}
Nota: input es un objeto, no un string. Más limpio que el de OpenAI. Nota también que los resultados de tools vuelven bajo role: "user" con un bloque de contenido tool_result — Anthropic modela el resultado de la tool como "más input desde el entorno".
JSON-Schema en crudo (el mínimo común denominador)¶
Despoja las convenciones del proveedor:
Esto es lo que usa MCP. Las convenciones específicas de role de mensaje del proveedor quedan abstraídas. La dataclass Tool de la Fase 31 la adopta:
@dataclass(frozen=True)
class Tool:
name: str
description: str
input_schema: dict # JSON-Schema
fn: Callable[..., Any]
Envolver para OpenAI, Anthropic o MCP es entonces un adaptador fino — envuelve cada Tool en el envoltorio preferido del proveedor.
La cuestión del formato de argumentos¶
A través de los proveedores, el trabajo del modelo es emitir JSON que coincida con el input_schema. El JSON de argumentos lo genera el LM igual que cualquier otra salida. Aquí es donde el JSONSchemaMask de la Fase 30 salva el día.
Sin máscara:
- El modelo a veces emite {verb: eat, ...} (sin comillas en la clave — JSON inválido).
- El modelo a veces emite {"verb": "ate", ...} (una forma conjugada, violando el enum).
- El modelo a veces emite {"verb": "eat", "tense": "past", "person": "3sg"} ("past" está fuera del enum).
Con máscara:
- La máscara prohíbe claves sin comillas → JSON inválido imposible.
- La máscara prohíbe valores string fuera del enum de verb → "ate" imposible en esa posición.
- La máscara prohíbe valores string fuera del enum de tense → "past" imposible.
Toda la clase de fallos "corrupción del tool call" queda eliminada por construcción. Esta es la afirmación operacional central de structured-decoding-más-tool-use.
El formato del tool result¶
Menos convergente que el formato de llamada. OpenAI usa role: "tool". Anthropic usa un bloque de contenido tool_result bajo role: "user". MCP usa un mensaje response JSON-RPC separado.
Para la Fase 31 adoptamos el estilo MCP: el tool result es un objeto JSON devuelto por tools/call:
{
"jsonrpc": "2.0",
"id": "request-1",
"result": {
"content": [{ "type": "text", "text": "ate" }],
"isError": false
}
}
content es un array porque algunas tools devuelven resultados multimodales (texto + imágenes). Para la Fase 31 toda tool devuelve texto.
Errores¶
Los errores son ciudadanos de primera clase. Una tool que falla devuelve:
{
"jsonrpc": "2.0",
"id": "request-1",
"result": {
"content": [{ "type": "text", "text": "verb 'run' is out of scope" }],
"isError": true
}
}
Crucialmente, isError: true no se traduce al campo error de JSON-RPC. El protocolo distingue:
- Errores de protocolo (
errorde JSON-RPC): mensaje malformado, método desconocido, args inválidos para el schema atrapados en la frontera del servidor. Estos impiden que la tool corra. - Errores de tool (
result.isError: true): la tool corrió pero devolvió un fallo lógico. El agente lo recibe como un resultado normal y puede razonar sobre él.
Esta distinción importa en la Fase 32: el agente puede recuperarse de errores de tool (replanificar, probar otra tool) pero no de errores de protocolo (el servidor ni siquiera está ahí).
Número-de-tool-calls-por-turno¶
OpenAI y Anthropic permiten múltiples tool calls en una sola respuesta del modelo. El mini servidor de la Fase 31 soporta una llamada por petición tools/call; las peticiones multi-call las descompone el cliente en llamadas individuales secuenciales. Esta es una simplificación por claridad pedagógica, no una limitación del protocolo.
Qué significa esto para el diseño de la Fase 31¶
src/miniagent/tools/base.py define una sola dataclass Tool con la forma JSON-Schema en crudo. mcp_server.py la expone bajo las convenciones MCP. Si una fase futura quiere adaptadores con formato OpenAI o Anthropic, son envoltorios de 20 líneas alrededor del mismo registro Tool. No escribimos esos adaptadores ahora.
Qué NO cubre esta página¶
- Tool calls en streaming. Algunos proveedores hacen streaming de los argumentos token a token. Nosotros no.
- Tool calls en paralelo. Algunos proveedores soportan disparar varias tools concurrentemente. Nosotros no.
- Caching de tool results. La Fase 33 podría añadirlo.
- Resultados multi-modales de tools. Todas nuestras tools devuelven texto.
Siguiente: theory/02-mcp-architecture.md — la estructura real del protocolo MCP.