chlorine 2024-03-08 07:23:41 -07:00 zatwierdzone przez GitHub
commit 6552cc4dd3
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
21 zmienionych plików z 762 dodań i 0 usunięć

1
.gitignore vendored
Wyświetl plik

@ -3,3 +3,4 @@ venv
.env
env/
.vscode/
wolverine/GoLang/internal/main/.env

Wyświetl plik

@ -62,3 +62,31 @@ This is just a quick prototype I threw together in a few hours. There are many p
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=biobootloader/wolverine&type=Date)](https://star-history.com/#biobootloader/wolverine)
# Go Usage:
#### The Wolverine supports .go files to be executed now!
## Setup step by step:
1) git clone https://....<the_link>
2) cd wolverine/wolverine/GoLang/internal/main
3) go mod install
4) go build -o ../../../../../your_wish_folder/wolverine.exe
5) cd ../../../../../your_wish_folder
6) > .env
7) fill the .env file like in the example below, but with your personal data
### Example .env:
OPENAI_API_KEY=gorinfwe:mfwbevnmowemo9fn20f439vn03v4
GPT_MODEL=text-davinci-003
ATTEMPTS_TO_TRY=15
#### Remember that each attempt is a request to GPT so think twice about \<ATTEMPTS_TO_TRY> value
#### GPT 4 is the most preferable model here, but you can try to use any model
## Example Usage
./wolverine.exe main
#### main here is your filename WITHOUT .go extension
#### If you see the "Success" message, then you must have obtained a file enterFilename+"__fixed".go>, and it's free of any compile errors. So you can freely run it
go run main__fixed.go

Wyświetl plik

@ -0,0 +1,14 @@
module wolverine
go 1.20
require (
github.com/fatih/color v1.15.0
github.com/joho/godotenv v1.5.1
)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/sys v0.6.0 // indirect
)

Wyświetl plik

@ -0,0 +1,12 @@
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

Wyświetl plik

@ -0,0 +1,84 @@
package main
import (
"embed"
"fmt"
"github.com/joho/godotenv"
"log"
"os"
"strconv"
"wolverine/internal/service"
"wolverine/internal/service/healFile"
)
//go:embed prompt.txt
var promptContent embed.FS
func main() {
content, err := promptContent.ReadFile("prompt.txt")
if err != nil {
fmt.Println("Failed to read prompt.txt file")
log.Println(err)
return
}
prompt := string(content)
godotenv.Load()
gptModel := os.Getenv("GPT_MODEL")
apiKey := os.Getenv("OPENAI_API_KEY")
attemptsToTryString := os.Getenv("ATTEMPTS_TO_TRY")
if gptModel == "" || apiKey == "" {
log.Println("You need to set GPT_MODEL and OPENAI_API_KEY environment variables to run the program.")
return
}
attemptsToTry, err := strconv.Atoi(attemptsToTryString)
if err != nil || attemptsToTryString == "" {
log.Println("ATTEMPTS_TO_TRY environment variable is invalid or not set. It should be an integer.")
log.Println(err)
return
}
sourceFilename, err := service.ReceiveFile()
if err != nil {
fmt.Println("Failed to extract a filename from the inputted string")
log.Println(err)
return
}
healedFilename := sourceFilename + "__fixed.go"
sourceFilename += ".go"
_, err = os.Stat(sourceFilename)
if os.IsNotExist(err) {
log.Println("The file you entered doesn't exist. Enter another one and try again.")
return
}
// prepare file to be filled code with changes
err = service.PrepareNewFile(healedFilename)
if err != nil {
fmt.Println("Failed to prepare a new file to have the code with changes")
log.Println(err)
return
}
// check the inputted model
modelIsValid := service.ValidateGPTModel(apiKey, gptModel)
if !modelIsValid {
log.Println("The GPT model you entered is invalid or doesn't exist. Enter another one and try again.")
return
}
err = healFile.HealFile(sourceFilename, healedFilename, apiKey, gptModel, prompt, attemptsToTry)
if err != nil {
fmt.Println("Failed to heal the file")
log.Println(err)
return
}
fmt.Println("\n+++ <<-- Success! -->> +++")
fmt.Println("Now you can successfully run " + healedFilename)
}

Wyświetl plik

@ -0,0 +1,56 @@
You are part of an elite automated software fixing team. You will be given a script followed by the arguments it provided and the stack trace of the error it produced. Your job is to figure out what went wrong and suggest changes to the code.
Because you are part of an automated system, the format you respond in is very strict and sharp. You must provide changes in JSON format, using one of 3 actions: 'Replace', 'Delete', or 'InsertAfter'. 'Delete' will remove that line from the code. 'Replace' will replace the existing line with the content you provide. 'InsertAfter' will insert the new lines you provide AFTER the code is already at the specified line number. For instance, if you want to write line as <x>th, so "line" field should be <x-1> to write to the <x> line etc. For multi-line insertions or replacements, provide the content as a single string with '\n' as the newline character. The first line in each file is given line number 1. Edits will be applied in reverse line order so that line numbers won't be impacted by other edits.
In addition to the changes, please also provide short explanations of what went wrong. A single explanation message is required, but if you think it's helpful, feel free to provide more explanation messages groups of more complicated changes, but each explanations object must have corresponding action object, connected with unique id field. Each operation item from operations block must have same operation type. Be careful to use proper indentation and spacing in your changes. An example response could be:
Be ABSOLUTELY SURE to include the CORRECT INDENTATION when making replacements and respond exceptionally in JSON format.
example response:
{
"explanations": [
{
"id": 1,
"messages": [
"This is just an example, this would usually be a brief explanation of what went wrong"
]
},
{
"id": 2,
"messages": [
"This is another example, this would usually be a brief explanation of what went wrong",
"another one"
]
},
{
"id": 3,
"messages": [
"This is a third example, this would usually be a brief explanation of what went wrong",
"another one",
"another one"
]
}
],
"actions": [
{
"id": 1,
"operations": [
{"operation": "Delete", "line": 20, "content": ""},
{"operation": "Delete", "line": 21, "content": ""},
{"operation": "Delete", "line": 22, "content": ""}
]
},
{
"id": 2,
"operations": [
{"operation": "InsertAfter", "line": 48, "content": "x := 1\ny := 2\nz := x * y"}
]
},
{
"id": 3,
"operations": [
{"operation": "Replace", "line": 50, "content": "return nil"}
]
}
]
}

Wyświetl plik

@ -0,0 +1,108 @@
package healFile
import (
"fmt"
"strings"
"wolverine/pkg/cli"
)
func applyChanges(sourceFilename, targetFilename string, changes GPTResponse) error {
fileLines, err := readFileLines(sourceFilename)
if err != nil {
return err
}
sortChanges(&changes)
// define entities only after sorting the object
explanations := changes.Explanations
actions := changes.Actions
for _, action := range actions {
switch action.Operations[0].Operation {
case DeleteOperationType:
fmt.Println("Delete Operation:")
printExplanations(explanations, action, cli.PrintRed)
for _, operation := range action.Operations {
deleteLine(&fileLines, operation.Line)
}
case InsertOperationType:
fmt.Println("Insert Operation:")
printExplanations(explanations, action, cli.PrintGreen)
for _, operation := range action.Operations {
insertLine(&fileLines, operation.Line, operation.Content)
}
case ReplaceOperationType:
fmt.Println("Replace Operation:")
printExplanations(explanations, action, cli.PrintYellow)
for _, operation := range action.Operations {
replaceLine(&fileLines, operation.Line, operation.Content)
}
}
}
entireCode := strings.Join(fileLines, "\n")
err = writeToExistingFile(targetFilename, entireCode)
if err != nil {
return err
}
return nil
}
func sortChanges(changes *GPTResponse) {
// reverse the actions
for i := 0; i < len(changes.Actions)/2; i++ {
changes.Actions[i], changes.Actions[len(changes.Actions)-i-1] = changes.Actions[len(changes.Actions)-i-1], changes.Actions[i]
}
// reverse the explanations
for i := 0; i < len(changes.Explanations)/2; i++ {
changes.Explanations[i], changes.Explanations[len(changes.Explanations)-i-1] = changes.Explanations[len(changes.Explanations)-i-1], changes.Explanations[i]
}
}
func deleteLine(fileLines *[]string, line int) {
// GPT would respond like 1st line has an error, but we work with 0th item, not 1st
line--
*fileLines = append((*fileLines)[:line], (*fileLines)[line+1:]...)
}
func insertLine(fileLines *[]string, line int, content string) {
// GPT would respond like 1st line has an error, but we work with 0th item, not 1st
line--
*fileLines = append((*fileLines)[:line+1], append([]string{content}, (*fileLines)[line+1:]...)...)
}
func replaceLine(fileLines *[]string, line int, content string) {
// GPT would respond like 1st line has an error, but we work with 0th item, not 1st
line--
(*fileLines)[line] = content
}
func printExplanations(explanations []GPTExplanation, action GPTAction, printFunc func(string)) {
currentExplanationIndex := -1
for i, explanation := range explanations {
if explanation.Id == action.Id {
currentExplanationIndex = i
break
}
}
if currentExplanationIndex == -1 {
fmt.Println("The explanation is not provided.")
} else {
for _, explanation := range explanations[currentExplanationIndex].Messages {
fmt.Println(explanation)
}
}
for _, operation := range action.Operations {
printFunc(operation.Content)
}
}

Wyświetl plik

@ -0,0 +1,23 @@
package healFile
func attempt(sourceFilename, targetFilename, compileError, apiToken, model, prompt string) error {
// get file content
code, err := getFileContent(sourceFilename)
if err != nil {
return err
}
// make request to gpt
gptResponse, err := makeRequestToGPT(code, compileError, apiToken, model, prompt)
if err != nil {
return err
}
// apply changes: write them to the targetFilename
err = applyChanges(sourceFilename, targetFilename, gptResponse)
if err != nil {
return err
}
return nil
}

Wyświetl plik

@ -0,0 +1,57 @@
package healFile
import (
"bufio"
"io"
"os"
)
func writeToExistingFile(filename, content string) error {
// Open file with O_TRUNC flag to truncate the file before writing
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(content)
if err != nil {
return err
}
return nil
}
func getFileContent(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
code, err := io.ReadAll(file)
if err != nil {
return "", err
}
return string(code), nil
}
func readFileLines(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return lines, nil
}

Wyświetl plik

@ -0,0 +1,48 @@
package healFile
import (
"errors"
)
func HealFile(sourceFilename, resultFilename, apiToken, model, prompt string, attemptsToTry int) error {
var compileError string
if isCompilable(sourceFilename, &compileError) {
return nil
}
// initial attempt
err := attempt(sourceFilename, resultFilename, compileError, apiToken, model, prompt)
if err != nil {
return err
}
if isCompilable(resultFilename, &compileError) {
return nil
}
// if didn't work, try to heal file, which you are working with
sourceFilename = resultFilename
attempts := 1
for {
if attempts >= attemptsToTry {
return errors.New(string(attemptsToTry) + " attempts to heal file failed")
}
attempts++
err = attempt(sourceFilename, resultFilename, compileError, apiToken, model, prompt)
if err != nil {
return err
}
if isCompilable(resultFilename, &compileError) {
break
}
}
return nil
}

Wyświetl plik

@ -0,0 +1,20 @@
package healFile
import (
"fmt"
"os/exec"
)
func isCompilable(filename string, compileError *string) bool {
cmd := exec.Command("go", "run", filename)
output, err := cmd.CombinedOutput()
if err != nil {
*compileError = string(output)
fmt.Println("The file contains compile errors:")
fmt.Println(*compileError)
fmt.Printf("\nWait for our brainstorm outcome...\n\n")
}
return err == nil
}

Wyświetl plik

@ -0,0 +1,69 @@
package healFile
import (
"bytes"
"encoding/json"
"net/http"
)
func makeRequestToGPT(code, compileError, apiToken, model string, prompt string) (GPTResponse, error) {
if compileError == "" {
prompt += "\n\nHere is the script that needs fixing:\n\n" +
code + "\n\n" +
"Please provide your suggested changes, and remember to stick to the " +
"exact format as described above."
} else {
prompt += "\n\nHere is the script that needs fixing:\n\n" +
code + "\n\n" +
"Here is the error message:\n\n" +
compileError + "\n" +
"Please provide your suggested changes, and remember to stick to the " +
"exact format as described above."
}
var request = Request{
Model: model,
Prompt: prompt,
MaxTokens: 1000,
}
reqBody, err := json.Marshal(request)
if err != nil {
return GPTResponse{}, err
}
url := "https://api.openai.com/v1/completions"
req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody))
if err != nil {
return GPTResponse{}, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiToken)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return GPTResponse{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return GPTResponse{}, err
}
var response Response
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return GPTResponse{}, err
}
gptResponse, err := validateResponse(response.Choices[0].Text)
if err != nil {
return GPTResponse{}, err
}
return gptResponse, nil
}

Wyświetl plik

@ -0,0 +1,44 @@
package healFile
type Request struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
MaxTokens int `json:"max_tokens"`
}
type Response struct {
Model string `json:"model"`
Choices []struct {
Text string `json:"text"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
}
type GPTExplanation struct {
Id int `json:"id"`
Messages []string `json:"messages"`
}
type GPTAction struct {
Id int `json:"id"`
Operations []GPTOperation `json:"operations"`
}
type GPTOperation struct {
Operation string `json:"operation"`
Line int `json:"line"`
Content string `json:"content"`
}
type GPTResponse struct {
Explanations []GPTExplanation `json:"explanations"`
Actions []GPTAction `json:"actions"`
}
const DeleteOperationType = "Delete"
const InsertOperationType = "InsertAfter"
const ReplaceOperationType = "Replace"

Wyświetl plik

@ -0,0 +1,22 @@
package healFile
import (
"encoding/json"
)
func validateResponse(response string) (GPTResponse, error) {
/*
The original idea of recursive forcing response
to be in JSON format was abandoned, because there is
no need to make it so complicated. Anyway we make
requests until we get valid JSON response.
*/
var jsonGPTResponse GPTResponse
err := json.Unmarshal([]byte(response), &jsonGPTResponse)
if err != nil {
return GPTResponse{}, err
}
return jsonGPTResponse, nil
}

Wyświetl plik

@ -0,0 +1,26 @@
package service
import (
"errors"
"os"
)
func PrepareNewFile(backupFilename string) error {
err := createNewFile(backupFilename)
if err != nil {
return errors.New("failed to prepare file with fixes")
}
return nil
}
func createNewFile(backupFilename string) error {
// Create the file
file, err := os.Create(backupFilename)
if err != nil {
return err
}
defer file.Close()
return nil
}

Wyświetl plik

@ -0,0 +1,26 @@
package service
import (
"encoding/json"
"errors"
"os"
)
type Config struct {
OPENAI_API_KEY string
GPT_MODEL string
}
func ReadConfig(path string, container *Config) error {
configFile, err := os.Open(path)
if err != nil {
return errors.New("Failed to open config.json\nMake sure you have set all necessary fields to it and try again.")
}
defer configFile.Close()
err = json.NewDecoder(configFile).Decode(container)
if err != nil {
return errors.New("Failed to read and decode config.json\nMake sure you have set all necessary fields to it and try again.")
}
return nil
}

Wyświetl plik

@ -0,0 +1,26 @@
package service
import (
"errors"
"os"
"regexp"
)
func ReceiveFile() (string, error) {
args := os.Args[1:]
if len(args) != 1 {
return "", errors.New("entered invalid flags")
} else {
filename := args[0]
if !isValidFilename(filename) {
return "", errors.New("entered invalid filename")
}
return filename, nil
}
}
func isValidFilename(filename string) bool {
validFilenameRegex := regexp.MustCompile(`^[a-zA-Z0-9_.-]*$`)
return validFilenameRegex.MatchString(filename)
}

Wyświetl plik

@ -0,0 +1,22 @@
package service
import (
"wolverine/pkg/request"
)
func ValidateGPTModel(apiKey string, model string) bool {
modelsList, err := request.ModelsList(apiKey)
// if something went wrong due to requesting the list -> acts like invalid inp
if err != nil {
return false
}
// looking for the model
for _, m := range modelsList {
if m.Id == model {
return true
}
}
return false
}

Wyświetl plik

@ -0,0 +1,24 @@
package cli
import (
"fmt"
"github.com/fatih/color"
)
func PrintRed(message string) {
fmt.Println(" ")
color.Red(message)
fmt.Println(" ")
}
func PrintGreen(message string) {
fmt.Println(" ")
color.Green(message)
fmt.Println(" ")
}
func PrintYellow(message string) {
fmt.Println(" ")
color.Yellow(message)
fmt.Println(" ")
}

Wyświetl plik

@ -0,0 +1,5 @@
package request
func Completion() {
}

Wyświetl plik

@ -0,0 +1,47 @@
package request
import (
"encoding/json"
"io"
"net/http"
)
type ModelStruct struct {
Id string `json:"id"`
}
type ModelsListStruct struct {
Data []ModelStruct `json:"data"`
}
var targetUrl = "https://api.openai.com/v1/models"
func ModelsList(apiKey string) ([]ModelStruct, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", targetUrl, nil)
if err != nil {
return []ModelStruct{}, err
}
req.Header.Add("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return []ModelStruct{}, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return []ModelStruct{}, err
}
var modelsList ModelsListStruct
err = json.Unmarshal(body, &modelsList)
if err != nil {
return []ModelStruct{}, err
}
return modelsList.Data, nil
}