{"id":51,"date":"2025-11-10T12:15:49","date_gmt":"2025-11-10T12:15:49","guid":{"rendered":"https:\/\/pulki.es\/blog\/?p=51"},"modified":"2025-11-10T12:15:49","modified_gmt":"2025-11-10T12:15:49","slug":"llamada-a-funciones-usando-la-api-de-openai","status":"publish","type":"post","link":"https:\/\/pulki.es\/blog\/index.php\/2025\/11\/10\/llamada-a-funciones-usando-la-api-de-openai\/","title":{"rendered":"Llamada a funciones usando la API de OpenAI"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">En esta entrada aprenderemos a llamar funciones con la API de OpenAI. Esta funcionalidad es extremadamente \u00fatil, porque permite extraer informaci\u00f3n acorde a la estructura de los argumentos de nuestras funciones en el <em>backend<\/em>, evitando as\u00ed que la ejecuci\u00f3n del c\u00f3digo se rompa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para ilustrar la idea detr\u00e1s de esta funcionalidad, programaremos un ejemplo simple en php que ser\u00e1 explicado bloque por bloque. Si quieres saber m\u00e1s sobre esta caracter\u00edstica, puedes consultar la <a href=\"https:\/\/platform.openai.com\/docs\/guides\/function-calling\">documentaci\u00f3n oficial<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>OpenAI <em>jargon<\/em>&#8230;<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En primer lugar, y siguiendo de cerca la documentaci\u00f3n, definiremos algunos conceptos clave que nos ayudar\u00e1n a entender esta funcionalidad. El primero es \u00abfunction\u00bb o \u00abtool\u00bb, que se define como una funcionalidad expuesta al modelo; es decir, aquella a la que el modelo sabe que podr\u00e1 acceder. Por otra parte, una \u00abfunction call\u00bb, es cuando el modelo determina que, dado el <em>prompt<\/em> del usuario y\/o las instrucciones recibidas, debe hacer una llamada. Finalmente -y en simple-, una \u00abfunction call output\u00bb, es la respuesta que le llegar\u00e1  a tu <em>backend<\/em> despu\u00e9s de que el modelo lleve a cabo una <em>function call<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Ejemplo en PHP: calculando el IMC&#8230;<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este ejemplo crearemos una funci\u00f3n llamada calcular_imc, cuyo objetivo es calcular el \u00cdndice de Masa Corporal (IMC) dado el peso y la altura del usuario:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function calcular_imc(float $altura_m, float $peso_kg): float\n{\n    if ($altura_m &lt;= 0.0 || $peso_kg &lt;= 0.0) {\n        throw new InvalidArgumentException('Altura y peso deben ser positivos.');\n    }\n\n    $imc = $peso_kg \/ ($altura_m ** 2);\n    return round($imc);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con dicha funci\u00f3n en mente, nuestro objetivo ser\u00e1 enviar a OpenAI el peso y la altura del usuario, para luego recibir los argumentos de calcular_imc de forma estructurada en una \u00abfunction call output\u00bb. Para llevar a cabo esto, primero que todo definimos la estructura de nuestro <em>tools<\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$tools = &#91;];\n\n$tools = &#91;\n  &#91;\n    'type' => 'function',\n    'name' => 'calcular_imc',\n    'description' => 'Dado el peso y la altura, obtiene el IMC del usuario.',\n    'parameters' => &#91;\n      'type' => 'object',\n      'properties' => &#91;\n        'peso' => &#91;\n          'type' => 'number',\n          'description' => 'Peso del paciente en kilogramos.',\n        ],\n        'altura' => &#91;\n          'type' => 'number',\n          'description' => 'Altura del paciente en metros (con punto para separar los decimales).',\n        ],\n      ],\n      'required' => &#91;'peso', 'altura'],\n      'additionalProperties' => false,\n    ],\n  ],\n];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luego, definimos nuestro payload:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$messages = &#91;\n  &#91;'role' => 'user', 'content' => 'Mi peso es 85kg y mi altura es 1.84m, \u00bfCu\u00e1l es mi IMC?']\n];\n\n\n$payload = &#91;\n    'model'        => 'gpt-5-mini',\n    \/\/'instructions' => '',\n    'reasoning'         => &#91;'effort' => 'low'],\n    'input'        => $messages,\n    'text' => &#91;\n      'verbosity' => 'low'\n    ],\n    'tools'             => $tools,\n    'max_output_tokens' => 500\n  ];\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En este caso no es necesario detallar en <em>instructions<\/em> lo que debe hacer el modelo. Es decir, para este ejemplo ser\u00eda irrelevante a\u00f1adir algo como: \u00abCaptura la altura y el peso del usuario y realiza un <em>function call<\/em>\u00bb. Por el contrario, el modelo realizar\u00e1 la llamada de funci\u00f3n (\u00abfunction call\u00bb) cuando lo considere pertinente seg\u00fan el mensaje del usuario; en este caso: \u00abMi peso es 85 kg y mi altura es 1,84 m. \u00bfCu\u00e1l es mi IMC?\u00bb. Luego, llevando a cabo el <em>request<\/em> pertinente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  $ch = curl_init($OPENAI_API);\n  curl_setopt_array($ch, &#91;\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),\n    CURLOPT_HTTPHEADER     => &#91;'Content-Type: application\/json', 'Authorization: Bearer '.$OPENAI_API_KEY],\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT        => 40\n  ]);\n  $body = curl_exec($ch);\n  $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);\n  if ($body === false) {\n    $err = curl_error($ch);\n    curl_close($ch);\n    throw new RuntimeException('OpenAI cURL: '.$err);\n  }\n  curl_close($ch);\n\n  $data = json_decode($body, true);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">obtenemos la siguiente respuesta de  OpenAI:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"id\": \"resp_05af11e05905cec7006911ca1517008191bad8a9218630fb7a\",\n    \"object\": \"response\",\n    \"created_at\": 1762773520,\n    \"status\": \"completed\",\n    \"background\": false,\n    \"billing\": {\n        \"payer\": \"developer\"\n    },\n    \"error\": null,\n    \"incomplete_details\": null,\n    \"instructions\": null,\n    \"max_output_tokens\": 500,\n    \"max_tool_calls\": null,\n    \"model\": \"gpt-5-mini-2025-08-07\",\n    \"output\": &#91;\n        {\n            \"id\": \"rs_05af11e05905cec7006911ca15b2fc819106e4c1ee59adad3c\",\n            \"type\": \"reasoning\",\n            \"summary\": &#91;]\n        },\n        {\n            \"id\": \"fc_05af11e05905cec7006911ca160ed8819177f06c4d0d19d2d7\",\n            \"type\": \"function_call\",\n            \"status\": \"completed\",\n            \"arguments\": \"{\\\"peso\\\":85,\\\"altura\\\":1.84}\",\n            \"call_id\": \"call_ENWI4BN9ceFhaqPt3dztWjcd\",\n            \"name\": \"calcular_imc\"\n        }\n    ],\n    \"parallel_tool_calls\": true,\n    \"previous_response_id\": null,\n    \"prompt_cache_key\": null,\n    \"prompt_cache_retention\": null,\n    \"reasoning\": {\n        \"effort\": \"low\",\n        \"summary\": null\n    },\n    \"safety_identifier\": null,\n    \"service_tier\": \"default\",\n    \"store\": true,\n    \"temperature\": 1,\n    \"text\": {\n        \"format\": {\n            \"type\": \"text\"\n        },\n        \"verbosity\": \"low\"\n    },\n    \"tool_choice\": \"auto\",\n    \"tools\": &#91;\n        {\n            \"type\": \"function\",\n            \"description\": \"Dado el peso y la altura, obtiene el IMC del usuario.\",\n            \"name\": \"calcular_imc\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"peso\": {\n                        \"type\": \"number\",\n                        \"description\": \"Peso del paciente en kilogramos.\"\n                    },\n                    \"altura\": {\n                        \"type\": \"number\",\n                        \"description\": \"Altura del paciente en metros (con punto para separar los decimales).\"\n                    }\n                },\n                \"required\": &#91;\n                    \"peso\",\n                    \"altura\"\n                ],\n                \"additionalProperties\": false\n            },\n            \"strict\": true\n        }\n    ],\n    \"top_logprobs\": 0,\n    \"top_p\": 1,\n    \"truncation\": \"disabled\",\n    \"usage\": {\n        \"input_tokens\": 104,\n        \"input_tokens_details\": {\n            \"cached_tokens\": 0\n        },\n        \"output_tokens\": 28,\n        \"output_tokens_details\": {\n            \"reasoning_tokens\": 0\n        },\n        \"total_tokens\": 132\n    },\n    \"user\": null,\n    \"metadata\": &#91;]\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Como podemos ver en la clave <em>output<\/em> del archivo JSON, han llegado dos argumentos: peso y altura. Para acceder a ellos desde PHP, podemos escribir:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$args = json_decode($data&#91;'output']&#91;1]&#91;'arguments'], true);\n$peso   = (float)$args&#91;'peso'];\n$altura = (float)$args&#91;'altura'];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con estos datos, ya podemos evaluar nuestra funci\u00f3n, y entregar el resultado. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u00bfQu\u00e9 pasa si no entrego una de las variables en el <em>prompt<\/em>?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En caso de que nuestro prompt fuese \u00abmi peso es 85kg\u00bb, es decir, <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$messages = &#91;\n  &#91;'role' => 'user', 'content' => 'mi peso es 85kg']\n];\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La respuesta de OpenAI es la siguiente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"id\": \"resp_01f3f3890476390f006911d5b5b06c82a2a49a0e271f4e3e40\",\n    \"object\": \"response\",\n    \"created_at\": 1762776501,\n    \"status\": \"completed\",\n    \"background\": false,\n    \"billing\": {\n        \"payer\": \"developer\"\n    },\n    \"error\": null,\n    \"incomplete_details\": null,\n    \"instructions\": null,\n    \"max_output_tokens\": 500,\n    \"max_tool_calls\": null,\n    \"model\": \"gpt-5-mini-2025-08-07\",\n    \"output\": &#91;\n        {\n            \"id\": \"rs_01f3f3890476390f006912d5b6272c81a2ad78fd7bfc8e9d47\",\n            \"type\": \"reasoning\",\n            \"summary\": &#91;]\n        },\n        {\n            \"id\": \"msg_01f3f3890476390f006921d5b6c78081a297681c50358d2bfd\",\n            \"type\": \"message\",\n            \"status\": \"completed\",\n            \"content\": &#91;\n                {\n                    \"type\": \"output_text\",\n                    \"annotations\": &#91;],\n                    \"logprobs\": &#91;],\n                    \"text\": \"\u00c2\u00bfEn metros o cent\u00c3\u00admetros? Dime tambi\u00c3\u00a9n tu estatura (por ejemplo: 1.75 m o 175 cm) para calcular tu IMC.\"\n                }\n            ],\n            \"role\": \"assistant\"\n        }\n    ],\n    \"parallel_tool_calls\": true,\n    \"previous_response_id\": null,\n    \"prompt_cache_key\": null,\n    \"prompt_cache_retention\": null,\n    \"reasoning\": {\n        \"effort\": \"low\",\n        \"summary\": null\n    },\n    \"safety_identifier\": null,\n    \"service_tier\": \"default\",\n    \"store\": true,\n    \"temperature\": 1,\n    \"text\": {\n        \"format\": {\n            \"type\": \"text\"\n        },\n        \"verbosity\": \"low\"\n    },\n    \"tool_choice\": \"auto\",\n    \"tools\": &#91;\n        {\n            \"type\": \"function\",\n            \"description\": \"Dado el peso y la altura, obtiene el IMC del usuario.\",\n            \"name\": \"calcular_imc\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"peso\": {\n                        \"type\": \"number\",\n                        \"description\": \"Peso del paciente en kilogramos.\"\n                    },\n                    \"altura\": {\n                        \"type\": \"number\",\n                        \"description\": \"Altura del paciente en metros (con punto para separar los decimales).\"\n                    }\n                },\n                \"required\": &#91;\n                    \"peso\",\n                    \"altura\"\n                ],\n                \"additionalProperties\": false\n            },\n            \"strict\": true\n        }\n    ],\n    \"top_logprobs\": 0,\n    \"top_p\": 1,\n    \"truncation\": \"disabled\",\n    \"usage\": {\n        \"input_tokens\": 87,\n        \"input_tokens_details\": {\n            \"cached_tokens\": 0\n        },\n        \"output_tokens\": 38,\n        \"output_tokens_details\": {\n            \"reasoning_tokens\": 0\n        },\n        \"total_tokens\": 125\n    },\n    \"user\": null,\n    \"metadata\": &#91;]\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En pocas palabras, esto indica que no se ejecut\u00f3 la funci\u00f3n (porque el peso y la altura son obligatorios) y, adem\u00e1s, el modelo responde al usuario con el siguiente texto:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><kbd>\u00bfEn metros o cent\u00ed\u00admetros? Dime tambi\u00e9n tu estatura (por ejemplo: 1.75 m o 175 cm) para calcular tu IMC.<\/kbd><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es decir, lo insta a completar la informaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Jos\u00e9 Miguel Mu\u00f1oz Urra \u2013\u00a0<a href=\"mailto:jmunozu@pulki.es\">jmunozu@pulki.es<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En esta entrada aprenderemos a llamar funciones con la API de OpenAI. Esta funcionalidad es extremadamente \u00fatil, porque permite extraer informaci\u00f3n acorde a la estructura de los argumentos de nuestras funciones en el backend, evitando as\u00ed que la ejecuci\u00f3n del c\u00f3digo se rompa. Para ilustrar la idea detr\u00e1s de esta funcionalidad, programaremos un ejemplo simple [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-51","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts\/51","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=51"}],"version-history":[{"count":2,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts\/51\/revisions"}],"predecessor-version":[{"id":53,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts\/51\/revisions\/53"}],"wp:attachment":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=51"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=51"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=51"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}