mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Traffic Control: Add new config MAX_CHARS (#1015)
* Add new config MAX_CHARS * Fix mypy linting issues
This commit is contained in:
@@ -24,7 +24,7 @@ Apart from the standard bash commands, you can also use the following special co
|
||||
{COMMAND_DOCS}
|
||||
"""
|
||||
if COMMAND_DOCS is not None
|
||||
else ""
|
||||
else ''
|
||||
)
|
||||
SYSTEM_MESSAGE = f"""You are a helpful assistant. You will be provided access (as root) to a bash shell to complete user-provided tasks.
|
||||
You will be able to execute commands in the bash shell, interact with the file system, install packages, and receive the output of your commands.
|
||||
@@ -46,27 +46,29 @@ print(math.pi)" > math.py
|
||||
{COMMAND_SEGMENT}
|
||||
|
||||
When you are done, execute the following to close the shell and end the conversation:
|
||||
<execute>exit</execute>
|
||||
<execute>exit</execute>
|
||||
"""
|
||||
|
||||
INVALID_INPUT_MESSAGE = (
|
||||
"I don't understand your input. \n"
|
||||
"If you want to execute command, please use <execute> YOUR_COMMAND_HERE </execute>.\n"
|
||||
"If you already completed the task, please exit the shell by generating: <execute> exit </execute>."
|
||||
'If you want to execute command, please use <execute> YOUR_COMMAND_HERE </execute>.\n'
|
||||
'If you already completed the task, please exit the shell by generating: <execute> exit </execute>.'
|
||||
)
|
||||
|
||||
|
||||
def parse_response(response) -> str:
|
||||
action = response.choices[0].message.content
|
||||
if "<execute>" in action and "</execute>" not in action:
|
||||
action += "</execute>"
|
||||
if '<execute>' in action and '</execute>' not in action:
|
||||
action += '</execute>'
|
||||
return action
|
||||
|
||||
|
||||
class CodeActAgent(Agent):
|
||||
"""
|
||||
The Code Act Agent is a minimalist agent.
|
||||
The Code Act Agent is a minimalist agent.
|
||||
The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm: LLM,
|
||||
@@ -82,7 +84,7 @@ class CodeActAgent(Agent):
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
Performs one step using the Code Act Agent.
|
||||
Performs one step using the Code Act Agent.
|
||||
This includes gathering info on previous steps and prompting the model to make a command to execute.
|
||||
|
||||
Parameters:
|
||||
@@ -97,42 +99,47 @@ class CodeActAgent(Agent):
|
||||
"""
|
||||
|
||||
if len(self.messages) == 0:
|
||||
assert state.plan.main_goal, "Expecting instruction to be set"
|
||||
assert state.plan.main_goal, 'Expecting instruction to be set'
|
||||
self.messages = [
|
||||
{"role": "system", "content": SYSTEM_MESSAGE},
|
||||
{"role": "user", "content": state.plan.main_goal},
|
||||
{'role': 'system', 'content': SYSTEM_MESSAGE},
|
||||
{'role': 'user', 'content': state.plan.main_goal},
|
||||
]
|
||||
updated_info = state.updated_info
|
||||
if updated_info:
|
||||
for prev_action, obs in updated_info:
|
||||
assert isinstance(
|
||||
prev_action, (CmdRunAction, AgentEchoAction)
|
||||
), "Expecting CmdRunAction or AgentEchoAction for Action"
|
||||
), 'Expecting CmdRunAction or AgentEchoAction for Action'
|
||||
if isinstance(
|
||||
obs, AgentMessageObservation
|
||||
): # warning message from itself
|
||||
self.messages.append({"role": "user", "content": obs.content})
|
||||
self.messages.append(
|
||||
{'role': 'user', 'content': obs.content})
|
||||
elif isinstance(obs, CmdOutputObservation):
|
||||
content = "OBSERVATION:\n" + obs.content
|
||||
content = 'OBSERVATION:\n' + obs.content
|
||||
# FIXME: autopep8 and mypy are fighting each other on this line
|
||||
# autopep8: off
|
||||
content += f"\n[Command {obs.command_id} finished with exit code {obs.exit_code}]]"
|
||||
self.messages.append({"role": "user", "content": content})
|
||||
self.messages.append({'role': 'user', 'content': content})
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Unknown observation type: {obs.__class__}"
|
||||
)
|
||||
response = self.llm.completion(
|
||||
messages=self.messages,
|
||||
stop=["</execute>"],
|
||||
stop=['</execute>'],
|
||||
temperature=0.0
|
||||
)
|
||||
action_str: str = parse_response(response)
|
||||
self.messages.append({"role": "assistant", "content": action_str})
|
||||
state.num_of_chars += sum(len(message['content'])
|
||||
for message in self.messages) + len(action_str)
|
||||
self.messages.append({'role': 'assistant', 'content': action_str})
|
||||
|
||||
command = re.search(r"<execute>(.*)</execute>", action_str, re.DOTALL)
|
||||
command = re.search(r'<execute>(.*)</execute>', action_str, re.DOTALL)
|
||||
if command is not None:
|
||||
# a command was found
|
||||
command_group = command.group(1)
|
||||
if command_group.strip() == "exit":
|
||||
if command_group.strip() == 'exit':
|
||||
return AgentFinishAction()
|
||||
return CmdRunAction(command=command_group)
|
||||
# # execute the code
|
||||
@@ -149,4 +156,4 @@ class CodeActAgent(Agent):
|
||||
) # warning message to itself
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
raise NotImplementedError("Implement this abstract method")
|
||||
raise NotImplementedError('Implement this abstract method')
|
||||
|
||||
@@ -32,46 +32,46 @@ MAX_MONOLOGUE_LENGTH = 20000
|
||||
MAX_OUTPUT_LENGTH = 5000
|
||||
|
||||
INITIAL_THOUGHTS = [
|
||||
"I exist!",
|
||||
"Hmm...looks like I can type in a command line prompt",
|
||||
"Looks like I have a web browser too!",
|
||||
'I exist!',
|
||||
'Hmm...looks like I can type in a command line prompt',
|
||||
'Looks like I have a web browser too!',
|
||||
"Here's what I want to do: $TASK",
|
||||
"How am I going to get there though?",
|
||||
"It seems like I have some kind of short term memory.",
|
||||
"Each of my thoughts seems to be stored in a JSON array.",
|
||||
"It seems whatever I say next will be added as an object to the list.",
|
||||
"But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.",
|
||||
"Fortunately I have long term memory!",
|
||||
"I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!",
|
||||
'How am I going to get there though?',
|
||||
'It seems like I have some kind of short term memory.',
|
||||
'Each of my thoughts seems to be stored in a JSON array.',
|
||||
'It seems whatever I say next will be added as an object to the list.',
|
||||
'But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.',
|
||||
'Fortunately I have long term memory!',
|
||||
'I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!',
|
||||
"Sometimes they're random thoughts that don't really have to do with what I wanted to remember. But usually they're exactly what I need!",
|
||||
"Let's try it out!",
|
||||
"RECALL what it is I want to do",
|
||||
'RECALL what it is I want to do',
|
||||
"Here's what I want to do: $TASK",
|
||||
"How am I going to get there though?",
|
||||
'How am I going to get there though?',
|
||||
"Neat! And it looks like it's easy for me to use the command line too! I just have to perform a run action and include the command I want to run in the command argument. The command output just jumps into my head!",
|
||||
'RUN echo "hello world"',
|
||||
"hello world",
|
||||
"Cool! I bet I can write files too using the write action.",
|
||||
'hello world',
|
||||
'Cool! I bet I can write files too using the write action.',
|
||||
"WRITE echo \"console.log('hello world')\" > test.js",
|
||||
"",
|
||||
'',
|
||||
"I just created test.js. I'll try and run it now.",
|
||||
"RUN node test.js",
|
||||
"hello world",
|
||||
"It works!",
|
||||
'RUN node test.js',
|
||||
'hello world',
|
||||
'It works!',
|
||||
"I'm going to try reading it now using the read action.",
|
||||
"READ test.js",
|
||||
'READ test.js',
|
||||
"console.log('hello world')",
|
||||
"Nice! I can read files too!",
|
||||
"And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument",
|
||||
'Nice! I can read files too!',
|
||||
'And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument',
|
||||
"Let's try that...",
|
||||
"BROWSE google.com",
|
||||
'BROWSE google.com',
|
||||
'<form><input type="text"></input><button type="submit"></button></form>',
|
||||
"I can browse the web too!",
|
||||
"And once I have completed my task, I can use the finish action to stop working.",
|
||||
'I can browse the web too!',
|
||||
'And once I have completed my task, I can use the finish action to stop working.',
|
||||
"But I should only use the finish action when I'm absolutely certain that I've completed my task and have tested my work.",
|
||||
"Very cool. Now to accomplish my task.",
|
||||
'Very cool. Now to accomplish my task.',
|
||||
"I'll need a strategy. And as I make progress, I'll need to keep refining that strategy. I'll need to set goals, and break them into sub-goals.",
|
||||
"In between actions, I must always take some time to think, strategize, and set new goals. I should never take two actions in a row.",
|
||||
'In between actions, I must always take some time to think, strategize, and set new goals. I should never take two actions in a row.',
|
||||
"OK so my task is to $TASK. I haven't made any progress yet. Where should I start?",
|
||||
"It seems like there might be an existing project here. I should probably start by running `ls` to see what's here.",
|
||||
]
|
||||
@@ -106,15 +106,15 @@ class MonologueAgent(Agent):
|
||||
- event (dict): The event that will be added to monologue and memory
|
||||
"""
|
||||
|
||||
if "extras" in event and "screenshot" in event["extras"]:
|
||||
del event["extras"]["screenshot"]
|
||||
if 'extras' in event and 'screenshot' in event['extras']:
|
||||
del event['extras']['screenshot']
|
||||
if (
|
||||
"args" in event
|
||||
and "output" in event["args"]
|
||||
and len(event["args"]["output"]) > MAX_OUTPUT_LENGTH
|
||||
'args' in event
|
||||
and 'output' in event['args']
|
||||
and len(event['args']['output']) > MAX_OUTPUT_LENGTH
|
||||
):
|
||||
event["args"]["output"] = (
|
||||
event["args"]["output"][:MAX_OUTPUT_LENGTH] + "..."
|
||||
event['args']['output'] = (
|
||||
event['args']['output'][:MAX_OUTPUT_LENGTH] + '...'
|
||||
)
|
||||
|
||||
self.monologue.add_event(event)
|
||||
@@ -137,51 +137,52 @@ class MonologueAgent(Agent):
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
if task is None or task == "":
|
||||
raise ValueError("Instruction must be provided")
|
||||
if task is None or task == '':
|
||||
raise ValueError('Instruction must be provided')
|
||||
self.monologue = Monologue()
|
||||
self.memory = LongTermMemory()
|
||||
|
||||
output_type = ""
|
||||
output_type = ''
|
||||
for thought in INITIAL_THOUGHTS:
|
||||
thought = thought.replace("$TASK", task)
|
||||
if output_type != "":
|
||||
observation: Observation = NullObservation(content="")
|
||||
thought = thought.replace('$TASK', task)
|
||||
if output_type != '':
|
||||
observation: Observation = NullObservation(content='')
|
||||
if output_type == ObservationType.RUN:
|
||||
observation = CmdOutputObservation(
|
||||
content=thought, command_id=0, command=""
|
||||
content=thought, command_id=0, command=''
|
||||
)
|
||||
elif output_type == ObservationType.READ:
|
||||
observation = FileReadObservation(content=thought, path="")
|
||||
observation = FileReadObservation(content=thought, path='')
|
||||
elif output_type == ObservationType.RECALL:
|
||||
observation = AgentRecallObservation(content=thought, memories=[])
|
||||
observation = AgentRecallObservation(
|
||||
content=thought, memories=[])
|
||||
elif output_type == ObservationType.BROWSE:
|
||||
observation = BrowserOutputObservation(
|
||||
content=thought, url="", screenshot=""
|
||||
content=thought, url='', screenshot=''
|
||||
)
|
||||
self._add_event(observation.to_dict())
|
||||
output_type = ""
|
||||
output_type = ''
|
||||
else:
|
||||
action: Action = NullAction()
|
||||
if thought.startswith("RUN"):
|
||||
command = thought.split("RUN ")[1]
|
||||
if thought.startswith('RUN'):
|
||||
command = thought.split('RUN ')[1]
|
||||
action = CmdRunAction(command)
|
||||
output_type = ActionType.RUN
|
||||
elif thought.startswith("WRITE"):
|
||||
parts = thought.split("WRITE ")[1].split(" > ")
|
||||
elif thought.startswith('WRITE'):
|
||||
parts = thought.split('WRITE ')[1].split(' > ')
|
||||
path = parts[1]
|
||||
content = parts[0]
|
||||
action = FileWriteAction(path=path, content=content)
|
||||
elif thought.startswith("READ"):
|
||||
path = thought.split("READ ")[1]
|
||||
elif thought.startswith('READ'):
|
||||
path = thought.split('READ ')[1]
|
||||
action = FileReadAction(path=path)
|
||||
output_type = ActionType.READ
|
||||
elif thought.startswith("RECALL"):
|
||||
query = thought.split("RECALL ")[1]
|
||||
elif thought.startswith('RECALL'):
|
||||
query = thought.split('RECALL ')[1]
|
||||
action = AgentRecallAction(query=query)
|
||||
output_type = ActionType.RECALL
|
||||
elif thought.startswith("BROWSE"):
|
||||
url = thought.split("BROWSE ")[1]
|
||||
elif thought.startswith('BROWSE'):
|
||||
url = thought.split('BROWSE ')[1]
|
||||
action = BrowseURLAction(url=url)
|
||||
output_type = ActionType.BROWSE
|
||||
else:
|
||||
@@ -211,9 +212,10 @@ class MonologueAgent(Agent):
|
||||
self.monologue.get_thoughts(),
|
||||
state.background_commands_obs,
|
||||
)
|
||||
messages = [{"content": prompt, "role": "user"}]
|
||||
messages = [{'content': prompt, 'role': 'user'}]
|
||||
resp = self.llm.completion(messages=messages)
|
||||
action_resp = resp["choices"][0]["message"]["content"]
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
state.num_of_chars += len(prompt) + len(action_resp)
|
||||
action = prompts.parse_action_response(action_resp)
|
||||
self.latest_action = action
|
||||
return action
|
||||
|
||||
@@ -7,6 +7,7 @@ from opendevin.llm.llm import LLM
|
||||
from opendevin.state import State
|
||||
from opendevin.action import Action
|
||||
|
||||
|
||||
class PlannerAgent(Agent):
|
||||
"""
|
||||
The planner agent utilizes a special prompting strategy to create long term plans for solving problems.
|
||||
@@ -24,7 +25,7 @@ class PlannerAgent(Agent):
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
Checks to see if current step is completed, returns AgentFinishAction if True.
|
||||
Checks to see if current step is completed, returns AgentFinishAction if True.
|
||||
Otherwise, creates a plan prompt and sends to model for inference, returning the result as the next action.
|
||||
|
||||
Parameters:
|
||||
@@ -38,12 +39,12 @@ class PlannerAgent(Agent):
|
||||
if state.plan.task.state in ['completed', 'verified', 'abandoned']:
|
||||
return AgentFinishAction()
|
||||
prompt = get_prompt(state.plan, state.history)
|
||||
messages = [{"content": prompt, "role": "user"}]
|
||||
messages = [{'content': prompt, 'role': 'user'}]
|
||||
resp = self.llm.completion(messages=messages)
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
state.num_of_chars += len(prompt) + len(action_resp)
|
||||
action = parse_response(action_resp)
|
||||
return action
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ enum ArgConfigType {
|
||||
LLM_COOLDOWN_TIME = "LLM_COOLDOWN_TIME",
|
||||
DIRECTORY_REWRITE = "DIRECTORY_REWRITE",
|
||||
MAX_ITERATIONS = "MAX_ITERATIONS",
|
||||
MAX_CHARS = "MAX_CHARS",
|
||||
AGENT = "AGENT",
|
||||
|
||||
LANGUAGE = "LANGUAGE",
|
||||
|
||||
@@ -22,6 +22,10 @@ DEFAULT_CONFIG: dict = {
|
||||
ConfigType.LLM_COOLDOWN_TIME: 1,
|
||||
ConfigType.DIRECTORY_REWRITE: '',
|
||||
ConfigType.MAX_ITERATIONS: 100,
|
||||
# GPT-4 pricing is $10 per 1M input tokens. Since tokenization happens on LLM side,
|
||||
# we cannot easily count number of tokens, but we can count characters.
|
||||
# Assuming 5 characters per token, 5 million is a reasonable default limit.
|
||||
ConfigType.MAX_CHARS: 5_000_000,
|
||||
ConfigType.AGENT: 'MonologueAgent',
|
||||
ConfigType.SANDBOX_TYPE: 'ssh',
|
||||
ConfigType.DISABLE_COLOR: 'false',
|
||||
@@ -47,7 +51,7 @@ def get(key: str, required: bool = False):
|
||||
"""
|
||||
value = os.environ.get(key)
|
||||
if not value:
|
||||
value = config.get(key)
|
||||
value = config.get(key)
|
||||
if not value and required:
|
||||
raise KeyError(f"Please set '{key}' in `config.toml` or `.env`.")
|
||||
return value
|
||||
|
||||
@@ -15,45 +15,47 @@ from opendevin.action import (
|
||||
)
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.logger import opendevin_logger as logger
|
||||
from opendevin.exceptions import MaxCharsExceedError
|
||||
from opendevin.observation import Observation, AgentErrorObservation, NullObservation
|
||||
from opendevin.plan import Plan
|
||||
from opendevin.state import State
|
||||
from .command_manager import CommandManager
|
||||
|
||||
ColorType = Literal[
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"blue",
|
||||
"magenta",
|
||||
"cyan",
|
||||
"light_grey",
|
||||
"dark_grey",
|
||||
"light_red",
|
||||
"light_green",
|
||||
"light_yellow",
|
||||
"light_blue",
|
||||
"light_magenta",
|
||||
"light_cyan",
|
||||
"white",
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'magenta',
|
||||
'cyan',
|
||||
'light_grey',
|
||||
'dark_grey',
|
||||
'light_red',
|
||||
'light_green',
|
||||
'light_yellow',
|
||||
'light_blue',
|
||||
'light_magenta',
|
||||
'light_cyan',
|
||||
'white',
|
||||
]
|
||||
|
||||
DISABLE_COLOR_PRINTING = (
|
||||
config.get('DISABLE_COLOR').lower() == 'true'
|
||||
)
|
||||
MAX_ITERATIONS = config.get("MAX_ITERATIONS")
|
||||
MAX_ITERATIONS = config.get('MAX_ITERATIONS')
|
||||
MAX_CHARS = config.get('MAX_CHARS')
|
||||
|
||||
|
||||
def print_with_color(text: Any, print_type: str = "INFO"):
|
||||
def print_with_color(text: Any, print_type: str = 'INFO'):
|
||||
TYPE_TO_COLOR: Mapping[str, ColorType] = {
|
||||
"BACKGROUND LOG": "blue",
|
||||
"ACTION": "green",
|
||||
"OBSERVATION": "yellow",
|
||||
"INFO": "cyan",
|
||||
"ERROR": "red",
|
||||
"PLAN": "light_magenta",
|
||||
'BACKGROUND LOG': 'blue',
|
||||
'ACTION': 'green',
|
||||
'OBSERVATION': 'yellow',
|
||||
'INFO': 'cyan',
|
||||
'ERROR': 'red',
|
||||
'PLAN': 'light_magenta',
|
||||
}
|
||||
color = TYPE_TO_COLOR.get(print_type.upper(), TYPE_TO_COLOR["INFO"])
|
||||
color = TYPE_TO_COLOR.get(print_type.upper(), TYPE_TO_COLOR['INFO'])
|
||||
if DISABLE_COLOR_PRINTING:
|
||||
print(f'\n{print_type.upper()}:\n{str(text)}', flush=True)
|
||||
else:
|
||||
@@ -76,16 +78,19 @@ class AgentController:
|
||||
self,
|
||||
agent: Agent,
|
||||
workdir: str,
|
||||
sid: str = "",
|
||||
sid: str = '',
|
||||
max_iterations: int = MAX_ITERATIONS,
|
||||
max_chars: int = MAX_CHARS,
|
||||
container_image: str | None = None,
|
||||
callbacks: List[Callable] = [],
|
||||
):
|
||||
self.id = sid
|
||||
self.agent = agent
|
||||
self.max_iterations = max_iterations
|
||||
self.max_chars = max_chars
|
||||
self.workdir = workdir
|
||||
self.command_manager = CommandManager(self.id, workdir, container_image)
|
||||
self.command_manager = CommandManager(
|
||||
self.id, workdir, container_image)
|
||||
self.callbacks = callbacks
|
||||
|
||||
def update_state_for_step(self, i):
|
||||
@@ -97,9 +102,9 @@ class AgentController:
|
||||
|
||||
def add_history(self, action: Action, observation: Observation):
|
||||
if not isinstance(action, Action):
|
||||
raise ValueError("action must be an instance of Action")
|
||||
raise ValueError('action must be an instance of Action')
|
||||
if not isinstance(observation, Observation):
|
||||
raise ValueError("observation must be an instance of Observation")
|
||||
raise ValueError('observation must be an instance of Observation')
|
||||
self.state.history.append((action, observation))
|
||||
self.state.updated_info.append((action, observation))
|
||||
|
||||
@@ -111,40 +116,44 @@ class AgentController:
|
||||
try:
|
||||
finished = await self.step(i)
|
||||
except Exception as e:
|
||||
logger.error("Error in loop", exc_info=True)
|
||||
logger.error('Error in loop', exc_info=True)
|
||||
raise e
|
||||
if finished:
|
||||
break
|
||||
if not finished:
|
||||
logger.info("Exited before finishing the task.")
|
||||
logger.info('Exited before finishing the task.')
|
||||
|
||||
async def step(self, i: int):
|
||||
print("\n\n==============", flush=True)
|
||||
print("STEP", i, flush=True)
|
||||
print_with_color(self.state.plan.main_goal, "PLAN")
|
||||
print('\n\n==============', flush=True)
|
||||
print('STEP', i, flush=True)
|
||||
print_with_color(self.state.plan.main_goal, 'PLAN')
|
||||
|
||||
if self.state.num_of_chars > self.max_chars:
|
||||
raise MaxCharsExceedError(
|
||||
self.state.num_of_chars, self.max_chars)
|
||||
|
||||
log_obs = self.command_manager.get_background_obs()
|
||||
for obs in log_obs:
|
||||
self.add_history(NullAction(), obs)
|
||||
await self._run_callbacks(obs)
|
||||
print_with_color(obs, "BACKGROUND LOG")
|
||||
print_with_color(obs, 'BACKGROUND LOG')
|
||||
|
||||
self.update_state_for_step(i)
|
||||
action: Action = NullAction()
|
||||
observation: Observation = NullObservation("")
|
||||
observation: Observation = NullObservation('')
|
||||
try:
|
||||
action = self.agent.step(self.state)
|
||||
if action is None:
|
||||
raise ValueError("Agent must return an action")
|
||||
print_with_color(action, "ACTION")
|
||||
raise ValueError('Agent must return an action')
|
||||
print_with_color(action, 'ACTION')
|
||||
except Exception as e:
|
||||
observation = AgentErrorObservation(str(e))
|
||||
print_with_color(observation, "ERROR")
|
||||
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
|
||||
'The api_key client option must be set' in observation.content
|
||||
or 'Incorrect API key provided:' in observation.content
|
||||
):
|
||||
raise
|
||||
self.update_state_after_step()
|
||||
@@ -153,22 +162,23 @@ class AgentController:
|
||||
|
||||
finished = isinstance(action, AgentFinishAction)
|
||||
if finished:
|
||||
print_with_color(action, "INFO")
|
||||
print_with_color(action, 'INFO')
|
||||
return True
|
||||
|
||||
if isinstance(action, AddTaskAction):
|
||||
try:
|
||||
self.state.plan.add_subtask(action.parent, action.goal, action.subtasks)
|
||||
self.state.plan.add_subtask(
|
||||
action.parent, action.goal, action.subtasks)
|
||||
except Exception as e:
|
||||
observation = AgentErrorObservation(str(e))
|
||||
print_with_color(observation, "ERROR")
|
||||
print_with_color(observation, 'ERROR')
|
||||
traceback.print_exc()
|
||||
elif isinstance(action, ModifyTaskAction):
|
||||
try:
|
||||
self.state.plan.set_subtask_state(action.id, action.state)
|
||||
except Exception as e:
|
||||
observation = AgentErrorObservation(str(e))
|
||||
print_with_color(observation, "ERROR")
|
||||
print_with_color(observation, 'ERROR')
|
||||
traceback.print_exc()
|
||||
|
||||
if action.executable:
|
||||
@@ -179,11 +189,11 @@ class AgentController:
|
||||
observation = action.run(self)
|
||||
except Exception as e:
|
||||
observation = AgentErrorObservation(str(e))
|
||||
print_with_color(observation, "ERROR")
|
||||
print_with_color(observation, 'ERROR')
|
||||
traceback.print_exc()
|
||||
|
||||
if not isinstance(observation, NullObservation):
|
||||
print_with_color(observation, "OBSERVATION")
|
||||
print_with_color(observation, 'OBSERVATION')
|
||||
|
||||
self.add_history(action, observation)
|
||||
await self._run_callbacks(observation)
|
||||
|
||||
9
opendevin/exceptions.py
Normal file
9
opendevin/exceptions.py
Normal file
@@ -0,0 +1,9 @@
|
||||
class MaxCharsExceedError(Exception):
|
||||
def __init__(self, num_of_chars=None, max_chars_limit=None):
|
||||
if num_of_chars is not None and max_chars_limit is not None:
|
||||
# FIXME: autopep8 and mypy are fighting each other on this line
|
||||
# autopep8: off
|
||||
message = f"Number of characters {num_of_chars} exceeds MAX_CHARS limit: {max_chars_limit}"
|
||||
else:
|
||||
message = 'Number of characters exceeds MAX_CHARS limit'
|
||||
super().__init__(message)
|
||||
@@ -5,6 +5,7 @@ from typing import Type
|
||||
|
||||
import agenthub # noqa F401 (we import this to get the agents registered)
|
||||
from opendevin import config
|
||||
from opendevin.schema import ConfigType
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.controller import AgentController
|
||||
from opendevin.llm.llm import LLM
|
||||
@@ -51,17 +52,24 @@ def parse_arguments():
|
||||
parser.add_argument(
|
||||
'-m',
|
||||
'--model-name',
|
||||
default=config.get('LLM_MODEL'),
|
||||
default=config.get(ConfigType.LLM_MODEL),
|
||||
type=str,
|
||||
help='The (litellm) model name to use',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-i',
|
||||
'--max-iterations',
|
||||
default=100,
|
||||
default=config.get(ConfigType.MAX_ITERATIONS),
|
||||
type=int,
|
||||
help='The maximum number of iterations to run the agent',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
'--max-chars',
|
||||
default=config.get(ConfigType.MAX_CHARS),
|
||||
type=int,
|
||||
help='The maximum number of characters to send to and receive from LLM per task',
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -81,6 +89,8 @@ async def main():
|
||||
raise ValueError(
|
||||
'No task provided. Please specify a task through -t, -f.')
|
||||
|
||||
# FIXME: autopep8 and mypy are fighting each other on this line
|
||||
# autopep8: off
|
||||
print(
|
||||
f'Running agent {args.agent_cls} (model: {args.model_name}, directory: {args.directory}) with task: "{task}"'
|
||||
)
|
||||
@@ -88,7 +98,7 @@ async def main():
|
||||
AgentCls: Type[Agent] = Agent.get_cls(args.agent_cls)
|
||||
agent = AgentCls(llm=llm)
|
||||
controller = AgentController(
|
||||
agent=agent, workdir=args.directory, max_iterations=args.max_iterations
|
||||
agent=agent, workdir=args.directory, max_iterations=args.max_iterations, max_chars=args.max_chars
|
||||
)
|
||||
|
||||
await controller.start_loop(task)
|
||||
|
||||
@@ -15,6 +15,7 @@ class ConfigType(str, Enum):
|
||||
LLM_COOLDOWN_TIME = 'LLM_COOLDOWN_TIME'
|
||||
DIRECTORY_REWRITE = 'DIRECTORY_REWRITE'
|
||||
MAX_ITERATIONS = 'MAX_ITERATIONS'
|
||||
MAX_CHARS = 'MAX_CHARS'
|
||||
AGENT = 'AGENT'
|
||||
SANDBOX_TYPE = 'SANDBOX_TYPE'
|
||||
DISABLE_COLOR = 'DISABLE_COLOR'
|
||||
|
||||
@@ -113,6 +113,7 @@ class AgentUnit:
|
||||
container_image = config.get(ConfigType.SANDBOX_CONTAINER_IMAGE)
|
||||
max_iterations = self.get_arg_or_default(
|
||||
args, ConfigType.MAX_ITERATIONS)
|
||||
max_chars = self.get_arg_or_default(args, ConfigType.MAX_CHARS)
|
||||
|
||||
if not os.path.exists(directory):
|
||||
logger.info(
|
||||
@@ -127,6 +128,7 @@ class AgentUnit:
|
||||
agent=Agent.get_cls(agent_cls)(llm),
|
||||
workdir=directory,
|
||||
max_iterations=int(max_iterations),
|
||||
max_chars=int(max_chars),
|
||||
container_image=container_image,
|
||||
callbacks=[self.on_agent_event],
|
||||
)
|
||||
|
||||
@@ -11,10 +11,15 @@ from opendevin.observation import (
|
||||
CmdOutputObservation,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class State:
|
||||
plan: Plan
|
||||
iteration: int = 0
|
||||
background_commands_obs: List[CmdOutputObservation] = field(default_factory=list)
|
||||
# number of characters we have sent to and received from LLM so far for current task
|
||||
num_of_chars: int = 0
|
||||
background_commands_obs: List[CmdOutputObservation] = field(
|
||||
default_factory=list)
|
||||
history: List[Tuple[Action, Observation]] = field(default_factory=list)
|
||||
updated_info: List[Tuple[Action, Observation]] = field(default_factory=list)
|
||||
updated_info: List[Tuple[Action, Observation]
|
||||
] = field(default_factory=list)
|
||||
|
||||
Reference in New Issue
Block a user