Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge develop #14

Merged
merged 29 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1ba9396
Add ollama, support, memGPT services
lehcode Apr 4, 2024
0ca8af9
feat: Docker services
lehcode Apr 4, 2024
309df69
hotfix: Restore useTranslation()
lehcode Apr 10, 2024
751442b
hotfix: Frontend integration
lehcode Apr 11, 2024
94619eb
hotfix: Backend app service dependencies fix under Conda
lehcode Apr 12, 2024
6fe6b59
feat: Add API startup script
lehcode Apr 12, 2024
ffbad57
feat: Add FastAPI server and Vite dev server logging for debug and li…
lehcode Apr 12, 2024
187ca9d
chore: Cleanup after local rebase
lehcode Apr 12, 2024
a3d6c03
feat: Improve docker compose services integration
lehcode Apr 12, 2024
03a530a
hotfix: Frontend and API integration. Build improvements.
lehcode Apr 14, 2024
e826a5c
feat/poetry-build (#8)
lehcode Apr 14, 2024
0676e94
fix: fix some of the styling to more closely match figma (#927)
Sparkier Apr 12, 2024
dea68c4
Add Italian, Spanish and Português (#1017)
PierrunoYT Apr 12, 2024
a52a495
Add Azure configuration doc (#1035)
enyst Apr 12, 2024
9165ba1
Formatting AZURE_LLM_GUIDE (#1046)
enyst Apr 12, 2024
c1754bf
Feat add agent manager (#904)
iFurySt Apr 12, 2024
1e73863
simplified get (#962)
SmartManoj Apr 12, 2024
8fdc728
Response recognition for weak llms (#523)
namtacs Apr 12, 2024
48efce0
Traffic Control: Add new config MAX_CHARS (#1015)
li-boxuan Apr 12, 2024
ea2abcf
fix: print the wrong ssh port number (#1054)
iFurySt Apr 13, 2024
30c4969
fix(editor): ui enhancements and code refactor (#1069)
akhilvc10 Apr 13, 2024
043ee5a
Add new sandbox type - local (#1029)
foragerr Apr 14, 2024
a53d4af
Auto-close stale issues and PRs (#1032)
rbren Apr 14, 2024
5a8553d
Throw error if an illegal sandbox type is used (#1087)
yimothysu Apr 14, 2024
fb30ad3
Unify linter behaviour across CI and pre-commit-hook (#1071)
li-boxuan Apr 14, 2024
a5051cb
Revamp Exception handling (#1080)
li-boxuan Apr 14, 2024
c5af998
doc: Add supplementary notes for WSL2 users to Local LLM Guide (#1031)
FZFR Apr 14, 2024
784f7ab
added to sudo group (#1091)
SmartManoj Apr 14, 2024
cddc385
chore: Merge .dockerignore
lehcode Apr 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Revamp Exception handling (All-Hands-AI#1080)
* Revamp exception handling

* Agent controller: sleep 3 seconds if APIConnection error

* Fix AuthenticationError capture

* Revert unrelated style fixes

* Add type enforcement for action_from_dict call
  • Loading branch information
li-boxuan authored and lehcode committed Apr 14, 2024
commit a5051cb13d43953a5fb7b85139fc8116c4d4250e
5 changes: 3 additions & 2 deletions agenthub/monologue_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from opendevin.state import State
from opendevin.llm.llm import LLM
from opendevin.schema import ActionType, ObservationType
from opendevin.exceptions import AgentNoInstructionError

from opendevin.action import (
Action,
Expand Down Expand Up @@ -131,14 +132,14 @@ def _initialize(self, task: str):
- task (str): The initial goal statement provided by the user

Raises:
- ValueError: If task is not provided
- AgentNoInstructionError: If task is not provided
"""

if self._initialized:
return

if task is None or task == '':
raise ValueError('Instruction must be provided')
raise AgentNoInstructionError()
self.monologue = Monologue()
self.memory = LongTermMemory()

Expand Down
6 changes: 4 additions & 2 deletions agenthub/monologue_agent/utils/monologue.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import traceback

from opendevin.llm.llm import LLM
from opendevin.exceptions import AgentEventTypeError
import agenthub.monologue_agent.utils.json as json
import agenthub.monologue_agent.utils.prompts as prompts

Expand All @@ -24,10 +26,10 @@ def add_event(self, t: dict):
- t (dict): The thought that we want to add to memory

Raises:
- ValueError: If t is not a dict
- AgentEventTypeError: If t is not a dict
"""
if not isinstance(t, dict):
raise ValueError('Event must be a dictionary')
raise AgentEventTypeError()
self.thoughts.append(t)

def get_thoughts(self):
Expand Down
3 changes: 2 additions & 1 deletion agenthub/monologue_agent/utils/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from opendevin.observation import (
CmdOutputObservation,
)
from opendevin.exceptions import LLMOutputError

ACTION_PROMPT = """
You're a thoughtful robot. Your main task is this:
Expand Down Expand Up @@ -170,7 +171,7 @@ def rank(match):
try:
action_dict = json.loads(max(response_json_matches, key=rank)[0]) # Use the highest ranked response
except ValueError as e:
raise ValueError(
raise LLMOutputError(
"Output from the LLM isn't properly formatted. The model may be misconfigured."
) from e
if 'content' in action_dict:
Expand Down
2 changes: 2 additions & 0 deletions opendevin/action/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@


def action_from_dict(action: dict) -> Action:
if not isinstance(action, dict):
raise TypeError('action must be a dictionary')
action = action.copy()
if 'action' not in action:
raise KeyError(f"'action' key is not found in {action=}")
Expand Down
16 changes: 13 additions & 3 deletions opendevin/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from opendevin.action import Action
from opendevin.state import State
from opendevin.llm.llm import LLM
from opendevin.exceptions import AgentAlreadyRegisteredError, AgentNotRegisteredError


class Agent(ABC):
Expand Down Expand Up @@ -71,9 +72,12 @@ def register(cls, name: str, agent_cls: Type['Agent']):
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 ValueError(f"Agent class already registered under '{name}'.")
raise AgentAlreadyRegisteredError(name)
cls._registry[name] = agent_cls

@classmethod
Expand All @@ -86,16 +90,22 @@ def get_cls(cls, name: str) -> Type['Agent']:

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 ValueError(f"No agent class registered under '{name}'.")
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 ValueError('No agent class registered.')
raise AgentNotRegisteredError()
return list(cls._registry.keys())
29 changes: 20 additions & 9 deletions opendevin/controller/agent_controller.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import asyncio
import inspect
import traceback
import time
from typing import List, Callable, Literal, Mapping, Awaitable, Any, cast

from termcolor import colored
from litellm.exceptions import APIConnectionError
from openai import AuthenticationError

from opendevin import config
from opendevin.action import (
Expand All @@ -15,7 +18,7 @@
)
from opendevin.agent import Agent
from opendevin.logger import opendevin_logger as logger
from opendevin.exceptions import MaxCharsExceedError
from opendevin.exceptions import MaxCharsExceedError, AgentNoActionError
from opendevin.observation import Observation, AgentErrorObservation, NullObservation
from opendevin.plan import Plan
from opendevin.state import State
Expand Down Expand Up @@ -102,9 +105,11 @@ def update_state_after_step(self):

def add_history(self, action: Action, observation: Observation):
if not isinstance(action, Action):
raise ValueError('action must be an instance of Action')
raise TypeError(
f'action must be an instance of Action, got {type(action).__name__} instead')
if not isinstance(observation, Observation):
raise ValueError('observation must be an instance of Observation')
raise TypeError(
f'observation must be an instance of Observation, got {type(observation).__name__} instead')
self.state.history.append((action, observation))
self.state.updated_info.append((action, observation))

Expand Down Expand Up @@ -144,17 +149,23 @@ async def step(self, i: int):
try:
action = self.agent.step(self.state)
if action is None:
raise ValueError('Agent must return an action')
raise AgentNoActionError()
print_with_color(action, 'ACTION')
except Exception as e:
observation = AgentErrorObservation(str(e))
print_with_color(observation, 'ERROR')
traceback.print_exc()
# TODO Change to more robust error handling
if (
'The api_key client option must be set' in observation.content
or 'Incorrect API key provided:' in observation.content
):
if isinstance(e, APIConnectionError):
time.sleep(3)

# raise specific exceptions that need to be handled outside
# note: we are using AuthenticationError class from openai rather than
# litellm because:
# 1) litellm.exceptions.AuthenticationError is a subclass of openai.AuthenticationError
# 2) embeddings call, initiated by llama-index, has no wrapper for authentication
# errors. This means we have to catch individual authentication errors
# from different providers, and OpenAI is one of these.
if isinstance(e, (AuthenticationError, AgentNoActionError)):
raise
self.update_state_after_step()

Expand Down
56 changes: 56 additions & 0 deletions opendevin/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,59 @@ def __init__(self, num_of_chars=None, max_chars_limit=None):
else:
message = 'Number of characters exceeds MAX_CHARS limit'
super().__init__(message)


class AgentNoActionError(Exception):
def __init__(self, message='Agent must return an action'):
super().__init__(message)


class AgentNoInstructionError(Exception):
def __init__(self, message='Instruction must be provided'):
super().__init__(message)


class AgentEventTypeError(Exception):
def __init__(self, message='Event must be a dictionary'):
super().__init__(message)


class AgentAlreadyRegisteredError(Exception):
def __init__(self, name=None):
if name is not None:
message = f"Agent class already registered under '{name}'"
else:
message = 'Agent class already registered'
super().__init__(message)


class AgentNotRegisteredError(Exception):
def __init__(self, name=None):
if name is not None:
message = f"No agent class registered under '{name}'"
else:
message = 'No agent class registered'
super().__init__(message)


class LLMOutputError(Exception):
def __init__(self, message):
super().__init__(message)


class SandboxInvalidBackgroundCommandError(Exception):
def __init__(self, id=None):
if id is not None:
message = f'Invalid background command id {id}'
else:
message = 'Invalid background command id'
super().__init__(message)


class PlanInvalidStateError(Exception):
def __init__(self, state=None):
if state is not None:
message = f'Invalid state {state}'
else:
message = 'Invalid state'
super().__init__(message)
6 changes: 4 additions & 2 deletions opendevin/plan.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import List

from opendevin.logger import opendevin_logger as logger
from opendevin.exceptions import PlanInvalidStateError

OPEN_STATE = 'open'
COMPLETED_STATE = 'completed'
Expand Down Expand Up @@ -87,11 +89,11 @@ def set_state(self, state):
Args: state: The new state of the task.

Raises:
ValueError: If the provided state is invalid.
PlanInvalidStateError: If the provided state is invalid.
"""
if state not in STATES:
logger.error('Invalid state: %s', state)
raise ValueError('Invalid state:' + state)
raise PlanInvalidStateError(state)
self.state = state
if state == COMPLETED_STATE or state == ABANDONED_STATE or state == VERIFIED_STATE:
for subtask in self.subtasks:
Expand Down
5 changes: 3 additions & 2 deletions opendevin/sandbox/exec_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from opendevin.logger import opendevin_logger as logger
from opendevin.sandbox.sandbox import Sandbox, BackgroundCommand
from opendevin.schema import ConfigType
from opendevin.exceptions import SandboxInvalidBackgroundCommandError

InputType = namedtuple('InputType', ['content'])
OutputType = namedtuple('OutputType', ['content'])
Expand Down Expand Up @@ -107,7 +108,7 @@ def get_exec_cmd(self, cmd: str) -> List[str]:

def read_logs(self, id) -> str:
if id not in self.background_commands:
raise ValueError('Invalid background command id')
raise SandboxInvalidBackgroundCommandError()
bg_cmd = self.background_commands[id]
return bg_cmd.read_logs()

Expand Down Expand Up @@ -157,7 +158,7 @@ def get_pid(self, cmd):

def kill_background(self, id: int) -> BackgroundCommand:
if id not in self.background_commands:
raise ValueError('Invalid background command id')
raise SandboxInvalidBackgroundCommandError()
bg_cmd = self.background_commands[id]
if bg_cmd.pid is not None:
self.container.exec_run(
Expand Down
5 changes: 3 additions & 2 deletions opendevin/sandbox/ssh_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from opendevin.sandbox.sandbox import Sandbox, BackgroundCommand
from opendevin.schema import ConfigType
from opendevin.utils import find_available_tcp_port
from opendevin.exceptions import SandboxInvalidBackgroundCommandError

InputType = namedtuple('InputType', ['content'])
OutputType = namedtuple('OutputType', ['content'])
Expand Down Expand Up @@ -187,7 +188,7 @@ def get_exec_cmd(self, cmd: str) -> List[str]:

def read_logs(self, id) -> str:
if id not in self.background_commands:
raise ValueError('Invalid background command id')
raise SandboxInvalidBackgroundCommandError()
bg_cmd = self.background_commands[id]
return bg_cmd.read_logs()

Expand Down Expand Up @@ -238,7 +239,7 @@ def get_pid(self, cmd):

def kill_background(self, id: int) -> BackgroundCommand:
if id not in self.background_commands:
raise ValueError('Invalid background command id')
raise SandboxInvalidBackgroundCommandError()
bg_cmd = self.background_commands[id]
if bg_cmd.pid is not None:
self.container.exec_run(
Expand Down