mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-06 05:40:38 +08:00
Compare commits
48 Commits
ok/fix_bit
...
ok/json_lo
Author | SHA1 | Date | |
---|---|---|---|
9a585de364 | |||
c27dc436c4 | |||
7374243d0b | |||
5c568bc0c5 | |||
22c196cb3b | |||
d2cc856cfc | |||
d772213cfc | |||
638db96311 | |||
4dffabf397 | |||
6f2bbd3baa | |||
9e41f3780c | |||
f53ec1d0cc | |||
f7666cb59a | |||
a7cb59ca8b | |||
ca0ea77415 | |||
0cf27e5fee | |||
f3bdbfc103 | |||
20e3acdd86 | |||
f965b09571 | |||
b8583c998d | |||
726594600b | |||
c77cc1d6ed | |||
b6c9e01a59 | |||
ec673214c8 | |||
16777a5334 | |||
1a89c7eadf | |||
07617eab5a | |||
f9e4c2b098 | |||
fa24413201 | |||
b6cabda586 | |||
abbce60f18 | |||
5daaaf2c1d | |||
e8f207691e | |||
b0dce4ceae | |||
fc494296d7 | |||
67b4069540 | |||
e6defcc846 | |||
096fcbbc17 | |||
eb7add1c77 | |||
1b6fb3ea53 | |||
c57b70f1d4 | |||
a2c3db463a | |||
193da1c356 | |||
5bc26880b3 | |||
21a1cc970e | |||
954727ad67 | |||
1314898cbf | |||
3673924fe9 |
104
INSTALL.md
104
INSTALL.md
@ -4,66 +4,69 @@
|
||||
To get started with PR-Agent quickly, you first need to acquire two tokens:
|
||||
|
||||
1. An OpenAI key from [here](https://platform.openai.com/), with access to GPT-4.
|
||||
2. A GitHub personal access token (classic) with the repo scope.
|
||||
2. A GitHub\GitLab\BitBucket personal access token (classic) with the repo scope.
|
||||
|
||||
There are several ways to use PR-Agent:
|
||||
|
||||
- [Method 1: Use Docker image (no installation required)](INSTALL.md#method-1-use-docker-image-no-installation-required)
|
||||
- [Method 2: Run from source](INSTALL.md#method-2-run-from-source)
|
||||
- [Method 3: Run as a GitHub Action](INSTALL.md#method-3-run-as-a-github-action)
|
||||
- [Method 4: Run as a polling server](INSTALL.md#method-4-run-as-a-polling-server)
|
||||
- [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app)
|
||||
- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function)
|
||||
- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup)
|
||||
- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server)
|
||||
- [Method 9: Run as a Bitbucket Pipeline](INSTALL.md#method-9-run-as-a-bitbucket-pipeline)
|
||||
**Locally**
|
||||
- [Using Docker image (no installation required)](INSTALL.md#use-docker-image-no-installation-required)
|
||||
- [Run from source](INSTALL.md#run-from-source)
|
||||
|
||||
**GitHub specific methods**
|
||||
- [Run as a GitHub Action](INSTALL.md#run-as-a-github-action)
|
||||
- [Run as a polling server](INSTALL.md#run-as-a-polling-server)
|
||||
- [Run as a GitHub App](INSTALL.md#run-as-a-github-app)
|
||||
- [Deploy as a Lambda Function](INSTALL.md#deploy-as-a-lambda-function)
|
||||
- [AWS CodeCommit](INSTALL.md#aws-codecommit-setup)
|
||||
|
||||
**GitLab specific methods**
|
||||
- [Run a GitLab webhook server](INSTALL.md#run-a-gitlab-webhook-server)
|
||||
|
||||
**BitBucket specific methods**
|
||||
- [Run as a Bitbucket Pipeline](INSTALL.md#run-as-a-bitbucket-pipeline)
|
||||
- [Run on a hosted app](INSTALL.md#run-on-a-hosted-bitbucket-app)
|
||||
---
|
||||
|
||||
### Method 1: Use Docker image (no installation required)
|
||||
### Use Docker image (no installation required)
|
||||
|
||||
To request a review for a PR, or ask a question about a PR, you can run directly from the Docker image. Here's how:
|
||||
|
||||
1. To request a review for a PR, run the following command:
|
||||
|
||||
For GitHub:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> review
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
For GitLab:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> review
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
For BitBucket:
|
||||
```
|
||||
docker run --rm -it -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=<pr_url> review
|
||||
```
|
||||
|
||||
For other git providers, update CONFIG.GIT_PROVIDER accordingly, and check the `pr_agent/settings/.secrets_template.toml` file for the environment variables expected names and values.
|
||||
|
||||
2. To ask a question about a PR, run the following command:
|
||||
|
||||
Similarly, to ask a question about a PR, run the following command:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>"
|
||||
```
|
||||
Note: If you want to ensure you're running a specific version of the Docker image, consider using the image's digest.
|
||||
The digest is a unique identifier for a specific version of an image. You can pull and run an image using its digest by referencing it like so: repository@sha256:digest. Always ensure you're using the correct and trusted digest for your operations.
|
||||
|
||||
1. To request a review for a PR using a specific digest, run the following command:
|
||||
A list of the relevant tools can be found in the [tools guide](./docs/TOOLS_GUIDE.md).
|
||||
|
||||
|
||||
Note: If you want to ensure you're running a specific version of the Docker image, consider using the image's digest:
|
||||
```bash
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
2. To ask a question about a PR using the same digest, run the following command:
|
||||
```bash
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url <pr_url> ask "<your question>"
|
||||
in addition, you can run a [specific released versions](./RELEASE_NOTES.md) of pr-agent, for example:
|
||||
```
|
||||
codiumai/pr-agent@v0.8
|
||||
```
|
||||
|
||||
Possible questions you can ask include:
|
||||
|
||||
- What is the main theme of this PR?
|
||||
- Is the PR ready for merge?
|
||||
- What are the main changes in this PR?
|
||||
- Should this PR be split into smaller parts?
|
||||
- Can you compose a rhymed song about this PR?
|
||||
|
||||
---
|
||||
|
||||
### Method 2: Run from source
|
||||
### Run from source
|
||||
|
||||
1. Clone this repository:
|
||||
|
||||
@ -93,11 +96,14 @@ python3 -m pr_agent.cli --pr_url <pr_url> review
|
||||
python3 -m pr_agent.cli --pr_url <pr_url> ask <your question>
|
||||
python3 -m pr_agent.cli --pr_url <pr_url> describe
|
||||
python3 -m pr_agent.cli --pr_url <pr_url> improve
|
||||
python3 -m pr_agent.cli --pr_url <pr_url> add_docs
|
||||
python3 -m pr_agent.cli --issue_url <issue_url> similar_issue
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Method 3: Run as a GitHub Action
|
||||
### Run as a GitHub Action
|
||||
|
||||
You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action.
|
||||
|
||||
@ -167,10 +173,11 @@ When you open your next PR, you should see a comment from `github-actions` bot w
|
||||
|
||||
---
|
||||
|
||||
### Method 4: Run as a polling server
|
||||
Request reviews by tagging your Github user on a PR
|
||||
### Run as a polling server
|
||||
Request reviews by tagging your GitHub user on a PR
|
||||
|
||||
Follow [steps 1-3](#run-as-a-github-action) of the GitHub Action setup.
|
||||
|
||||
Follow steps 1-3 of method 2.
|
||||
Run the following command to start the server:
|
||||
|
||||
```
|
||||
@ -179,7 +186,7 @@ python pr_agent/servers/github_polling.py
|
||||
|
||||
---
|
||||
|
||||
### Method 5: Run as a GitHub App
|
||||
### Run as a GitHub App
|
||||
Allowing you to automate the review process on your private or public repositories.
|
||||
|
||||
1. Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app).
|
||||
@ -260,13 +267,13 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository
|
||||
9. Install the app by navigating to the "Install App" tab and selecting your desired repositories.
|
||||
|
||||
> **Note:** When running PR-Agent from GitHub App, the default configuration file (configuration.toml) will be loaded.<br>
|
||||
> However, you can override the default tool parameters by uploading a local configuration file<br>
|
||||
> For more information please check out [CONFIGURATION.md](Usage.md#working-from-github-app-pre-built-repo)
|
||||
> However, you can override the default tool parameters by uploading a local configuration file `.pr_agent.toml`<br>
|
||||
> For more information please check out the [USAGE GUIDE](./Usage.md#working-with-github-app)
|
||||
---
|
||||
|
||||
### Method 6 - Deploy as a Lambda Function
|
||||
### Deploy as a Lambda Function
|
||||
|
||||
1. Follow steps 1-5 of [Method 5](#method-5-run-as-a-github-app).
|
||||
1. Follow steps 1-5 of [Method 5](#run-as-a-github-app).
|
||||
2. Build a docker image that can be used as a lambda function
|
||||
```shell
|
||||
docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:serverless -f docker/Dockerfile.lambda
|
||||
@ -278,12 +285,12 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository
|
||||
```
|
||||
4. Create a lambda function that uses the uploaded image. Set the lambda timeout to be at least 3m.
|
||||
5. Configure the lambda function to have a Function URL.
|
||||
6. Go back to steps 8-9 of [Method 5](#method-5-run-as-a-github-app) with the function url as your Webhook URL.
|
||||
6. Go back to steps 8-9 of [Method 5](#run-as-a-github-app) with the function url as your Webhook URL.
|
||||
The Webhook URL would look like `https://<LAMBDA_FUNCTION_URL>/api/v1/github_webhooks`
|
||||
|
||||
---
|
||||
|
||||
### Method 7 - AWS CodeCommit Setup
|
||||
### AWS CodeCommit Setup
|
||||
|
||||
Not all features have been added to CodeCommit yet. As of right now, CodeCommit has been implemented to run the pr-agent CLI on the command line, using AWS credentials stored in environment variables. (More features will be added in the future.) The following is a set of instructions to have pr-agent do a review of your CodeCommit pull request from the command line:
|
||||
|
||||
@ -353,7 +360,7 @@ PYTHONPATH="/PATH/TO/PROJECTS/pr-agent" python pr_agent/cli.py \
|
||||
|
||||
---
|
||||
|
||||
### Method 8 - Run a GitLab webhook server
|
||||
### Run a GitLab webhook server
|
||||
|
||||
1. From the GitLab workspace or group, create an access token. Enable the "api" scope only.
|
||||
2. Generate a random secret for your app, and save it for later. For example, you can use:
|
||||
@ -372,7 +379,7 @@ In the "Trigger" section, check the ‘comments’ and ‘merge request events
|
||||
|
||||
|
||||
|
||||
### Method 9: Run as a Bitbucket Pipeline
|
||||
### Run as a Bitbucket Pipeline
|
||||
|
||||
|
||||
You can use the Bitbucket Pipeline system to run PR-Agent on every pull request open or update.
|
||||
@ -396,7 +403,12 @@ pipelines:
|
||||
OPENAI_API_KEY: <your key>
|
||||
BITBUCKET_BEARER_TOKEN: <your token>
|
||||
|
||||
You can get a Bitbucket token for your repository by following Repository Settings -> Security -> Access Tokens
|
||||
You can get a Bitbucket token for your repository by following Repository Settings -> Security -> Access Tokens.
|
||||
|
||||
|
||||
### Run on a hosted Bitbucket app
|
||||
|
||||
Please contact <support@codium.ai> if you're interested in a hosted BitBucket app solution that provides full functionality including PR reviews and comment handling. It's based on the [bitbucket_app.py](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/git_providers/bitbucket_provider.py) implmentation.
|
||||
|
||||
|
||||
=======
|
||||
|
18
README.md
18
README.md
@ -28,16 +28,16 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull
|
||||
\
|
||||
‣ **Update Changelog ([`/update_changelog`](./docs/UPDATE_CHANGELOG.md))**: Automatically updating the CHANGELOG.md file with the PR changes.
|
||||
\
|
||||
‣ **Find similar issue ([`/similar_issue`](./docs/SIMILAR_ISSUE.md))**: Automatically retrieves and presents similar issues
|
||||
‣ **Find Similar Issue ([`/similar_issue`](./docs/SIMILAR_ISSUE.md))**: Automatically retrieves and presents similar issues
|
||||
\
|
||||
‣ **Add Documentation ([`/add_docs`](./docs/ADD_DOCUMENTATION.md))**: Automatically adds documentation to un-documented functions/classes in the PR.
|
||||
|
||||
See the [Usage Guide](./Usage.md) for instructions how to run the different tools from _CLI_, _online usage_, Or by _automatically triggering_ them when a new PR is opened.
|
||||
See the [Installation Guide](./INSTALL.md) for instructions how to install and run the tool on different platforms.
|
||||
|
||||
See the [Usage Guide](./Usage.md) for instructions how to run the different tools from _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
|
||||
See the [Tools Guide](./docs/TOOLS_GUIDE.md) for detailed description of the different tools.
|
||||
|
||||
See the [Release notes](./RELEASE_NOTES.md) for updates on the latest changes.
|
||||
|
||||
<h3>Example results:</h3>
|
||||
</div>
|
||||
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1687561986">/describe:</a></h4>
|
||||
@ -204,6 +204,9 @@ Here are some advantages of PR-Agent:
|
||||
- [x] Documentation (is the PR properly documented)
|
||||
- [ ] ...
|
||||
|
||||
See the [Release notes](./RELEASE_NOTES.md) for updates on the latest changes.
|
||||
|
||||
|
||||
## Similar Projects
|
||||
|
||||
- [CodiumAI - Meaningful tests for busy devs](https://github.com/Codium-ai/codiumai-vscode-release) (although various capabilities are much more advanced in the CodiumAI IDE plugins)
|
||||
@ -211,7 +214,12 @@ Here are some advantages of PR-Agent:
|
||||
- [openai-pr-reviewer](https://github.com/coderabbitai/openai-pr-reviewer)
|
||||
- [CodeReview BOT](https://github.com/anc95/ChatGPT-CodeReview)
|
||||
- [AI-Maintainer](https://github.com/merwanehamadi/AI-Maintainer)
|
||||
|
||||
|
||||
## Data Privacy
|
||||
|
||||
If you use self-host PR-Agent, e.g. via CLI running on your computer, with your OpenAI API key, it is between you and OpenAI. You can read their API data privacy policy here:
|
||||
https://openai.com/enterprise-privacy
|
||||
|
||||
## Links
|
||||
|
||||
[](https://discord.gg/kG35uSHDBc)
|
||||
|
2
Usage.md
2
Usage.md
@ -12,7 +12,7 @@
|
||||
|
||||
### Introduction
|
||||
|
||||
See the **[installation guide](/INSTALL.md)** for instructions on how to setup PR-Agent. After installation, there are three basic ways to invoke CodiumAI PR-Agent:
|
||||
After [installation](/INSTALL.md), there are three basic ways to invoke CodiumAI PR-Agent:
|
||||
1. Locally running a CLI command
|
||||
2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR
|
||||
3. Enabling PR-Agent tools to run automatically when a new PR is opened
|
||||
|
@ -27,18 +27,14 @@ Under the section 'pr_description', the [configuration file](./../pr_agent/setti
|
||||
|
||||
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
||||
|
||||
#### Markers template
|
||||
### Markers template
|
||||
|
||||
markers enable to easily integrate user's content and auto-generated content, with a template-like mechanism.
|
||||
|
||||
- `use_description_markers`: if set to true, the tool will use markers template. It replaces every marker of the form `pr_agent:marker_name` with the relevant content. Default is false.
|
||||
|
||||
For example, if the PR original description was:
|
||||
```
|
||||
User content...
|
||||
|
||||
## PR Type:
|
||||
pr_agent:pr_type
|
||||
|
||||
## PR Description:
|
||||
pr_agent:summary
|
||||
@ -46,6 +42,21 @@ pr_agent:summary
|
||||
## PR Walkthrough:
|
||||
pr_agent:walkthrough
|
||||
```
|
||||
The marker `pr_agent:pr_type` will be replaced with the PR type, `pr_agent:summary` will be replaced with the PR summary, and `pr_agent:walkthrough` will be replaced with the PR walkthrough.
|
||||
The marker `pr_agent:summary` will be replaced with the PR summary, and `pr_agent:walkthrough` will be replaced with the PR walkthrough.
|
||||
|
||||
##### Example:
|
||||
```
|
||||
env:
|
||||
pr_description.use_description_markers: 'true'
|
||||
```
|
||||
|
||||
<kbd><img src=./../pics/describe_markers_before.png width="768"></kbd>
|
||||
|
||||
==>
|
||||
|
||||
<kbd><img src=./../pics/describe_markers_after.png width="768"></kbd>
|
||||
|
||||
##### Configuration params:
|
||||
|
||||
- `use_description_markers`: if set to true, the tool will use markers template. It replaces every marker of the form `pr_agent:marker_name` with the relevant content. Default is false.
|
||||
- `include_generated_by_header`: if set to true, the tool will add a dedicated header: 'Generated by PR Agent at ...' to any automatic content. Default is true.
|
||||
|
35
docs/GENERATE_CUSTOM_LABELS.md
Normal file
35
docs/GENERATE_CUSTOM_LABELS.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Generate Custom Labels
|
||||
The `generte_labels` tool scans the PR code changes, and given a list of labels and their descriptions, it automatically suggests labels that match the PR code changes.
|
||||
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
/generate_labels
|
||||
```
|
||||
For example:
|
||||
|
||||
If we wish to add detect changes to SQL queries in a given PR, we can add the following custom label along with its description:
|
||||
|
||||
<kbd><img src=./../pics/custom_labels_list.png width="768"></kbd>
|
||||
When running the `generte_labels` tool on a PR that includes changes in SQL queries, it will automatically suggest the custom label:
|
||||
<kbd><img src=./../pics/custom_label_published.png width="768"></kbd>
|
||||
|
||||
### Configuration options
|
||||
To enable custom labels, you need to add the following configuration to the [custom_labels file](./../pr_agent/settings/custom_labels.toml):
|
||||
- Change `enable_custom_labels` to True: This will turn off the default labels and enable the custom labels provided in the custom_labels.toml file.
|
||||
- Add the custom labels to the custom_labels.toml file. It should be formatted as follows:
|
||||
```
|
||||
[custom_labels."Custom Label Name"]
|
||||
description = "Description of when AI should suggest this label"
|
||||
```
|
||||
- You can add modify the list to include all the custom labels you wish to use in your repository.
|
||||
|
||||
#### Github Action
|
||||
To use the `generte_labels` tool with Github Action:
|
||||
|
||||
- Add the following file to your repository under `env` section in `.github/workflows/pr_agent.yml`
|
||||
- Comma separated list of custom labels and their descriptions
|
||||
- The number of labels and descriptions should be the same and in the same order (empty descriptions are allowed):
|
||||
```
|
||||
CUSTOM_LABELS: "label1, label2, ..."
|
||||
CUSTOM_LABELS_DESCRIPTION: "label1 description, label2 description, ..."
|
||||
```
|
@ -31,4 +31,15 @@ Under the section 'pr_code_suggestions', the [configuration file](./../pr_agent/
|
||||
- `num_code_suggestions_per_chunk`: number of code suggestions provided by the 'improve' tool, per chunk. Default is 8.
|
||||
- `rank_extended_suggestions`: if set to true, the tool will rank the suggestions, based on importance. Default is true.
|
||||
- `max_number_of_calls`: maximum number of chunks. Default is 5.
|
||||
- `final_clip_factor`: factor to remove suggestions with low confidence. Default is 0.9.
|
||||
- `final_clip_factor`: factor to remove suggestions with low confidence. Default is 0.9.
|
||||
|
||||
|
||||
#### A note on code suggestions quality
|
||||
|
||||
- With current level of AI for code (GPT-4), mistakes can happen. Not all the suggestions will be perfect, and a user should not accept all of them automatically.
|
||||
|
||||
- Suggestions are not meant to be [simplistic](./../pr_agent/settings/pr_code_suggestions_prompts.toml#L34). Instead, they aim to give deep feedback and raise questions, ideas and thoughts to the user, who can then use his judgment, experience, and understanding of the code base.
|
||||
|
||||
- Recommended to use the 'extra_instructions' field to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
|
||||
- Best quality will be obtained by using 'improve --extended' mode.
|
@ -43,4 +43,15 @@ The tool will first ask the author questions about the PR, and will guide the re
|
||||
|
||||
<kbd><img src=./../pics/reflection_questions.png width="768"></kbd>
|
||||
<kbd><img src=./../pics/reflection_answers.png width="768"></kbd>
|
||||
<kbd><img src=./../pics/reflection_insights.png width="768"></kbd>
|
||||
<kbd><img src=./../pics/reflection_insights.png width="768"></kbd>
|
||||
|
||||
|
||||
#### A note on code suggestions quality
|
||||
|
||||
- With current level of AI for code (GPT-4), mistakes can happen. Not all the suggestions will be perfect, and a user should not accept all of them automatically.
|
||||
|
||||
- Suggestions are not meant to be [simplistic](./../pr_agent/settings/pr_reviewer_prompts.toml#L29). Instead, they aim to give deep feedback and raise questions, ideas and thoughts to the user, who can then use his judgment, experience, and understanding of the code base.
|
||||
|
||||
- Recommended to use the 'extra_instructions' field to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
|
||||
- Unlike the 'review' feature, which does a lot of things, the ['improve --extended'](./IMPROVE.md) feature is dedicated only to suggestions, and usually gives better results.
|
BIN
pics/custom_label_published.png
Normal file
BIN
pics/custom_label_published.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 253 KiB |
BIN
pics/custom_labels_list.png
Normal file
BIN
pics/custom_labels_list.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
BIN
pics/describe_markers_after.png
Normal file
BIN
pics/describe_markers_after.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
BIN
pics/describe_markers_before.png
Normal file
BIN
pics/describe_markers_before.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@ -7,6 +7,7 @@ from pr_agent.tools.pr_add_docs import PRAddDocs
|
||||
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
||||
from pr_agent.tools.pr_config import PRConfig
|
||||
from pr_agent.tools.pr_description import PRDescription
|
||||
from pr_agent.tools.pr_generate_labels import PRGenerateLabels
|
||||
from pr_agent.tools.pr_information_from_user import PRInformationFromUser
|
||||
from pr_agent.tools.pr_questions import PRQuestions
|
||||
from pr_agent.tools.pr_reviewer import PRReviewer
|
||||
@ -31,6 +32,7 @@ command2class = {
|
||||
"settings": PRConfig,
|
||||
"similar_issue": PRSimilarIssue,
|
||||
"add_docs": PRAddDocs,
|
||||
"generate_labels": PRGenerateLabels,
|
||||
}
|
||||
|
||||
commands = list(command2class.keys())
|
||||
|
@ -304,3 +304,19 @@ def try_fix_yaml(review_text: str) -> dict:
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
|
||||
def set_custom_labels(variables):
|
||||
labels = get_settings().custom_labels
|
||||
if not labels:
|
||||
# set default labels
|
||||
labels = ['Bug fix', 'Tests', 'Bug fix with tests', 'Refactoring', 'Enhancement', 'Documentation', 'Other']
|
||||
labels_list = "\n - ".join(labels) if labels else ""
|
||||
labels_list = f" - {labels_list}" if labels_list else ""
|
||||
variables["custom_labels"] = labels_list
|
||||
return
|
||||
final_labels = ""
|
||||
for k, v in labels.items():
|
||||
final_labels += f" - {k} ({v['description']})\n"
|
||||
variables["custom_labels"] = final_labels
|
||||
variables["custom_labels_examples"] = f" - {list(labels.keys())[0]}"
|
||||
|
@ -23,8 +23,10 @@ global_settings = Dynaconf(
|
||||
"settings/pr_sort_code_suggestions_prompts.toml",
|
||||
"settings/pr_information_from_user_prompts.toml",
|
||||
"settings/pr_update_changelog_prompts.toml",
|
||||
"settings/pr_custom_labels.toml",
|
||||
"settings/pr_add_docs.toml",
|
||||
"settings_prod/.secrets.toml"
|
||||
"settings_prod/.secrets.toml",
|
||||
"settings/custom_labels.toml"
|
||||
]]
|
||||
)
|
||||
|
||||
|
@ -258,7 +258,10 @@ class GithubProvider(GitProvider):
|
||||
|
||||
def get_repo_settings(self):
|
||||
try:
|
||||
contents = self.repo_obj.get_contents(".pr_agent.toml", ref=self.pr.head.sha).decoded_content
|
||||
# contents = self.repo_obj.get_contents(".pr_agent.toml", ref=self.pr.head.sha).decoded_content
|
||||
|
||||
# more logical to take 'pr_agent.toml' from the default branch
|
||||
contents = self.repo_obj.get_contents(".pr_agent.toml").decoded_content
|
||||
return contents
|
||||
except Exception:
|
||||
return ""
|
||||
|
@ -26,6 +26,7 @@ def apply_repo_settings(pr_url):
|
||||
section_dict[key] = value
|
||||
get_settings().unset(section)
|
||||
get_settings().set(section, section_dict, merge=False)
|
||||
get_logger().info(f"Applying repo settings for section {section}, contents: {contents}")
|
||||
|
||||
finally:
|
||||
if repo_settings_file:
|
||||
|
@ -1,9 +1,8 @@
|
||||
import ujson
|
||||
|
||||
from google.cloud import storage
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers.gitlab_provider import logger
|
||||
from pr_agent.log import get_logger
|
||||
from pr_agent.secret_providers.secret_provider import SecretProvider
|
||||
|
||||
|
||||
@ -15,7 +14,7 @@ class GoogleCloudStorageSecretProvider(SecretProvider):
|
||||
self.bucket_name = get_settings().google_cloud_storage.bucket_name
|
||||
self.bucket = self.client.bucket(self.bucket_name)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize Google Cloud Storage Secret Provider: {e}")
|
||||
get_logger().error(f"Failed to initialize Google Cloud Storage Secret Provider: {e}")
|
||||
raise e
|
||||
|
||||
def get_secret(self, secret_name: str) -> str:
|
||||
@ -23,7 +22,7 @@ class GoogleCloudStorageSecretProvider(SecretProvider):
|
||||
blob = self.bucket.blob(secret_name)
|
||||
return blob.download_as_string()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get secret {secret_name} from Google Cloud Storage: {e}")
|
||||
get_logger().error(f"Failed to get secret {secret_name} from Google Cloud Storage: {e}")
|
||||
return ""
|
||||
|
||||
def store_secret(self, secret_name: str, secret_value: str):
|
||||
@ -31,5 +30,5 @@ class GoogleCloudStorageSecretProvider(SecretProvider):
|
||||
blob = self.bucket.blob(secret_name)
|
||||
blob.upload_from_string(secret_value)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store secret {secret_name} in Google Cloud Storage: {e}")
|
||||
get_logger().error(f"Failed to store secret {secret_name} in Google Cloud Storage: {e}")
|
||||
raise e
|
||||
|
@ -65,15 +65,17 @@ async def handle_manifest(request: Request, response: Response):
|
||||
|
||||
@router.post("/webhook")
|
||||
async def handle_github_webhooks(background_tasks: BackgroundTasks, request: Request):
|
||||
print(request.headers)
|
||||
log_context = {"server_type": "bitbucket_app"}
|
||||
get_logger().debug(request.headers)
|
||||
jwt_header = request.headers.get("authorization", None)
|
||||
if jwt_header:
|
||||
input_jwt = jwt_header.split(" ")[1]
|
||||
data = await request.json()
|
||||
print(data)
|
||||
get_logger().debug(data)
|
||||
async def inner():
|
||||
try:
|
||||
owner = data["data"]["repository"]["owner"]["username"]
|
||||
log_context["sender"] = owner
|
||||
secrets = json.loads(secret_provider.get_secret(owner))
|
||||
shared_secret = secrets["shared_secret"]
|
||||
client_key = secrets["client_key"]
|
||||
@ -85,11 +87,17 @@ async def handle_github_webhooks(background_tasks: BackgroundTasks, request: Req
|
||||
agent = PRAgent()
|
||||
if event == "pullrequest:created":
|
||||
pr_url = data["data"]["pullrequest"]["links"]["html"]["href"]
|
||||
await agent.handle_request(pr_url, "review")
|
||||
log_context["api_url"] = pr_url
|
||||
log_context["event"] = "pull_request"
|
||||
with get_logger().contextualize(**log_context):
|
||||
await agent.handle_request(pr_url, "review")
|
||||
elif event == "pullrequest:comment_created":
|
||||
pr_url = data["data"]["pullrequest"]["links"]["html"]["href"]
|
||||
log_context["api_url"] = pr_url
|
||||
log_context["event"] = "comment"
|
||||
comment_body = data["data"]["comment"]["content"]["raw"]
|
||||
await agent.handle_request(pr_url, comment_body)
|
||||
with get_logger().contextualize(**log_context):
|
||||
await agent.handle_request(pr_url, comment_body)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to handle webhook: {e}")
|
||||
background_tasks.add_task(inner)
|
||||
@ -102,9 +110,10 @@ async def handle_github_webhooks(request: Request, response: Response):
|
||||
@router.post("/installed")
|
||||
async def handle_installed_webhooks(request: Request, response: Response):
|
||||
try:
|
||||
print(request.headers)
|
||||
get_logger().info("handle_installed_webhooks")
|
||||
get_logger().info(request.headers)
|
||||
data = await request.json()
|
||||
print(data)
|
||||
get_logger().info(data)
|
||||
shared_secret = data["sharedSecret"]
|
||||
client_key = data["clientKey"]
|
||||
username = data["principal"]["username"]
|
||||
@ -119,8 +128,10 @@ async def handle_installed_webhooks(request: Request, response: Response):
|
||||
|
||||
@router.post("/uninstalled")
|
||||
async def handle_uninstalled_webhooks(request: Request, response: Response):
|
||||
get_logger().info("handle_uninstalled_webhooks")
|
||||
|
||||
data = await request.json()
|
||||
print(data)
|
||||
get_logger().info(data)
|
||||
|
||||
|
||||
def start():
|
||||
|
@ -5,6 +5,8 @@ import os
|
||||
from pr_agent.agent.pr_agent import PRAgent
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import get_git_provider
|
||||
from pr_agent.git_providers.utils import apply_repo_settings
|
||||
from pr_agent.log import get_logger
|
||||
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
||||
from pr_agent.tools.pr_description import PRDescription
|
||||
from pr_agent.tools.pr_reviewer import PRReviewer
|
||||
@ -17,8 +19,11 @@ async def run_action():
|
||||
OPENAI_KEY = os.environ.get('OPENAI_KEY') or os.environ.get('OPENAI.KEY')
|
||||
OPENAI_ORG = os.environ.get('OPENAI_ORG') or os.environ.get('OPENAI.ORG')
|
||||
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
|
||||
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
|
||||
CUSTOM_LABELS = os.environ.get('CUSTOM_LABELS')
|
||||
CUSTOM_LABELS_DESCRIPTIONS = os.environ.get('CUSTOM_LABELS_DESCRIPTIONS')
|
||||
# CUSTOM_LABELS is a comma separated list of labels (string), convert to list and strip spaces
|
||||
|
||||
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
|
||||
|
||||
# Check if required environment variables are set
|
||||
if not GITHUB_EVENT_NAME:
|
||||
@ -33,6 +38,7 @@ async def run_action():
|
||||
if not GITHUB_TOKEN:
|
||||
print("GITHUB_TOKEN not set")
|
||||
return
|
||||
# CUSTOM_LABELS_DICT = handle_custom_labels(CUSTOM_LABELS, CUSTOM_LABELS_DESCRIPTIONS)
|
||||
|
||||
# Set the environment variables in the settings
|
||||
get_settings().set("OPENAI.KEY", OPENAI_KEY)
|
||||
@ -40,6 +46,7 @@ async def run_action():
|
||||
get_settings().set("OPENAI.ORG", OPENAI_ORG)
|
||||
get_settings().set("GITHUB.USER_TOKEN", GITHUB_TOKEN)
|
||||
get_settings().set("GITHUB.DEPLOYMENT_TYPE", "user")
|
||||
# get_settings().set("CUSTOM_LABELS", CUSTOM_LABELS_DICT)
|
||||
|
||||
# Load the event payload
|
||||
try:
|
||||
@ -49,6 +56,15 @@ async def run_action():
|
||||
print(f"Failed to parse JSON: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
get_logger().info("Applying repo settings")
|
||||
pr_url = event_payload.get("pull_request", {}).get("html_url")
|
||||
if pr_url:
|
||||
apply_repo_settings(pr_url)
|
||||
get_logger().info(f"enable_custom_labels: {get_settings().config.enable_custom_labels}")
|
||||
except Exception as e:
|
||||
get_logger().info(f"github action: failed to apply repo settings: {e}")
|
||||
|
||||
# Handle pull request event
|
||||
if GITHUB_EVENT_NAME == "pull_request":
|
||||
action = event_payload.get("action")
|
||||
@ -88,5 +104,31 @@ async def run_action():
|
||||
await PRAgent().handle_request(url, body)
|
||||
|
||||
|
||||
def handle_custom_labels(CUSTOM_LABELS, CUSTOM_LABELS_DESCRIPTIONS):
|
||||
if CUSTOM_LABELS:
|
||||
CUSTOM_LABELS = [x.strip() for x in CUSTOM_LABELS.split(',')]
|
||||
else:
|
||||
# Set default labels
|
||||
CUSTOM_LABELS = ['Bug fix', 'Tests', 'Bug fix with tests', 'Refactoring', 'Enhancement', 'Documentation',
|
||||
'Other']
|
||||
print(f"Using default labels: {CUSTOM_LABELS}")
|
||||
if CUSTOM_LABELS_DESCRIPTIONS:
|
||||
CUSTOM_LABELS_DESCRIPTIONS = [x.strip() for x in CUSTOM_LABELS_DESCRIPTIONS.split(',')]
|
||||
else:
|
||||
# Set default labels
|
||||
CUSTOM_LABELS_DESCRIPTIONS = ['Fixes a bug in the code', 'Adds or modifies tests',
|
||||
'Fixes a bug in the code and adds or modifies tests',
|
||||
'Refactors the code without changing its functionality',
|
||||
'Adds new features or functionality',
|
||||
'Adds or modifies documentation',
|
||||
'Other changes that do not fit in any of the above categories']
|
||||
print(f"Using default labels: {CUSTOM_LABELS_DESCRIPTIONS}")
|
||||
# create a dictionary of labels and descriptions
|
||||
CUSTOM_LABELS_DICT = dict()
|
||||
for i in range(len(CUSTOM_LABELS)):
|
||||
CUSTOM_LABELS_DICT[CUSTOM_LABELS[i]] = {'description': CUSTOM_LABELS_DESCRIPTIONS[i]}
|
||||
return CUSTOM_LABELS_DICT
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(run_action())
|
@ -12,23 +12,33 @@ from starlette_context.middleware import RawContextMiddleware
|
||||
|
||||
from pr_agent.agent.pr_agent import PRAgent
|
||||
from pr_agent.config_loader import get_settings, global_settings
|
||||
from pr_agent.log import get_logger, setup_logger
|
||||
from pr_agent.log import LoggingFormat, get_logger, setup_logger
|
||||
from pr_agent.secret_providers import get_secret_provider
|
||||
|
||||
setup_logger()
|
||||
setup_logger(fmt=LoggingFormat.JSON)
|
||||
router = APIRouter()
|
||||
|
||||
secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None
|
||||
|
||||
|
||||
def handle_request(background_tasks: BackgroundTasks, url: str, body: str, log_context: dict):
|
||||
log_context["action"] = body
|
||||
log_context["event"] = "pull_request" if body == "/review" else "comment"
|
||||
log_context["api_url"] = url
|
||||
with get_logger().contextualize(**log_context):
|
||||
background_tasks.add_task(PRAgent().handle_request, url, body)
|
||||
|
||||
|
||||
@router.post("/webhook")
|
||||
async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
log_context = {"server_type": "gitlab_app"}
|
||||
if request.headers.get("X-Gitlab-Token") and secret_provider:
|
||||
request_token = request.headers.get("X-Gitlab-Token")
|
||||
secret = secret_provider.get_secret(request_token)
|
||||
try:
|
||||
secret_dict = json.loads(secret)
|
||||
gitlab_token = secret_dict["gitlab_token"]
|
||||
log_context["sender"] = secret_dict["id"]
|
||||
context["settings"] = copy.deepcopy(global_settings)
|
||||
context["settings"].gitlab.personal_access_token = gitlab_token
|
||||
except Exception as e:
|
||||
@ -48,13 +58,13 @@ async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
if data.get('object_kind') == 'merge_request' and data['object_attributes'].get('action') in ['open', 'reopen']:
|
||||
get_logger().info(f"A merge request has been opened: {data['object_attributes'].get('title')}")
|
||||
url = data['object_attributes'].get('url')
|
||||
background_tasks.add_task(PRAgent().handle_request, url, "/review")
|
||||
handle_request(background_tasks, url, "/review")
|
||||
elif data.get('object_kind') == 'note' and data['event_type'] == 'note':
|
||||
if 'merge_request' in data:
|
||||
mr = data['merge_request']
|
||||
url = mr.get('url')
|
||||
body = data.get('object_attributes', {}).get('note')
|
||||
background_tasks.add_task(PRAgent().handle_request, url, body)
|
||||
handle_request(background_tasks, url, body)
|
||||
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
|
||||
|
@ -31,11 +31,15 @@ publish_labels=true
|
||||
publish_description_as_comment=false
|
||||
add_original_user_description=false
|
||||
keep_original_user_title=false
|
||||
use_bullet_points=true
|
||||
extra_instructions = ""
|
||||
|
||||
# markers
|
||||
use_description_markers=false
|
||||
include_generated_by_header=true
|
||||
|
||||
#custom_labels = ['Bug fix', 'Tests', 'Bug fix with tests', 'Refactoring', 'Enhancement', 'Documentation', 'Other']
|
||||
|
||||
[pr_questions] # /ask #
|
||||
|
||||
[pr_code_suggestions] # /improve #
|
||||
@ -122,4 +126,4 @@ max_issues_to_scan = 500
|
||||
[pinecone]
|
||||
# fill and place in .secrets.toml
|
||||
#api_key = ...
|
||||
# environment = "gcp-starter"
|
||||
# environment = "gcp-starter"
|
||||
|
18
pr_agent/settings/custom_labels.toml
Normal file
18
pr_agent/settings/custom_labels.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[config]
|
||||
enable_custom_labels=false
|
||||
|
||||
## template for custom labels
|
||||
#[custom_labels."Bug fix"]
|
||||
#description = "Fixes a bug in the code"
|
||||
#[custom_labels."Tests"]
|
||||
#description = "Adds or modifies tests"
|
||||
#[custom_labels."Bug fix with tests"]
|
||||
#description = "Fixes a bug in the code and adds or modifies tests"
|
||||
#[custom_labels."Refactoring"]
|
||||
#description = "Code refactoring without changing functionality"
|
||||
#[custom_labels."Enhancement"]
|
||||
#description = "Adds new features or functionality"
|
||||
#[custom_labels."Documentation"]
|
||||
#description = "Adds or modifies documentation"
|
||||
#[custom_labels."Other"]
|
||||
#description = "Other changes that do not fit in any of the above categories"
|
@ -433,3 +433,6 @@ reStructuredText = [".rst", ".rest", ".rest.txt", ".rst.txt", ]
|
||||
wisp = [".wisp", ]
|
||||
xBase = [".prg", ".prw", ]
|
||||
|
||||
[docs_blacklist_extensions]
|
||||
# Disable docs for these extensions of text files and scripts that are not programming languages of function, classes and methods
|
||||
docs_blacklist = ['sql', 'txt', 'yaml', 'json', 'xml', 'md', 'rst', 'rest', 'rest.txt', 'rst.txt', 'mdpolicy', 'mdown', 'markdown', 'mdwn', 'mkd', 'mkdn', 'mkdown', 'sh']
|
72
pr_agent/settings/pr_custom_labels.toml
Normal file
72
pr_agent/settings/pr_custom_labels.toml
Normal file
@ -0,0 +1,72 @@
|
||||
[pr_custom_labels_prompt]
|
||||
system="""You are CodiumAI-PR-Reviewer, a language model designed to review git pull requests.
|
||||
Your task is to label the type of the PR content.
|
||||
- Make sure not to focus the new PR code (the '+' lines).
|
||||
- If needed, each YAML output should be in block scalar format ('|-')
|
||||
{%- if extra_instructions %}
|
||||
|
||||
Extra instructions from the user:
|
||||
'
|
||||
{{ extra_instructions }}
|
||||
'
|
||||
{% endif %}
|
||||
|
||||
You must use the following YAML schema to format your answer:
|
||||
```yaml
|
||||
PR Type:
|
||||
type: array
|
||||
{%- if enable_custom_labels %}
|
||||
description: One or more labels that describe the PR type. Don't output the description in the parentheses.
|
||||
{%- endif %}
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
{%- if enable_custom_labels %}
|
||||
{{ custom_labels }}
|
||||
{%- else %}
|
||||
- Bug fix
|
||||
- Tests
|
||||
- Refactoring
|
||||
- Enhancement
|
||||
- Documentation
|
||||
- Other
|
||||
{%- endif %}
|
||||
|
||||
Example output:
|
||||
```yaml
|
||||
PR Type:
|
||||
{%- if enable_custom_labels %}
|
||||
{{ custom_labels_examples }}
|
||||
{%- else %}
|
||||
- Bug fix
|
||||
- Tests
|
||||
{%- endif %}
|
||||
```
|
||||
|
||||
Make sure to output a valid YAML. Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields.
|
||||
"""
|
||||
|
||||
user="""PR Info:
|
||||
Previous title: '{{title}}'
|
||||
Previous description: '{{description}}'
|
||||
Branch: '{{branch}}'
|
||||
{%- if language %}
|
||||
|
||||
Main language: {{language}}
|
||||
{%- endif %}
|
||||
{%- if commit_messages_str %}
|
||||
|
||||
Commit messages:
|
||||
{{commit_messages_str}}
|
||||
{%- endif %}
|
||||
|
||||
|
||||
The PR Git Diff:
|
||||
```
|
||||
{{diff}}
|
||||
```
|
||||
Note that lines in the diff body are prefixed with a symbol that represents the type of change: '-' for deletions, '+' for additions, and ' ' (a space) for unchanged lines.
|
||||
|
||||
Response (should be a valid YAML, and nothing else):
|
||||
```yaml
|
||||
"""
|
@ -19,19 +19,26 @@ PR Title:
|
||||
description: an informative title for the PR, describing its main theme
|
||||
PR Type:
|
||||
type: array
|
||||
{%- if enable_custom_labels %}
|
||||
description: One or more labels that describe the PR type. Don't output the description in the parentheses.
|
||||
{%- endif %}
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
{%- if enable_custom_labels %}
|
||||
{{ custom_labels }}
|
||||
{%- else %}
|
||||
- Bug fix
|
||||
- Tests
|
||||
- Bug fix with tests
|
||||
- Refactoring
|
||||
- Enhancement
|
||||
- Documentation
|
||||
- Other
|
||||
{%- endif %}
|
||||
PR Description:
|
||||
type: string
|
||||
description: an informative and concise description of the PR
|
||||
description: an informative and concise description of the PR.
|
||||
{%- if use_bullet_points %} Use bullet points. {% endif %}
|
||||
PR Main Files Walkthrough:
|
||||
type: array
|
||||
maxItems: 10
|
||||
@ -51,7 +58,11 @@ Example output:
|
||||
PR Title: |-
|
||||
...
|
||||
PR Type:
|
||||
{%- if enable_custom_labels %}
|
||||
{{ custom_labels_examples }}
|
||||
{%- else %}
|
||||
- Bug fix
|
||||
{%- endif %}
|
||||
PR Description: |-
|
||||
...
|
||||
PR Main Files Walkthrough:
|
||||
|
@ -25,7 +25,7 @@ code line that already existed in the file....
|
||||
The review should focus on new code added in the PR (lines starting with '+'), and not on code that already existed in the file (lines starting with '-', or without prefix).
|
||||
|
||||
{%- if num_code_suggestions > 0 %}
|
||||
- Provide up to {{ num_code_suggestions }} code suggestions.
|
||||
- Provide up to {{ num_code_suggestions }} code suggestions. Try to provide diverse and insightful suggestions.
|
||||
- Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices.
|
||||
- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the PR code.
|
||||
- Don't suggest to add docstring, type hints, or comments.
|
||||
@ -51,13 +51,22 @@ PR Analysis:
|
||||
description: summary of the PR in 2-3 sentences.
|
||||
Type of PR:
|
||||
type: string
|
||||
{%- if enable_custom_labels %}
|
||||
description: One or more labels that describe the PR type. Don't output the description in the parentheses.
|
||||
{%- endif %}
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
{%- if enable_custom_labels %}
|
||||
{{ custom_labels }}
|
||||
{%- else %}
|
||||
- Bug fix
|
||||
- Tests
|
||||
- Refactoring
|
||||
- Enhancement
|
||||
- Documentation
|
||||
- Other
|
||||
{%- endif %}
|
||||
{%- if require_score %}
|
||||
Score:
|
||||
type: int
|
||||
@ -99,10 +108,10 @@ PR Feedback:
|
||||
General suggestions:
|
||||
type: string
|
||||
description: |-
|
||||
General suggestions and feedback for the contributors and maintainers of
|
||||
this PR. May include important suggestions for the overall structure,
|
||||
primary purpose, best practices, critical bugs, and other aspects of the
|
||||
PR. Don't address PR title and description, or lack of tests. Explain your suggestions.
|
||||
General suggestions and feedback for the contributors and maintainers of this PR.
|
||||
May include important suggestions for the overall structure,
|
||||
primary purpose, best practices, critical bugs, and other aspects of the PR.
|
||||
Don't address PR title and description, or lack of tests. Explain your suggestions.
|
||||
{%- if num_code_suggestions > 0 %}
|
||||
Code feedback:
|
||||
type: array
|
||||
@ -115,11 +124,10 @@ PR Feedback:
|
||||
suggestion:
|
||||
type: string
|
||||
description: |-
|
||||
a concrete suggestion for meaningfully improving the new PR code. Also
|
||||
describe how, specifically, the suggestion can be applied to new PR
|
||||
code. Add tags with importance measure that matches each suggestion
|
||||
('important' or 'medium'). Do not make suggestions for updating or
|
||||
adding docstrings, renaming PR title and description, or linter like.
|
||||
a concrete suggestion for meaningfully improving the new PR code.
|
||||
Also describe how, specifically, the suggestion can be applied to new PR code.
|
||||
Add tags with importance measure that matches each suggestion ('important' or 'medium').
|
||||
Do not make suggestions for updating or adding docstrings, renaming PR title and description, or linter like.
|
||||
relevant line:
|
||||
type: string
|
||||
description: |-
|
||||
|
@ -65,6 +65,11 @@ class PRAddDocs:
|
||||
|
||||
async def _prepare_prediction(self, model: str):
|
||||
get_logger().info('Getting PR diff...')
|
||||
|
||||
# Disable adding docs to scripts and other non-relevant text files
|
||||
from pr_agent.algo.language_handler import bad_extensions
|
||||
bad_extensions += get_settings().docs_blacklist_extensions.docs_blacklist
|
||||
|
||||
self.patches_diff = get_pr_diff(self.git_provider,
|
||||
self.token_handler,
|
||||
model,
|
||||
|
@ -7,7 +7,7 @@ from jinja2 import Environment, StrictUndefined
|
||||
from pr_agent.algo.ai_handler import AiHandler
|
||||
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
|
||||
from pr_agent.algo.token_handler import TokenHandler
|
||||
from pr_agent.algo.utils import load_yaml
|
||||
from pr_agent.algo.utils import load_yaml, set_custom_labels
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import get_git_provider
|
||||
from pr_agent.git_providers.git_provider import get_main_pr_language
|
||||
@ -40,8 +40,12 @@ class PRDescription:
|
||||
"description": self.git_provider.get_pr_description(full=False),
|
||||
"language": self.main_pr_language,
|
||||
"diff": "", # empty diff for initial calculation
|
||||
"use_bullet_points": get_settings().pr_description.use_bullet_points,
|
||||
"extra_instructions": get_settings().pr_description.extra_instructions,
|
||||
"commit_messages_str": self.git_provider.get_commit_messages()
|
||||
"commit_messages_str": self.git_provider.get_commit_messages(),
|
||||
"enable_custom_labels": get_settings().config.enable_custom_labels,
|
||||
"custom_labels": "",
|
||||
"custom_labels_examples": "",
|
||||
}
|
||||
|
||||
self.user_description = self.git_provider.get_user_description()
|
||||
@ -139,6 +143,7 @@ class PRDescription:
|
||||
variables["diff"] = self.patches_diff # update diff
|
||||
|
||||
environment = Environment(undefined=StrictUndefined)
|
||||
set_custom_labels(variables)
|
||||
system_prompt = environment.from_string(get_settings().pr_description_prompt.system).render(variables)
|
||||
user_prompt = environment.from_string(get_settings().pr_description_prompt.user).render(variables)
|
||||
|
||||
@ -155,7 +160,6 @@ class PRDescription:
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def _prepare_data(self):
|
||||
# Load the AI prediction data into a dictionary
|
||||
self.data = load_yaml(self.prediction.strip())
|
||||
@ -185,6 +189,11 @@ class PRDescription:
|
||||
else:
|
||||
ai_header = ""
|
||||
|
||||
ai_type = self.data.get('PR Type')
|
||||
if ai_type and not re.search(r'<!--\s*pr_agent:type\s*-->', body):
|
||||
pr_type = f"{ai_header}{ai_type}"
|
||||
body = body.replace('pr_agent:type', pr_type)
|
||||
|
||||
ai_summary = self.data.get('PR Description')
|
||||
if ai_summary and not re.search(r'<!--\s*pr_agent:summary\s*-->', body):
|
||||
summary = f"{ai_header}{ai_summary}"
|
||||
|
163
pr_agent/tools/pr_generate_labels.py
Normal file
163
pr_agent/tools/pr_generate_labels.py
Normal file
@ -0,0 +1,163 @@
|
||||
import copy
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from pr_agent.algo.ai_handler import AiHandler
|
||||
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
|
||||
from pr_agent.algo.token_handler import TokenHandler
|
||||
from pr_agent.algo.utils import load_yaml, set_custom_labels
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import get_git_provider
|
||||
from pr_agent.git_providers.git_provider import get_main_pr_language
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
|
||||
class PRGenerateLabels:
|
||||
def __init__(self, pr_url: str, args: list = None):
|
||||
"""
|
||||
Initialize the PRGenerateLabels object with the necessary attributes and objects for generating labels
|
||||
corresponding to the PR using an AI model.
|
||||
Args:
|
||||
pr_url (str): The URL of the pull request.
|
||||
args (list, optional): List of arguments passed to the PRGenerateLabels class. Defaults to None.
|
||||
"""
|
||||
# Initialize the git provider and main PR language
|
||||
self.git_provider = get_git_provider()(pr_url)
|
||||
self.main_pr_language = get_main_pr_language(
|
||||
self.git_provider.get_languages(), self.git_provider.get_files()
|
||||
)
|
||||
self.pr_id = self.git_provider.get_pr_id()
|
||||
|
||||
# Initialize the AI handler
|
||||
self.ai_handler = AiHandler()
|
||||
|
||||
# Initialize the variables dictionary
|
||||
self.vars = {
|
||||
"title": self.git_provider.pr.title,
|
||||
"branch": self.git_provider.get_pr_branch(),
|
||||
"description": self.git_provider.get_pr_description(full=False),
|
||||
"language": self.main_pr_language,
|
||||
"diff": "", # empty diff for initial calculation
|
||||
"use_bullet_points": get_settings().pr_description.use_bullet_points,
|
||||
"extra_instructions": get_settings().pr_description.extra_instructions,
|
||||
"commit_messages_str": self.git_provider.get_commit_messages(),
|
||||
"custom_labels": "",
|
||||
"custom_labels_examples": "",
|
||||
"enable_custom_labels": get_settings().config.enable_custom_labels,
|
||||
}
|
||||
|
||||
# Initialize the token handler
|
||||
self.token_handler = TokenHandler(
|
||||
self.git_provider.pr,
|
||||
self.vars,
|
||||
get_settings().pr_custom_labels_prompt.system,
|
||||
get_settings().pr_custom_labels_prompt.user,
|
||||
)
|
||||
|
||||
# Initialize patches_diff and prediction attributes
|
||||
self.patches_diff = None
|
||||
self.prediction = None
|
||||
|
||||
async def run(self):
|
||||
"""
|
||||
Generates a PR labels using an AI model and publishes it to the PR.
|
||||
"""
|
||||
|
||||
try:
|
||||
get_logger().info(f"Generating a PR labels {self.pr_id}")
|
||||
if get_settings().config.publish_output:
|
||||
self.git_provider.publish_comment("Preparing PR labels...", is_temporary=True)
|
||||
|
||||
await retry_with_fallback_models(self._prepare_prediction)
|
||||
|
||||
get_logger().info(f"Preparing answer {self.pr_id}")
|
||||
if self.prediction:
|
||||
self._prepare_data()
|
||||
else:
|
||||
return None
|
||||
|
||||
pr_labels = self._prepare_labels()
|
||||
|
||||
if get_settings().config.publish_output:
|
||||
get_logger().info(f"Pushing labels {self.pr_id}")
|
||||
if self.git_provider.is_supported("get_labels"):
|
||||
current_labels = self.git_provider.get_labels()
|
||||
if current_labels is None:
|
||||
current_labels = []
|
||||
self.git_provider.publish_labels(pr_labels + current_labels)
|
||||
self.git_provider.remove_initial_comment()
|
||||
except Exception as e:
|
||||
get_logger().error(f"Error generating PR labels {self.pr_id}: {e}")
|
||||
|
||||
return ""
|
||||
|
||||
async def _prepare_prediction(self, model: str) -> None:
|
||||
"""
|
||||
Prepare the AI prediction for the PR labels based on the provided model.
|
||||
|
||||
Args:
|
||||
model (str): The name of the model to be used for generating the prediction.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
Any exceptions raised by the 'get_pr_diff' and '_get_prediction' functions.
|
||||
|
||||
"""
|
||||
|
||||
get_logger().info(f"Getting PR diff {self.pr_id}")
|
||||
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
||||
get_logger().info(f"Getting AI prediction {self.pr_id}")
|
||||
self.prediction = await self._get_prediction(model)
|
||||
|
||||
async def _get_prediction(self, model: str) -> str:
|
||||
"""
|
||||
Generate an AI prediction for the PR labels based on the provided model.
|
||||
|
||||
Args:
|
||||
model (str): The name of the model to be used for generating the prediction.
|
||||
|
||||
Returns:
|
||||
str: The generated AI prediction.
|
||||
"""
|
||||
variables = copy.deepcopy(self.vars)
|
||||
variables["diff"] = self.patches_diff # update diff
|
||||
|
||||
environment = Environment(undefined=StrictUndefined)
|
||||
set_custom_labels(variables)
|
||||
system_prompt = environment.from_string(get_settings().pr_custom_labels_prompt.system).render(variables)
|
||||
user_prompt = environment.from_string(get_settings().pr_custom_labels_prompt.user).render(variables)
|
||||
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
||||
|
||||
response, finish_reason = await self.ai_handler.chat_completion(
|
||||
model=model,
|
||||
temperature=0.2,
|
||||
system=system_prompt,
|
||||
user=user_prompt
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def _prepare_data(self):
|
||||
# Load the AI prediction data into a dictionary
|
||||
self.data = load_yaml(self.prediction.strip())
|
||||
|
||||
|
||||
|
||||
def _prepare_labels(self) -> List[str]:
|
||||
pr_types = []
|
||||
|
||||
# If the 'PR Type' key is present in the dictionary, split its value by comma and assign it to 'pr_types'
|
||||
if 'PR Type' in self.data:
|
||||
if type(self.data['PR Type']) == list:
|
||||
pr_types = self.data['PR Type']
|
||||
elif type(self.data['PR Type']) == str:
|
||||
pr_types = self.data['PR Type'].split(',')
|
||||
|
||||
return pr_types
|
@ -9,7 +9,7 @@ from yaml import SafeLoader
|
||||
from pr_agent.algo.ai_handler import AiHandler
|
||||
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
|
||||
from pr_agent.algo.token_handler import TokenHandler
|
||||
from pr_agent.algo.utils import convert_to_markdown, load_yaml, try_fix_yaml
|
||||
from pr_agent.algo.utils import convert_to_markdown, load_yaml, try_fix_yaml, set_custom_labels
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import get_git_provider
|
||||
from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language
|
||||
@ -63,6 +63,8 @@ class PRReviewer:
|
||||
'answer_str': answer_str,
|
||||
"extra_instructions": get_settings().pr_reviewer.extra_instructions,
|
||||
"commit_messages_str": self.git_provider.get_commit_messages(),
|
||||
"custom_labels": "",
|
||||
"enable_custom_labels": get_settings().config.enable_custom_labels,
|
||||
}
|
||||
|
||||
self.token_handler = TokenHandler(
|
||||
@ -149,6 +151,7 @@ class PRReviewer:
|
||||
variables["diff"] = self.patches_diff # update diff
|
||||
|
||||
environment = Environment(undefined=StrictUndefined)
|
||||
set_custom_labels(variables)
|
||||
system_prompt = environment.from_string(get_settings().pr_review_prompt.system).render(variables)
|
||||
user_prompt = environment.from_string(get_settings().pr_review_prompt.user).render(variables)
|
||||
|
||||
|
@ -48,9 +48,9 @@ class PRSimilarIssue:
|
||||
# check if index exists, and if repo is already indexed
|
||||
run_from_scratch = False
|
||||
if run_from_scratch: # for debugging
|
||||
if not index_name in pinecone.list_indexes():
|
||||
pinecone.init(api_key=api_key, environment=environment)
|
||||
if index_name in pinecone.list_indexes():
|
||||
get_logger().info('Removing index...')
|
||||
pinecone.init(api_key=api_key, environment=environment)
|
||||
pinecone.delete_index(index_name)
|
||||
get_logger().info('Done')
|
||||
|
||||
|
Reference in New Issue
Block a user