Saltar al contenido
Nuevo

DeepSeek V4.1 Flash gratis por 7 días: 10M tokens al día tras cualquier recarga. Hasta el 30/11.

Cómo funciona

Agentes de IA y uso de herramientas: cómo un modelo llama a funciones

Cómo funciona el uso de herramientas: el modelo devuelve una llamada estructurada, tu programa la ejecuta y la aprobación decide qué se permite.

Principiante12 min de lecturaPor NotHoly

Un modelo de chat produce texto. Un agente es un sistema que le permite al modelo hacer algo más: lee una petición, decide que necesita información o una acción fuera de la conversación, pide a un programa anfitrión que haga ese trabajo y continúa con el resultado. Casi todos los agentes se apoyan en el mismo mecanismo, el uso de herramientas, también llamado llamada a funciones.

Este artículo explica ese mecanismo paso a paso, con los formatos de petición y respuesta que describe la documentación pública de las APIs. Verás por qué el programa anfitrión, y no el modelo, decide qué se ejecuta de verdad, por qué las acciones con efectos secundarios necesitan una decisión humana y qué llama la lista de OWASP "agencia excesiva".

No hace falta un laboratorio. Una pequeña simulación local, al final, ejecuta el ciclo con un modelo de mentira, para que veas el flujo de control sin enviar nada a una API.

De qué está hecho un agente

Un agente tiene tres partes.

El modelo recibe texto y devuelve texto. En el uso de herramientas, además puede producir una solicitud estructurada que dice "llama a esta función con estos argumentos".

El anfitrión es el programa que escribes o ejecutas. Envía las peticiones al modelo, mantiene la conversación, lee la salida y decide qué hacer con ella. El código real de cada herramienta vive en el anfitrión.

Las herramientas son funciones que el anfitrión acepta ejecutar en nombre del modelo: consultar un archivo, leer una base de datos, obtener una página, enviar un mensaje. Cada herramienta tiene un nombre, una descripción y un esquema para sus argumentos.

El modelo propone y el anfitrión dispone. Buena parte de las preguntas de seguridad de este artículo sale de esa división.

Por qué importa el ciclo

Un modelo sin herramientas solo responde con lo que tiene en el contexto. Si le pides la cotización de hoy, adivinará o dirá que no lo sabe. Con una herramienta de clima, puede pedir al anfitrión el valor, recibirlo y responder a partir de él.

El ciclo también permite varios pasos. El modelo puede buscar, leer un resultado, considerar que no basta, abrir un enlace que encontró y luego resumir. Cada paso es un viaje de ida y vuelta, y el anfitrión controla todos: cuántos se permiten, qué herramientas se ofrecen y qué pasa cuando una llamada falla.

Cómo funciona una llamada a herramienta

Todo intercambio con herramientas sigue la misma secuencia, sin importar la API.

  1. El anfitrión envía al modelo la conversación y una lista de definiciones de herramientas.
  2. El modelo responde con texto o devuelve una llamada estructurada, y marca la respuesta como una parada para una herramienta.
  3. El anfitrión lee la llamada, la valida y ejecuta la función correspondiente, si existe y la política lo permite.
  4. El anfitrión agrega el resultado a la conversación, refiriéndose a la llamada por su ID.
  5. El anfitrión envía de vuelta la conversación actualizada. El modelo llama a otra herramienta o escribe la respuesta final.

El ciclo termina cuando el modelo devuelve texto simple, cuando el anfitrión llega al límite de turnos o cuando el anfitrión lo detiene.

Las definiciones de herramienta son JSON Schema

Una definición de herramienta tiene un nombre, una descripción escrita para el modelo y un esquema de entrada. El esquema usa JSON Schema para declarar el tipo de cada argumento y cuáles son obligatorios. El modelo elige herramientas leyendo sus descripciones, así que una descripción vaga produce llamadas equivocadas.

El modelo devuelve una llamada estructurada

El modelo no ejecuta nada. Devuelve un dato que indica el nombre de la función y sus argumentos como un objeto JSON. Como la salida es estructurada, el anfitrión no tiene que adivinar un nombre de función a partir de texto libre.

Estructurado no significa correcto. El modelo puede nombrar una herramienta que no existe, o pasar un valor que cumple el esquema pero no tiene sentido en esa situación.

El anfitrión ejecuta la herramienta

Este paso es exclusivo del anfitrión. Busca el nombre en su propio registro. Si el nombre no está, responde con un error. Si está, aun así debe decidir si esa llamada, con esos argumentos y para ese usuario, puede ejecutarse. Solo después ejecuta el código.

El resultado vuelve

El resultado se envía como un mensaje que lleva el ID de la llamada original. El modelo lo lee como dato y decide el siguiente paso. Trata ese contenido como dato, no como instrucción. Una página web obtenida puede contener frases que parecen órdenes. Ese es el núcleo de la inyección de prompt, y el anfitrión no puede permitir que el contenido devuelto cambie sus reglas.

Formatos de petición y respuesta anotados

Los formatos siguientes siguen dos referencias públicas sobre uso de herramientas, consultadas el 8/11/2026. Los identificadores de modelo son marcadores. Los nombres de los campos son exactos.

Una petición con una herramienta

json
{
  "model": "<model-id>",
  "max_tokens": 1024,
  "tools": [
    {
      "name": "get_weather",
      "description": "Get the current weather for a given location.",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "City and state, e.g. San Francisco, CA"
          }
        },
        "required": ["location"]
      }
    }
  ],
  "messages": [
    { "role": "user", "content": "What's the weather in San Francisco?" }
  ]
}
  • tools es la lista completa de funciones que el modelo puede llamar. Lo que no esté en ella no existe para el modelo.
  • name es lo que el modelo devuelve, y con él el anfitrión elige la función.
  • description la lee el modelo, así que debe decir qué hace la herramienta y cuándo usarla.
  • input_schema es JSON Schema. required indica qué argumentos debe proporcionar el modelo.

Una respuesta que pide la herramienta

json
{
  "stop_reason": "tool_use",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_01A09q90qw90lq917835lq9",
      "name": "get_weather",
      "input": { "location": "San Francisco, CA" }
    }
  ]
}
  • stop_reason con el valor tool_use indica que el modelo espera un resultado. Un anfitrión que solo lee texto pasará por alto la llamada.
  • Un bloque con type igual a tool_use es la llamada estructurada. Su id debe devolverse.
  • input debe respetar el esquema. Valídalo igual, porque el modelo no es un motor de esquemas.

El mensaje siguiente con el resultado

json
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "15 degrees Celsius, partly cloudy"
    }
  ]
}
  • tool_use_id debe ser igual al id de la llamada. Si no coincide, el emparejamiento se rompe.
  • Mantén content corto. Los volcados grandes gastan contexto y pueden llevar texto inyectado.

El mismo ciclo en el formato de llamada a función

Una segunda referencia usa otros nombres para la misma idea. La herramienta tiene type igual a function, y el modelo devuelve un elemento function_call cuyo campo arguments es una cadena JSON que el anfitrión debe interpretar.

json
{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": { "type": "string" }
    },
    "required": ["location"],
    "additionalProperties": false
  },
  "strict": true
}
json
{
  "type": "function_call",
  "id": "fc_12345xyz",
  "call_id": "call_12345xyz",
  "name": "get_weather",
  "arguments": "{\"location\":\"Paris, France\"}"
}
json
{
  "type": "function_call_output",
  "call_id": "call_12345xyz",
  "output": "{\"temperature\":\"25\",\"unit\":\"C\"}"
}

Esa referencia lo dice directamente: cuando el modelo llama a una función, tu aplicación debe ejecutarla y devolver el resultado. Los nombres de los campos cambian entre endpoints y versiones, así que revisa antes de copiar un formato.

Por qué el anfitrión decide qué se ejecuta

Es tentador imaginar que el modelo "usa" una herramienta como una persona usa una calculadora. Esa imagen oculta el hecho clave. El modelo escribe un texto que describe una llamada. Es el anfitrión quien decide si ese texto se convierte en acción.

De eso se desprenden tres funciones del anfitrión. Es el único lugar donde se ejecuta código, así que una función de borrado solo corre cuando el anfitrión la ejecuta. Es también el único lugar donde puede vivir la política: una frase en el prompt que diga "no escribas archivos" es una petición, mientras que una herramienta de escritura que no existe o un rol de base de datos de solo lectura es un control. Y controla el alcance, porque decide qué herramientas se ofrecen en cada turno. Ofrecer menos herramientas es el control más barato que tienes.

La especificación de MCP describe la misma división en términos de protocolo. El host es la aplicación con LLM, los clientes son conectores dentro de ella, y los servidores exponen herramientas. La especificación dice que los hosts deben obtener el consentimiento explícito del usuario antes de invocar cualquier herramienta, y que las descripciones de comportamiento de las herramientas, como las anotaciones, deben tratarse como no confiables salvo que vengan de un servidor de confianza. La página de la especificación que revisé el 8/11/2026 apunta a una revisión fechada el 28/07/2026. Lee la revisión vigente antes de construir sobre ella.

Por qué importa la aprobación en los efectos secundarios

Algunas llamadas solo leen. Una búsqueda o una consulta no cambian nada fuera del anfitrión. Otras cambian algo: envían un mensaje, modifican un registro, gastan dinero o borran datos.

Una lectura errónea da una respuesta equivocada que alguien puede comprobar. Una escritura errónea puede ser permanente, puede afectar a otras personas y puede pasar desapercibida durante un tiempo. Por eso un patrón sensato es separar las herramientas por efecto y exigir una decisión explícita de una persona antes de cualquier llamada con efectos secundarios. El anfitrión muestra la acción en lenguaje simple, con los argumentos reales, y espera un sí.

Aprueba la llamada específica, no la sesión. Un sí dado una vez para "enviar correo" no debería cubrir en silencio los próximos cien mensajes con destinatarios distintos. La aprobación también falla cuando quien revisa no mira con atención, así que combínala con argumentos visibles, límites para lo que una llamada puede hacer y registros.

Agencia excesiva, según OWASP

La lista OWASP Top 10 para aplicaciones con LLM incluye la agencia excesiva como LLM06:2025. La entrada describe una vulnerabilidad que permite que se ejecuten acciones dañinas en respuesta a salidas del modelo inesperadas, ambiguas o manipuladas, sea cual sea la causa. La alucinación y la inyección de prompt, directa o indirecta, son disparadores habituales.

Nombra tres causas raíz: funcionalidad excesiva (herramientas que la tarea no necesita), permisos excesivos (herramientas que pueden más de lo que la tarea exige) y autonomía excesiva (acciones de alto impacto sin ninguna verificación). Las mitigaciones se deducen de ellas. Ofrece solo las herramientas necesarias. Prefiere herramientas acotadas a shells generales o lectores de URL arbitrarias. Aplica el acceso mínimo en el sistema de destino, por ejemplo con roles de base de datos o alcances OAuth reducidos. Ejecuta las acciones con la autorización del propio usuario. Exige aprobación humana para acciones de alto impacto. Aplica la autorización en el sistema de destino en lugar de confiar en que el modelo decida.

El registro, el monitoreo y el límite de tasa aparecen como medidas para limitar el daño. No impiden el problema.

Una simulación local del ciclo

Esta simulación no tiene proveedor, ni llamada de red, ni secreto. El "modelo" es un stub fijo. Devuelve una llamada a herramienta en el primer turno y texto cuando llega el resultado. Su función es mostrar dónde está el anfitrión. Reemplaza el stub por una llamada real a la API en tu entorno y conserva la lógica del anfitrión.

Guárdalo como loop_demo.py:

python
# Local sketch: no provider, no network. The "model" is a fixed stub that
# returns the same shape a provider returns for a tool call.
import json

def get_weather(location: str) -> str:
    return json.dumps({"location": location, "temp_c": 15, "sky": "partly cloudy"})

def delete_file(path: str) -> str:
    return json.dumps({"deleted": path})  # never reached in this demo

# The host decides what may run. The model only names a tool.
REGISTRY = {"get_weather": get_weather}          # delete_file is NOT registered
REQUIRES_APPROVAL = {"delete_file"}

def fake_model(messages):
    # Stub: first turn asks for weather, second turn answers with text.
    last = messages[-1]
    if last["role"] == "user" and isinstance(last["content"], str):
        return {"stop_reason": "tool_use",
                "content": [{"type": "tool_use", "id": "toolu_demo_1",
                             "name": "get_weather", "input": {"location": "Lisboa, PT"}}]}
    return {"stop_reason": "end_turn", "content": [{"type": "text", "text": "done"}]}

messages = [{"role": "user", "content": "Weather in Lisboa?"}]
for step in range(5):  # hard cap on turns
    reply = fake_model(messages)
    messages.append({"role": "assistant", "content": reply["content"]})
    if reply["stop_reason"] != "tool_use":
        print("final:", reply["content"][0]["text"])
        break
    results = []
    for block in reply["content"]:
        if block["type"] != "tool_use":
            continue
        name, args = block["name"], block["input"]
        if name not in REGISTRY:
            out, err = f"unknown tool {name}", True
        elif name in REQUIRES_APPROVAL:
            out, err = "approval required", True
        else:
            out, err = REGISTRY[name](**args), False
        print(f"host ran {name}({args}) -> {out}")
        results.append({"type": "tool_result", "tool_use_id": block["id"],
                        "content": out, "is_error": err})
    messages.append({"role": "user", "content": results})

Ejecútalo desde una carpeta que contenga solo este archivo:

bash
python -I loop_demo.py

La opción -I inicia Python en modo aislado. Ignora variables de entorno y deja la carpeta actual fuera de la ruta de importación, un hábito útil con código que no escribiste.

En una máquina Windows 11 con Git Bash y Python 3.12.10, la ejecución imprimió:

text
host ran get_weather({'location': 'Lisboa, PT'}) -> {"location": "Lisboa, PT", "temp_c": 15, "sky": "partly cloudy"}
final: done

El stub devolvió un stop reason tool_use y una llamada a get_weather. El anfitrión encontró el nombre en su registro, ejecutó la función y escribió un tool_result en la conversación. Después el stub respondió con texto y el ciclo terminó. delete_file nunca se ejecutó porque el anfitrión no lo registró. Para ver la rama de aprobación, haz que el stub pida delete_file y observa cómo el anfitrión lo rechaza.

Errores comunes

Ofrecer todas las herramientas del código. El modelo usa lo que ve, y también cualquier texto que lee. Empieza con una lista corta y agrega herramientas de una en una.

Escribir descripciones vagas. "Hace cosas con archivos" invita a llamadas equivocadas. Di qué lee o escribe la herramienta y cuándo no usarla.

Confiar en los argumentos porque pasaron el esquema. La validación de esquema confirma la forma, no el sentido. Una ruta bien formada puede apuntar fuera de la carpeta que querías permitir.

Confiar en el contenido devuelto. Un resultado de herramienta es un dato. Pegarlo en el siguiente prompt como si fuera una instrucción le da a una página web el poder de dirigir tu anfitrión.

Olvidar el límite de turnos. Un ciclo sin tope puede repetir una llamada que falla hasta costar dinero real.

Aprobar la sesión completa. Aprueba llamadas concretas, con los argumentos a la vista.

Registrar lo incorrecto. Guarda nombres de herramientas, decisiones y resultados con cuidado con los datos personales. Los prompts y respuestas completos en los registros pueden contener datos que no deberías conservar.

Cómo seguir

Construye un ciclo con una clave de API real y una herramienta de solo lectura, como una función que devuelve la hora actual. Inspecciona los cuerpos de la petición y la respuesta, y encuentra el bloque tool_use y el tool_result que devuelves.

Después agrega una herramienta que escriba en un archivo de borrador dentro de una carpeta tuya, con un paso de aprobación delante. Pide al modelo una ruta fuera de esa carpeta y comprueba que el anfitrión detiene la llamada.

Lee las secciones de la especificación de MCP sobre herramientas y seguridad, y la entrada de OWASP sobre agencia excesiva completa. Asocia cada mitigación con una línea de tu código.

Fuentes

Consultadas el 8/11/2026. Las páginas de documentación cambian, así que confirma la versión vigente antes de confiar en un nombre de campo.

Fuentes

  1. Tool use overview, public API documentation
  2. Function calling guide, public API documentation
  3. OWASP Top 10 for LLM Applications: LLM06:2025 Excessive Agency
  4. Model Context Protocol specification

Más en Agentes de IA para seguridad

Agentes de IA para seguridad

¿Qué es MCP? El Model Context Protocol explicado

MCP es el protocolo abierto que conecta apps de IA con herramientas y datos. Revisa roles, primitivas, transportes y seguridad en un laboratorio aislado.

Intermedio13 min de lectura