Compare commits

...

14 Commits

Author SHA1 Message Date
Anas Dorbani
d0ba806f35 Improve fileop logic to handle multiple cases 2024-04-10 16:23:53 +00:00
Jack Quimby
04e9caab8b Merge branch 'OpenDevin:main' into main 2024-04-09 16:58:59 -04:00
Jack
26a1ce2d73 Prompt Engineering 2024-04-09 16:58:13 -04:00
Jack
87e36570f6 State management, bugs, prompts 2024-04-09 02:48:20 -04:00
Jack Quimby
ce9bd95009 Merge branch 'OpenDevin:main' into main 2024-04-08 19:32:28 -04:00
Jack
0f76f70a81 Lint code changes to ensure code is proper 2024-04-08 17:37:03 -04:00
Jack
e830627b4d bug fixes and test updates 2024-04-08 15:15:06 -04:00
Jack
30f084713a Merge branch 'main' of github.com:JayQuimby/OpenDevin 2024-04-08 13:32:14 -04:00
Jack
24b21de8bc added start and end index to read and write 2024-04-08 13:31:33 -04:00
Jack Quimby
db608aba83 Merge branch 'OpenDevin:main' into main 2024-04-08 13:23:48 -04:00
Jack
1a882acb1b parsing, prompting, and actions modifications 2024-04-08 02:00:52 -04:00
Jack
823a631a12 Using commands.sh for ACI 2024-04-07 15:52:09 -04:00
Jack
cca6ba7f25 Merge branch 'main' of github.com:JayQuimby/OpenDevin 2024-04-07 02:33:51 -04:00
Jack
ed608ee23c Merge branch 'main' of https://github.com/JayQuimby/OpenDevin 2024-04-07 02:31:41 -04:00
13 changed files with 613 additions and 109 deletions

View File

@@ -2,8 +2,10 @@ from dotenv import load_dotenv
load_dotenv()
# Import agents after environment variables are loaded
from . import monologue_agent # noqa: E402
from . import codeact_agent # noqa: E402
from . import planner_agent # noqa: E402
from . import monologue_agent # noqa: E402
from . import codeact_agent # noqa: E402
from . import planner_agent # noqa: E402
from . import thinkact_agent # noqa: E402
__all__ = ['monologue_agent', 'codeact_agent', 'planner_agent']
__all__ = ['monologue_agent', 'codeact_agent',
'planner_agent', 'thinkact_agent']

View File

@@ -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.",
"WRITE echo \"console.log('hello world')\" > test.js",
"",
'hello world',
'Cool! I bet I can write files too using the write action.',
"WRITE 0 1 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)
@@ -125,7 +125,7 @@ class MonologueAgent(Agent):
def _initialize(self, task: str):
"""
Utilizes the INITIAL_THOUGHTS list to give the agent a context for it's capabilities and how to navigate the /workspace.
Short circuted to return when already initialized.
Short circuited to return when already initialized.
Parameters:
- task (str): The initial goal statement provided by the user
@@ -137,51 +137,54 @@ 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]
start, end, content = parts[0].split(' ')
start_ind, end_ind = int(start), int(end)
action = FileWriteAction(
path=path, start=start_ind, end=end_ind, content=content)
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 +214,9 @@ 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']
action = prompts.parse_action_response(action_resp)
self.latest_action = action
return action

View File

@@ -0,0 +1,4 @@
from opendevin.agent import Agent
from .agent import ThinkActAgent
Agent.register('ThinkActAgent', ThinkActAgent)

View File

@@ -0,0 +1,99 @@
from typing import List
from opendevin.agent import Agent
from opendevin.llm.llm import LLM
from opendevin.state import State
from opendevin.action import Action, AgentThinkAction
from opendevin.observation import Observation
from .parser import parse_command
from .prompts import (
SYSTEM_MESSAGE,
STEP_PROMPT,
MEMORY_FORMAT,
NO_ACTION,
CONTEXT_PROMPT
)
# TODO: Add state management
class ThinkActAgent(Agent):
"""
An attempt to recreate swe_agent with output parsing, prompting style, and Application Computer Interface (ACI).
SWE-agent includes ACI functions like 'goto', 'search_for', 'edit', 'scroll', 'run'
"""
def __init__(self, llm: LLM):
super().__init__(llm)
self.memory_window = 5
self.max_retries = 5
self.running_memory: List[str] = []
def _remember(self, action: Action, observation: Observation):
"""Agent has a limited memory of the few steps implemented as a queue"""
memory = MEMORY_FORMAT(action.to_dict(), observation.to_dict())
self.running_memory.append(memory)
def _think_act(self, messages: List[dict]) -> tuple[Action, str]:
resp = self.llm.completion(
messages=messages,
temperature=0.0
)
action_resp = resp['choices'][0]['message']['content']
return parse_command(action_resp)
def step(self, state: State) -> Action:
"""
SWE-Agent step:
1. Get context - past actions, custom commands, current step
2. Perform think-act - prompt model for action and reasoning
3. Catch errors - ensure model takes action (5 attempts max)
"""
for prev_action, obs in state.updated_info:
self._remember(prev_action, obs)
prompt = STEP_PROMPT(
state.plan.main_goal,
state.working_dir,
state.file_name,
state.cur_line
)
context = CONTEXT_PROMPT(
self.running_memory,
self.memory_window
)
msgs = [
{'content': SYSTEM_MESSAGE, 'role': 'user'},
{'content': prompt, 'role': 'user'}
]
if len(self.running_memory) > 0:
msgs.insert(0, context)
# print('\n\n\n'.join([m['content'] for m in msgs]))
action, thought = self._think_act(messages=msgs)
start_msg_len = len(msgs)
while not action and len(msgs) < self.max_retries + start_msg_len:
error = NO_ACTION(thought)
error_msg = {'content': error, 'role': 'user'}
msgs.append(error_msg)
action, thought = self._think_act(messages=msgs)
if not action:
action = AgentThinkAction(thought)
self.latest_action = action
return action
def search_memory(self, query: str) -> List[str]:
return [item for item in self.running_memory if query in item]
def reset(self) -> None:
"""Used to reset the agent"""
self.running_memory = []
super().reset()

View File

@@ -0,0 +1,75 @@
from opendevin.action import (
Action,
AgentFinishAction,
CmdRunAction,
FileReadAction,
FileWriteAction,
BrowseURLAction,
)
# commands: exit, read, write, browse, kill, search_file, search_dir
def get_action_from_string(command_string: str) -> Action | None:
"""
Parses the command string to find which command the agent wants to run
Converts the command into a proper Action and returns
"""
vars = command_string.split(' ')
cmd = vars[0]
args = [] if len(vars) == 1 else ' '.join(vars[1:])
# TODO: add exception handling for improper commands
if 'exit' == cmd:
return AgentFinishAction()
elif 'read' == cmd:
if len(args) == 1:
file = args[0]
start = 0
elif len(args) == 2:
file, start = args[0], int(args[1])
elif len(args) > 2:
file, start = args[0], int(args[1])
else:
return None
return FileReadAction(file, start)
elif 'write' == cmd:
assert len(args) >= 4, 'Not enough arguments for this command'
file = args[0]
start, end = [int(arg) for arg in args[1:3]]
content = ' '.join(args[3:])
return FileWriteAction(file, content, start, end)
elif 'browse' == cmd:
return BrowseURLAction(args[0].strip())
# TODO: need to integrate all of the custom commands
else:
return CmdRunAction(command_string)
def parse_command(input_str: str):
"""
Parses a given string and separates the command (enclosed in triple backticks) from any accompanying text.
Args:
input_str (str): The input string to be parsed.
Returns:
tuple: A tuple containing the command and the accompanying text (if any).
"""
input_str = input_str.strip()
if '```' in input_str:
parts = input_str.split('```')
command_str = parts[1].strip()
action = get_action_from_string(command_str)
if action:
ind = 2 if len(parts) > 2 else 1
accompanying_text = ''.join(parts[:-ind]).strip()
return action, accompanying_text
return None, input_str # used for retry

View File

@@ -0,0 +1,36 @@
# Things that need to be done for SWE-Agent
## 1. Add in all of the following commands to ACI
```
'search_for': {
'params': '<keywords>',
'description': 'Will allow you to search your working directory for files and folders that match your <keyword>.',
}
'edit ': {
'params': '<filename>',
'description': 'This will allow you to modify files within your working directory. Usage: "edit example.txt" -> opens the example.txt file and shows the first 100 lines',
},
'goto': {
'params': '<line_num>',
'description': 'This will allow you to go through a file to any line. Usage: "Goto 124" -> returns lines 124-224 within current file',
},
'scroll_up': {
'params': '',
'description': 'When you are in a file you can see the 100 lines above your current view. Usage: "scroll_up" -> returns the 100 lines above what you were reading',
},
'scroll_down': {
'params': '',
'description': 'When you are in a file you can see the 100 lines below your current view. Usage: "scroll_down" -> returns the 100 lines below what you were reading',
},
'modify': {
'params': '<start_line>:<end_line> "<replacement>"',
'description': 'This will make changes to a file by deleting all lines from <start_line> to <end_line> and replacing them with <replacement>',
}
```
## 2. Add linter to check code edits before modifying file

View File

@@ -0,0 +1,158 @@
from opendevin.parse_commands import parse_command_file
DEFAULT_COMMANDS = {
'exit': 'Executed when task is complete',
'read <file_name> [<start_line>]': 'shows a given file\'s contents starting from <start_line>, default start is 0',
'write <file> <changes> [<start_line>] [<end_line>]': 'modifies a <file> by replacing the current lines between <start_line> and <end_line> with <changes>',
'browse <url>': 'returns the text version of any url',
'<bash_command> <args>': 'Any bash command is valid (cd, ls, rm, grep, dir, mv, wget, git, zip, etc.) with their arguments included',
}
DEFAULT_COMMAND_STR = '\n'.join(
[k + ' - ' + v for k, v in DEFAULT_COMMANDS.items()])
COMMAND_DOCS = parse_command_file()
CUSTOM_COMMANDS = f"""
Apart from the standard bash commands, you can also use the following special commands:
{COMMAND_DOCS}
"""
COMMAND_SEGMENT = CUSTOM_COMMANDS if COMMAND_DOCS is not None else ''
DOCUMENTATION = f"""
Commands:
{DEFAULT_COMMAND_STR}
{COMMAND_SEGMENT}
"""
RESPONSE_FORMAT = '''
This is the format of the response you will make in order to solve the current issue.
You will be given multiple iterations to complete this task so break it into steps and solve them one by one.
Your output must contain the following:
- First, think about what your next action should be and plan it out. you will have a memory of your thoughts so you can use this to remember things for the next step.
- Second, create a piece of code that will execute your next action based on the thoughts you have.
- The code MUST be surrounded in triple back ticks EXACTLY like this: ```\n<code>\n```
This is a template using the format described above
Items in <> are suggestions for you, fill them out based on the context of the problem you are solving.
Format:
Thoughts:
<Provide clear and concise thoughts on the next steps to take, highlighting any important details or context that should be remembered.>
Action to execute:
```
<command> <params>
```
Notes:
- If you give more than one command as your output then only the last command in your output will be executed.
- To execute multiple commands you should write them down in your thoughts so you can remember it on the next step and execute them then.
- The only commands you are not capable of executing are interactive commands like `python` or `vim`.
- When you have finished the task you should run the `exit` command so the system knows you have finished.
- The write command requires proper indentation in the content section ex. `write hw.py def hello():\n print(\'Hello World\')` this is how you would have to format your write command.
- The white spaces matter as the code changes will be added to the code so they must have proper syntax.
'''
SYSTEM_MESSAGE = f'''
SYSTEM INFO:
You are an autonomous coding agent, here to provide solutions for coding issues. I have been designed to assist you with a wide range of programming tasks, from code editing and debugging to testing and deployment. I have access to a variety of tools and commands that I can use to help you solve problems efficiently.
{DOCUMENTATION}
{RESPONSE_FORMAT}
'''.strip()
GENERAL_GUIDELINES = '''INSTRUCTIONS:
Now, you're going to solve this issue on your own. You can use any bash commands or custom commands you wish to complete your task. Edit all the files you need to and run any checks or tests that you want.
Remember, YOU CAN ONLY ENTER ONE COMMAND AT A TIME. You should always wait for feedback after every command.
When you're satisfied with all of the changes you've made, you can indicate that you are done by running the exit command.
Note however that you cannot use any interactive session commands (e.g. python, vim) in this environment, but you can write scripts and run them. E.g. you can write a python script and then run it with `python <script_name>.py`.
NOTE ABOUT THE write COMMAND: Indentation really matters! When editing a file, make sure to insert appropriate indentation before each line!
IMPORTANT TIPS:
1. Reproduce the bug: Always start by trying to replicate the bug that the issue discusses. If the issue includes code for reproducing the bug, we recommend that you re-implement that in your environment and run it to ensure you can reproduce the bug. Then, start trying to fix it. When you think you've fixed the bug, re-run the bug reproduction script to make sure that the issue has indeed been resolved.
If the bug reproduction script does not print anything when it successfully runs, we recommend adding a print("Script completed successfully, no errors.") command at the end of the file, so that you can be sure the script ran fine all the way through.
2. Try different commands: If you run a command and it doesn't work, try running a different command. A command that did not work once will not work the second time unless you modify it.
3. Navigate large files: If you open a file and need to get to an area around a specific line that is not in the first 100 lines, say line 583, you would use the 'read' command like this: 'read <file> 583'. This is a much faster way to read through the file.
4. Handle input files: If the bug reproduction script requires inputting/reading a specific file, such as 'buggy-input.png', and you'd like to understand how to input that file, conduct a search in the existing repository code to see whether someone else has already done that. Do this by running the command: 'search_dir "buggy-input.png"'. If that doesn't work, use the Linux 'find' command.
5. Understand your context: Always make sure to look at the currently open file and the current working directory. The currently open file might be in a different directory than the working directory.
6. Verify your edits: When editing files, it is easy to accidentally specify a wrong line number or to write code with incorrect indentation. Always check the code after you issue an edit to make sure that it reflects what you wanted to accomplish. If it didn't, issue another command to fix it.
7. Thoroughly test your solution: After making any changes to fix a bug, be sure to thoroughly test your solution to ensure the bug has been resolved. Re-run the bug reproduction script and verify that the issue has been addressed.
'''
def NO_ACTION(latest): return f'''
You did not include any action to take in your most recent output:
===== Output ======
{latest}
==== End Output ===
Remember these are the custom commands you can use:
{DOCUMENTATION}
Lets try that again, it is very important that you adhere to the output format
This time, be sure to use the exact format below, replacing anything in <> with the appropriate value(s):
{RESPONSE_FORMAT}
It is crucial you use the format provided as the output will be parsed automatically.
'''
def file_info(dir: str, file: str, line: int):
res = 'Here is an overview of your current workspace:\n'
res += f'\t- Working Directory: {dir}'
if file:
res += f'\t- Open File: {file}, Lines {line}:{line+100}'
return res
def STEP_PROMPT(task, dir, file, line_num): return f'''
{GENERAL_GUIDELINES}
You are currently trying to complete this task:
{task}
{file_info(dir, file, line_num)}
Keep all of the guidelines above in mind when you are thinking and making code.
Please come up with a thought and action based on your current task and latest steps.
'''.strip()
def MEMORY_FORMAT(act, obs): return f'''
You performed this action:
'{act}'
The following happened as a result:
{obs}
'''.strip()
def CONTEXT_PROMPT(memory, window):
res = f'These are your past {window} memories:\n'
window_size = window if len(memory) > window else len(memory)
cur_mems = memory[-window_size:]
res += '===== Memories =====\n'
for idx, mem in enumerate(cur_mems):
res += f'\nMemory {idx}:\n{mem}\n'
res += '======= End =======\n'
res += 'Use these memories to provide additional context to the problem you are solving.\n'
return res

View File

@@ -18,9 +18,9 @@ class AgentRecallAction(ExecutableAction):
query: str
action: str = ActionType.RECALL
def run(self, controller: "AgentController") -> AgentRecallObservation:
def run(self, controller: 'AgentController') -> AgentRecallObservation:
return AgentRecallObservation(
content="Recalling memories...",
content='Recalling memories...',
memories=controller.agent.search_memory(self.query),
)
@@ -34,7 +34,7 @@ class AgentThinkAction(NotExecutableAction):
thought: str
action: str = ActionType.THINK
def run(self, controller: "AgentController") -> "Observation":
def run(self, controller: 'AgentController') -> 'Observation':
raise NotImplementedError
@property
@@ -45,9 +45,9 @@ class AgentThinkAction(NotExecutableAction):
@dataclass
class AgentEchoAction(ExecutableAction):
content: str
action: str = "echo"
action: str = 'echo'
def run(self, controller: "AgentController") -> "Observation":
def run(self, controller: 'AgentController') -> 'Observation':
return AgentMessageObservation(self.content)
@property
@@ -58,7 +58,6 @@ class AgentEchoAction(ExecutableAction):
@dataclass
class AgentSummarizeAction(NotExecutableAction):
summary: str
action: str = ActionType.SUMMARIZE
@property
@@ -70,7 +69,7 @@ class AgentSummarizeAction(NotExecutableAction):
class AgentFinishAction(NotExecutableAction):
action: str = ActionType.FINISH
def run(self, controller: "AgentController") -> "Observation":
def run(self, controller: 'AgentController') -> 'Observation':
raise NotImplementedError
@property

View File

@@ -1,49 +1,170 @@
import os
import subprocess
import sys
from pathlib import Path
from typing import Optional
from dataclasses import dataclass
from opendevin.observation import FileReadObservation, FileWriteObservation
from opendevin.observation import (
FileReadObservation,
FileWriteObservation,
AgentErrorObservation,
Observation
)
from opendevin.schema import ActionType
from .base import ExecutableAction
# This is the path where the workspace is mounted in the container
# The LLM sometimes returns paths with this prefix, so we need to remove it
PATH_PREFIX = "/workspace/"
PATH_PREFIX = '/workspace/'
def resolve_path(base_path, file_path):
def validate_file_content(file_path: str, content: str) -> Optional[str]:
"""
Validates the content of a code file by checking for syntax errors.
Args:
file_path (str): The full path to the file being validated.
content (str): The content of the file to be validated.
Returns:
str or None: Error message if there are syntax errors, otherwise None.
"""
file_extension = os.path.splitext(
file_path)[1][1:] # Get the file extension without the dot
# Determine the appropriate validation command based on the file extension
validation_commands = {
'py': [sys.executable, '-c', 'import sys; compile(sys.stdin.read(), "<string>", "exec")'],
'js': ['node', '-c', '-'],
'java': ['javac', '-encoding', 'UTF-8', '-Xlint:all', '-'],
'cpp': ['g++', '-std=c++11', '-Wall', '-Wextra', '-Werror', '-x', 'c++', '-c', '-'],
'c': ['gcc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-x', 'c', '-c', '-']
}
ignore_types = ['txt', 'md', 'doc', 'pdf']
if file_extension in ignore_types:
return None
elif file_extension in validation_commands:
try:
# Run the validation command and capture the output
subprocess.run(
validation_commands[file_extension],
input=content.encode(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True, check=True
)
except subprocess.CalledProcessError as e:
return e.stderr.strip()
else:
return None
else:
# If the file extension is not recognized, return a default error message
return f'Unsupported file type: {file_extension}'
def resolve_path(base_path: str, file_path: str) -> str:
if file_path.startswith(PATH_PREFIX):
file_path = file_path[len(PATH_PREFIX) :]
file_path = file_path[len(PATH_PREFIX):]
return os.path.join(base_path, file_path)
@dataclass
class FileReadAction(ExecutableAction):
"""
Reads a file from a given path up to 100 lines.
Default lines 0:100
"""
path: str
start_index: int = 0
max_lines: int = 100
action: str = ActionType.READ
def run(self, controller) -> FileReadObservation:
path = resolve_path(controller.workdir, self.path)
with open(path, "r", encoding="utf-8") as file:
return FileReadObservation(path=path, content=file.read())
# def run(self, controller) -> FileReadObservation:
# path = resolve_path(controller.workdir, self.path)
def run(self):
path = resolve_path('./workspace', self.path)
if not os.path.exists(path):
return FileReadObservation(path=path, content='File not found')
try:
all_lines = []
with open(path, 'r', encoding='utf-8') as file:
for line in file:
all_lines.append(line.strip('\n'))
total_lines = len(all_lines)
if total_lines >= self.max_lines:
end_index = self.start_index + self.max_lines - 1 if total_lines - \
self.start_index - self.max_lines >= 0 else -1
code_slice = all_lines[self.start_index - 1: end_index]
else:
code_slice = all_lines[:]
code_view = '\n'.join(code_slice)
except (IOError, UnicodeDecodeError) as e:
return FileReadObservation(path=path, content=f'Error reading file: {e}')
return FileReadObservation(path=path, content=code_view)
@property
def message(self) -> str:
return f"Reading file: {self.path}"
return f'Reading file: {self.path}'
@dataclass
class FileWriteAction(ExecutableAction):
path: str
content: str
start: int
end: int
action: str = ActionType.WRITE
def run(self, controller) -> FileWriteObservation:
whole_path = resolve_path(controller.workdir, self.path)
with open(whole_path, "w", encoding="utf-8") as file:
file.write(self.content)
return FileWriteObservation(content="", path=self.path)
def run(self, controller) -> Observation:
full_path = resolve_path(controller.workdir, self.path)
parent_dir = os.path.dirname(full_path)
Path(parent_dir).mkdir(parents=True, exist_ok=True)
all_lines = []
try:
with open(full_path, 'r', encoding='utf-8') as file:
for line in file:
all_lines.append(line.strip('\n'))
except (IOError, UnicodeDecodeError):
all_lines = []
# Split the content into lines
new_lines = self.content.split('\n')
# Check if the start and end indices are valid
if self.start < 0 or self.end < 0:
return AgentErrorObservation(content=f'Invalid start or end index: {self.start}, {self.end}')
elif self.start <= len(all_lines) and self.start + len(new_lines) <= len(all_lines):
new_file_lines = all_lines[:self.start-1] + \
new_lines + all_lines[len(all_lines)-1:]
elif self.start <= len(all_lines) and self.start + len(new_lines) > len(all_lines):
new_file_lines = all_lines[:self.start-1] + new_lines
elif self.start > len(all_lines):
new_file_lines = all_lines + \
['' for i in range(len(all_lines), self.start-1)] + new_lines
new_file_content = '\n'.join(new_file_lines)
validation_status = validate_file_content(full_path, new_file_content)
if not validation_status:
try:
with open(full_path, 'w', encoding='utf-8') as file:
file.write(new_file_content)
except IOError as e:
return AgentErrorObservation(content=f'Error writing file: {e}')
return FileWriteObservation(content=self.content, path=self.path)
else:
return AgentErrorObservation(content=validation_status)
@property
def message(self) -> str:
return f"Writing file: {self.path}"
return f'Writing file: {self.path}'

View File

@@ -58,10 +58,10 @@ def print_with_color(text: Any, print_type: str = '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)
print(f'\n{print_type.upper()}:\n{str(text)}', flush=True)
else:
print(
colored(f"\n{print_type.upper()}:\n", color, attrs=['bold'])
colored(f'\n{print_type.upper()}:\n', color, attrs=['bold'])
+ colored(str(text), color),
flush=True,
)
@@ -105,7 +105,7 @@ class AgentController:
async def start_loop(self, task: str):
finished = False
plan = Plan(task)
self.state = State(plan)
self.state = State(plan, self.workdir)
for i in range(self.max_iterations):
try:
finished = await self.step(i)

View File

@@ -12,41 +12,42 @@ class Command:
def parse_command_file() -> str | None:
if not os.path.exists("commands.sh"):
if not os.path.exists('evaluation/SWE-bench/commands.sh'):
print('Error reading commands, could not find commands.sh')
return None
content = open("commands.sh", "r").read()
lines = content.split("\n")
content = open('evaluation/SWE-bench/commands.sh', 'r').read()
lines = content.split('\n')
commands: list[Command] = []
idx = 0
docs: list[str] = []
while idx < len(lines):
line = lines[idx]
idx += 1
if line.startswith("# "):
if line.startswith('# '):
docs.append(line[2:])
elif line.strip().endswith("() {"):
elif line.strip().endswith('() {'):
name = line.split()[0][:-2]
while lines[idx].strip() != "}":
while lines[idx].strip() != '}':
idx += 1
docstring, signature = None, name
docs_dict = yaml.safe_load("\n".join(docs).replace("@yaml", ""))
docs_dict = yaml.safe_load('\n'.join(docs).replace('@yaml', ''))
if docs_dict is not None:
docstring = docs_dict.get("docstring")
arguments = docs_dict.get("arguments", None)
if "signature" in docs_dict:
signature = docs_dict["signature"]
docstring = docs_dict.get('docstring')
arguments = docs_dict.get('arguments', None)
if 'signature' in docs_dict:
signature = docs_dict['signature']
else:
if arguments is not None:
for param, settings in arguments.items():
if "required" in settings:
signature += f" <{param}>"
if 'required' in settings:
signature += f' <{param}>'
else:
signature += f" [<{param}>]"
signature += f' [<{param}>]'
command = Command(name, docstring, signature)
commands.append(command)
docs = []
function_docs = ""
function_docs = ''
for cmd in commands:
if cmd.docstring is not None:
function_docs += f"{cmd.signature or cmd.name} - {cmd.docstring}\n"
function_docs += f'{cmd.signature or cmd.name} - {cmd.docstring}\n'
return function_docs

View File

@@ -11,10 +11,16 @@ from opendevin.observation import (
CmdOutputObservation,
)
@dataclass
class State:
plan: Plan
working_dir: str
iteration: int = 0
background_commands_obs: List[CmdOutputObservation] = field(default_factory=list)
file_name: str = ''
cur_line: 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)

View File

@@ -76,7 +76,7 @@ def test_browse_url_action_serialization_deserialization():
def test_file_read_action_serialization_deserialization():
original_action_dict = {
'action': 'read',
'args': {'path': '/path/to/file.txt'}
'args': {'path': '/path/to/file.txt', 'start_index': 0}
}
serialization_deserialization(original_action_dict, FileReadAction)
@@ -84,7 +84,7 @@ def test_file_read_action_serialization_deserialization():
def test_file_write_action_serialization_deserialization():
original_action_dict = {
'action': 'write',
'args': {'path': '/path/to/file.txt', 'content': 'Hello world'}
'args': {'path': '/path/to/file.txt', 'content': 'Hello world', 'start': 0, 'end': 1}
}
serialization_deserialization(original_action_dict, FileWriteAction)