forked from OpenHands/OpenHands
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathagent.py
More file actions
180 lines (148 loc) Β· 6.14 KB
/
agent.py
File metadata and controls
180 lines (148 loc) Β· 6.14 KB
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from openhands.controller.state.state import State
from openhands.events.action import Action
from openhands.events.action.message import SystemMessageAction
from openhands.utils.prompt import PromptManager
from litellm import ChatCompletionToolParam
from openhands.core.config import AgentConfig
from openhands.core.exceptions import (
AgentAlreadyRegisteredError,
AgentNotRegisteredError,
)
from openhands.core.logger import openhands_logger as logger
from openhands.events.event import EventSource
from openhands.llm.llm import LLM
from openhands.runtime.plugins import PluginRequirement
class Agent(ABC):
DEPRECATED = False
"""
This abstract base class is an general interface for an agent dedicated to
executing a specific instruction and allowing human interaction with the
agent during execution.
It tracks the execution status and maintains a history of interactions.
"""
_registry: dict[str, type['Agent']] = {}
sandbox_plugins: list[PluginRequirement] = []
config_model: type[AgentConfig] = AgentConfig
"""Class field that specifies the config model to use for the agent. Subclasses may override with a derived config model if needed."""
def __init__(
self,
llm: LLM,
config: AgentConfig,
):
self.llm = llm
self.config = config
self._complete = False
self._prompt_manager: 'PromptManager' | None = None
self.mcp_tools: dict[str, ChatCompletionToolParam] = {}
self.tools: list = []
@property
def prompt_manager(self) -> 'PromptManager':
if self._prompt_manager is None:
raise ValueError(f'Prompt manager not initialized for agent {self.name}')
return self._prompt_manager
def get_system_message(self) -> 'SystemMessageAction | None':
"""
Returns a SystemMessageAction containing the system message and tools.
This will be added to the event stream as the first message.
Returns:
SystemMessageAction: The system message action with content and tools
None: If there was an error generating the system message
"""
# Import here to avoid circular imports
from openhands.events.action.message import SystemMessageAction
try:
if not self.prompt_manager:
logger.warning(
f'[{self.name}] Prompt manager not initialized before getting system message'
)
return None
system_message = self.prompt_manager.get_system_message()
# Get tools if available
tools = getattr(self, 'tools', None)
system_message_action = SystemMessageAction(
content=system_message, tools=tools, agent_class=self.name
)
# Set the source attribute
system_message_action._source = EventSource.AGENT # type: ignore
return system_message_action
except Exception as e:
logger.warning(f'[{self.name}] Failed to generate system message: {e}')
return None
@property
def complete(self) -> bool:
"""Indicates whether the current instruction execution is complete.
Returns:
- complete (bool): True if execution is complete; False otherwise.
"""
return self._complete
@abstractmethod
def step(self, state: 'State') -> 'Action':
"""Starts the execution of the assigned instruction. This method should
be implemented by subclasses to define the specific execution logic.
"""
pass
def reset(self) -> None:
"""Resets the agent's execution status."""
# Only reset the completion status, not the LLM metrics
self._complete = False
@property
def name(self) -> str:
return self.__class__.__name__
@classmethod
def register(cls, name: str, agent_cls: type['Agent']) -> None:
"""Registers an agent class in the registry.
Parameters:
- name (str): The name to register the class under.
- agent_cls (Type['Agent']): The class to register.
Raises:
- AgentAlreadyRegisteredError: If name already registered
"""
if name in cls._registry:
raise AgentAlreadyRegisteredError(name)
cls._registry[name] = agent_cls
@classmethod
def get_cls(cls, name: str) -> type['Agent']:
"""Retrieves an agent class from the registry.
Parameters:
- name (str): The name of the class to retrieve
Returns:
- agent_cls (Type['Agent']): The class registered under the specified name.
Raises:
- AgentNotRegisteredError: If name not registered
"""
if name not in cls._registry:
raise AgentNotRegisteredError(name)
return cls._registry[name]
@classmethod
def list_agents(cls) -> list[str]:
"""Retrieves the list of all agent names from the registry.
Raises:
- AgentNotRegisteredError: If no agent is registered
"""
if not bool(cls._registry):
raise AgentNotRegisteredError()
return list(cls._registry.keys())
def set_mcp_tools(self, mcp_tools: list[dict]) -> None:
"""Sets the list of MCP tools for the agent.
Args:
- mcp_tools (list[dict]): The list of MCP tools.
"""
logger.info(
f'Setting {len(mcp_tools)} MCP tools for agent {self.name}: {[tool["function"]["name"] for tool in mcp_tools]}'
)
for tool in mcp_tools:
_tool = ChatCompletionToolParam(**tool)
if _tool['function']['name'] in self.mcp_tools:
logger.warning(
f'Tool {_tool["function"]["name"]} already exists, skipping'
)
continue
self.mcp_tools[_tool['function']['name']] = _tool
self.tools.append(_tool)
logger.info(
f'Tools updated for agent {self.name}, total {len(self.tools)}: {[tool["function"]["name"] for tool in self.tools]}'
)