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:
cattrunca de forma impredecible,sedfalla fácilmente con caracteres especiales- Cada llamada a bash es un punto de seguridad sin restricciones, propenso a operaciones no autorizadas
- 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_TRABAJOcomo 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
limitepara 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_HERRAMIENTASsegú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
- 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_HERRAMIENTASyHERRAMIENTAS - El bucle central no necesita modificar ni una sola línea de código
- Mayor seguridad:
- El sandbox de rutas previene accesos no autorizados a archivos
- Herramientas especializadas son más controlables que bash, reduciendo riesgos de seguridad
- 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