File size: 3,359 Bytes
0d3f7cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""Tool executor for agent actions."""

from __future__ import annotations

import logging
import time
from typing import Any

from hermes.core.exceptions import ToolError, ToolNotFoundError
from hermes.core.types import ToolCall, ToolResult
from hermes.tools.base.registry import tool_registry

logger = logging.getLogger(__name__)


class ToolExecutor:
    """Executes tool calls with error handling and retries."""

    def __init__(self, allowed_tools: list[str] | None = None) -> None:
        self.allowed_tools = allowed_tools

    async def execute(self, tool_call: ToolCall) -> ToolResult:
        """Execute a tool call and return the result."""
        start_time = time.monotonic()

        try:
            tool = tool_registry.get(tool_call.tool_name)
            if tool is None:
                raise ToolNotFoundError(tool_call.tool_name)

            if self.allowed_tools and tool_call.tool_name not in self.allowed_tools:
                raise ToolError(
                    f"Tool '{tool_call.tool_name}' not in allowed tools",
                    {"allowed": self.allowed_tools},
                )

            output = await tool.execute(**tool_call.arguments)
            execution_time = (time.monotonic() - start_time) * 1000

            from hermes.observability.metrics import metrics as m
            m.record_tool_call(tool_call.tool_name, True, execution_time)

            return ToolResult(
                tool_call_id=tool_call.id,
                tool_name=tool_call.tool_name,
                success=True,
                output=output,
                execution_time_ms=execution_time,
            )

        except (ToolNotFoundError, ToolError) as e:
            execution_time = (time.monotonic() - start_time) * 1000
            logger.error(f"Tool error: {e}")
            from hermes.observability.metrics import metrics as m
            m.record_tool_call(tool_call.tool_name, False, execution_time)
            return ToolResult(
                tool_call_id=tool_call.id,
                tool_name=tool_call.tool_name,
                success=False,
                error=str(e),
                execution_time_ms=execution_time,
            )

        except TimeoutError:
            execution_time = (time.monotonic() - start_time) * 1000
            return ToolResult(
                tool_call_id=tool_call.id,
                tool_name=tool_call.tool_name,
                success=False,
                error=f"Tool '{tool_call.tool_name}' timed out",
                execution_time_ms=execution_time,
            )

        except Exception as e:
            execution_time = (time.monotonic() - start_time) * 1000
            logger.exception(f"Unexpected error executing tool: {e}")
            return ToolResult(
                tool_call_id=tool_call.id,
                tool_name=tool_call.tool_name,
                success=False,
                error=f"Unexpected error: {str(e)}",
                execution_time_ms=execution_time,
            )

    async def execute_raw(self, tool_name: str, arguments: dict[str, Any]) -> ToolResult:
        """Execute a tool by name with arguments."""
        tool_call = ToolCall(tool_name=tool_name, arguments=arguments)
        return await self.execute(tool_call)