Skip to content

Commit

Permalink
feat: add leetgo fix to fix your code using OpenAI GPT-3 (#114)
Browse files Browse the repository at this point in the history
* feat: add `leetgo fix` to fix your code using OpenAI GPT-3

* fix lint
  • Loading branch information
j178 authored Feb 17, 2023
1 parent c30ecee commit 8550d22
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Available Commands:
info Show question info
test Run question test cases
submit Submit solution
fix Use OpenAI GPT-3 API to fix your solution code (just for fun)
edit Open solution in editor
extract Extract solution code from generated file
contest Generate contest questions
Expand Down
1 change: 1 addition & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Available Commands:
info Show question info
test Run question test cases
submit Submit solution
fix Use OpenAI GPT-3 API to fix your solution code (just for fun)
edit Open solution in editor
extract Extract solution code from generated file
contest Generate contest questions
Expand Down
138 changes: 138 additions & 0 deletions cmd/fix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package cmd

import (
"context"
"errors"
"fmt"
"os"
"regexp"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/PullRequestInc/go-gpt3"
"github.com/charmbracelet/glamour"
"github.com/hashicorp/go-hclog"
"github.com/spf13/cobra"

"github.com/j178/leetgo/lang"
"github.com/j178/leetgo/leetcode"
)

// Use OpenAI GPT-3 API to fix solution code

var fixCmd = &cobra.Command{
Use: "fix qid",
Short: "Use OpenAI GPT-3 API to fix your solution code (just for fun)",
Example: `leetgo fix 429`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c := leetcode.NewClient(leetcode.WithCredentials(leetcode.CredentialsFromConfig()))
qs, err := leetcode.ParseQID(args[0], c)
if err != nil {
return err
}
if len(qs) > 1 {
return fmt.Errorf("multiple questions found")
}
q := qs[0]
err = q.Fulfill()
if err != nil {
return err
}

code, err := lang.GetSolutionCode(q)
if err != nil {
return err
}
response, err := askOpenAI(cmd, q, code)
if err != nil {
return err
}
response = "# Here is the fix from OpenAI GPT-3 API\n" + response
output, err := glamour.Render(response, "dark")
if err != nil {
return err
}
cmd.Println(output)

var accept bool
err = survey.AskOne(
&survey.Confirm{
Message: "Do you want to accept the fix?",
}, &accept,
)
if err != nil {
return err
}
if accept {
fixedCode, err := extractCode(response)
if err != nil {
return err
}
err = lang.UpdateSolutionCode(q, fixedCode)
if err != nil {
return err
}
}
return nil
},
}

const fixPrompt = `Given a LeetCode problem %s, the problem description below is wrapped in <question> and </question> tags. The solution code is wrapped in <code> and </code> tags:
<question>
%s
</question>
I have written the following solution:
%s
Please find anything incorrect and fix it or improve it. Mark sure to wrap the fixed code in markdown code block with corresponding language specifier.
`

var errNoFix = errors.New("no fix found")

func askOpenAI(cmd *cobra.Command, q *leetcode.QuestionData, code string) (string, error) {
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
return "", errors.New("missing OPENAI_API_KEY environment variable")
}
client := gpt3.NewClient(apiKey)
prompt := fmt.Sprintf(
fixPrompt,
q.Title,
q.GetFormattedContent(),
code,
)
hclog.L().Debug("requesting openai", "prompt", prompt)
spin := newSpinner(cmd.OutOrStdout())
spin.Suffix = " Waiting for OpenAI..."
spin.Start()
defer spin.Stop()

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
resp, err := client.CompletionWithEngine(
ctx, gpt3.TextDavinci003Engine, gpt3.CompletionRequest{
Prompt: []string{prompt},
MaxTokens: gpt3.IntPtr(3000),
Temperature: gpt3.Float32Ptr(0),
},
)
if err != nil {
return "", err
}
if len(resp.Choices) == 0 {
return "", errNoFix
}
hclog.L().Debug("got response from openai", "response", resp.Choices)
text := resp.Choices[0].Text
return text, nil
}

func extractCode(text string) (string, error) {
fixedCode := regexp.MustCompile("(?s)```\\w+\n(.*)```").FindStringSubmatch(text)
if fixedCode == nil {
return "", errors.New("no code found")
}
return fixedCode[1], nil
}
2 changes: 1 addition & 1 deletion cmd/pick.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ leetgo pick two-sum`,
return err
}
if len(qs) > 1 {
return fmt.Errorf("`leetgo pick` cannot handle multiple contest questions, use `leetgo contest` instead.")
return fmt.Errorf("`leetgo pick` cannot handle multiple contest questions, use `leetgo contest` instead")
}
q = qs[0]
} else {
Expand Down
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func UsageString() string {

func initWorkDir() error {
if dir := os.Getenv("LEETGO_WORKDIR"); dir != "" {
hclog.L().Info("change workdir to LEETGO_WORKDIR", "dir", dir)
hclog.L().Debug("change workdir to LEETGO_WORKDIR", "dir", dir)
return os.Chdir(dir)
}
return nil
Expand Down Expand Up @@ -113,6 +113,7 @@ func initCommands() {
infoCmd,
testCmd,
submitCmd,
fixCmd,
editCmd,
extractCmd,
contestCmd,
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ require (
replace github.com/zellyn/kooky => github.com/j178/kooky v0.0.0-20230203021902-e439c6617a99

require (
github.com/PullRequestInc/go-gpt3 v1.1.11 // indirect
github.com/Velocidex/json v0.0.0-20220224052537-92f3c0326e5a // indirect
github.com/Velocidex/ordereddict v0.0.0-20221110130714-6a7cb85851cd // indirect
github.com/Velocidex/yaml/v2 v2.2.8 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52 v1.2.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89 // indirect
github.com/charmbracelet/glamour v0.6.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand All @@ -55,6 +59,7 @@ require (
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gonuts/binary v0.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
Expand All @@ -66,11 +71,13 @@ require (
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.14.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
Expand All @@ -83,6 +90,8 @@ require (
github.com/subosito/gotenv v1.4.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/yuin/goldmark v1.5.2 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
Expand Down
Loading

0 comments on commit 8550d22

Please sign in to comment.