From a532b06c61b9c8be9f9ace03e0b17f8eb291e7e0 Mon Sep 17 00:00:00 2001 From: kevin_mesiab Date: Wed, 17 Jan 2024 00:10:16 -0800 Subject: [PATCH] feat: Make model configurable Introduces a configuration option: CADRE_AI_MODEL and command line switch: `--model` for supplying the OpenAI GPT Model string. 'gpt-4' is the default when non is set or supplied --- README.md | 18 ++++++++++-- main.go | 81 +++++++++++++++++++++++++++++++++++----------------- main_test.go | 15 ++++++++++ 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3a1b3ce..c944a7a 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,15 @@ languages. - **User-Friendly CLI**: Simple and intuitive command-line interface for easy usage. - **Markdown Reports**: Outputs code reviews in markdown for flexible viewing -and integration. + and integration. +- **Configurable Model**: Use the default OpenAI model, or choose another ![Screenshot](./assets/cadre-screenshot.png) ## Review Details -Each diff file is reviewed in isolation. Code reviews are broken into several sections: +Each diff file is reviewed in isolation. Code reviews are broken into several +sections: - Technical Accuracy - Best Practices @@ -81,6 +83,8 @@ or ```bash # Run the application with a GitHub pull request URL and API key ./cadre --url=https://github.com/user/repo/pull/123 --key=your_api_key +./cadre --url=https://github.com/a/repo/pull/123 --model gpt-3.5-turbo-instruct + ``` ### Command-Line Switches @@ -88,8 +92,18 @@ or - `--url`: The GitHub pull request URL. Example: `--url=https://github.com/user/repo/pull/123` - `--key`: Your OpenAI API key. You can also set this using the `OPENAI_API_KEY` environment variable. Example: `--key=your_api_key` +- `--model`: You can specify the (OpenAI Model)[https://platform.openai. + com/docs/models] to use by passing it here. - `--help`: Show help information. +## Configuring The Model + +You can tell Cadre what OpenAI Model to use by passing it via +the command line argument `--model` or by setting the `CADRE_COMPLETION_MODEL` +environment variable. + +The most common models are: `gpt-4` and `gpt-3.5-turbo-1106` + ### Running Tests āœ”ļø ```bash diff --git a/main.go b/main.go index ef3f497..d9a26f7 100644 --- a/main.go +++ b/main.go @@ -13,9 +13,15 @@ import ( "github.com/mkideal/cli" ) +const ( + DefaultFilePerms = 0o644 + DefaultOpenAIModel = "gpt-4-1106-preview" +) + type argT struct { - URL string `cli:"*url" usage:"The GitHub pull request URL"` - ApiKey string `cli:"key" env:"OPENAI_API_KEY" usage:"Your OpenAI API key. Leave this blank to use environment variable OPENAI_API_KEY"` + URL string `cli:"*url" usage:"The GitHub pull request URL"` + ApiKey string `cli:"key" env:"OPENAI_API_KEY" usage:"Your OpenAI API key. Leave this blank to use environment variable OPENAI_API_KEY"` + CompletionModel string `cli:"model" env:"CADRE_COMPLETION_MODEL" usage:"The OpenAI API model to use for code reviews."` } type ReviewedDiff struct { @@ -43,50 +49,68 @@ func main() { } fmt.Printf("šŸ“” Getting pull request from GitHub...\n") - parsedDiffFiles, err := processPullRequest(mergedArgs.URL, &GithubDiffClient{}) - if err != nil { return err } fmt.Printf("\nāŒ› Processing %d diff files. This may take a while...\n\n", len(parsedDiffFiles)) - - reviews, err := getCodeReviews(parsedDiffFiles, "gpt-4-1106-preview", mergedArgs.ApiKey, &OpenAICompletionService{}) + reviews, err := getCodeReviews( + parsedDiffFiles, + argv.CompletionModel, + mergedArgs.ApiKey, + &OpenAICompletionService{}, + ) if err != nil { return err } - for _, review := range reviews { + saveReviews(reviews) + fmt.Println("Done! šŸ") - if review.Error != nil { - fmt.Printf("āš ļø couldn't get the review for %s: %s\n", - path.Base(review.Diff.FilePathNew), - review.Error, - ) + return nil + })) +} - continue - } +func saveReviews(reviews []ReviewedDiff) { + for _, review := range reviews { - filename := path.Base(review.Diff.FilePathNew) + ".md" + if review.Error != nil { + fmt.Printf("āš ļø couldn't get the review for %s: %s\n", + path.Base(review.Diff.FilePathNew), + review.Error, + ) - err := saveReviewToFile(filename, review.Review) + continue + } - if err != nil { - fmt.Printf("āš ļø couldn't save the review for %s: %s\n", - filename, - err, - ) + filename := path.Base(review.Diff.FilePathNew) + ".md" + + // Ensure the directory exists + dir := path.Dir(filename) + + // If it doesn't exist, create it + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, DefaultFilePerms); err != nil { + fmt.Printf("āš ļø couldn't create directory for %s: %s\n", dir, err) continue } + } - fmt.Printf("šŸ’¾ Saved review to %s\n", filename) + // Save the review to disk + err := saveReviewToFile(filename, review.Review) + if err != nil { + fmt.Printf("āš ļø couldn't save the review for %s: %s\n", + filename, + err, + ) + + continue } - fmt.Println("Done! šŸ") - return nil - })) + fmt.Printf("šŸ’¾ Saved review to %s\n", filename) + } } func getCodeReviews(diffs []*gh.GitDiff, model, apiKey string, svc CompletionServiceInterface) ([]ReviewedDiff, error) { @@ -157,7 +181,7 @@ func saveReviewToFile(filename, reviewContent string) error { } // Write the review content to the file - err := os.WriteFile(filename, []byte(reviewContent), 0644) + err := os.WriteFile(filename, []byte(reviewContent), DefaultFilePerms) if err != nil { return fmt.Errorf("failed to write review to file: %s", err) } @@ -180,6 +204,11 @@ func coalesceConfiguration(cliArgs *argT) (*argT, error) { cliArgs.ApiKey = envArgs.ApiKey } + // If no model is provided, use the default model + if cliArgs.CompletionModel == "" { + cliArgs.CompletionModel = DefaultOpenAIModel + } + return cliArgs, nil } diff --git a/main_test.go b/main_test.go index dddbc36..367b36f 100644 --- a/main_test.go +++ b/main_test.go @@ -24,6 +24,21 @@ func TestCoalesceConfiguration_Key_CLIOverride(t *testing.T) { assert.Equal(t, "overridden_key", result.ApiKey) } +func TestCoalesceConfiguration_Model_CLIOverride(t *testing.T) { + // Set up to restore the value later + currentEnvVar := os.Getenv("CADRE_COMPLETION_MODEL") + defer os.Setenv("CADRE_COMPLETION_MODEL", currentEnvVar) + + // Set up an override + err := os.Setenv("CADRE_COMPLETION_MODEL", "gpt-4") + require.NoError(t, err) + + args := &argT{CompletionModel: "overridden_model"} + result, _ := coalesceConfiguration(args) + + assert.Equal(t, "overridden_model", result.CompletionModel) +} + func TestProcessPullRequest(t *testing.T) { // Create a mock GitHub client mockClient := &MockGithubClient{}