Desarrollo de Agentes Claude Code: Sistema de Herramientas Modulares

Desarrollo de Agentes Claude Code: Sistema de Herramientas Modulares

Visión General

La implementación central de distribución de múltiples herramientas es una extensión directa del bucle básico del agente, con el concepto fundamental de:

"Para añadir una herramienta, solo se necesita añadir un manejador" – El bucle permanece sin cambios, la nueva herramienta se registra en el mapa de distribución.

Esta es la implementación central del mecanismo de distribución de herramientas del agente, que refleja el principio de diseño más importante de la capa de Arneses: extender las herramientas sin modificar el bucle central.

Problemas Principales Resueltos

Las herramientas de solo bash presentan numerosas limitaciones:

  1. cat trunca de forma impredecible, sed falla fácilmente con caracteres especiales
  2. Cada llamada a bash es un punto de seguridad sin restricciones, propenso a operaciones no autorizadas
  3. Extender herramientas requiere modificar el código del bucle central, violando el principio de abierto/cerrado

Concepto de Diseño Central

+--------+      +-------+      +------------------+
|  User  | ---> |  LLM  | ---> | Tool Dispatch    |
| prompt |      |       |      | {                |
+--------+      +---+---+      |   bash: ejecutar_bash |
                    ^           |   leer: ejecutar_leer  |
                    |           |   escribir: ejecutar_esc |
                    +-----------|   editar: ejecutar_ed  |
                    tool_result | }                |
                                +------------------+

Innovación clave: Reemplazar las llamadas a herramientas codificadas con un diccionario de distribución (dispatch map) para lograr una extensión modular de herramientas.

Análisis Detallado

1. Inicialización del Entorno

DIRECTORIO_TRABAJO = Path.cwd()
cliente = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODELO = os.environ["ID_MODELO"]

SISTEMA = f"Eres un agente de programación en {DIRECTORIO_TRABAJO}. Utiliza herramientas para resolver tareas. Actúa, no expliques."


  • Se añade la constante DIRECTORIO_TRABAJO como directorio raíz del área de trabajo, base del sandbox de rutas
  • El sistema de instrucciones cambia de "Usa bash" a "Usa herramientas", indicando explícitamente soporte para múltiples herramientas

2. Sandbox de Rutas Seguro ⭐ Mecanismo de Seguridad Central

def ruta_segura(p: str) -> Path:
    ruta = (DIRECTORIO_TRABAJO / p).resolve()
    if not ruta.is_relative_to(DIRECTORIO_TRABAJO):
        raise ValueError(f"La ruta escapa del espacio de trabajo: {p}")
    return ruta


Función: Evitar que el agente acceda a archivos fuera del directorio de trabajo

  • Convierte la ruta de entrada en una ruta absoluta
  • Verifica si la ruta está bajo DIRECTORIO_TRABAJO
  • Si la ruta escapa (por ejemplo ../etc/passwd) lanza un error directamente
  • Todas las herramientas de operación de archivos llaman primero a esta función para verificación de seguridad

3. Implementación de Herramientas

Se proporcionan 4 herramientas, cada una es una función de procesamiento independiente:

(1) Herramienta bash

Básicamente idéntica a la implementación base, pero con cwd fijo en DIRECTORIO_TRABAJO, mejorando la seguridad.

(2) Herramienta leer_archivo
def ejecutar_leer(ruta: str, limite: int = None) -> str:
    try:
        texto = ruta_segura(ruta).read_text()
        lineas = texto.splitlines()
        if limite and limite < len(lineas):
            lineas = lineas[:limite] + [f"... ({len(lineas) - limite} líneas más)"]
        return "\n".join(lineas)[:50000]
    except Exception as e:
        return f"Error: {e}"


Función: Leer contenido de archivo de forma segura

  • Soporta parámetro limite para restringir líneas leídas, evitando archivos grandes que saturen el contexto
  • Al exceder el límite de líneas, muestra el número de líneas restantes, más amigable
  • Manejo unificado de excepciones, los errores se devuelven directamente al modelo
(3) Herramienta escribir_archivo
def ejecutar_escribir(ruta: str, contenido: str) -> str:
    try:
        fp = ruta_segura(ruta)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(contenido)
        return f"Escribidos {len(contenido)} bytes en {ruta}"
    except Exception as e:
        return f"Error: {e}"


Función: Escribir en archivo

  • Crea automáticamente directorios padres si no existen
  • Devuelve el resultado de la escritura al modelo para confirmación
(4) Herramienta editar_archivo
def ejecutar_editar(ruta: str, texto_antiguo: str, texto_nuevo: str) -> str:
    try:
        fp = ruta_segura(ruta)
        contenido = fp.read_text()
        if texto_antiguo not in contenido:
            return f"Error: Texto no encontrado en {ruta}"
        fp.write_text(contenido.replace(texto_antiguo, texto_nuevo, 1))
        return f"Editado {ruta}"
    except Exception as e:
        return f"Error: {e}"


Función: Reemplazar contenido de archivo de forma precisa

  • Solo reemplaza la primera aparición del texto coincidente, evitando modificaciones accidentales
  • Si no encuentra coincidencia, devuelve un error claro, permitiendo al modelo ajustar el texto y reintentar

4. Diccionario de Distribución de Herramientas ⭐ Mecanismo de Extensión Central

MANEJADORES_HERRAMIENTAS = {
    "bash":       lambda **kw: ejecutar_bash(kw["comando"]),
    "leer_archivo":  lambda **kw: ejecutar_leer(kw["ruta"], kw.get("limite")),
    "escribir_archivo": lambda **kw: ejecutar_escribir(kw["ruta"], kw["contenido"]),
    "editar_archivo":  lambda **kw: ejecutar_editar(kw["ruta"], kw["texto_antiguo"], kw["texto_nuevo"]),
}


  • La clave es el nombre de la herramienta, el valor es la función de procesamiento correspondiente
  • Usar lambda unifica el paso de parámetros, adaptando las diferencias de parámetros entre herramientas
  • Para añadir una nueva herramienta, solo se necesita agregar una línea de mapeo aquí, sin modificar otro código

5. Arreglo de Definiciones de Herramientas

HERRAMIENTAS = [
    {"nombre": "bash", "descripcion": "Ejecutar un comando de shell.",
     "esquema_entrada": {"type": "object", "properties": {"comando": {"type": "string"}}, "required": ["comando"]}},
    {"nombre": "leer_archivo", "descripcion": "Leer contenido de archivo.",
     "esquema_entrada": {"type": "object", "properties": {"ruta": {"type": "string"}, "limite": {"type": "integer"}}, "required": ["ruta"]}},
    {"nombre": "escribir_archivo", "descripcion": "Escribir contenido en archivo.",
     "esquema_entrada": {"type": "object", "properties": {"ruta": {"type": "string"}, "contenido": {"type": "string"}}, "required": ["ruta", "contenido"]}},
    {"nombre": "editar_archivo", "descripcion": "Reemplazar texto exacto en archivo.",
     "esquema_entrada": {"type": "object", "properties": {"ruta": {"type": "string"}, "texto_antiguo": {"type": "string"}, "texto_nuevo": {"type": "string"}}, "required": ["ruta", "texto_antiguo", "texto_nuevo"]}},
]


  • Descripción de herramientas proporcionadas al LLM, siguiendo el formato de llamada de función de Anthropic
  • Cada herramienta contiene nombre, descripción funcional, estructura de parámetros (JSON Schema)
  • El LLM decide qué herramienta llamar y qué parámetros pasar según estas descripciones

6. Bucle Central del Agente

Casi idéntico al bucle base del agente, solo se modificó la parte de llamada a herramientas:

def bucle_agente(mensajes: list):
    while True:
        respuesta = cliente.messages.create(
            model=MODELO, system=SISTEMA, messages=mensajes,
            tools=HERRAMIENTAS, max_tokens=8000,
        )
        mensajes.append({"role": "assistant", "content": respuesta.content})
        if respuesta.stop_reason != "tool_use":
            return
        resultados = []
        for bloque in respuesta.content:
            if bloque.type == "tool_use":
                # Obtener función de procesamiento del diccionario de distribución, reemplazando llamada codificada
                manejador = MANEJADORES_HERRAMIENTAS.get(bloque.name)
                salida = manejador(**bloque.input) if manejador else f"Herramienta desconocida: {bloque.name}"
                print(f"> {bloque.name}: {salida[:200]}")
                resultados.append({"type": "tool_result", "tool_use_id": bloque.id, "content": salida})
        mensajes.append({"role": "user", "content": resultados})


Cambios clave:

  • Se eliminó la llamada codificada a ejecutar_bash
  • Se cambió para buscar la función de procesamiento en el diccionario MANEJADORES_HERRAMIENTAS según el nombre de la herramienta
  • Soporta mensajes de error para herramientas desconocidas, con mayor robustez
  • La lógica del bucle central permanece completamente invariable, implementando el principio de abierto/cerrado

7. Bucle Principal Interactivo

Idéntico a la implementación base, solo se modificó el indicador.

Comparación con la Versión Base

Componente Versión Base (s01) Versión Multi-herramienta (s02)
Número de herramientas 1 (solo bash) 4 (bash, leer, escribir, editar)
Método de llamada a herramienta Llamada codificada a bash MANEJADORES_HERRAMIENTAS diccionario de distribución
Seguridad de rutas Sin ruta_segura() protección de sandbox
Bucle del Agente Fijo Completamente invariable

Ventajas de Diseño

  1. Extremadamente extensible: Para añadir una nueva herramienta solo se necesitan dos pasos:
  • Escribir la función de procesamiento de la herramienta
  • Registrar en MANEJADORES_HERRAMIENTAS y HERRAMIENTAS
  • El bucle central no necesita modificar ni una sola línea de código
  1. Mayor seguridad:
  • El sandbox de rutas previene accesos no autorizados a archivos
  • Herramientas especializadas son más controlables que bash, reduciendo riesgos de seguridad
  1. Mayor estabilidad:
  • El bucle central, ya verificado, no necesita modificaciones, reduciendo probabilidades de errores
  • Las herramientas son independientes, modificar una no afecta otras funcionalidades

Ejemplo de Uso

Al iniciar, se pueden probar estas instrucciones:

python agentes/s02_uso_herramientas.py
s02 >> Leer el archivo requisitos.txt
> leer_archivo: [contenido del archivo]
s02 >> Crear un archivo llamado saludar.py con una función saludar(nombre)
> escribir_archivo: Escritos 50 bytes en saludar.py
s02 >> Editar saludar.py para añadir un docstring a la función
> editar_archivo: Editado saludar.py
s02 >> Leer saludar.py para verificar que la edición funcionó
> leer_archivo: [contenido del archivo modificado]


Aprendizaje Central

Esta versión establece el paradigma de diseño más importante de la capa de Arneses:

El bucle nunca cambia, solo cambian las herramientas y las reglas de distribución

Todas las extensiones de funcionalidad posteriores se basan en añadir más herramientas y mecanismos sobre esta base, manteniendo el bucle central siempre estable. Este concepto de diseño también impregna toda la arquitectura de Claude Code.

Este contenido se basa en el proyecto de código abierto learn-claude-code

Etiquetas: agentes-ia herramientas-modulares claudede-code desarrollo-ia seguridad-de-sistemas

Publicado el 5-30 21:34