# Tool Registry Pattern - Documentación ## 📋 Descripción General El **Tool Registry** es un patrón de diseño que centraliza la gestión de herramientas (tools) del agente conversacional. Proporciona: - ✅ Registro centralizado de herramientas - ✅ Validación de herramientas - ✅ Gestión dinámica (añadir/remover tools) - ✅ Interfaz fluida con Builder - ✅ Fácil integración con el Agent - ✅ Conversión a formato LangChain --- ## 🏗️ Arquitectura ### Componentes Principales ``` BaseTool (ABC) ├── CalculatorTool ├── GreeterTool ├── WeatherTool └── TimeTool ToolRegistry └── Gestiona instancias de BaseTool ToolRegistryBuilder └── Interfaz fluida para crear registros Agent └── Integración con ToolRegistry ``` --- ## 📝 Uso Básico ### 1. Crear un Registry Básico ```python from src.naliiabot.bot.tools.tool_registry import ToolRegistry from src.naliiabot.bot.tools.tools import CalculatorTool, GreeterTool # Crear el registro registry = ToolRegistry() # Registrar herramientas registry.register(CalculatorTool()) registry.register(GreeterTool()) # Verificar print(f"Tools registradas: {len(registry)}") # Output: 2 ``` ### 2. Usar el Builder Pattern ```python from src.naliiabot.bot.tools.tool_registry import ToolRegistryBuilder registry = (ToolRegistryBuilder() .add(CalculatorTool()) .add(GreeterTool()) .add(WeatherTool()) .build()) ``` ### 3. Integrar con el Agent ```python from src.naliiabot.bot.agent.agent import Agent, AgentConfig registry = (ToolRegistryBuilder() .add(CalculatorTool()) .add(GreeterTool()) .build()) config = AgentConfig( system_prompt="You are a helpful assistant", max_iterations=5 ) agent = Agent(model=your_model, config=config, tools=registry) ``` --- ## 🔧 API del ToolRegistry ### Métodos Principales #### `register(tool: BaseTool) -> None` Registra una nueva herramienta. ```python registry.register(CalculatorTool()) ``` #### `register_multiple(tools: List[BaseTool]) -> None` Registra múltiples herramientas de una vez. ```python registry.register_multiple([ CalculatorTool(), GreeterTool(), WeatherTool() ]) ``` #### `unregister(tool_name: str) -> None` Desregistra una herramienta. ```python registry.unregister("calculator") ``` #### `get_tool(tool_name: str) -> Optional[BaseTool]` Obtiene una herramienta específica. ```python calculator = registry.get_tool("calculator") result = calculator.invoke(a=5, b=3, operation="add") ``` #### `get_all_tools() -> List[BaseTool]` Obtiene todas las herramientas registradas. ```python all_tools = registry.get_all_tools() for tool in all_tools: print(f"- {tool.name}") ``` #### `get_langchain_tools() -> List[Tool]` Convierte a formato LangChain Tool. ```python langchain_tools = registry.get_langchain_tools() ``` #### `has_tool(tool_name: str) -> bool` Verifica si una herramienta existe. ```python if registry.has_tool("calculator"): print("Calculator disponible") ``` #### `list_tools() -> Dict[str, str]` Lista todas las herramientas con descripción. ```python tools_info = registry.list_tools() # {"calculator": "Performs math operations", ...} ``` #### `clear() -> None` Limpia el registro. ```python registry.clear() ``` --- ## 🛠️ Crear una Nueva Tool ### Paso 1: Extender BaseTool ```python from src.naliiabot.bot.tools.tool_registry import BaseTool from typing import Any, Dict class MiHerramienta(BaseTool): @property def name(self) -> str: return "mi_herramienta" @property def description(self) -> str: return "Descripción de qué hace mi herramienta" @property def args_schema(self) -> Dict[str, Any]: return { "type": "object", "properties": { "param1": { "type": "string", "description": "Parámetro 1" } }, "required": ["param1"] } def invoke(self, **kwargs) -> str: param1 = kwargs.get("param1") # Implementar lógica return f"Resultado: {param1}" ``` ### Paso 2: Registrar ```python registry = ToolRegistry() registry.register(MiHerramienta()) ``` --- ## 📊 Herramientas Incluidas ### CalculatorTool Realiza operaciones matemáticas básicas. ```python tool = CalculatorTool() result = tool.invoke(a=10, b=5, operation="add") # "Result: 10 add 5 = 15" ``` Operaciones soportadas: - `add` - Suma - `subtract` - Resta - `multiply` - Multiplicación - `divide` - División ### GreeterTool Proporciona saludos personalizados. ```python tool = GreeterTool() result = tool.invoke(name="Alice", tone="formal") ``` Tonos disponibles: - `formal` - Saludo formal - `casual` - Saludo casual - `friendly` - Saludo amigable ### WeatherTool Obtiene información del clima. ```python tool = WeatherTool() result = tool.invoke(location="Madrid", units="celsius") ``` ### TimeTool Obtiene la hora actual. ```python tool = TimeTool() result = tool.invoke(format="24h", timezone="UTC") ``` --- ## 🧪 Testing ### Tests Disponibles ```bash pytest tests/test_tool_registry.py -v ``` Categorías de tests: 1. **TestBaseTool** - Pruebas de herramientas individuales 2. **TestToolRegistry** - Pruebas del registro 3. **TestToolRegistryBuilder** - Pruebas del builder 4. **TestAgentWithRegistry** - Integración con Agent 5. **TestPracticalUseCases** - Casos de uso prácticos ### Escribir un Test ```python def test_mi_herramienta(): """Prueba mi herramienta.""" registry = ToolRegistry() registry.register(MiHerramienta()) tool = registry.get_tool("mi_herramienta") result = tool.invoke(param1="test") assert "test" in result ``` --- ## 🔄 Casos de Uso ### Caso 1: Agente de Servicio al Cliente ```python customer_service_registry = (ToolRegistryBuilder() .add(GreeterTool()) .add(WeatherTool()) .add(TimeTool()) .build()) ``` ### Caso 2: Agente Matemático ```python math_agent_registry = (ToolRegistryBuilder() .add(CalculatorTool()) .build()) ``` ### Caso 3: Agente Multipropósito ```python general_registry = (ToolRegistryBuilder() .add_multiple([ CalculatorTool(), GreeterTool(), WeatherTool(), TimeTool() ]) .build()) ``` ### Caso 4: Registro Dinámico ```python registry = ToolRegistry() # Añadir basado en configuración if config.enable_calculator: registry.register(CalculatorTool()) if config.enable_greeting: registry.register(GreeterTool()) ``` --- ## ✨ Ventajas del Patrón ### 1. **Centralización** Todas las tools en un lugar. ### 2. **Extensibilidad** Fácil añadir nuevas tools sin modificar el Agent. ### 3. **Validación** Valida que las tools sean de tipo correcto. ### 4. **Mantenibilidad** Código limpio y organizado. ### 5. **Testing** Fácil de testear y mockear. ### 6. **Reutilización** Registros pueden compartirse entre agentes. --- ## 🚀 Ejemplos Completos Ver `examples/tool_registry_examples.py` para: - Ejemplo 1: Registry Básico - Ejemplo 2: Builder Pattern - Ejemplo 3: Integración con Agent - Ejemplo 4: Registración Dinámica - Ejemplo 5: Manejo de Errores - Ejemplo 6: Ejecución de Herramientas - Ejemplo 7: Formato LangChain ```bash python examples/tool_registry_examples.py ``` --- ## 🎯 Mejores Prácticas ### 1. Nombres Descriptivos ```python # ✅ Bien tool.name = "get_weather" tool.name = "calculate_math" # ❌ Evitar tool.name = "tool1" tool.name = "t" ``` ### 2. Descripciones Claras ```python # ✅ Bien description = "Gets weather info for a location. Returns temperature, humidity, conditions." # ❌ Evitar description = "Gets weather" ``` ### 3. Usar Builder para Registros Complejos ```python # ✅ Bien registry = (ToolRegistryBuilder() .add(Tool1()) .add(Tool2()) .build()) # ❌ Evitar registry = ToolRegistry() registry.register(Tool1()) registry.register(Tool2()) ``` ### 4. Validar Argumentos ```python # ✅ Bien def invoke(self, **kwargs) -> str: a = kwargs.get("a") if a is None: return "Error: Missing parameter 'a'" return str(a * 2) ``` --- ## 📚 Referencias - [BaseTool](src/naliiabot/bot/tools/tool_registry.py#L8) - [ToolRegistry](src/naliiabot/bot/tools/tool_registry.py#L60) - [ToolRegistryBuilder](src/naliiabot/bot/tools/tool_registry.py#L190) - [Herramientas](src/naliiabot/bot/tools/tools.py) - [Tests](tests/test_tool_registry.py) - [Ejemplos](examples/tool_registry_examples.py) --- ## ❓ FAQ **P: ¿Puedo cambiar el nombre de una herramienta?** R: No directamente. Desregistra con el nombre antiguo y registra una nueva instancia. **P: ¿Qué pasa si dos tools tienen el mismo nombre?** R: Se lanza `ValueError`. Cada tool debe tener un nombre único. **P: ¿Puedo heredar de BaseTool múltiples veces?** R: No es recomendado. Usa composición en su lugar. **P: ¿Cómo convertir todo a formato LangChain?** R: Usa `registry.get_langchain_tools()`. --- ## 📞 Soporte Para reportar bugs o sugerencias, abre un issue en el repositorio.