Spaces:
Sleeping
Sleeping
| """Registro de herramientas para agentes de IA. | |
| Provee un sistema de definición, validación y ejecución de herramientas | |
| compatible con los formatos de OpenAI y Anthropic. Incluye herramientas | |
| de ejemplo para búsqueda, fecha/hora y cálculo seguro. | |
| """ | |
| from __future__ import annotations | |
| import ast | |
| import datetime | |
| import logging | |
| import operator | |
| from dataclasses import dataclass | |
| from typing import Any, Callable | |
| logger = logging.getLogger("orchestration.tools") | |
| # Operadores permitidos para la calculadora segura | |
| _ALLOWED_OPERATORS = { | |
| ast.Add: operator.add, | |
| ast.Sub: operator.sub, | |
| ast.Mult: operator.mul, | |
| ast.Div: operator.truediv, | |
| ast.FloorDiv: operator.floordiv, | |
| ast.Mod: operator.mod, | |
| ast.Pow: operator.pow, | |
| ast.USub: operator.neg, | |
| ast.UAdd: operator.pos, | |
| } | |
| _JSON_TYPE_MAP: dict[str, type | tuple[type, ...]] = { | |
| "string": str, | |
| "integer": int, | |
| "number": (int, float), | |
| "boolean": bool, | |
| "array": list, | |
| "object": dict, | |
| } | |
| class ToolDefinition: | |
| """Definición de una herramienta para agentes. | |
| Args: | |
| name: Nombre único de la herramienta. | |
| description: Descripción de lo que hace la herramienta. | |
| parameters: Esquema JSON Schema de los parámetros. | |
| function: Función que implementa la herramienta. | |
| requires_confirmation: Si requiere confirmación del usuario. | |
| timeout_seconds: Tiempo máximo de ejecución. | |
| """ | |
| name: str | |
| description: str | |
| parameters: dict | |
| function: Callable | |
| requires_confirmation: bool = False | |
| timeout_seconds: float = 30.0 | |
| def validate_params(self, params: dict) -> tuple[bool, str]: | |
| """Valida parámetros contra el JSON Schema (validación manual). | |
| Args: | |
| params: Diccionario de parámetros a validar. | |
| Returns: | |
| Tupla ``(válido, mensaje_error)``. Si es válido, el mensaje | |
| es una cadena vacía. | |
| """ | |
| schema = self.parameters | |
| # Verificar campos requeridos | |
| required = schema.get("required", []) | |
| for field_name in required: | |
| if field_name not in params: | |
| return False, f"Missing required parameter: '{field_name}'" | |
| # Verificar tipos y enums | |
| properties = schema.get("properties", {}) | |
| for param_name, value in params.items(): | |
| if param_name not in properties: | |
| continue | |
| prop_schema = properties[param_name] | |
| # Verificar tipo | |
| expected_type_str = prop_schema.get("type") | |
| if expected_type_str and expected_type_str in _JSON_TYPE_MAP: | |
| expected_type = _JSON_TYPE_MAP[expected_type_str] | |
| # bool es subclase de int en Python, tratar como caso especial | |
| if expected_type_str == "integer" and isinstance(value, bool): | |
| return ( | |
| False, | |
| f"Parameter '{param_name}' expected type " | |
| f"'{expected_type_str}', got 'boolean'", | |
| ) | |
| if not isinstance(value, expected_type): | |
| actual = type(value).__name__ | |
| return ( | |
| False, | |
| f"Parameter '{param_name}' expected type " | |
| f"'{expected_type_str}', got '{actual}'", | |
| ) | |
| # Verificar enum | |
| enum_values = prop_schema.get("enum") | |
| if enum_values is not None and value not in enum_values: | |
| return ( | |
| False, | |
| f"Parameter '{param_name}' must be one of {enum_values}, " | |
| f"got '{value}'", | |
| ) | |
| return True, "" | |
| def execute(self, params: dict) -> str: | |
| """Ejecuta la herramienta con los parámetros dados. | |
| Args: | |
| params: Diccionario de parámetros. | |
| Returns: | |
| Resultado como cadena, o mensaje de error. | |
| """ | |
| valid, error_msg = self.validate_params(params) | |
| if not valid: | |
| return f"Validation error: {error_msg}" | |
| try: | |
| result = self.function(**params) | |
| return str(result) | |
| except Exception as exc: | |
| return f"Execution error: {type(exc).__name__}: {exc}" | |
| class ToolRegistry: | |
| """Registro centralizado de herramientas disponibles para agentes. | |
| Permite registrar, consultar y ejecutar herramientas, y exportar | |
| sus definiciones en formatos compatibles con OpenAI y Anthropic. | |
| """ | |
| def __init__(self) -> None: | |
| self._tools: dict[str, ToolDefinition] = {} | |
| def register(self, tool: ToolDefinition) -> None: | |
| """Registra una herramienta. | |
| Raises: | |
| ValueError: Si ya existe una herramienta con el mismo nombre. | |
| """ | |
| if tool.name in self._tools: | |
| raise ValueError(f"Tool '{tool.name}' is already registered") | |
| self._tools[tool.name] = tool | |
| logger.info("Registered tool: %s", tool.name) | |
| def register_function( | |
| self, | |
| name: str, | |
| description: str, | |
| parameters: dict, | |
| function: Callable, | |
| **kwargs: Any, | |
| ) -> None: | |
| """Atajo para registrar una función como herramienta. | |
| Args: | |
| name: Nombre de la herramienta. | |
| description: Descripción. | |
| parameters: JSON Schema de parámetros. | |
| function: Función implementadora. | |
| **kwargs: Parámetros extra para ``ToolDefinition``. | |
| """ | |
| tool = ToolDefinition( | |
| name=name, | |
| description=description, | |
| parameters=parameters, | |
| function=function, | |
| **kwargs, | |
| ) | |
| self.register(tool) | |
| def get(self, name: str) -> ToolDefinition: | |
| """Obtiene una herramienta por nombre. | |
| Raises: | |
| KeyError: Si la herramienta no existe. | |
| """ | |
| if name not in self._tools: | |
| raise KeyError( | |
| f"Tool '{name}' not found. " | |
| f"Available: {list(self._tools.keys())}" | |
| ) | |
| return self._tools[name] | |
| def remove(self, name: str) -> None: | |
| """Elimina una herramienta del registro. | |
| Raises: | |
| KeyError: Si la herramienta no existe. | |
| """ | |
| if name not in self._tools: | |
| raise KeyError(f"Tool '{name}' not found") | |
| del self._tools[name] | |
| logger.info("Removed tool: %s", name) | |
| def list_tools(self) -> list[str]: | |
| """Retorna la lista de nombres de herramientas registradas.""" | |
| return list(self._tools.keys()) | |
| def to_openai_format(self) -> list[dict]: | |
| """Exporta las herramientas en formato OpenAI function calling.""" | |
| return [ | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": tool.name, | |
| "description": tool.description, | |
| "parameters": tool.parameters, | |
| }, | |
| } | |
| for tool in self._tools.values() | |
| ] | |
| def to_anthropic_format(self) -> list[dict]: | |
| """Exporta las herramientas en formato Anthropic tool use.""" | |
| return [ | |
| { | |
| "name": tool.name, | |
| "description": tool.description, | |
| "input_schema": tool.parameters, | |
| } | |
| for tool in self._tools.values() | |
| ] | |
| def execute_tool(self, name: str, params: dict) -> str: | |
| """Ejecuta una herramienta por nombre con los parámetros dados. | |
| Args: | |
| name: Nombre de la herramienta. | |
| params: Diccionario de parámetros. | |
| Returns: | |
| Resultado como cadena, o mensaje de error. | |
| """ | |
| try: | |
| tool = self.get(name) | |
| return tool.execute(params) | |
| except Exception as exc: | |
| return f"Error: {type(exc).__name__}: {exc}" | |
| # --------------------------------------------------------------------------- | |
| # Herramientas de ejemplo | |
| # --------------------------------------------------------------------------- | |
| def search_documents(query: str, top_k: int = 5) -> str: | |
| """Busca documentos relevantes (mock). | |
| Args: | |
| query: Consulta de búsqueda. | |
| top_k: Número de resultados a retornar. | |
| Returns: | |
| Texto con resultados placeholder. | |
| """ | |
| results = [ | |
| f"Document {i + 1}: Result for '{query}' (relevance: {0.9 - i * 0.1:.1f})" | |
| for i in range(top_k) | |
| ] | |
| return "\n".join(results) | |
| def get_current_datetime() -> str: | |
| """Retorna la fecha y hora actual en formato ISO.""" | |
| return datetime.datetime.now().isoformat() | |
| def _safe_eval_node(node: ast.AST) -> int | float: | |
| """Evalúa un nodo AST aritmético de forma segura.""" | |
| if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): | |
| return node.value | |
| if isinstance(node, ast.BinOp): | |
| op_type = type(node.op) | |
| if op_type not in _ALLOWED_OPERATORS: | |
| raise ValueError(f"Unsupported operator: {op_type.__name__}") | |
| left = _safe_eval_node(node.left) | |
| right = _safe_eval_node(node.right) | |
| return _ALLOWED_OPERATORS[op_type](left, right) | |
| if isinstance(node, ast.UnaryOp): | |
| op_type = type(node.op) | |
| if op_type not in _ALLOWED_OPERATORS: | |
| raise ValueError(f"Unsupported operator: {op_type.__name__}") | |
| operand = _safe_eval_node(node.operand) | |
| return _ALLOWED_OPERATORS[op_type](operand) | |
| raise ValueError(f"Unsupported expression node: {type(node).__name__}") | |
| def calculate(expression: str) -> str: | |
| """Evalúa una expresión aritmética de forma segura usando AST. | |
| Solo permite constantes numéricas y operadores aritméticos básicos. | |
| Nunca usa ``eval()`` directamente. | |
| Args: | |
| expression: Expresión aritmética (e.g., ``"2 + 3 * 4"``). | |
| Returns: | |
| Resultado como cadena. | |
| Raises: | |
| ValueError: Si la expresión contiene elementos no permitidos. | |
| """ | |
| tree = ast.parse(expression, mode="eval") | |
| result = _safe_eval_node(tree.body) | |
| return str(result) | |
| if __name__ == "__main__": | |
| # Demo de herramientas | |
| registry = ToolRegistry() | |
| registry.register( | |
| ToolDefinition( | |
| name="search", | |
| description="Search documents", | |
| parameters={ | |
| "type": "object", | |
| "properties": { | |
| "query": {"type": "string", "description": "Search query"}, | |
| "top_k": {"type": "integer", "description": "Results count"}, | |
| }, | |
| "required": ["query"], | |
| }, | |
| function=search_documents, | |
| ) | |
| ) | |
| registry.register( | |
| ToolDefinition( | |
| name="datetime", | |
| description="Get current date and time", | |
| parameters={"type": "object", "properties": {}}, | |
| function=get_current_datetime, | |
| ) | |
| ) | |
| registry.register( | |
| ToolDefinition( | |
| name="calculate", | |
| description="Evaluate arithmetic expression", | |
| parameters={ | |
| "type": "object", | |
| "properties": { | |
| "expression": { | |
| "type": "string", | |
| "description": "Arithmetic expression", | |
| }, | |
| }, | |
| "required": ["expression"], | |
| }, | |
| function=calculate, | |
| ) | |
| ) | |
| print("Tools:", registry.list_tools()) | |
| print("\nOpenAI format:", registry.to_openai_format()) | |
| print("\nSearch result:", registry.execute_tool("search", {"query": "AI agents"})) | |
| print("\nDatetime:", registry.execute_tool("datetime", {})) | |
| print("\nCalculate:", registry.execute_tool("calculate", {"expression": "2 + 3 * 4"})) | |