Cómo un agente usa herramientas (Bases) — Python (implementación completa)

Ejemplo runnable completo de tool calling con allowlist, ejecución de herramientas y bucle de agente.
En esta página
  1. Qué demuestra este ejemplo
  2. Estructura del proyecto
  3. Cómo ejecutar
  4. Código
  5. tools.py — herramientas que realmente se ejecutan
  6. executor.py — límite de seguridad y ejecución
  7. llm.py — llamada al modelo y descripción de herramientas disponibles
  8. main.py — bucle de agente (model → tool → model)
  9. requirements.txt
  10. Ejemplo de salida
  11. Por qué esto es un agente y no un solo model call
  12. Qué cambiar en este ejemplo
  13. Código completo en GitHub

Esta es una implementación completa del ejemplo del artículo Cómo un agente usa herramientas (Bases).

Si todavía no leíste el artículo, empieza por ahí. Aquí el foco está solo en el código.


Qué demuestra este ejemplo

  • Cómo LLM decide cuándo hay que llamar una herramienta
  • Cómo el sistema verifica si la herramienta está permitida (allowlist)
  • Cómo ejecutar una invocación de herramienta y devolver el resultado al modelo
  • Cómo el agente termina el bucle cuando los datos son suficientes

Estructura del proyecto

TEXT
examples/
└── foundations/
    └── tool-calling-basics/
        └── python/
            ├── main.py           # agent loop
            ├── llm.py            # model call + tool definitions
            ├── executor.py       # allowlist check + tool execution
            ├── tools.py          # tools (business logic)
            └── requirements.txt

Esta separación es práctica en un proyecto real: modelo, policy y ejecución de herramientas no están mezclados en un solo archivo.


Cómo ejecutar

1. Clona el repositorio y entra en la carpeta:

BASH
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd examples/foundations/tool-calling-basics/python

2. Instala dependencias:

BASH
pip install -r requirements.txt

3. Define la API key:

BASH
export OPENAI_API_KEY="sk-..."

4. Ejecuta el ejemplo:

BASH
python main.py

⚠️ Si te olvidas de definir la clave, el agente te lo dirá al instante con una pista de qué hacer.


Código

tools.py — herramientas que realmente se ejecutan

PYTHON
from typing import Any

USERS = {
    42: {"id": 42, "name": "Anna", "tier": "pro"},
    7: {"id": 7, "name": "Max", "tier": "free"},
}

BALANCES = {
    42: {"currency": "USD", "value": 128.40},
    7: {"currency": "USD", "value": 0.0},
}


def get_user_profile(user_id: int) -> dict[str, Any]:
    user = USERS.get(user_id)
    if not user:
        return {"error": f"user {user_id} not found"}
    return {"user": user}


def get_user_balance(user_id: int) -> dict[str, Any]:
    balance = BALANCES.get(user_id)
    if not balance:
        return {"error": f"balance for user {user_id} not found"}
    return {"balance": balance}

El modelo no tiene acceso directo a los diccionarios. Solo puede pedir llamar estas funciones.


executor.py — límite de seguridad y ejecución

PYTHON
import json
from typing import Any

from tools import get_user_balance, get_user_profile

TOOL_REGISTRY = {
    "get_user_profile": get_user_profile,
    "get_user_balance": get_user_balance,
}

ALLOWED_TOOLS = {"get_user_profile", "get_user_balance"}


def execute_tool_call(tool_name: str, arguments_json: str) -> dict[str, Any]:
    if tool_name not in ALLOWED_TOOLS:
        return {"error": f"tool '{tool_name}' is not allowed"}

    tool = TOOL_REGISTRY.get(tool_name)
    if tool is None:
        return {"error": f"tool '{tool_name}' not found"}

    try:
        args = json.loads(arguments_json or "{}")
    except json.JSONDecodeError:
        return {"error": "invalid JSON arguments"}

    try:
        result = tool(**args)
    except TypeError as exc:
        return {"error": f"invalid arguments: {exc}"}

    return {"tool": tool_name, "result": result}

La idea principal aquí: aunque el modelo pida algo raro, el sistema ejecuta solo lo que está explícitamente permitido.


llm.py — llamada al modelo y descripción de herramientas disponibles

PYTHON
import os
from openai import OpenAI

api_key = os.environ.get("OPENAI_API_KEY")

if not api_key:
    raise EnvironmentError(
        "OPENAI_API_KEY is not set.\n"
        "Run: export OPENAI_API_KEY='sk-...'"
    )

client = OpenAI(api_key=api_key)

SYSTEM_PROMPT = """
You are an AI support agent. When you need data, call the available tools.
Once you have enough information, give a short answer.
""".strip()

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_user_profile",
            "description": "Returns user profile by user_id",
            "parameters": {
                "type": "object",
                "properties": {"user_id": {"type": "integer"}},
                "required": ["user_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_user_balance",
            "description": "Returns user balance by user_id",
            "parameters": {
                "type": "object",
                "properties": {"user_id": {"type": "integer"}},
                "required": ["user_id"],
            },
        },
    },
]


def ask_model(messages: list[dict]):
    completion = client.chat.completions.create(
        model="gpt-4.1-mini",
        messages=[{"role": "system", "content": SYSTEM_PROMPT}, *messages],
        tools=TOOLS,
        tool_choice="auto",
    )
    return completion.choices[0].message

Las herramientas aquí están descritas como schema. El modelo ve esta lista y elige de ahí.


main.py — bucle de agente (model → tool → model)

PYTHON
import json

from executor import execute_tool_call
from llm import ask_model

MAX_STEPS = 6

TASK = "Prepare a short account summary for user_id=42: name, tier, and balance."


def to_assistant_message(message) -> dict:
    tool_calls = []
    for tc in message.tool_calls or []:
        tool_calls.append(
            {
                "id": tc.id,
                "type": "function",
                "function": {
                    "name": tc.function.name,
                    "arguments": tc.function.arguments,
                },
            }
        )
    return {
        "role": "assistant",
        "content": message.content or "",
        "tool_calls": tool_calls,
    }


def run():
    messages: list[dict] = [{"role": "user", "content": TASK}]

    for step in range(1, MAX_STEPS + 1):
        print(f"\n=== STEP {step} ===")
        assistant = ask_model(messages)
        messages.append(to_assistant_message(assistant))

        text = assistant.content or ""
        if text.strip():
            print(f"Assistant: {text.strip()}")

        tool_calls = assistant.tool_calls or []
        if not tool_calls:
            print("\nDone: model finished without a new tool call.")
            return

        for tc in tool_calls:
            print(f"Tool call: {tc.function.name}({tc.function.arguments})")
            execution = execute_tool_call(
                tool_name=tc.function.name,
                arguments_json=tc.function.arguments,
            )
            print(f"Tool result: {execution}")

            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": json.dumps(execution, ensure_ascii=False),
                }
            )

    print("\nStop: MAX_STEPS reached.")


if __name__ == "__main__":
    run()

Este es el bucle clásico: el modelo pide herramienta, el sistema ejecuta, el resultado vuelve al modelo, luego viene la siguiente decisión.


requirements.txt

TEXT
openai>=1.0.0

Ejemplo de salida

TEXT
=== STEP 1 ===
Tool call: get_user_profile({"user_id": 42})
Tool result: {'tool': 'get_user_profile', 'result': {'user': {'id': 42, 'name': 'Anna', 'tier': 'pro'}}}

=== STEP 2 ===
Tool call: get_user_balance({"user_id": 42})
Tool result: {'tool': 'get_user_balance', 'result': {'balance': {'currency': 'USD', 'value': 128.4}}}

=== STEP 3 ===
Assistant: User Anna is a pro tier member with a current balance of 128.4 USD.

Done: model finished without a new tool call.

Por qué esto es un agente y no un solo model call

Un model callAgente con tools
Trabaja solo con texto ya disponible
Puede pedir datos nuevos vía tool
Tiene un límite explícito de ejecución (allowlist)
Hace varios pasos hasta la respuesta final

Qué cambiar en este ejemplo

  • Bloquea get_user_balance en ALLOWED_TOOLS — ¿qué devolverá el agente al usuario?
  • Cambia user_id=42 por user_id=999 — ¿cómo manejará el agente el error?
  • Agrega una herramienta get_user_orders pero no la agregues en ALLOWED_TOOLS — ¿intentará el modelo llamarla?
  • Agrega un límite para la cantidad de tool calls separado de MAX_STEPS

Código completo en GitHub

En el repositorio está la versión completa de esta demo: con ALLOWED_TOOLS, bucle de pasos y manejo de errores. Si quieres ejecutarlo rápido o revisar el código línea por línea, abre el ejemplo completo.

Ver código completo en GitHub ↗
⏱️ 6 min de lecturaActualizado Mar, 2026Dificultad: ★☆☆
Integrado: control en producciónOnceOnly
Guardrails para agentes con tool-calling
Lleva este patrón a producción con gobernanza:
  • Presupuestos (pasos / topes de gasto)
  • Permisos de herramientas (allowlist / blocklist)
  • Kill switch y parada por incidente
  • Idempotencia y dedupe
  • Audit logs y trazabilidad
Mención integrada: OnceOnly es una capa de control para sistemas de agentes en producción.
Autor

Esta documentación está curada y mantenida por ingenieros que despliegan agentes de IA en producción.

El contenido es asistido por IA, con responsabilidad editorial humana sobre la exactitud, la claridad y la relevancia en producción.

Los patrones y las recomendaciones se basan en post-mortems, modos de fallo e incidentes operativos en sistemas desplegados, incluido durante el desarrollo y la operación de infraestructura de gobernanza para agentes en OnceOnly.