spenceryonce 2023-04-14 18:37:52 -05:00 zatwierdzone przez GitHub
commit 4c28632510
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
2 zmienionych plików z 120 dodań i 41 usunięć

3
.gitignore vendored
Wyświetl plik

@ -1,4 +1,5 @@
venv
openai_key.txt
wolverine.log
.venv
.env
env/

Wyświetl plik

@ -6,10 +6,12 @@ import shutil
import subprocess
import sys
import openai
import logging
from termcolor import cprint
from dotenv import load_dotenv
# Set up the OpenAI API
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
@ -19,8 +21,17 @@ DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "gpt-4")
with open("prompt.txt") as f:
SYSTEM_PROMPT = f.read()
# Set up logging
def configure_logging():
logging.basicConfig(
filename="wolverine.log",
format="%(asctime)s - %(levelname)s - %(message)s",
level=logging.INFO,
)
# Run the provided script and return the output and return code
def run_script(script_name, script_args):
script_args = [str(arg) for arg in script_args]
try:
@ -73,8 +84,8 @@ def json_validated_response(model, messages):
raise e
return json_response
def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL):
# Send the error to GPT and receive suggestions
def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL, prompt_length_limit=4096):
with open(file_path, "r") as f:
file_lines = f.readlines()
@ -94,6 +105,10 @@ def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL):
"exact format as described above."
)
# Truncate the prompt if it exceeds the limit
if len(prompt) > prompt_length_limit:
prompt = prompt[:prompt_length_limit]
# print(prompt)
messages = [
{
@ -107,47 +122,85 @@ def send_error_to_gpt(file_path, args, error_message, model=DEFAULT_MODEL):
]
return json_validated_response(model, messages)
# Apply the changes suggested by GPT
def apply_changes(file_path, changes: list):
"""
Pass changes as loaded json (list of dicts)
"""
try:
with open(file_path, "r") as f:
original_file_lines = f.readlines()
operation_changes = [change for change in changes if "operation" in change]
explanations = [
change["explanation"] for change in changes if "explanation" in change
]
operation_changes.sort(key=lambda x: x["line"], reverse=True)
file_lines = original_file_lines.copy()
for change in operation_changes:
operation = change["operation"]
line = change["line"]
content = change["content"]
if operation == "Replace":
file_lines[line - 1] = content + "\n"
elif operation == "Delete":
del file_lines[line - 1]
elif operation == "InsertAfter":
file_lines.insert(line, content + "\n")
with open(file_path, "w") as f:
f.writelines(file_lines)
# Print explanations
cprint("Explanations:", "blue")
for explanation in explanations:
cprint(f"- {explanation}", "blue")
# Show the diff
print("\nChanges:")
print_diff(original_file_lines, file_lines)
except Exception as e:
raise Exception(f"Failed to apply changes: {str(e)}")
# Apply a single change suggested by GPT interactively
def apply_change_interactive(file_path, change):
with open(file_path, "r") as f:
original_file_lines = f.readlines()
# Filter out explanation elements
operation_changes = [change for change in changes if "operation" in change]
explanations = [
change["explanation"] for change in changes if "explanation" in change
]
# Sort the changes in reverse line order
operation_changes.sort(key=lambda x: x["line"], reverse=True)
operation = change["operation"]
line = change["line"]
content = change["content"]
file_lines = original_file_lines.copy()
for change in operation_changes:
operation = change["operation"]
line = change["line"]
content = change["content"]
if operation == "Replace":
file_lines[line - 1] = content + "\n"
elif operation == "Delete":
del file_lines[line - 1]
elif operation == "InsertAfter":
file_lines.insert(line, content + "\n")
if operation == "Replace":
file_lines[line - 1] = content + "\n"
elif operation == "Delete":
del file_lines[line - 1]
elif operation == "InsertAfter":
file_lines.insert(line, content + "\n")
print("\nSuggested change:")
print_diff(original_file_lines, file_lines)
with open(file_path, "w") as f:
f.writelines(file_lines)
while True:
decision = input("Do you want to apply this change? (y/n): ").lower()
if decision == "y":
with open(file_path, "w") as f:
f.writelines(file_lines)
logging.info(f"Applied change: {change}")
return True
elif decision == "n":
logging.info(f"Rejected change: {change}")
return False
else:
print("Invalid input. Please enter 'y' or 'n'.")
# Print explanations
cprint("Explanations:", "blue")
for explanation in explanations:
cprint(f"- {explanation}", "blue")
# Show the diff
print("\nChanges:")
# Print the differences between two file contents
def print_diff(original_file_lines, file_lines):
diff = difflib.unified_diff(original_file_lines, file_lines, lineterm="")
for line in diff:
if line.startswith("+"):
@ -157,17 +210,15 @@ def apply_changes(file_path, changes: list):
else:
print(line, end="")
def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL):
def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL, interactive=False):
if revert:
backup_file = script_name + ".bak"
if os.path.exists(backup_file):
shutil.copy(backup_file, script_name)
print(f"Reverted changes to {script_name}")
sys.exit(0)
return
else:
print(f"No backup file found for {script_name}")
sys.exit(1)
raise Exception(f"No backup file found for {script_name}")
# Make a backup of the original script
shutil.copy(script_name, script_name + ".bak")
@ -178,9 +229,11 @@ def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL):
if returncode == 0:
cprint("Script ran successfully.", "blue")
print("Output:", output)
logging.info("Script ran successfully.")
break
else:
cprint("Script crashed. Trying to fix...", "blue")
logging.error(f"Script crashed with return code {returncode}.")
print("Output:", output)
json_response = send_error_to_gpt(
@ -189,10 +242,35 @@ def main(script_name, *script_args, revert=False, model=DEFAULT_MODEL):
error_message=output,
model=model,
)
if interactive:
changes = json_response
operation_changes = [change for change in changes if "operation" in change]
explanations = [
change["explanation"] for change in changes if "explanation" in change
]
apply_changes(script_name, json_response)
cprint("Changes applied. Rerunning...", "blue")
for change in operation_changes:
if apply_change_interactive(script_name, change):
cprint("Change applied.", "green")
else:
cprint("Change rejected.", "red")
cprint("Finished applying changes. Rerunning...", "blue")
logging.info("Finished applying changes in interactive mode.")
else:
try:
apply_changes(script_name, json_response)
cprint("Changes applied. Rerunning...", "blue")
logging.info("Changes applied.")
except Exception as e:
raise Exception(f"Failed to fix the script: {str(e)}")
if __name__ == "__main__":
fire.Fire(main)
configure_logging()
try:
fire.Fire(main)
except Exception as e:
print(str(e))
logging.error(f"Error: {str(e)}")
sys.exit(1)