Compare commits

...

289 Commits

Author SHA1 Message Date
Tal
7168326911 Update TOOLS_GUIDE.md 2024-01-30 08:22:29 +02:00
Tal
e1ae51e7a0 Update TOOLS_GUIDE.md 2024-01-30 08:21:43 +02:00
Tal
c69962479a Merge pull request #630 from Codium-ai/tr/language
Enhancements in Patch Formatting and Code Suggestions Handling
2024-01-29 12:11:23 -08:00
15c8fe94bb feat: Improve patch formatting and handle empty data in pr_code_suggestions.py 2024-01-29 22:00:11 +02:00
0d86779799 feat: Improve patch formatting and handle empty data in pr_code_suggestions.py 2024-01-29 21:52:54 +02:00
6565556e01 feat: Add 'language' field to CodeSuggestion, FileDescription, and ReviewerPrompt models in settings files 2024-01-29 20:51:24 +02:00
Tal
6998089549 Update README.md 2024-01-29 20:21:23 +02:00
8d36e2e2f7 feat: Add new configuration options in pr_test section and update TEST.md documentation 2024-01-29 20:17:39 +02:00
Tal
93f1854c68 Merge pull request #629 from Codium-ai/tr/tests
s
2024-01-29 01:43:46 -08:00
40a7ef9132 s 2024-01-29 11:42:32 +02:00
042eab1641 s 2024-01-29 11:39:50 +02:00
567b400f97 Revert "s1"
This reverts commit 412159bba5.
2024-01-29 11:30:58 +02:00
412159bba5 s1 2024-01-29 11:28:58 +02:00
Tal
e6f548920b Merge branch 'main' into tr/tests 2024-01-29 01:23:27 -08:00
f1fe2563f4 s 2024-01-29 11:22:46 +02:00
467e2ae68e s 2024-01-29 11:19:37 +02:00
78bb54bd8f s 2024-01-29 11:11:30 +02:00
Tal
2bebfba4b6 Update README.md 2024-01-28 20:39:45 +02:00
Tal
815073e04f Merge pull request #628 from Codium-ai/tr/tests
tests readme
2024-01-28 10:32:27 -08:00
5f1722ed4a s 2024-01-28 20:30:40 +02:00
47af04d158 s 2024-01-28 20:26:58 +02:00
Tal
335654b02a Update Usage.md 2024-01-27 21:38:25 +02:00
Tal
68e17ed2be Merge pull request #627 from Codium-ai/tr/updates2
Tr/updates2
2024-01-27 11:33:33 -08:00
ecb46435b3 s 2024-01-27 21:29:19 +02:00
98ce0a7036 s 2024-01-27 21:25:43 +02:00
76f44b13f8 docs: Update GitHub app configurations section in Usage.md 2024-01-27 21:20:10 +02:00
06dede29f2 feat: Update configuration and handling of GitHub Action settings 2024-01-27 21:15:23 +02:00
Tal
dbf5ebcb8d Merge pull request #622 from eltociear/fix-filename
docs: fix file name
2024-01-25 02:09:27 -08:00
Tal
d6a45663f1 Merge pull request #624 from Codium-ai/hl/small_fixes
small fixes
2024-01-25 02:09:04 -08:00
07eaa59e78 small fixes 2024-01-25 11:07:43 +02:00
1e2d4e9830 docs: fix file name 2024-01-25 15:03:58 +09:00
Tal
cc03f7f615 Merge pull request #620 from Codium-ai/tr/updates
Configuration updates
2024-01-24 09:59:43 -08:00
Tal
040da2fbb1 Merge pull request #612 from Codium-ai/mrT23-patch-1
Update README.md
2024-01-24 09:59:25 -08:00
Tal
a83a492b22 Merge branch 'main' into mrT23-patch-1 2024-01-24 09:58:23 -08:00
e056cd5988 type 2024-01-24 19:55:33 +02:00
4077c5556d enable_review_labels_effort set to true by default 2024-01-24 19:49:43 +02:00
d8465ea9f9 removed include_improved_code 2024-01-24 19:47:30 +02:00
f4037e0dfa feat: Add LanceDB support for similar_issue tool and refactor SOC2 compliance feature name 2024-01-24 19:40:58 +02:00
9986f5307c Merge pull request #618 from Codium-ai/hl/describe_usage_guide
Enhance Documentation for "Inline File Walkthrough" Feature
2024-01-24 10:15:21 +02:00
60c0371854 highlight options 2024-01-23 18:13:08 +02:00
139bbfc67a update docs and usage guide 2024-01-23 17:58:55 +02:00
b33b8c12cd Merge pull request #616 from Codium-ai/hl/walkthrough_title_ui_improvements
update default config for inline_file_summary to false
2024-01-22 10:37:02 +02:00
968684b461 update default config for inline_file_summary to false 2024-01-22 10:25:34 +02:00
Tal
4a5cff4995 Update CUSTOM_SUGGESTIONS.md 2024-01-21 17:58:01 +02:00
599c6773f3 Merge pull request #613 from Codium-ai/hl/walkthrough_title_ui_improvements
Add changes title of files and improve table style and alignments
2024-01-21 17:52:44 +02:00
Tal
7178ddac10 Update CUSTOM_SUGGESTIONS.md 2024-01-21 17:31:33 +02:00
Tal
5dedc381a6 Merge pull request #615 from Codium-ai/mrT23-patch-2
Update README.md
2024-01-21 07:30:14 -08:00
Tal
cba14ada2c Update README.md 2024-01-21 17:29:29 +02:00
Tal
f81fe0a12d Merge pull request #614 from Codium-ai/tr/custom_suggestions
feat: Add custom suggestions tool to README.md
2024-01-21 07:19:22 -08:00
78d886459a feat: Add custom suggestions tool to README.md 2024-01-21 17:15:34 +02:00
27aafb06cb feat: Add custom suggestions tool to README.md 2024-01-21 17:10:23 +02:00
329f7fa9d6 feat: Add custom suggestions tool to README.md 2024-01-21 17:06:25 +02:00
e79919b5c6 update describe screenshot to the new describe 2024-01-21 14:09:17 +02:00
8d513e078a Add changes title of files and improve table style and alignments 2024-01-21 13:43:37 +02:00
Tal
69f7923552 Update README.md 2024-01-20 13:02:07 +02:00
Tal
2430a1a608 Merge pull request #594 from Codium-ai/tr/fallback_bad_review_comment
Enhanced Comment Verification and Fallback Mechanism for Inline Comment Publishing
2024-01-20 02:04:06 -08:00
e54388d807 s 2024-01-20 11:59:45 +02:00
d942bdb8bd s 2024-01-20 11:56:17 +02:00
Tal
84d87aa870 Merge pull request #607 from Codium-ai/tr/edge_cases
feat: Improve error handling and code readability in pr_agent tools
2024-01-18 07:09:07 -08:00
39891e4ab1 feat: Improve error handling and code readability in pr_agent tools 2024-01-18 17:01:25 +02:00
d7858efbbe Merge pull request #581 from Codium-ai/sm/azure_devops
Enhancement of AzureDevopsProvider with new functionalities and refactoring
2024-01-18 16:28:28 +02:00
Tal
b3365b8d6c Merge pull request #605 from Codium-ai/tr/edge_cases
No suggestions found
2024-01-18 06:18:43 -08:00
fc5b00f4d3 s 2024-01-18 16:11:44 +02:00
Tal
5150e66723 Merge pull request #603 from Codium-ai/mrT23-patch-1
Update README.md
2024-01-17 22:21:44 -08:00
Tal
4dad1af77b Update README.md 2024-01-18 08:20:09 +02:00
Tal
02129b40cf Merge pull request #601 from Codium-ai/hl/diffview_file_summary
Readme Inline file summary 💎
2024-01-17 06:37:53 -08:00
3fb6d17338 width 2024-01-17 16:36:26 +02:00
3be7bfce79 feat: Add repository labels retrieval function in gitlab_provider.py
docs: Update links and add Inline file summary to TOC in DESCRIBE.md
2024-01-17 16:33:48 +02:00
472646ddfd Readme 2024-01-17 16:27:07 +02:00
eb4a1c515e Merge pull request #600 from Codium-ai/tr/improve_usage_guide
readme updates
2024-01-17 15:55:42 +02:00
e4af0b22ad s 2024-01-17 15:51:42 +02:00
a3e59a418e Merge remote-tracking branch 'origin/main' into tr/improve_usage_guide 2024-01-17 15:46:05 +02:00
4e833c0c28 s 2024-01-17 15:43:01 +02:00
Tal
0b811d97a7 Merge pull request #598 from Codium-ai/tr/improve_usage_guide
Enhancements to the 'improve' tool and updates to the related documentation
2024-01-17 03:13:33 -08:00
8f510dc553 s 2024-01-17 11:47:59 +02:00
2132771f46 s 2024-01-17 11:29:50 +02:00
e66bd7caa7 fallback to commitable 2024-01-17 11:18:30 +02:00
17ce2f0ed0 improve usage guide 2024-01-17 10:09:44 +02:00
7298548f82 improve usage guide 2024-01-17 10:06:27 +02:00
298c41a100 improve usage guide 2024-01-17 10:03:48 +02:00
58163e5129 improve usage guide 2024-01-17 09:50:48 +02:00
Tal
fae3bf6309 Merge pull request #590 from EduardDurech/patch-2
Fixed Run from source instructions for Python
2024-01-16 22:53:36 -08:00
06f0235577 Merge pull request #597 from Codium-ai/hl/improve_ui_table
Hl/improve UI table
2024-01-16 09:46:34 +02:00
d7e0aad527 small fixes 2024-01-16 09:41:31 +02:00
31576b77ff improve backticks 2024-01-15 19:07:41 +02:00
ea39e8684f works 2024-01-15 16:42:50 +02:00
afefc15b9c improve doce suggestions UI with difflib 2024-01-15 15:56:48 +02:00
5e17ccaf86 add colaplsable 2024-01-15 15:17:57 +02:00
9b1eb86d75 first iteration of improved UI for /improve --extended 2024-01-15 15:10:54 +02:00
9f5c2e5f17 feat: Refactor comment verification in github_provider.py 2024-01-14 11:55:07 +02:00
7377f4e4b2 feat: Refactor comment verification in github_provider.py 2024-01-14 11:49:51 +02:00
d6f4c1638d feat: Refactor comment verification in github_provider.py 2024-01-14 10:49:05 +02:00
a58c385b0f Fixed Rust warning tip as behaviour is inconsistent 2024-01-14 04:16:32 +01:00
7a3830d228 Fixed Run from source instructions for Python
Previously only installed dependencies but not pr_agent

+ Fixed link to OpenAI API Key and added for GitHub access token
2024-01-13 06:29:07 +01:00
Tal
e7251ada3f Merge pull request #588 from barnett-yuxiang/ignore-ds-store
Ignore .DS_Store files
2024-01-10 07:25:49 -08:00
aca3fcb571 Ignore .DS_Store files
This commit updates the .gitignore file to ignore .DS_Store files, which are created by macOS. These files are not relevant to the project and should not be included in version control.

Signed-off-by: Kamakura.Yx <barnett.yuxiang@gmail.com>
2024-01-10 23:13:57 +08:00
b9951fce62 Typo when parsing the suggestion part 2024-01-10 11:59:45 +02:00
Tal
609836bd6a Merge pull request #587 from Codium-ai/tr/abbrevations
Enhancement: Improve PR-Agent Tool with Custom Labels, Extra Instructions, and Summarize Mode
2024-01-09 23:39:04 -08:00
09ee0b64ba feat: Refactor instructions and fields in pr_code_suggestions_prompts.toml 2024-01-10 09:37:05 +02:00
Tal
fb4746fd09 Merge pull request #584 from samanhappy/link
Fix link formatting for relevant_line
2024-01-09 23:22:55 -08:00
729b5d11c9 feat: Refactor instructions and fields in pr_code_suggestions_prompts.toml 2024-01-09 22:56:25 +02:00
fc502a6fd5 feat: Refactor instructions and fields in pr_code_suggestions_prompts.toml 2024-01-09 22:49:26 +02:00
2b607dbd9a feat: Refactor instructions and fields in pr_code_suggestions_prompts.toml 2024-01-09 22:32:09 +02:00
9c6aabb0bb feat: Add custom labels and extra instructions sections to help.py, summarize mode to pr_code_suggestions.py, and summarize mode condition to pr_code_suggestions_prompts.toml 2024-01-09 22:09:48 +02:00
Tal
da3ac656ee Merge pull request #586 from Codium-ai/tr/ask_usage
ask helper
2024-01-09 06:55:30 -08:00
a42e57d09b ask helper 2024-01-09 16:36:39 +02:00
e56c443fd6 ask helper 2024-01-09 16:34:27 +02:00
abc05e7711 ask helper 2024-01-09 16:25:23 +02:00
Tal
a77d539866 Merge pull request #585 from samanhappy/url
Enhancement: Update GitLab link generation to support self-managed GitLab server and different projects
2024-01-09 06:12:05 -08:00
19c14b940e Try fixing invalid inline comments 2024-01-09 09:54:29 +02:00
36f1cfb51f Enhancement: Update GitLab link generation to support self-managed GitLab server and different projects 2024-01-09 15:11:27 +08:00
0f2a4654a7 Fix link formatting for relevant_line 2024-01-09 14:56:18 +08:00
28c5ad1d8b nit 2024-01-08 13:06:03 +02:00
2bb5ae8c0d Remove redundant condition (status 422 already means the same) 2024-01-08 13:05:10 +02:00
b0bffdec84 Refactor and add configuration toggle 2024-01-08 12:00:20 +02:00
Tal
11b96b1c1a Merge pull request #583 from Codium-ai/tr/unique_titles
feat: Remove bot help text from github_polling.py
2024-01-08 01:51:43 -08:00
e0f4bc7ded feat: Remove bot help text from github_polling.py 2024-01-08 11:45:01 +02:00
Tal
62d83f6753 Merge pull request #582 from Codium-ai/tr/unique_titles
Enhance PR description headers with bold formatting and include original user description
2024-01-08 01:28:56 -08:00
e9a2a0a96f s 2024-01-08 10:37:51 +02:00
46a38473e4 Merge remote-tracking branch 'origin/main' into tr/unique_titles 2024-01-08 10:30:58 +02:00
c9e55be275 s 2024-01-08 10:30:47 +02:00
Tal
f714592dec Merge pull request #579 from Codium-ai/tr/user_description
Enhancements to Logging, Help Messages, and PR Descriptions and Reviews
2024-01-07 23:46:25 -08:00
8bb2eb48af s 2024-01-08 09:43:34 +02:00
9cfb8ce475 s 2024-01-08 09:39:19 +02:00
67cb133c52 s 2024-01-08 09:28:44 +02:00
9c054bb80f s 2024-01-08 09:18:46 +02:00
b776e5069c feat: Refactor AzureDevopsProvider class in azuredevops_provider.py
- Reorder class methods and constructor for better readability
- Add error logging for failed operations
- Implement get_pr_description_full method
- Update get_pr_description method to always return full description
- Modify _parse_pr_url method to return workspace_slug, repo_slug, and pr_number
- Make _get_azure_devops_client a static method
- Add error handling in get_pr_id method
2024-01-08 09:15:34 +02:00
c8bca487e5 feat: Implement methods in AzureDevopsProvider for publishing code suggestions, labels, and removing comments 2024-01-08 08:59:12 +02:00
Tal
78fa61eac6 Merge branch 'main' into tr/user_description 2024-01-07 22:55:18 -08:00
Tal
582cbd623f Merge pull request #580 from Codium-ai/tr/add_logs
feat: Add debug logs to git_provider and pr_description modules
2024-01-07 09:58:20 -08:00
3ea08a6cf5 feat: Add debug logs to git_provider and pr_description modules 2024-01-07 19:57:49 +02:00
3154ebbf9f feat: Add debug logs to git_provider and pr_description modules 2024-01-07 19:56:05 +02:00
Tal
8fe608884d Merge pull request #578 from Codium-ai/ok/atlassian_version
feat: Update atlassian-python-api version in requirements.txt
2024-01-07 09:17:56 -08:00
60e79f0134 feat: Update atlassian-python-api version in requirements.txt 2024-01-07 18:08:11 +02:00
a6bbd04efb s 2024-01-07 17:02:18 +02:00
578d15c6fc usage guide 2024-01-07 16:38:08 +02:00
22d17985a1 Less noisy fallback for publish_code_suggestions in case of invalid comments
As a first option, `publish_code_suggestions` will try to post all review comments in a single GitHub review. This is preferred because it will group all comments together in the GitHub UI under the same review, and will trigger just one notification for any viewers of the PR.

If just one of the comments is malformed, the entire API request will fail and none of the comments will be posted to the PR. In the current implementation, the fallback mechanism is to just post each comment separately with `try/except` and skip the invalid comments. This works, but potentially creates a lot of noise in the PR as each comment is posted as in a separate review, creating multiple notifications.

This suggested fallback is based on a similar idea, but without creating multiple review notifications. The it works is by iterating over the potential comments, and starting a PENDING review for the current comment. The review is not submitted and does not trigger a notification, but it is verified against the GitHub API, and so we can verify if the comment is valid. After checking all comments we then submit a single review with all the verified comments which is guaranteed to succeed.

The end result is having the exact same comments posted to the PR as with the current fallback method, but the downside is having twice as many API calls (for each comment we have 1 extra API call to delete the pending review).
2024-01-07 16:00:44 +02:00
Tal
faba5a224a Merge pull request #575 from Codium-ai/tr/user_description
docs: Enhance 'improve' tool documentation and update 'DESCRIBE.md' w…
2024-01-07 01:30:44 -08:00
19f85f08b0 docs: Enhance 'improve' tool documentation and update 'DESCRIBE.md' with hyperlink 2024-01-07 11:29:07 +02:00
8512e9dffb docs: Enhance 'improve' tool documentation and update 'DESCRIBE.md' with hyperlink 2024-01-07 11:26:24 +02:00
7c0be8ca44 docs: Enhance 'improve' tool documentation and update 'DESCRIBE.md' with hyperlink 2024-01-07 11:22:53 +02:00
d9872d7031 docs: Enhance 'improve' tool documentation and update 'DESCRIBE.md' with hyperlink 2024-01-07 11:19:47 +02:00
Tal
be4b9c4991 Merge pull request #574 from Codium-ai/tr/user_description
Enhance and reorganize documentation for review and describe tools
2024-01-07 00:01:30 -08:00
94172104f0 docs: Reorganize and enhance documentation for review and describe tools 2024-01-07 09:56:09 +02:00
8ec790d4f7 Merge remote-tracking branch 'origin/main' into tr/user_description
# Conflicts:
#	docs/DESCRIBE.md
2024-01-07 09:49:43 +02:00
640972b00a docs: Reorganize and enhance documentation for review and describe tools 2024-01-07 09:48:17 +02:00
7810ba7d76 docs: Reorganize and enhance documentation for review and describe tools 2024-01-07 09:40:24 +02:00
d91f1fce09 docs: Reorganize and enhance documentation for review and describe tools 2024-01-07 09:36:03 +02:00
edcb666fbc docs: Reorganize and enhance documentation for review and describe tools 2024-01-07 09:28:06 +02:00
Tal
6d359fe1d8 Update DESCRIBE.md 2024-01-06 21:46:11 +02:00
Tal
19e38595f3 Merge pull request #573 from Codium-ai/tr/user_description
feat: Update adaptive collapsible file list logic in pr_description.py
2024-01-06 11:08:55 -08:00
355ef8c476 feat: Update adaptive collapsible file list logic in pr_description.py 2024-01-06 10:36:36 +02:00
Tal
f08d225360 Merge pull request #572 from Codium-ai/tr/user_description
describe docs
2024-01-06 00:15:36 -08:00
22b7dd9f2d s 2024-01-06 10:15:04 +02:00
a85a328791 s 2024-01-06 10:08:29 +02:00
82d10f24f0 s 2024-01-06 10:04:48 +02:00
Tal
c9717f1d73 Update README.md 2024-01-05 23:29:59 +02:00
Tal
82b58d5b09 Update README.md 2024-01-05 21:37:45 +02:00
Tal
b6d41d6a91 Merge pull request #571 from Codium-ai/tr/user_description
Enhance Documentation and Configuration of 'describe' Tool
2024-01-05 11:30:41 -08:00
ac74fa8431 docs 2024-01-05 21:28:54 +02:00
42704d5781 Merge remote-tracking branch 'origin/main' into tr/user_description 2024-01-05 17:03:30 +02:00
3628786a61 feat: Clarify PRType label member usage in pr_description_prompts.toml 2024-01-05 17:03:14 +02:00
Tal
c69ede0138 Update REVIEW.md 2024-01-05 11:10:55 +02:00
Tal
4349156e97 Merge pull request #570 from wesvetter/patch-1
📚 Minor update to REVIEW.md for inclusive language
2024-01-04 22:48:36 -08:00
9f88105f72 📚 Minor update to REVIEW.md for inclusive language
Replaces "his [judgement]" with "their".
2024-01-04 10:32:53 -08:00
Tal
fe6b2065fb Merge pull request #569 from zmeir/zmeir/enhance/auto_improve_extended_simple
Add toggle to automatically enable `/improve --extended`
2024-01-04 09:06:08 -08:00
c2b0891c0b Simpler auto-extended toggle and keep the default as false 2024-01-04 18:53:45 +02:00
Tal
782f1ca1bd Merge pull request #568 from Codium-ai/tr/user_description
Enhancement: Improved Extraction and Placement of User Descriptions in PRs
2024-01-04 08:35:34 -08:00
6d18a0c843 feat: Improve user description extraction in git_provider.py 2024-01-04 18:34:00 +02:00
Tal
e6093cd768 Merge pull request #567 from zmeir/zmeir/enhance/support_azure_in_langchain_ai_handler
Add support for Azure OpenAI in LangChainOpenAIHandler
2024-01-04 08:22:01 -08:00
Tal
aea0c4d45f Merge pull request #566 from zmeir/zmeir/fix/config_command_missing_arg
Fix failing `/config` command
2024-01-04 08:14:29 -08:00
1c2bb2ef3d feat: Update user description extraction and placement in PR description 2024-01-04 18:05:28 +02:00
7762bf59bf feat: Update user description extraction and placement in PR description 2024-01-04 18:01:55 +02:00
3e29848cd0 Merge remote-tracking branch 'origin/main' into tr/user_description
# Conflicts:
#	pr_agent/git_providers/git_provider.py
2024-01-04 17:49:10 +02:00
c3b5aaf8cc feat: Update user description extraction and placement in PR description 2024-01-04 17:46:24 +02:00
ba3f22d81e Move logging to a central location for all AI Handlers 2024-01-04 16:22:22 +02:00
ac7aaa0cd3 Add support for Azure OpenAI in LangChainOpenAIHandler 2024-01-04 16:22:22 +02:00
1ade09eaa3 Fix failing /config command
All commands need the `ai_handler` argument. The PRConfig class was missing it in the `__init__` method and so it failed with this error:

```
File "/home/vcap/app/pr_agent/agent/pr_agent.py", line 76, in handle_request
    await command2class[action](pr_url, ai_handler=self.ai_handler, args=args).run()
TypeError: PRConfig.__init__() got an unexpected keyword argument 'ai_handler'
```
2024-01-04 14:49:34 +02:00
Tal
b7af45166a Merge pull request #561 from zmeir/zmeir/fix/get_user_description
Fix `get_user_description`
2024-01-04 00:40:08 -08:00
Tal
92f89e6ca0 Merge pull request #565 from Codium-ai/tr/remove_old_walkthrough
Remove old 'enable_file_walkthrough' mode
2024-01-04 00:36:05 -08:00
ed78bfd946 use_collapsible_file_list 2024-01-04 10:27:07 +02:00
4204d78d7e feat: Remove file walkthrough feature from PR agent 2024-01-04 09:59:44 +02:00
3c2ed8bbf1 feat: Remove file walkthrough feature from PR agent 2024-01-04 09:42:15 +02:00
8d2da74380 Find user description in a case-insensitive way 2024-01-04 09:41:55 +02:00
39c1866121 Revert title() to capitalize() 2024-01-04 09:41:24 +02:00
Tal
1bba0162f8 Merge pull request #563 from zmeir/zmeir/enhance/refine_add_docs_prompt
Refine the prompt for `add_docs` command
2024-01-03 23:14:27 -08:00
Tal
c07ea5ea32 Merge pull request #560 from zmeir/zmeir/fix/drop_python3.9
Drop support for python 3.9
2024-01-03 23:09:54 -08:00
2f9fbbf0ac Prevent reducing the number of suggestions if already low enough 2024-01-03 16:43:39 +02:00
0189e12fb1 Automatically enable improve extended mode for large PRs 2024-01-03 16:43:38 +02:00
58f93e0615 Drop support for python 3.9
The `bitbucket_server_provider.py` uses structural pattern matching that was introduced in python 3.10, and so trying to run any command with python 3.9 will fail (because we import all the providers right at the top of `pr_agent.git_providers`)
2024-01-03 12:30:09 +02:00
967494ce17 Refine the prompt for add_docs command
I found that without it, python docstrings are sometimes suggested above the function signature, instead of below.
2024-01-03 12:27:23 +02:00
560d30dbb1 Fix get_user_description
The headers changed from "PR Type"/"PR Description"/etc to "Type"/"Description"/etc
2024-01-03 12:20:51 +02:00
Tal
c31ce3de35 Merge pull request #559 from pzarfos/fix/prompt-spelling
feat: Fix typo
2024-01-02 21:41:34 -08:00
0bd2f045a3 feat: Fix typo 2024-01-02 08:11:31 -05:00
Tal
0b4a98b3aa Merge pull request #558 from Codium-ai/tr/soc2_ticket
feat: Add SOC2 compliance review feature to PR agent 💎
2024-01-01 22:02:22 -08:00
7dfc306e7c feat: Add SOC2 compliance review feature to PR agent 2024-01-01 20:15:36 +02:00
Tal
be88624e2a Merge pull request #556 from Codium-ai/mrT23-patch-1
Update Usage.md
2023-12-31 07:35:40 -08:00
Tal
ac9a46d4c4 Update Usage.md 2023-12-31 17:34:13 +02:00
3e1349ed1f Merge pull request #554 from xens/patch-1
fix: missing flag in INSTALL.md
2023-12-28 10:29:14 -08:00
0d89e6e760 fix: missing flag in INSTALL.md
Fixing a missing flag on the Docker cli to declare a variable.
2023-12-28 17:24:08 +01:00
a9c8fb6a73 Merge pull request #552 from KennyDizi/main
Add `enable_help_text` setting and update PR review preparation method
2023-12-26 21:45:21 -08:00
fce52a66ff feat: Update enable_help_text flag explaination 2023-12-27 10:22:43 +07:00
dff31ff8f5 feat: Fix typo 2023-12-27 10:17:56 +07:00
37b040b50a Use 'and' in lieu of '&' operator 2023-12-27 10:17:08 +07:00
31168cd7de Get PR review enable help text from setting 2023-12-27 10:12:41 +07:00
db6ca434ac Update Usage.md 2023-12-26 17:15:48 +02:00
958bfe1000 Merge pull request #551 from Codium-ai/tr/global_configuration
global configuration
2023-12-26 07:08:52 -08:00
815862e428 markdown 2023-12-26 17:06:29 +02:00
b1ce29e27a global configuration 2023-12-26 16:45:39 +02:00
f7c2b3128f Merge pull request #550 from Codium-ai/tr/gfm_markdown
feat: Refactor help text addition in pr_reviewer.py and update tool n…
2023-12-25 23:30:36 -08:00
a6764c9058 feat: Update help text addition condition in pr_reviewer.py 2023-12-26 09:25:15 +02:00
a854e1a408 feat: Refactor help text addition in pr_reviewer.py and update tool names in README.md 2023-12-26 09:18:38 +02:00
ba3a8b24f0 Merge pull request #548 from PrashantDixit0/main
LanceDB Integration
2023-12-25 06:39:11 -08:00
26cb85c4f5 default pinecone 2023-12-25 14:50:15 +05:30
1d435ef3fa removed comments 2023-12-25 00:45:24 +05:30
1632696c2f Merge branch 'main' of github.com:PrashantDixit0/pr-agent 2023-12-25 00:41:28 +05:30
d8d954bb0f lancedb integration 2023-12-25 00:38:24 +05:30
08e9a91021 Merge pull request #547 from Codium-ai/tr/readme_update
Readme for PR-Agent-Pro
2023-12-24 06:30:04 -08:00
648c22ed1e tools update 2023-12-24 16:22:45 +02:00
49592ba2d7 docs: Refine markdown formatting in Analyze.md and README.md 2023-12-24 16:21:41 +02:00
0c4d451d9a readme 2023-12-24 16:18:20 +02:00
e698c7e2f3 Merge pull request #546 from Codium-ai/tr/backticks_review
Single-label for suggestions
2023-12-24 03:35:49 -08:00
663632e2d9 fixed bug 2023-12-24 10:27:48 +02:00
5fd3fdfae1 feat: Add loop to populate suggestion_list in pr_code_suggestions.py 2023-12-24 10:08:36 +02:00
47b267a73d prompt 2023-12-24 09:52:59 +02:00
5c49ff216a feat: Update inline comment creation in git providers and improve code suggestion handling
- Update `create_inline_comment` method in various git providers to include `absolute_position` parameter
- Remove `create_inline_comment` method from providers that do not support inline comments
- Enhance `find_line_number_of_relevant_line_in_file` function to handle absolute position
- Modify `pr_code_suggestions.py` to handle improved code inclusion in suggestions
- Add `include_improved_code` configuration option in `configuration.toml` and update documentation accordingly
2023-12-24 09:44:08 +02:00
5dc2595dcf feat: Refactor code suggestion handling and update YAML schema in pr_code_suggestions.py and pr_code_suggestions_prompts.toml
- Update key names in pr_code_suggestions.py to use snake_case for consistency
- Implement removal of invalid suggestions where existing code is equal to improved code
- Update YAML parsing in _prepare_pr_code_suggestions method to include keys_fix_yaml parameter
- Refactor push_inline_code_suggestions method to use updated key names
- Update _prepare_prediction_extended method to use new key names
- Refactor _prepare_markdown method to include suggestion label and use updated key names
- Update instructions and YAML schema in pr_code_suggestions_prompts.toml to reflect changes in pr_code_suggestions.py
- Remove redundant removal of invalid suggestions in rank_suggestions method
2023-12-24 08:30:35 +02:00
664b1c9d17 Merge pull request #545 from Codium-ai/tr/backticks_review
feat: Improve suggestion formatting in markdown text generation
2023-12-23 10:41:17 -08:00
ba7781ba00 feat: Update instruction formatting in pr_code_suggestions_prompts.toml and pr_reviewer_prompts.toml 2023-12-23 20:40:30 +02:00
42be96a99b feat: Improve suggestion formatting in markdown text generation 2023-12-23 20:32:03 +02:00
64a2c55d48 Merge pull request #542 from Codium-ai/tr/title_last
Enhancement: Update PR description prompts and reorder keys in PR description data
2023-12-21 03:55:17 -08:00
eca8078071 feat: Reorder keys in PR description data and update PRDescription model in toml file 2023-12-21 08:51:57 +02:00
9995ccd4c7 feat: Update PR description prompts in toml file to include semantic file types and custom labels options 2023-12-21 08:31:54 +02:00
851c001aa5 Merge pull request #541 from Codium-ai/tr/changes
feat: Enhance YAML parsing with additional fallbacks and key customiz…
2023-12-20 22:26:15 -08:00
2b23700aec feat: Enhance YAML parsing with additional fallbacks and key customization in load_yaml and try_fix_yaml functions 2023-12-21 08:24:07 +02:00
553dad0bee feat: Enhance YAML parsing with additional fallbacks and key customization in load_yaml and try_fix_yaml functions 2023-12-21 08:21:34 +02:00
37259e550f Merge pull request #540 from Codium-ai/tr/backticks
Enhancement of PR Description Formatting and Instructions Update
2023-12-20 22:16:53 -08:00
66cbd6ef8f Merge pull request #537 from koid/feature/ignore-header-description-in-ai-response
Enhancement of AI Response Parsing Mechanism
2023-12-20 22:15:43 -08:00
a9d789978b fix: remove last line 2023-12-21 11:11:46 +09:00
f99862088e re-implemented test case 2023-12-21 11:09:25 +09:00
e2797ad09a re-implemented YAML extraction as a fallback 2023-12-21 11:06:41 +09:00
ccb116922f Merge pull request #529 from lukefx/bitbucket_webhook_improvements
feat: Improved server, security and commands
2023-12-20 17:27:06 +02:00
c079deba21 feat: Enhance PR description formatting and update instructions in pr_description_prompts.toml 2023-12-20 16:45:21 +02:00
16b61eb4e8 ignore header description in ai response 2023-12-20 11:50:27 +09:00
68c26b362b Merge pull request #533 from Codium-ai/hl/fix_incomplete_yaml
bug fix
2023-12-18 10:07:07 -08:00
6e63cf4014 Add log 2023-12-18 17:35:04 +02:00
c59e9f77a6 fix 2023-12-18 17:06:02 +02:00
2a3779776a Merge pull request #532 from Codium-ai/hl/native_labels_readme
Hl/native labels readme
2023-12-18 16:12:38 +02:00
e25980f141 fix: using the same get_settings convention 2023-12-18 14:58:25 +01:00
6c80fde6df fix 2023-12-18 13:44:37 +02:00
75dcb035a7 Update 2023-12-18 13:41:50 +02:00
2ac86f429f Merge pull request #531 from Codium-ai/disable_github_action
Update pr-agent-review.yaml
2023-12-18 13:39:09 +02:00
9d2003d789 Update pr-agent-review.yaml 2023-12-18 13:38:06 +02:00
d2aef95847 Merge pull request #530 from Codium-ai/tr/labels
Enhancement: Implement label case conversion and update label descriptions in settings files
2023-12-18 03:21:48 -08:00
1c4e64333c feat: Implement label case conversion and update label descriptions in settings files 2023-12-18 12:29:06 +02:00
f121a420c9 Add to describe 2023-12-18 10:08:29 +02:00
3b13738943 Add docs to custom labels page 2023-12-18 10:04:05 +02:00
7a5acb29ac feat: Improved server, security and commands
Signed-off-by: Luca Simone <info@lucasimone.info>
2023-12-17 17:38:27 +01:00
ce35addcd3 Merge pull request #528 from Codium-ai/tr/lazy_init
Refactor AI handler instantiation to use lazy initialization in PR tools
2023-12-17 07:01:24 -08:00
5fb373b212 Refactor AI handler instantiation to use lazy initialization in PR tools 2023-12-17 16:52:03 +02:00
54891ad1d2 Merge pull request #514 from brianpham93/abstract-BaseAiHandler
Abstract AiHandler to BaseAiHandler
2023-12-14 07:54:13 -08:00
02871b1e3d Remove logging from pr_agent.py and add line breaks in cli.py and github_action_runner.py 2023-12-14 09:08:47 +02:00
38ea9143f3 Make LangChain dependency optional in pr-agent and update requirements.txt 2023-12-14 09:05:53 +02:00
246be6147f Set LiteLLMAIHandler as default AI handler in all PR tools and simplify AI handler injection in PRAgent 2023-12-14 09:00:14 +02:00
3531016a2c Refactor AI handler instantiation in PRAgent and related classes 2023-12-14 08:53:22 +02:00
e37598fdca Merge remote-tracking branch 'upstream/main' into abstract-BaseAiHandler 2023-12-14 07:45:43 +08:00
557b39ec87 Merge branch 'base-ai-handler' into abstract-BaseAiHandler 2023-12-14 07:44:13 +08:00
69a7c77a0d Refactor PRAgent class and has_ai_handler_param
method

This commit refactors the PRAgent class and the has_ai_handler_param
method. The has_ai_handler_param method is moved outside the class and
made a standalone function. This change improves code organization and
readability. The has_ai_handler_param function now takes a class object
as a parameter and checks if the class constructor has an "ai_handler"
parameter. This refactoring is done to streamline the code and improve
its maintainability.

No issue references.
2023-12-14 07:15:56 +08:00
2a8c2e488a Merge pull request #524 from Codium-ai/hl/native_labels_gitlab
feat: Add repository labels retrieval function in gitlab_provider.py
2023-12-13 17:36:18 +02:00
89c30ab5dc feat: Add repository labels retrieval function in gitlab_provider.py 2023-12-13 17:21:58 +02:00
ebb2ed891b Add logging to pr_agent.py 2023-12-13 08:16:45 +08:00
be8d6af87f Add code documentation generation for PR diffs 2023-12-13 08:16:31 +08:00
8fb4a42ef1 Update AI handler instantiation in server files 2023-12-13 08:16:02 +08:00
ca1ccd7b91 update base 2023-12-12 23:56:20 +08:00
b7225cc674 update langchain 2023-12-12 23:52:50 +08:00
a627dcd64f Update langchain 2023-12-12 23:28:58 +08:00
0c66554d50 langchain: move model and temperature to chat_completion 2023-12-12 23:07:46 +08:00
506eafc0c5 add langchain in requirement 2023-12-12 23:04:01 +08:00
6c7beccb4f add LangChain AI Handler 2023-12-12 23:03:49 +08:00
7eb2e769cf Move ai handlers to specific folder 2023-12-12 23:03:38 +08:00
5239e1c3e9 Load default AI Handler from util function 2023-12-12 21:51:05 +08:00
ebf7027aab add openai handler 2023-12-11 17:49:20 +08:00
a1cbd80b2a update base ai handler 2023-12-11 17:49:09 +08:00
b8021d7ca3 rename file 2023-12-11 16:57:23 +08:00
523a896465 Rename AiHandler to LiteLLMAiHandler 2023-12-11 16:56:49 +08:00
b6409929d2 Remove extra code 2023-12-09 16:51:50 +00:00
c0303ff9ec Merge remote-tracking branch 'upstream/main' into abstract-BaseAiHandler 2023-12-09 16:47:13 +00:00
f2abe5c73e Abstract AiHandler to BaseAiHandler 2023-12-09 16:39:25 +00:00
7e47baa9db Refactor AI handler classes 2023-12-10 00:25:25 +08:00
60 changed files with 2652 additions and 914 deletions

View File

@ -5,8 +5,9 @@
name: PR-Agent
on:
pull_request:
issue_comment:
# pull_request:
# issue_comment:
workflow_dispatch:
permissions:
issues: write
@ -26,7 +27,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PINECONE.API_KEY: ${{ secrets.PINECONE_API_KEY }}
PINECONE.ENVIRONMENT: ${{ secrets.PINECONE_ENVIRONMENT }}
GITHUB_ACTION.AUTO_REVIEW: true
GITHUB_ACTION.AUTO_IMPROVE: true
GITHUB_ACTION_CONFIG.AUTO_DESCRIBE: true
GITHUB_ACTION_CONFIG.AUTO_REVIEW: true
GITHUB_ACTION_CONFIG.AUTO_IMPROVE: true

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ dist/
*.egg-info/
build/
review.md
.DS_Store

View File

@ -3,8 +3,8 @@
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\GitLab\BitBucket personal access token (classic) with the repo scope.
1. An OpenAI key from [here](https://platform.openai.com/api-keys), with access to GPT-4.
2. A GitHub\GitLab\BitBucket personal access token (classic), with the repo scope. [GitHub from [here](https://github.com/settings/tokens)]
There are several ways to use PR-Agent:
@ -46,7 +46,7 @@ docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GI
Note: If you have a dedicated GitLab instance, you need to specify the custom url as variable:
```
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> GITLAB.URL=<your gitlab instance url> codiumai/pr-agent:latest --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> -e GITLAB.URL=<your gitlab instance url> codiumai/pr-agent:latest --pr_url <pr_url> review
```
- For BitBucket:
@ -79,12 +79,14 @@ codiumai/pr-agent@v0.9
git clone https://github.com/Codium-ai/pr-agent.git
```
2. Install the requirements in your favorite virtual environment:
2. Navigate to the `/pr-agent` folder and install the requirements in your favorite virtual environment:
```
pip install -r requirements.txt
pip install -e .
```
*Note: If you get an error related to Rust in the dependency installation then make sure Rust is installed and in your `PATH`, instructions: https://rustup.rs*
3. Copy the secrets template file and fill in your OpenAI key and your GitHub user token:
```
@ -93,10 +95,9 @@ chmod 600 pr_agent/settings/.secrets.toml
# Edit .secrets.toml file
```
4. Add the pr_agent folder to your PYTHONPATH, then run the cli.py script:
4. Run the cli.py script:
```
export PYTHONPATH=[$PYTHONPATH:]<PATH to pr_agent folder>
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
@ -107,6 +108,11 @@ python3 -m pr_agent.cli --issue_url <issue_url> similar_issue
...
```
[Optional] Add the pr_agent folder to your PYTHONPATH
```
export PYTHONPATH=$PYTHONPATH:<PATH to pr_agent folder>
```
---
### Run as a GitHub Action

203
README.md
View File

@ -19,9 +19,81 @@ Making pull requests less painful with an AI agent
<img alt="GitHub" src="https://img.shields.io/github/last-commit/Codium-ai/pr-agent/main?style=for-the-badge" height="20">
</a>
</div>
## Table of Contents
- [News and Updates](#news-and-updates)
- [Overview](#overview)
- [Example results](#example-results)
- [Try it now](#try-it-now)
- [Installation](#installation)
- [PR-Agent Pro 💎](#pr-agent-pro-)
- [How it works](#how-it-works)
- [Why use PR-Agent?](#why-use-pr-agent)
## News and Updates
### Jan 28, 2024
- 💎 Test - A new tool, [`/test component_name`](https://github.com/Codium-ai/pr-agent/blob/main/docs/TEST.md), was added to PR-Agent Pro. The tool will generate tests for a selected component, based on the PR code changes.
- 💎 Analyze - The [`/analyze`](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) tool was updated and simplified. It now presents a summary of the code components that were changed in the PR.
### Jan 21, 2024
- 💎 Custom suggestions - A new tool, `/custom_suggestions`, was added to PR-Agent Pro. The tool will propose only suggestions that follow specific guidelines defined by the user.
See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) for more details.
### Jan 17, 2024
- 💎 Inline file summary - The `describe` tool has a new option `--pr_description.inline_file_summary`, which allows to add a summary of each file changes to the Diffview page. See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#inline-file-summary-)
- The `improve` tool can now present suggestions in a nice collapsible format, which significantly reduces the PR footprint. See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md#summarized-vs-commitable-code-suggestions) for more details.
- To accompany the improved interface of the `improve` tool, we change the [default automation settings](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L116) of our GithupApp to:
```
pr_commands = [
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
"/review --pr_reviewer.num_code_suggestions=0",
"/improve --pr_code_suggestions.summarize=true",
]
```
Meaning that by default, for each PR the `describe`, `review`, and `improve` tools will be triggered automatically, and the `improve` tool will present the suggestions in a single comment.
You can of course overwrite these defaults by adding a `.pr_agent.toml` file to your repo. See [here](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app).
### Jan 10, 2024
[LanceDB](https://lancedb.com/) is now supported as a locally hosted VectorDB for the `similar_issue` tool. See [here](./docs/SIMILAR_ISSUE.md) for more details.
## Overview
<div style="text-align:left;">
CodiumAI `PR-Agent` is an open-source tool for efficient pull request reviewing and handling. It automatically analyzes the pull request and can provide several types of commands:
CodiumAI PR-Agent is an open-source tool to help efficiently review and handle pull requests. It automatically analyzes the pull request and can provide several types of commands:
| | | GitHub | Gitlab | Bitbucket |
|-------|------------------------------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|
| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Incremental | :white_check_mark: | | |
| | ⮑ [SOC2 Compliance](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#soc2-ticket-compliance-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Describe | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ [Inline File Summary](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#inline-file-summary-) 💎 | :white_check_mark: | | |
| | Improve | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Custom Suggestions](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Test](https://github.com/Codium-ai/pr-agent/blob/main/docs/TEST.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Find Similar Issue | :white_check_mark: | | |
| | [Add PR Documentation](https://github.com/Codium-ai/pr-agent/blob/main/docs/ADD_DOCUMENTATION.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Custom Labels](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) 💎 | :white_check_mark: | :white_check_mark: | |
| | [Analyze](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | | | | |
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | App / webhook | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Tagging bot | :white_check_mark: | | |
| | Actions | :white_check_mark: | | :white_check_mark: |
| | | | | |
| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Adaptive and token-aware<br />file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Static code analysis](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Global configuration](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#global-configuration-file-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
- 💎 means this feature is available only in [PR-Agent Pro](https://www.codium.ai/pricing/)
- Support for additional git providers is described in [here](./docs/Full_environments.md)
___
**Auto Description ([`/describe`](./docs/DESCRIBE.md))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
\
@ -35,29 +107,47 @@ CodiumAI `PR-Agent` is an open-source tool for efficient pull request reviewing
\
**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.
**Add Documentation 💎 ([`/add_docs`](./docs/ADD_DOCUMENTATION.md))**: Automatically adds documentation to methods/functions/classes that changed in the PR.
\
**Generate Custom Labels ([`/generate_labels`](./docs/GENERATE_CUSTOM_LABELS.md))**: Automatically suggests custom labels based on the PR code changes.
**Generate Custom Labels 💎 ([`/generate_labels`](./docs/GENERATE_CUSTOM_LABELS.md))**: Automatically suggests custom labels based on the PR code changes.
\
**Analyze 💎 ([`/analyze`](./docs/Analyze.md))**: Automatically analyzes the PR, and presents changes walkthrough for each component.
\
**Custom Suggestions 💎 ([`/custom_suggestions`](./docs/CUSTOM_SUGGESTIONS.md))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
\
**Generate Tests 💎 ([`/test component_name`](./docs/TEST.md))**: Automatically generates unit tests for a selected component, based on the PR code changes.
See the [Installation Guide](./INSTALL.md) for instructions on installing and running the tool on different git platforms.
See the [Usage Guide](./Usage.md) for running the PR-Agent commands via different interfaces, including _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 (tools are run via the commands).
See the [Tools Guide](./docs/TOOLS_GUIDE.md) for a detailed description of the different tools (tools are run via the commands).
<h3>Example results:</h3>
## Example results
</div>
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1687561986">/describe:</a></h4>
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/530">/describe</a></h4>
<div align="center">
<p float="center">
<img src="https://www.codium.ai/images/describe-2.gif" width="800">
<img src="https://www.codium.ai/images/pr_agent/describe_new_short_main.png" width="800">
</p>
</div>
<hr>
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/472#discussion_r1435819374">/improve</a></h4>
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901">/review:</a></h4>
<div align="center">
<p float="center">
<img src="https://www.codium.ai/images/review-2.gif" width="800">
<kbd>
<img src="https://www.codium.ai/images/pr_agent/improve_short_main.png" width="768">
</kbd>
</p>
</div>
<hr>
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/530">/generate_labels</a></h4>
<div align="center">
<p float="center">
<kbd><img src="https://www.codium.ai/images/pr_agent/geneare_custom_labels_main_short.png" width="300"></kbd>
</p>
</div>
@ -98,46 +188,11 @@ See the [Tools Guide](./docs/TOOLS_GUIDE.md) for detailed description of the dif
[//]: # (</div>)
<div align="left">
## Table of Contents
- [Overview](#overview)
- [Try it now](#try-it-now)
- [Installation](#installation)
- [How it works](#how-it-works)
- [Why use PR-Agent?](#why-use-pr-agent)
- [Roadmap](#roadmap)
</div>
<hr>
## Overview
`PR-Agent` offers extensive pull request functionalities across various git providers:
| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | Gerrit |
|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:|:----------:|
| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Incremental | :white_check_mark: | | | | | |
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Auto-Description | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Improve Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: |
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | |
| | Find similar issue | :white_check_mark: | | | | | |
| | Add Documentation | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
| | Generate Labels | :white_check_mark: | :white_check_mark: | | | | |
| | | | | | | |
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | App / webhook | :white_check_mark: | :white_check_mark: | | | |
| | Tagging bot | :white_check_mark: | | | | |
| | Actions | :white_check_mark: | | | | |
| | Web server | | | | | | :white_check_mark: |
| | | | | | | |
| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Adaptive and token-aware<br />file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Incremental PR Review | :white_check_mark: | | | | | |
Review the [usage guide](./Usage.md) section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs.
## Try it now
Try the GPT-4 powered PR-Agent instantly on _your public GitHub repository_. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment. The agent will generate a response based on your command.
@ -156,8 +211,7 @@ Note that when you set your own PR-Agent or use CodiumAI hosted PR-Agent, there
---
## Installation
To get started with PR-Agent quickly, you first need to acquire two tokens:
To use your own version of PR-Agent, 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.
@ -176,6 +230,21 @@ There are several ways to use PR-Agent:
- [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)
## PR-Agent Pro 💎
[PR-Agent Pro](https://www.codium.ai/pricing/) is a hosted version of PR-Agent, provided by CodiumAI. It is available for a monthly fee, and provides the following benefits:
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the PR-Agent app to your GitHub\BitBucket repo.
2. **Improved privacy** - No data will be stored or used to train models. PR-Agent Pro will employ zero data retention, and will use an OpenAI account with zero data retention.
3. **Improved support** - PR-Agent Pro users will receive priority support, and will be able to request new features and capabilities.
4. **Extra features** -In addition to the benefits listed above, PR-Agent Pro will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results. It has the following additional features:
- [**SOC2 compliance check**](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#soc2-ticket-compliance-)
- [**PR documentation**](https://github.com/Codium-ai/pr-agent/blob/main/docs/ADD_DOCUMENTATION.md)
- [**Custom labels**](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem)
- [**Global configuration**](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#global-configuration-file-)
- [**Analyze PR components**](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md)
- **Custom Code Suggestions** [WIP]
- **Chat on Specific Code Lines** [WIP]
## How it works
The following diagram illustrates PR-Agent tools and their flow:
@ -186,50 +255,22 @@ Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more detai
## Why use PR-Agent?
A reasonable question that can be asked is: `"Why use PR-Agent? What make it stand out from existing tools?"`
A reasonable question that can be asked is: `"Why use PR-Agent? What makes it stand out from existing tools?"`
Here are some advantages of PR-Agent:
- We emphasize **real-life practical usage**. Each tool (review, improve, ask, ...) has a single GPT-4 call, no more. We feel that this is critical for realistic team usage - obtaining an answer quickly (~30 seconds) and affordably.
- Our [PR Compression strategy](./PR_COMPRESSION.md) is a core ability that enables to effectively tackle both short and long PRs.
- Our JSON prompting strategy enables to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](pr_agent/settings/configuration.toml) file. Adding additional categories is easy and accessible.
- We support **multiple git providers** (GitHub, Gitlab, Bitbucket, CodeCommit), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2).
- We are open-source, and welcome contributions from the community.
- We support **multiple git providers** (GitHub, Gitlab, Bitbucket), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2).
## Roadmap
## Data privacy
- [x] Support additional models, as a replacement for OpenAI (see [here](https://github.com/Codium-ai/pr-agent/pull/172))
- [x] Develop additional logic for handling large PRs (see [here](https://github.com/Codium-ai/pr-agent/pull/229))
- [ ] Add additional context to the prompt. For example, repo (or relevant files) summarization, with tools such a [ctags](https://github.com/universal-ctags/ctags)
- [x] PR-Agent for issues
- [ ] Adding more tools. Possible directions:
- [x] PR description
- [x] Inline code suggestions
- [x] Reflect and review
- [x] Rank the PR (see [here](https://github.com/Codium-ai/pr-agent/pull/89))
- [ ] Enforcing CONTRIBUTING.md guidelines
- [ ] Performance (are there any performance issues)
- [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)
- [Aider - GPT powered coding in your terminal](https://github.com/paul-gauthier/aider)
- [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 a self-hosted PR-Agent with your OpenAI API key, it is between you and OpenAI. You can read their API data privacy policy here:
If you host PR-Agent 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
When using a PR-Agent app hosted by CodiumAI, we will not store any of your data, nor will we used it for training.
When using PR-Agent Pro 💎, hosted by CodiumAI, we will not store any of your data, nor will we use it for training.
You will also benefit from an OpenAI account with zero data retention.
## Links

151
Usage.md
View File

@ -2,14 +2,12 @@
### Table of Contents
- [Introduction](#introduction)
- [Working from a local repo (CLI)](#working-from-a-local-repo-cli)
- [Online usage](#online-usage)
- [Working with GitHub App](#working-with-github-app)
- [Working with GitHub Action](#working-with-github-action)
- [Working with BitBucket App](#working-with-bitbucket-self-hosted-app)
- [Changing a model](#changing-a-model)
- [Working with large PRs](#working-with-large-prs)
- [Appendix - additional configurations walkthrough](#appendix---additional-configurations-walkthrough)
- [Local Repo (CLI)](#working-from-a-local-repo-cli)
- [Online Usage](#online-usage)
- [GitHub App](#working-with-github-app)
- [GitHub Action](#working-with-github-action)
- [BitBucket App](#working-with-bitbucket-self-hosted-app)
- [Additional Configurations Walkthrough](#appendix---additional-configurations-walkthrough)
### Introduction
@ -25,10 +23,36 @@ GitHub App and GitHub Action also enable to run PR-Agent specific tool automatic
#### The configuration file
The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)**.
- The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)**.
In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the [pr_reviewer](/pr_agent/settings/configuration.toml#L16) section in the configuration file.
The [Tools Guide](./docs/TOOLS_GUIDE.md) provides a detailed description of the different tools and their configurations.
- The [Tools Guide](./docs/TOOLS_GUIDE.md) provides a detailed description of the different tools and their configurations.
- By uploading a local `.pr_agent.toml` file to the root of the repo's main branch, you can edit and customize any configuration parameter. Note that you need to upload `.pr_agent.toml` prior to creating a PR, in order for the configuration to take effect.
For example, if you set in `.pr_agent.toml`:
```
[pr_reviewer]
extra_instructions="""\
- instruction a
- instruction b
...
"""
```
Then you can give a list of extra instructions to the `review` tool.
#### Global configuration file 💎
If you create a repo called `pr-agent-settings` in your **organization**, it's configuration file `.pr_agent.toml` will be used as a global configuration file for any other repo that belongs to the same organization.
Parameters from a local `.pr_agent.toml` file, in a specific repo, will override the global configuration parameters.
For example, in the GitHub organization `Codium-ai`:
- The repo [`https://github.com/Codium-ai/pr-agent-settings`](https://github.com/Codium-ai/pr-agent-settings/blob/main/.pr_agent.toml) contains a `.pr_agent.toml` file that serves as a global configuration file for all the repos in the GitHub organization `Codium-ai`.
- The repo [`https://github.com/Codium-ai/pr-agent`](https://github.com/Codium-ai/pr-agent/blob/main/.pr_agent.toml) inherits the global configuration file from `pr-agent-settings`.
#### Ignoring files from analysis
In some cases, you may want to exclude specific files or directories from the analysis performed by CodiumAI PR-Agent. This can be useful, for example, when you have files that are generated automatically or files that shouldn't be reviewed, like vendored code.
@ -53,18 +77,6 @@ The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configu
"github", "gitlab", "azure", "codecommit", "local", "gerrit"
`
[//]: # (** online usage:**)
[//]: # (Options that are available in the configuration file can be specified at run time when calling actions. Two examples:)
[//]: # (```)
[//]: # (- /review --pr_reviewer.extra_instructions="focus on the file: ...")
[//]: # (- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ...")
[//]: # (```)
### Working from a local repo (CLI)
When running from your local repo (CLI), your local configuration file will be used.
Examples of invoking the different tools via the CLI:
@ -130,28 +142,20 @@ num_code_suggestions=1
Then you will overwrite the default number of code suggestions to 1.
#### GitHub app automatic tools
The [github_app](pr_agent/settings/configuration.toml#L76) section defines GitHub app-specific configurations.
In this section you can define configurations to control the conditions for which tools will **run automatically**.
The [github_app](pr_agent/settings/configuration.toml#L108) section defines GitHub app specific configurations.
##### GitHub app automatic tools for PR actions
The GitHub app can respond to the following actions on a PR:
1. `opened` - Opening a new PR
2. `reopened` - Reopening a closed PR
3. `ready_for_review` - Moving a PR from Draft to Open
4. `review_requested` - Specifically requesting review (in the PR reviewers list) from the `github-actions[bot]` user
The configuration parameter `handle_pr_actions` defines the list of actions for which the GitHub app will trigger the PR-Agent.
The configuration parameter `pr_commands` defines the list of tools that will be **run automatically** when one of the above actions happens (e.g., a new PR is opened):
The configuration parameter `pr_commands` defines the list of tools that will be **run automatically** when a new PR is opened.
```
[github_app]
handle_pr_actions = ['opened', 'reopened', 'ready_for_review', 'review_requested']
pr_commands = [
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
"/auto_review",
"/review --pr_reviewer.num_code_suggestions=0",
"/improve",
]
```
This means that when a new PR is opened/reopened or marked as ready for review, PR-Agent will run the `describe` and `auto_review` tools.
For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true.
This means that when a new PR is opened/reopened or marked as ready for review, PR-Agent will run the `describe`, `review` and `improve` tools.
For the `describe` tool, for example, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true.
You can override the default tool parameters by uploading a local configuration file called `.pr_agent.toml` to the root of your repo.
For example, if your local `.pr_agent.toml` file contains:
@ -168,7 +172,7 @@ To cancel the automatic run of all the tools, set:
handle_pr_actions = []
```
##### GitHub app automatic tools for new code (PR push)
##### GitHub app automatic tools for push actions (commits to an open PR)
In addition to running automatic tools when a PR is opened, the GitHub app can also respond to new code that is pushed to an open PR.
The configuration toggle `handle_push_trigger` can be used to enable this feature.
@ -178,12 +182,12 @@ The configuration parameter `push_commands` defines the list of tools that will
handle_push_trigger = true
push_commands = [
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
"/auto_review -i --pr_reviewer.remove_previous_review_comment=true",
"/review -i --pr_reviewer.remove_previous_review_comment=true",
]
```
This means that when new code is pushed to the PR, the PR-Agent will run the `describe` and incremental `auto_review` tools.
For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true.
For the `auto_review` tool, it will run in incremental mode, and the `remove_previous_review_comment` parameter will be set to true.
This means that when new code is pushed to the PR, the PR-Agent will run the `describe` and incremental `review` tools.
For the `describe` tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true.
For the `review` tool, it will run in incremental mode, and the `remove_previous_review_comment` parameter will be set to true.
Much like the configurations for `pr_commands`, you can override the default tool parameters by uploading a local configuration file to the root of your repo.
@ -205,18 +209,19 @@ user="""
Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py#L137).
### Working with GitHub Action
You can configure settings in GitHub action by adding environment variables under the env section in `.github/workflows/pr_agent.yml` file.
`GitHub Action` is a different way to trigger PR-Agent tools, and uses a different configuration mechanism than `GitHub App`.
You can configure settings for `GitHub Action` by adding environment variables under the env section in `.github/workflows/pr_agent.yml` file.
Specifically, start by setting the following environment variables:
```yaml
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }} # Make sure to add your OpenAI key to your repo secrets
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Make sure to add your GitHub token to your repo secrets
github_action.auto_review: "true" # enable\disable auto review
github_action.auto_describe: "true" # enable\disable auto describe
github_action.auto_improve: "false" # enable\disable auto improve
github_action_config.auto_review: "true" # enable\disable auto review
github_action_config.auto_describe: "true" # enable\disable auto describe
github_action_config.auto_improve: "true" # enable\disable auto improve
```
`github_action.auto_review`, `github_action.auto_describe` and `github_action.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened.
If not set, the default option is that only the `review` tool will run automatically when a new PR is opened.
`github_action_config.auto_review`, `github_action_config.auto_describe` and `github_action_config.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened.
If not set, the default configuration is for all three tools to run automatically when a new PR is opened.
Note that you can give additional config parameters by adding environment variables to `.github/workflows/pr_agent.yml`, or by using a `.pr_agent.toml` file in the root of your repo, similar to the GitHub App usage.
@ -256,13 +261,34 @@ If not set, the default option is that only the `review` tool will run automatic
Note that due to limitations of the bitbucket platform, the `auto_describe` tool will be able to publish a PR description only as a comment.
In addition, some subsections like `PR changes walkthrough` will not appear, since they require the usage of collapsible sections, which are not supported by bitbucket.
### Changing a model
### Appendix - additional configurations walkthrough
#### Extra instructions
All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage:
```
/update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..."
```
#### Working with large PRs
The default mode of CodiumAI is to have a single call per tool, using GPT-4, which has a token limit of 8000 tokens.
This mode provide a very good speed-quality-cost tradeoff, and can handle most PRs successfully.
When the PR is above the token limit, it employs a [PR Compression strategy](./PR_COMPRESSION.md).
However, for very large PRs, or in case you want to emphasize quality over speed and cost, there are 2 possible solutions:
1) [Use a model](#changing-a-model) with larger context, like GPT-32K, or claude-100K. This solution will be applicable for all the tools.
2) For the `/improve` tool, there is an ['extended' mode](./docs/IMPROVE.md) (`/improve --extended`),
which divides the PR to chunks, and process each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur)
#### Changing a model
See [here](pr_agent/algo/__init__.py) for the list of available models.
To use a different model than the default (GPT-4), you need to edit [configuration file](pr_agent/settings/configuration.toml#L2).
For models and environments not from OPENAI, you might need to provide additional keys and other parameters. See below for instructions.
#### Azure
##### Azure
To use Azure, set in your `.secrets.toml` (working from CLI), or in the GitHub `Settings > Secrets and variables` (working from GitHub App or GitHub Action):
```
api_key = "" # your azure api key
@ -278,7 +304,7 @@ and set in your configuration file:
model="" # the OpenAI model you've deployed on Azure (e.g. gpt-3.5-turbo)
```
#### Huggingface
##### Huggingface
**Local**
You can run Huggingface models locally through either [VLLM](https://docs.litellm.ai/docs/providers/vllm) or [Ollama](https://docs.litellm.ai/docs/providers/ollama)
@ -327,7 +353,7 @@ api_base = ... # the base url for your huggingface inference endpoint
```
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
#### Replicate
##### Replicate
To use Llama2 model with Replicate, for example, set:
```
@ -341,7 +367,7 @@ key = ...
Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models.
#### Vertex AI
##### Vertex AI
To use Google's Vertex AI platform and its associated models (chat-bison/codechat-bison) set:
@ -359,7 +385,7 @@ Your [application default credentials](https://cloud.google.com/docs/authenticat
If you do want to set explicit credentials then you can use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable set to a path to a json credentials file.
#### Amazon Bedrock
##### Amazon Bedrock
To use Amazon Bedrock and its foundational models, add the below configuration:
@ -376,25 +402,6 @@ Note that you have to add access to foundational models before using them. Pleas
AWS session is automatically authenticated from your environment, but you can also explicitly set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
### Working with large PRs
The default mode of CodiumAI is to have a single call per tool, using GPT-4, which has a token limit of 8000 tokens.
This mode provide a very good speed-quality-cost tradeoff, and can handle most PRs successfully.
When the PR is above the token limit, it employs a [PR Compression strategy](./PR_COMPRESSION.md).
However, for very large PRs, or in case you want to emphasize quality over speed and cost, there are 2 possible solutions:
1) [Use a model](#changing-a-model) with larger context, like GPT-32K, or claude-100K. This solution will be applicable for all the tools.
2) For the `/improve` tool, there is an ['extended' mode](./docs/IMPROVE.md) (`/improve --extended`),
which divides the PR to chunks, and process each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur)
### Appendix - additional configurations walkthrough
#### Extra instructions
All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage:
```
/update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..."
```
#### Patch Extra Lines
By default, around any change in your PR, git patch provides 3 lines of context above and below the change.

View File

@ -1,5 +1,5 @@
# Add Documentation Tool
The `add_docs` tool scans the PR code changes, and automatically suggests documentation for the undocumented code components (functions, classes, etc.).
# Add Documentation Tool 💎
The `add_docs` tool scans the PR code changes, and automatically suggests documentation for any code components that changed in the PR (functions, classes, etc.).
It can be invoked manually by commenting on any PR:
```
@ -7,9 +7,18 @@ It can be invoked manually by commenting on any PR:
```
For example:
<kbd><img src=https://codium.ai/images/pr_agent/add_docs_comment.png width="768"></kbd>
<kbd><img src=https://codium.ai/images/pr_agent/add_docs.png width="768"></kbd>
<kbd><img src=https://codium.ai/images/pr_agent/docs_command.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/docs_components.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/docs_single_component.png width="768"></kbd>
### Configuration options
- `docs_style`: The exact style of the documentation (for python docstring). you can choose between: `google`, `numpy`, `sphinx`, `restructuredtext`, `plain`. Default is `sphinx`.
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
Notes
- Language that are currently fully supported: Python, Java, C++, JavaScript, TypeScript.
- For languages that are not fully supported, the tool will suggest documentation only for new components in the PR.
- A previous version of the tool, that offered support only for new components, was deprecated.

View File

@ -1,11 +1,15 @@
# ASK Tool
The `ask` tool answers questions about the PR, based on the PR code changes.
The `ask` tool answers questions about the PR, based on the PR code changes. Make sure to be specific and clear in your questions.
It can be invoked manually by commenting on any PR:
```
/ask "..."
```
For example:
___
<kbd><img src=https://codium.ai/images/pr_agent/ask_comment.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/ask.png width="768"></kbd>
___
Note that the tool does not have "memory" of previous questions, and answers each question independently.

21
docs/Analyze.md Normal file
View File

@ -0,0 +1,21 @@
# Analyze Tool 💎
The `analyze` tool combines static code analysis with LLM capabilities to provide a comprehensive analysis of the PR code changes.
The tool scans the PR code changes, find the code components (methods, functions, classes) that changed, and summarizes the changes in each component.
It can be invoked manually by commenting on any PR:
```
/analyze
```
An example [result](https://github.com/Codium-ai/pr-agent/pull/546#issuecomment-1868524805):
<kbd><img src=https://codium.ai/images/pr_agent/analyze_1.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/analyze_2.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/analyze_3.png width="768"></kbd>
Notes
- Language that are currently supported: Python, Java, C++, JavaScript, TypeScript.

View File

@ -0,0 +1,65 @@
# Custom Suggestions Tool 💎
## Table of Contents
- [Overview](#overview)
- [Example usage](#example-usage)
- [Configuration options](#configuration-options)
## Overview
The `custom_suggestions` tool scans the PR code changes, and automatically generates custom suggestions for improving the PR code.
It shares similarities with the `improve` tool, but with one main difference: the `custom_suggestions` tool will only propose suggestions that follow specific guidelines defined by the prompt in: `pr_custom_suggestions.prompt` configuration.
The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.
When commenting, use the following template:
```
/custom_suggestions --pr_custom_suggestions.prompt="The suggestions should focus only on the following:\n-...\n-...\n-..."
```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template:
```
[pr_custom_suggestions]
prompt="""\
The suggestions should focus only on the following:
-...
-...
-...
"""
```
Using a configuration file is recommended, since it allows to use multi-line instructions.
Don't forget - with this tool, you are the prompter. Be specific, clear, and concise in the instructions. Specify relevant aspects that you want the model to focus on. \
You might benefit from several trial-and-error iterations, until you get the correct prompt for your use case.
## Example usage
Here is an example of a possible prompt:
```
[pr_custom_suggestions]
prompt="""\
The suggestions should focus only on the following:
- look for edge cases when implementing a new function
- make sure every variable has a meaningful name
- make sure the code is efficient
"""
```
The instructions above are just an example. We want to emphasize that the prompt should be specific and clear, and be tailored to the needs of your project.
Results obtained with the prompt above:
___
<kbd><img src=https://codium.ai/images/pr_agent/custom_suggestions_prompt.png width="512"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/custom_suggestions_result.png width="768"></kbd>
___
## Configuration options
`prompt`: the prompt for the tool. It should be a multi-line string.
`num_code_suggestions`: number of code suggestions provided by the 'custom_suggestions' tool. Default is 4.
`enable_help_text`: if set to true, the tool will display a help text in the comment. Default is true.

View File

@ -1,22 +1,35 @@
# Describe Tool
## Table of Contents
- [Overview](#overview)
- [Configuration options](#configuration-options)
- [Inline file summary 💎](#inline-file-summary-)
- [Handle custom labels from the Repo's labels page :gem:](#handle-custom-labels-from-the-repos-labels-page-gem)
- [Markers template](#markers-template)
- [Usage Tips](#usage-tips)
- [Automation](#automation)
- [Custom labels](#custom-labels)
The `describe` tool scans the PR code changes, and automatically generates PR description - title, type, summary, walkthrough and labels.
It can be invoked manually by commenting on any PR:
## Overview
The `describe` tool scans the PR code changes, and generates a description for the PR - title, type, summary, walkthrough and labels.
The tool can be triggered automatically every time a new PR is [opened](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools), or it can be invoked manually by commenting on any PR:
```
/describe
```
For example:
___
<kbd><img src=https://codium.ai/images/pr_agent/describe_comment.png width="768"></kbd>
<kbd><img src=https://codium.ai/images/pr_agent/describe.png width="768"></kbd>
The `describe` tool can also be triggered automatically every time a new PR is opened. See examples for automatic triggers for [GitHub App](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) and [GitHub Action](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
___
<kbd><img src=https://codium.ai/images/pr_agent/describe_new.png width="768"></kbd>
___
### Configuration options
To edit [configurations](./../pr_agent/settings/configuration.toml#L46) related to the describe tool (`pr_description` section), use the following template:
```
/describe --pr_description.some_config1=... --pr_description.some_config2=...
```
Under the section 'pr_description', the [configuration file](./../pr_agent/settings/configuration.toml#L28) contains options to customize the 'describe' tool:
**Possible configurations:**
- `publish_labels`: if set to true, the tool will publish the labels to the PR. Default is true.
- `publish_description_as_comment`: if set to true, the tool will publish the description as a comment to the PR. If false, it will overwrite the origianl description. Default is false.
@ -33,16 +46,49 @@ Under the section 'pr_description', the [configuration file](./../pr_agent/setti
- `final_update_message`: if set to true, it will add a comment message [`PR Description updated to latest commit...`](https://github.com/Codium-ai/pr-agent/pull/499#issuecomment-1837412176) after finishing calling `/describe`. Default is true.
- `enable_semantic_files_types`: if set to true, "PR changes walkthrough" section will be generated. Default is true.
- `enable_semantic_files_types`: if set to true, "Changes walkthrough" section will be generated. Default is true.
- `collapsible_file_list`: if set to true, the file list in the "Changes walkthrough" section will be collapsible. If set to "adaptive", the file list will be collapsible only if there are more than 8 files. Default is "adaptive".
### Inline file summary 💎
> This feature is available only in PR-Agent Pro
This will enable you to quickly understand the changes in each file, while reviewing the code changes (diff view).
To enable inline file summary, set `pr_description.inline_file_summary` in the configuration file, possible values are:
- `'table'`: File changes walkthrough table will be displayed on the top of the "Files changed" tab, in addition to the "Conversation" tab.
<kbd><img src=https://codium.ai/images/pr_agent/diffview-table.png width="1024"></kbd>
- `true`: A collapsable file comment with changes title and a changes summary for each file in the PR.
<kbd><img src=https://codium.ai/images/pr_agent/diffview_changes.png width="1024"></kbd>
- `false` (`default`): File changes walkthrough will be added only to the "Conversation" tab.
*Note that this feature is currently available only for GitHub.
### Handle custom labels from the Repo's labels page :gem:
> This feature is available only in PR-Agent Pro
You can control the custom labels that will be suggested by the `describe` tool, from the repo's labels page:
* GitHub : go to `https://github.com/{owner}/{repo}/labels` (or click on the "Labels" tab in the issues or PRs page)
* GitLab : go to `https://gitlab.com/{owner}/{repo}/-/labels` (or click on "Manage" -> "Labels" on the left menu)
Now add/edit the custom labels. they should be formatted as follows:
* Label name: The name of the custom label.
* Description: Start the description of with prefix `pr_agent:`, for example: `pr_agent: Description of when AI should suggest this label`.<br>
The description should be comprehensive and detailed, indicating when to add the desired label. For example:
<kbd><img src=https://codium.ai/images/pr_agent/add_native_custom_labels.png width="880"></kbd>
### Markers template
To enable markers, set `pr_description.use_description_markers=true`.
markers enable to easily integrate user's content and auto-generated content, with a template-like mechanism.
For example, if the PR original description was:
```
User content...
## PR Type:
pr_agent:type
## PR Description:
pr_agent:summary
@ -50,13 +96,7 @@ pr_agent:summary
## PR Walkthrough:
pr_agent: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'
```
The marker `pr_agent: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.
<kbd><img src=https://codium.ai/images/pr_agent/describe_markers_before.png width="768"></kbd>
@ -64,7 +104,52 @@ The marker `pr_agent:summary` will be replaced with the PR summary, and `pr_agen
<kbd><img src=https://codium.ai/images/pr_agent/describe_markers_after.png width="768"></kbd>
##### Configuration params:
**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.
## Usage Tips
1) [Automation](#automation)
2) [Custom labels](#custom-labels)
### Automation
- When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the describe tool is:
```
pr_commands = ["/describe --pr_description.add_original_user_description=true"
"--pr_description.keep_original_user_title=true", ...]
```
meaning the `describe` tool will run automatically on every PR, will keep the original title, and will add the original user description above the generated description.
<br> This default settings aim to strike a good balance between automation and control:
If you want more automation, just give the PR a title, and the tool will auto-write a full description; If you want more control, you can add a detailed description, and the tool will add the complementary description below it.
- For maximal automation, you can change the default mode to:
```
pr_commands = ["/describe --pr_description.add_original_user_description=false"
"--pr_description.keep_original_user_title=true", ...]
```
so the title will be auto-generated as well.
- Markers are an alternative way to control the generated description, to give maximal control to the user. If you set:
```
pr_commands = ["/describe --pr_description.use_description_markers=true", ...]
```
the tool will replace every marker of the form `pr_agent:marker_name` in the PR description with the relevant content, where `marker_name` is one of the following:
- `type`: the PR type.
- `summary`: the PR summary.
- `walkthrough`: the PR walkthrough.
Note that when markers are enabled, if the original PR description does not contain any markers, the tool will not alter the description at all.
### Custom labels
The default labels of the describe tool are quite generic, since they are meant to be used in any repo: [`Bug fix`, `Tests`, `Enhancement`, `Documentation`, `Other`].
If you specify [custom labels](#handle-custom-labels-from-the-repos-labels-page-gem) in the repo's labels page, you can get tailored labels for your use cases.
Examples for custom labels:
- `Main topic:performance` - pr_agent:The main topic of this PR is performance
- `New endpoint` - pr_agent:A new endpoint was added in this PR
- `SQL query` - pr_agent:A new SQL query was added in this PR
- `Dockerfile changes` - pr_agent:The PR contains changes in the Dockerfile
- ...
The list above is eclectic, and aims to give an idea of different possibilities. Define custom labels that are relevant for your repo and use cases.
Note that Labels are not mutually exclusive, so you can add multiple label categories.
<br>Make sure to provide proper title, and a detailed and well-phrased description for each label, so the tool will know when to suggest it.

27
docs/Full_environments.md Normal file
View File

@ -0,0 +1,27 @@
## Overview
`PR-Agent` offers extensive pull request functionalities across various git providers:
| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | Gerrit |
|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:|:----------:|
| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Incremental | :white_check_mark: | | | | | |
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Auto-Description | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Improve Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: |
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | |
| | Find similar issue | :white_check_mark: | | | | | |
| | Add Documentation | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
| | Generate Custom Labels 💎 | :white_check_mark: | :white_check_mark: | | | | |
| | | | | | | |
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | App / webhook | :white_check_mark: | :white_check_mark: | | | |
| | Tagging bot | :white_check_mark: | | | | |
| | Actions | :white_check_mark: | | | | |
| | Web server | | | | | | :white_check_mark: |
| | | | | | | |
| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Adaptive and token-aware<br />file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Incremental PR Review | :white_check_mark: | | | | | |

View File

@ -1,4 +1,4 @@
# Generate Custom Labels
# Generate Custom Labels 💎
The `generate_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:
@ -14,16 +14,32 @@ If we wish to add detect changes to SQL queries in a given PR, we can add the fo
When running the `generate_labels` tool on a PR that includes changes in SQL queries, it will automatically suggest the custom label:
<kbd><img src=https://codium.ai/images/pr_agent/custom_label_published.png width="768"></kbd>
Note that in addition to the dedicated tool `generate_labels`, the custom labels will also be used by the `describe` tool.
### How to enable custom labels
There are 3 ways to enable custom labels:
Note that in addition to the dedicated tool `generate_labels`, the custom labels will also be used by the `review` and `describe` tools.
#### 1. CLI (local configuration file)
When working from CLI, you need to apply the [configuration changes](#configuration-changes) to the [custom_labels file](./../pr_agent/settings/custom_labels.toml):
#### CLI
To enable custom labels, you need to apply the [configuration changes](#configuration-changes) to the [custom_labels file](./../pr_agent/settings/custom_labels.toml):
#### GitHub Action and GitHub App
#### 2. Repo configuration file
To enable custom labels, you need to apply the [configuration changes](#configuration-changes) to the local `.pr_agent.toml` file in you repository.
#### 3. Handle custom labels from the Repo's labels page
> This feature is available only in PR-Agent Pro
* GitHub : `https://github.com/{owner}/{repo}/labels`, or click on the "Labels" tab in the issues or PRs page.
* GitLab : `https://gitlab.com/{owner}/{repo}/-/labels`, or click on "Manage" -> "Labels" on the left menu.
b. Add/edit the custom labels. It should be formatted as follows:
* Label name: The name of the custom label.
* Description: Start the description of with prefix `pr_agent:`, for example: `pr_agent: Description of when AI should suggest this label`.<br>
The description should be comprehensive and detailed, indicating when to add the desired label.
<kbd><img src=https://codium.ai/images/pr_agent/add_native_custom_labels.png width="880"></kbd>
c. Now the custom labels will be included in the `generate_labels` tool.
*This feature is supported in GitHub and GitLab.
#### Configuration changes
- 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. It should be formatted as follows:

View File

@ -1,57 +1,90 @@
# Improve Tool
The `improve` tool scans the PR code changes, and automatically generates committable suggestions for improving the PR code.
It can be invoked manually by commenting on any PR:
## Table of Contents
- [Overview](#overview)
- [Configuration options](#configuration-options)
- [Summarize mode](#summarize-mode)
- [Usage Tips](#usage-tips)
- [Extra instructions](#extra-instructions)
- [PR footprint - regular vs summarize mode](#pr-footprint---regular-vs-summarize-mode)
- [A note on code suggestions quality](#a-note-on-code-suggestions-quality)
## Overview
The `improve` tool scans the PR code changes, and automatically generates suggestions for improving the PR code.
The tool can be triggered automatically every time a new PR is [opened](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools), or it can be invoked manually by commenting on any PR:
```
/improve
```
For example:
<kbd><img src=https://codium.ai/images/pr_agent/improve_comment.png width="768"></kbd>
### Summarized vs commitable code suggestions
The code suggestions can be presented as a single comment (via `pr_code_suggestions.summarize=true`):
___
<kbd><img src=https://codium.ai/images/pr_agent/code_suggestions_as_comment.png width="768"></kbd>
___
Or as a separate commitable code comment for each suggestion:
___
<kbd><img src=https://codium.ai/images/pr_agent/improve.png width="768"></kbd>
The `improve` tool can also be triggered automatically every time a new PR is opened. See examples for automatic triggers for [GitHub App](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) and [GitHub Action](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
---
Note that a single comment has a significantly smaller PR footprint. We recommend this mode for most cases.
Also note that collapsible are not supported in _Bitbucket_. Hence, the suggestions are presented there as code comments.
### Extended mode
An extended mode, which does not involve PR Compression and provides more comprehensive suggestions, can be invoked by commenting on any PR:
```
/improve --extended
```
Note that the extended mode divides the PR code changes into chunks, up to the token limits, where each chunk is handled separately (multiple calls to GPT-4).
Note that the extended mode divides the PR code changes into chunks, up to the token limits, where each chunk is handled separately (might use multiple calls to GPT-4 for large PRs).
Hence, the total number of suggestions is proportional to the number of chunks, i.e., the size of the PR.
### Configuration options
Under the section 'pr_code_suggestions', the [configuration file](./../pr_agent/settings/configuration.toml#L40) contains options to customize the 'improve' tool:
To edit [configurations](./../pr_agent/settings/configuration.toml#L66) related to the improve tool (`pr_code_suggestions` section), use the following template:
```
/improve --pr_code_suggestions.some_config1=... --pr_code_suggestions.some_config2=...
```
#### General options
- `num_code_suggestions`: number of code suggestions provided by the 'improve' tool. Default is 4.
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
- `rank_suggestions`: if set to true, the tool will rank the suggestions, based on importance. Default is false.
- `summarize`: if set to true, the tool will display the suggestions in a single comment. Default is false.
- `enable_help_text`: if set to true, the tool will display a help text in the comment. Default is true.
#### params for '/improve --extended' mode
- `auto_extended_mode`: enable extended mode automatically (no need for the `--extended` option). Default is false.
- `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.
#### summarize mode
- `summarize`: if set to true, the tool will present the code suggestions in a compact way. Default is false.
In this mode, instead of presenting committable suggestions, the different suggestions will be combined into a single compact comment, with significantly smaller PR footprint.
## Usage Tips
For example:
### Extra instructions
Extra instructions are very important for the `imrpove` tool, since they enable you to guide the model to suggestions that are more relevant to the specific needs of the project.
`/improve --pr_code_suggestions.summarize=true`
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Specify relevant aspects that you want the model to focus on.
<kbd><img src=https://codium.ai/images/pr_agent/improved_summerize_open.png width="768"></kbd>
___
Examples for extra instructions:
```
[pr_code_suggestions] # /improve #
extra_instructions="""
Emphasize the following aspects:
- Does the code logic cover relevant edge cases?
- Is the code logic clear and easy to understand?
- Is the code logic efficient?
...
"""
```
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
### A note on code suggestions quality
- While the current AI for code is getting better and better (GPT-4), it's not flawless. 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.

View File

@ -1,40 +1,59 @@
# Review Tool
## Table of Contents
- [Overview](#overview)
- [Configuration options](#configuration-options)
- [Incremental Mode](#incremental-mode)
- [PR Reflection](#pr-reflection)
- [Usage Tips](#usage-tips)
- [General guidelines](#general-guidelines)
- [Code suggestions](#code-suggestions)
- [Automation](#automation)
- [Auto-labels](#auto-labels)
- [Extra instructions](#extra-instructions)
## Overview
The `review` tool scans the PR code changes, and automatically generates a PR review.
It can be invoked manually by commenting on any PR:
The tool can be triggered automatically every time a new PR is [opened](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools), or can be invoked manually by commenting on any PR:
```
/review
```
For example:
___
<kbd><img src=https://codium.ai/images/pr_agent/review_comment.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/review.png width="768"></kbd>
The `review` tool can also be triggered automatically every time a new PR is opened. See examples for automatic triggers for [GitHub App](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) and [GitHub Action](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
___
### Configuration options
Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L16) contains options to customize the 'review' tool:
To edit [configurations](./../pr_agent/settings/configuration.toml#L19) related to the review tool (`pr_reviewer` section), use the following template:
```
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
```
#### enable\\disable features
#### General options
- `num_code_suggestions`: number of code suggestions provided by the 'review' tool. Default is 4.
- `inline_code_comments`: if set to true, the tool will publish the code suggestions as comments on the code diff. Default is false.
- `persistent_comment`: if set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
#### Enable\\disable features
- `require_focused_review`: if set to true, the tool will add a section - 'is the PR a focused one'. Default is false.
- `require_score_review`: if set to true, the tool will add a section that scores the PR. Default is false.
- `require_tests_review`: if set to true, the tool will add a section that checks if the PR contains tests. Default is true.
- `require_security_review`: if set to true, the tool will add a section that checks if the PR contains security issues. Default is true.
- `require_estimate_effort_to_review`: if set to true, the tool will add a section that estimates thed effort needed to review the PR. Default is true.
#### general options
- `num_code_suggestions`: number of code suggestions provided by the 'review' tool. Default is 4.
- `inline_code_comments`: if set to true, the tool will publish the code suggestions as comments on the code diff. Default is false.
- `automatic_review`: if set to false, no automatic reviews will be done. Default is true.
- `remove_previous_review_comment`: if set to true, the tool will remove the previous review comment before adding a new one. Default is false.
- `persistent_comment`: if set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
#### review labels
- `require_estimate_effort_to_review`: if set to true, the tool will add a section that estimates the effort needed to review the PR. Default is true.
#### SOC2 ticket compliance 💎
This sub-tool checks if the PR description properly contains a ticket to a project management system (e.g., Jira, Asana, Trello, etc.), as required by SOC2 compliance. If not, it will add a label to the PR: "Missing SOC2 ticket".
- `require_soc2_ticket`: If set to true, the SOC2 ticket checker sub-tool will be enabled. Default is false.
- `soc2_ticket_prompt`: The prompt for the SOC2 ticket review. Default is: `Does the PR description include a link to ticket in a project management system (e.g., Jira, Asana, Trello, etc.) ?`. Edit this field if your compliance requirements are different.
#### Adding PR labels
- `enable_review_labels_security`: if set to true, the tool will publish a 'possible security issue' label if it detects a security issue. Default is true.
- `enable_review_labels_effort`: if set to true, the tool will publish a 'Review effort [1-5]: x' label. Default is false.
- To enable `custom labels`, apply the configuration changes described [here](./GENERATE_CUSTOM_LABELS.md#configuration-changes)
#### Incremental Mode
For an incremental review, which only considers changes since the last PR-Agent review, this can be useful when working on the PR in an iterative manner, and you want to focus on the changes since the last review instead of reviewing the entire PR again, the following command can be used:
- `enable_review_labels_effort`: if set to true, the tool will publish a 'Review effort [1-5]: x' label. Default is true.
### Incremental Mode
Incremental review only considers changes since the last PR-Agent review. This can be useful when working on the PR in an iterative manner, and you want to focus on the changes since the last review instead of reviewing the entire PR again.
For invoking the incremental mode, the following command can be used:
```
/review -i
```
@ -42,7 +61,7 @@ Note that the incremental mode is only available for GitHub.
<kbd><img src=https://codium.ai/images/pr_agent/incremental_review.png width="768"></kbd>
Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L16) contains options to customize the 'review -i' tool.
Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L19) contains options to customize the 'review -i' tool.
These configurations can be used to control the rate at which the incremental review tool will create new review comments when invoked automatically, to prevent making too much noise in the PR.
- `minimal_commits_for_incremental_review`: Minimal number of commits since the last review that are required to create incremental review.
If there are less than the specified number of commits since the last review, the tool will not perform any action.
@ -55,25 +74,75 @@ For example, if `minimal_commits_for_incremental_review=2` and `minimal_minutes_
When `require_all_thresholds_for_incremental_review=true` the incremental review __will not__ run, because only 1 out of 2 conditions were met (we have enough commits but the last review is too recent),
but when `require_all_thresholds_for_incremental_review=false` the incremental review __will__ run, because one condition is enough (we have 3 commits which is more than the configured 2).
Default is false - the tool will run as long as at least once conditions is met.
- `remove_previous_review_comment`: if set to true, the tool will remove the previous review comment before adding a new one. Default is false.
#### PR Reflection
### PR Reflection
By invoking:
```
/reflect_and_review
```
The tool will first ask the author questions about the PR, and will guide the review based on his answers.
The tool will first ask the author questions about the PR, and will guide the review based on their answers.
<kbd><img src=https://codium.ai/images/pr_agent/reflection_questions.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/reflection_answers.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/reflection_insights.png width="768"></kbd>
___
#### A note on code suggestions quality
## Usage Tips
1) [General guidelines](#general-guidelines)
2) [Code suggestions](#code-suggestions)
3) [Automation](#automation)
4) [Auto-labels](#auto-labels)
5) [Extra instructions](#extra-instructions)
- 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.
### General guidelines
The `review` tool provides a collection of possible feedbacks about a PR.
It is recommended to review the [Configuration options](#configuration-options) section, and choose the relevant options for your use case.
- 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.
Some of the feature that are disabled by default are quite useful, and should be considered for enabling. For example:
`require_score_review`, `require_soc2_ticket`, and more.
- Recommended to use the 'extra_instructions' field to guide the model to suggestions that are more relevant to the specific needs of the project.
On the other hand, if you find one of the enabled features to be irrelevant for your use case, disable it. No default configuration can fit all use cases.
### Code suggestions
The `review` tool provides several type of feedbacks, one of them is code suggestions.
If you are interested **only** in the code suggestions, it is recommended to use the [`improve`](./IMPROVE.md) feature instead, since it dedicated only to code suggestions, and usually gives better results.
Use the `review` tool if you want to get a more comprehensive feedback, which includes code suggestions as well.
### Automation
- When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the `review` tool is:
```
pr_commands = ["/review", ...]
```
meaning the `review` tool will run automatically on every PR, with the default configuration.
Edit this field to enable/disable the tool, or to change the used configurations
### Auto-labels
The `review` tool can auto-generate two specific types of labels for a PR:
- a `possible security issue` label that detects a possible [security issue](https://github.com/Codium-ai/pr-agent/blob/tr/user_description/pr_agent/settings/pr_reviewer_prompts.toml#L136) (`enable_review_labels_security` flag)
- a `Review effort [1-5]: x` label, where x is the estimated effort to review the PR (`enable_review_labels_effort` flag)
Both modes are useful, and we recommended to enable them.
### Extra instructions
Extra instruction are important.
The `review` tool can be configured with extra instructions, which can be used to guide the model to a feedback tailored to the needs of your project.
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Specify the relevant sub-tool, and the relevant aspects of the PR that you want to emphasize.
Examples for extra instructions:
```
[pr_reviewer] # /review #
extra_instructions="""
In the code feedback section, emphasize the following:
- Does the code logic cover relevant edge cases?
- Is the code logic clear and easy to understand?
- Is the code logic efficient?
...
"""
```
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
- 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.

View File

@ -12,7 +12,15 @@ For example:
Note that to perform retrieval, the `similar_issue` tool indexes all the repo previous issues (once).
To enable usage of the '**similar issue**' tool, you need to set the following keys in `.secrets.toml` (or in the relevant environment variables):
**Select VectorDBs** by changing `pr_similar_issue` parameter in `configuration.toml` file
2 VectorDBs are available to switch in
1. LanceDB
2. Pinecone
To enable usage of the '**similar issue**' tool for Pinecone, you need to set the following keys in `.secrets.toml` (or in the relevant environment variables):
```
[pinecone]
api_key = "..."

30
docs/TEST.md Normal file
View File

@ -0,0 +1,30 @@
# Test Tool 💎
By combining LLM abilities with static code analysis, the `test` tool generate tests for a selected component, based on the PR code changes.
It can be invoked manually by commenting on any PR:
```
/test component_name
```
where 'component_name' is the name of a specific component in the PR.
To get a list of the components that changed in the PR, use the [`analyze`](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) tool.
An example [result](https://github.com/Codium-ai/pr-agent/pull/598#issuecomment-1913679429):
<kbd><img src=https://codium.ai/images/pr_agent/test1.png width="704"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/test2.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/test3.png width="768"></kbd>
Language that are currently supported by the tool: Python, Java, C++, JavaScript, TypeScript.
### Configuration options
- `num_tests`: number of tests to generate. Default is 3.
- `testing_framework`: the testing framework to use. If not set, for Python it will use `pytest`, for Java it will use `JUnit`, for C++ it will use `Catch2`, and for JavaScript and TypeScript it will use `jest`.
- `avoid_mocks`: if set to true, the tool will try to avoid using mocks in the generated tests. Note that even if this option is set to true, the tool might still use mocks if it cannot generate a test without them. Default is true.
- `extra_instructions`: Optional extra instructions to the tool. For example: "use the following mock injection scheme: ...".
- `file`: in case there are several components with the same name, you can specify the relevant file.
- `class_name`: in case there are several methods with the same name in the same file, you can specify the relevant class name.
- `enable_help_text`: if set to true, the tool will add a help text to the PR comment. Default is true.

View File

@ -5,7 +5,9 @@
- [ASK](./ASK.md)
- [SIMILAR_ISSUE](./SIMILAR_ISSUE.md)
- [UPDATE CHANGELOG](./UPDATE_CHANGELOG.md)
- [ADD DOCUMENTATION](./ADD_DOCUMENTATION.md)
- [GENERATE CUSTOM LABELS](./GENERATE_CUSTOM_LABELS.md)
- [ADD DOCUMENTATION](./ADD_DOCUMENTATION.md) 💎
- [GENERATE CUSTOM LABELS](./GENERATE_CUSTOM_LABELS.md) 💎
- [Analyze](./Analyze.md) 💎
- [Test](./TEST.md) 💎
See the **[installation guide](/INSTALL.md)** for instructions on how to setup PR-Agent.
See the **[installation guide](/INSTALL.md)** for instructions on setting up PR-Agent.

View File

@ -1,8 +1,13 @@
import shlex
from functools import partial
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.utils import update_settings_from_args
from pr_agent.config_loader import get_settings
from pr_agent.git_providers.utils import apply_repo_settings
from pr_agent.log import get_logger
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
@ -38,8 +43,8 @@ command2class = {
commands = list(command2class.keys())
class PRAgent:
def __init__(self):
pass
def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.ai_handler = ai_handler # will be initialized in run_action
async def handle_request(self, pr_url, request, notify=None) -> bool:
# First, apply repo specific settings if exists
@ -61,13 +66,14 @@ class PRAgent:
if action == "answer":
if notify:
notify()
await PRReviewer(pr_url, is_answer=True, args=args).run()
await PRReviewer(pr_url, is_answer=True, args=args, ai_handler=self.ai_handler).run()
elif action == "auto_review":
await PRReviewer(pr_url, is_auto=True, args=args).run()
await PRReviewer(pr_url, is_auto=True, args=args, ai_handler=self.ai_handler).run()
elif action in command2class:
if notify:
notify()
await command2class[action](pr_url, args=args).run()
await command2class[action](pr_url, ai_handler=self.ai_handler, args=args).run()
else:
return False
return True

View File

@ -0,0 +1,28 @@
from abc import ABC, abstractmethod
class BaseAiHandler(ABC):
"""
This class defines the interface for an AI handler to be used by the PR Agents.
"""
@abstractmethod
def __init__(self):
pass
@property
@abstractmethod
def deployment_id(self):
pass
@abstractmethod
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
"""
This method should be implemented to return a chat completion from the AI model.
Args:
model (str): the name of the model to use for the chat completion
system (str): the system message string to use for the chat completion
user (str): the user message string to use for the chat completion
temperature (float): the temperature to use for the chat completion
"""
pass

View File

@ -0,0 +1,67 @@
try:
from langchain.chat_models import ChatOpenAI, AzureChatOpenAI
from langchain.schema import SystemMessage, HumanMessage
except: # we don't enforce langchain as a dependency, so if it's not installed, just move on
pass
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger
from openai.error import APIError, RateLimitError, Timeout, TryAgain
from retry import retry
import functools
OPENAI_RETRIES = 5
class LangChainOpenAIHandler(BaseAiHandler):
def __init__(self):
# Initialize OpenAIHandler specific attributes here
super().__init__()
self.azure = get_settings().get("OPENAI.API_TYPE", "").lower() == "azure"
try:
if self.azure:
# using a partial function so we can set the deployment_id later to support fallback_deployments
# but still need to access the other settings now so we can raise a proper exception if they're missing
self._chat = functools.partial(
lambda **kwargs: AzureChatOpenAI(**kwargs),
openai_api_key=get_settings().openai.key,
openai_api_base=get_settings().openai.api_base,
openai_api_version=get_settings().openai.api_version,
)
else:
self._chat = ChatOpenAI(openai_api_key=get_settings().openai.key)
except AttributeError as e:
if getattr(e, "name"):
raise ValueError(f"OpenAI {e.name} is required") from e
else:
raise e
@property
def chat(self):
if self.azure:
# we must set the deployment_id only here (instead of the __init__ method) to support fallback_deployments
return self._chat(deployment_name=self.deployment_id)
else:
return self._chat
@property
def deployment_id(self):
"""
Returns the deployment ID for the OpenAI API.
"""
return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
@retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError),
tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
try:
messages=[SystemMessage(content=system), HumanMessage(content=user)]
# get a chat completion from the formatted messages
resp = self.chat(messages, model=model, temperature=temperature)
finish_reason="completed"
return resp.content, finish_reason
except (Exception) as e:
get_logger().error("Unknown error during OpenAI inference: ", e)
raise e

View File

@ -6,13 +6,14 @@ import openai
from litellm import acompletion
from openai.error import APIError, RateLimitError, Timeout, TryAgain
from retry import retry
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger
OPENAI_RETRIES = 5
class AiHandler:
class LiteLLMAIHandler(BaseAiHandler):
"""
This class handles interactions with the OpenAI API for chat completions.
It initializes the API key and other settings from a configuration file,
@ -100,11 +101,6 @@ class AiHandler:
"""
try:
deployment_id = self.deployment_id
if get_settings().config.verbosity_level >= 2:
get_logger().debug(
f"Generating completion with {model}"
f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
)
if self.azure:
model = 'azure/' + model
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]

View File

@ -0,0 +1,67 @@
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
import openai
from openai.error import APIError, RateLimitError, Timeout, TryAgain
from retry import retry
from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger
OPENAI_RETRIES = 5
class OpenAIHandler(BaseAiHandler):
def __init__(self):
# Initialize OpenAIHandler specific attributes here
try:
super().__init__()
openai.api_key = get_settings().openai.key
if get_settings().get("OPENAI.ORG", None):
openai.organization = get_settings().openai.org
if get_settings().get("OPENAI.API_TYPE", None):
if get_settings().openai.api_type == "azure":
self.azure = True
openai.azure_key = get_settings().openai.key
if get_settings().get("OPENAI.API_VERSION", None):
openai.api_version = get_settings().openai.api_version
if get_settings().get("OPENAI.API_BASE", None):
openai.api_base = get_settings().openai.api_base
except AttributeError as e:
raise ValueError("OpenAI key is required") from e
@property
def deployment_id(self):
"""
Returns the deployment ID for the OpenAI API.
"""
return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
@retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError),
tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
try:
deployment_id = self.deployment_id
get_logger().info("System: ", system)
get_logger().info("User: ", user)
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
chat_completion = await openai.ChatCompletion.acreate(
model=model,
deployment_id=deployment_id,
messages=messages,
temperature=temperature,
)
resp = chat_completion["choices"][0]['message']['content']
finish_reason = chat_completion["choices"][0]["finish_reason"]
usage = chat_completion.get("usage")
get_logger().info("AI response", response=resp, messages=messages, finish_reason=finish_reason,
model=model, usage=usage)
return resp, finish_reason
except (APIError, Timeout, TryAgain) as e:
get_logger().error("Error during OpenAI inference: ", e)
raise
except (RateLimitError) as e:
get_logger().error("Rate limit error during OpenAI inference: ", e)
raise
except (Exception) as e:
get_logger().error("Unknown error during OpenAI inference: ", e)
raise TryAgain from e

View File

@ -181,7 +181,7 @@ __old hunk__
...
"""
patch_with_lines_str = f"\n\n## {file.filename}\n"
patch_with_lines_str = f"\n\n## file: '{file.filename.strip()}'\n"
patch_lines = patch.splitlines()
RE_HUNK_HEADER = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
@ -202,11 +202,11 @@ __old hunk__
if new_content_lines:
if prev_header_line:
patch_with_lines_str += f'\n{prev_header_line}\n'
patch_with_lines_str += '__new hunk__\n'
patch_with_lines_str = patch_with_lines_str.rstrip()+'\n__new hunk__\n'
for i, line_new in enumerate(new_content_lines):
patch_with_lines_str += f"{start2 + i} {line_new}\n"
if old_content_lines:
patch_with_lines_str += '__old hunk__\n'
patch_with_lines_str = patch_with_lines_str.rstrip()+'\n__old hunk__\n'
for line_old in old_content_lines:
patch_with_lines_str += f"{line_old}\n"
new_content_lines = []
@ -236,11 +236,11 @@ __old hunk__
if match and new_content_lines:
if new_content_lines:
patch_with_lines_str += f'\n{header_line}\n'
patch_with_lines_str += '\n__new hunk__\n'
patch_with_lines_str = patch_with_lines_str.rstrip()+ '\n__new hunk__\n'
for i, line_new in enumerate(new_content_lines):
patch_with_lines_str += f"{start2 + i} {line_new}\n"
if old_content_lines:
patch_with_lines_str += '\n__old hunk__\n'
patch_with_lines_str = patch_with_lines_str.rstrip() + '\n__old hunk__\n'
for line_old in old_content_lines:
patch_with_lines_str += f"{line_old}\n"

View File

@ -209,9 +209,9 @@ def pr_generate_compressed_diff(top_langs: list, token_handler: TokenHandler, mo
if patch:
if not convert_hunks_to_line_numbers:
patch_final = f"## {file.filename}\n\n{patch}\n"
patch_final = f"\n\n## file: '{file.filename.strip()}\n\n{patch.strip()}\n'"
else:
patch_final = patch
patch_final = "\n\n" + patch.strip()
patches.append(patch_final)
total_tokens += token_handler.count_tokens(patch_final)
if get_settings().config.verbosity_level >= 2:
@ -226,6 +226,11 @@ async def retry_with_fallback_models(f: Callable):
# try each (model, deployment_id) pair until one is successful, otherwise raise exception
for i, (model, deployment_id) in enumerate(zip(all_models, all_deployments)):
try:
if get_settings().config.verbosity_level >= 2:
get_logger().debug(
f"Generating prediction with {model}"
f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
)
get_settings().set("openai.deployment_id", deployment_id)
return await f(model)
except Exception as e:
@ -264,19 +269,10 @@ def _get_all_deployments(all_models: List[str]) -> List[str]:
def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
relevant_file: str,
relevant_line_in_file: str) -> Tuple[int, int]:
"""
Find the line number and absolute position of a relevant line in a file.
Args:
diff_files (List[FilePatchInfo]): A list of FilePatchInfo objects representing the patches of files.
relevant_file (str): The name of the file where the relevant line is located.
relevant_line_in_file (str): The content of the relevant line.
Returns:
Tuple[int, int]: A tuple containing the line number and absolute position of the relevant line in the file.
"""
relevant_line_in_file: str,
absolute_position: int = None) -> Tuple[int, int]:
position = -1
if absolute_position is None:
absolute_position = -1
re_hunk_header = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
@ -285,15 +281,32 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
if file.filename and (file.filename.strip() == relevant_file):
patch = file.patch
patch_lines = patch.splitlines()
delta = 0
start1, size1, start2, size2 = 0, 0, 0, 0
if absolute_position != -1: # matching absolute to relative
for i, line in enumerate(patch_lines):
# new hunk
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
#
absolute_position_curr = start2 + delta - 1
if absolute_position_curr == absolute_position:
position = i
break
else:
# try to find the line in the patch using difflib, with some margin of error
matches_difflib: list[str | Any] = difflib.get_close_matches(relevant_line_in_file,
patch_lines, n=3, cutoff=0.93)
if len(matches_difflib) == 1 and matches_difflib[0].startswith('+'):
relevant_line_in_file = matches_difflib[0]
delta = 0
start1, size1, start2, size2 = 0, 0, 0, 0
for i, line in enumerate(patch_lines):
if line.startswith('@@'):
delta = 0
@ -362,6 +375,13 @@ def get_pr_multi_diffs(git_provider: GitProvider,
for lang in pr_languages:
sorted_files.extend(sorted(lang['files'], key=lambda x: x.tokens, reverse=True))
# try first a single run with standard diff string, with patch extension, and no deletions
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
pr_languages, token_handler, add_line_numbers_to_hunks=True)
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < get_max_tokens(model):
return ["\n".join(patches_extended)]
patches = []
final_diff_list = []
total_tokens = token_handler.prompt_tokens

View File

@ -49,7 +49,7 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool=True) -> str:
}
for key, value in output_data.items():
if value is None or value == '' or value == {}:
if value is None or value == '' or value == {} or value == []:
continue
if isinstance(value, dict):
markdown_text += f"## {key}\n\n"
@ -58,10 +58,10 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool=True) -> str:
emoji = emojis.get(key, "")
if key.lower() == 'code feedback':
if gfm_supported:
markdown_text += f"\n\n- "
markdown_text += f"<details><summary> { emoji } Code feedback:</summary>"
markdown_text += f"\n\n"
markdown_text += f"<details><summary> <strong>{ emoji } Code feedback:</strong></summary>"
else:
markdown_text += f"\n\n- **{emoji} Code feedback:**\n\n"
markdown_text += f"\n\n**{emoji} Code feedback:**\n\n"
else:
markdown_text += f"- {emoji} **{key}:**\n\n"
for i, item in enumerate(value):
@ -76,6 +76,12 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool=True) -> str:
markdown_text += "\n\n"
elif value != 'n/a':
emoji = emojis.get(key, "")
if key.lower() == 'general suggestions':
if gfm_supported:
markdown_text += f"\n\n<strong>{emoji} General suggestions:</strong> {value}\n"
else:
markdown_text += f"{emoji} **General suggestions:** {value}\n"
else:
markdown_text += f"- {emoji} **{key}:** {value}\n"
return markdown_text
@ -102,14 +108,15 @@ def parse_code_suggestion(code_suggestions: dict, i: int = 0, gfm_supported: boo
markdown_text += f"<tr><td>{sub_key}</td><td>{relevant_file}</td></tr>"
# continue
elif sub_key.lower() == 'suggestion':
markdown_text += f"<tr><td>{sub_key} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td><strong>{sub_value}</strong></td></tr>"
markdown_text += (f"<tr><td>{sub_key} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>"
f"<td><br>\n\n**{sub_value.strip()}**\n<br></td></tr>")
elif sub_key.lower() == 'relevant line':
markdown_text += f"<tr><td>relevant line</td>"
sub_value_list = sub_value.split('](')
relevant_line = sub_value_list[0].lstrip('`').lstrip('[')
if len(sub_value_list) > 1:
link = sub_value_list[1].rstrip(')').strip('`')
markdown_text += f"<td><a href={link}>{relevant_line}</a></td>"
markdown_text += f"<td><a href='{link}'>{relevant_line}</a></td>"
else:
markdown_text += f"<td>{relevant_line}</td>"
markdown_text += "</tr>"
@ -316,19 +323,21 @@ def _fix_key_value(key: str, value: str):
return key, value
def load_yaml(response_text: str) -> dict:
def load_yaml(response_text: str, keys_fix_yaml: List[str] = []) -> dict:
response_text = response_text.removeprefix('```yaml').rstrip('`')
try:
data = yaml.safe_load(response_text)
except Exception as e:
get_logger().error(f"Failed to parse AI prediction: {e}")
data = try_fix_yaml(response_text)
data = try_fix_yaml(response_text, keys_fix_yaml=keys_fix_yaml)
return data
def try_fix_yaml(response_text: str) -> dict:
def try_fix_yaml(response_text: str, keys_fix_yaml: List[str] = []) -> dict:
response_text_lines = response_text.split('\n')
keys = ['relevant line:', 'suggestion content:', 'relevant file:']
keys = ['relevant line:', 'suggestion content:', 'relevant file:', 'existing code:', 'improved code:']
keys = keys + keys_fix_yaml
# first fallback - try to convert 'relevant line: ...' to relevant line: |-\n ...'
response_text_lines_copy = response_text_lines.copy()
for i in range(0, len(response_text_lines_copy)):
@ -343,22 +352,34 @@ def try_fix_yaml(response_text: str) -> dict:
except:
get_logger().info(f"Failed to parse AI prediction after adding |-\n")
# second fallback - try to remove last lines
# second fallback - try to extract only range from first ```yaml to ````
snippet_pattern = r'```(yaml)?[\s\S]*?```'
snippet = re.search(snippet_pattern, '\n'.join(response_text_lines_copy))
if snippet:
snippet_text = snippet.group()
try:
data = yaml.safe_load(snippet_text.removeprefix('```yaml').rstrip('`'))
get_logger().info(f"Successfully parsed AI prediction after extracting yaml snippet")
return data
except:
pass
# third fallback - try to remove leading and trailing curly brackets
response_text_copy = response_text.strip().rstrip().removeprefix('{').removesuffix('}')
try:
data = yaml.safe_load(response_text_copy,)
get_logger().info(f"Successfully parsed AI prediction after removing curly brackets")
return data
except:
pass
# fourth fallback - try to remove last lines
data = {}
for i in range(1, len(response_text_lines)):
response_text_lines_tmp = '\n'.join(response_text_lines[:-i])
try:
data = yaml.safe_load(response_text_lines_tmp,)
get_logger().info(f"Successfully parsed AI prediction after removing {i} lines")
break
except:
pass
# thrid fallback - try to remove leading and trailing curly brackets
response_text_copy = response_text.strip().rstrip().removeprefix('{').removesuffix('}')
try:
data = yaml.safe_load(response_text_copy,)
get_logger().info(f"Successfully parsed AI prediction after removing curly brackets")
return data
except:
pass
@ -379,9 +400,15 @@ def set_custom_labels(variables, git_provider=None):
# Set custom labels
variables["custom_labels_class"] = "class Label(str, Enum):"
counter = 0
labels_minimal_to_labels_dict = {}
for k, v in labels.items():
description = v['description'].strip('\n').replace('\n', '\\n')
variables["custom_labels_class"] += f"\n {k.lower().replace(' ', '_')} = '{k}' # {description}"
description = "'" + v['description'].strip('\n').replace('\n', '\\n') + "'"
# variables["custom_labels_class"] += f"\n {k.lower().replace(' ', '_')} = '{k}' # {description}"
variables["custom_labels_class"] += f"\n {k.lower().replace(' ', '_')} = {description}"
labels_minimal_to_labels_dict[k.lower().replace(' ', '_')] = k
counter += 1
variables["labels_minimal_to_labels_dict"] = labels_minimal_to_labels_dict
def get_user_labels(current_labels: List[str] = None):
"""
@ -448,3 +475,12 @@ def clip_tokens(text: str, max_tokens: int, add_three_dots=True) -> str:
except Exception as e:
get_logger().warning(f"Failed to clip tokens: {e}")
return text
def replace_code_tags(text):
"""
Replace odd instances of ` with <code> and even instances of ` with </code>
"""
parts = text.split('`')
for i in range(1, len(parts), 2):
parts[i] = '<code>' + parts[i] + '</code>'
return ''.join(parts)

View File

@ -1,29 +1,40 @@
import json
import os
from typing import Optional, Tuple
from urllib.parse import urlparse
import os
from ..log import get_logger
from ..algo.language_handler import is_valid_file
from ..algo.utils import clip_tokens, load_large_diff
from ..config_loader import get_settings
from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider
AZURE_DEVOPS_AVAILABLE = True
try:
# noinspection PyUnresolvedReferences
from msrest.authentication import BasicAuthentication
# noinspection PyUnresolvedReferences
from azure.devops.connection import Connection
from azure.devops.v7_1.git.models import Comment, CommentThread, GitVersionDescriptor, GitPullRequest
# noinspection PyUnresolvedReferences
from azure.devops.v7_1.git.models import (
Comment,
CommentThread,
GitVersionDescriptor,
GitPullRequest,
)
except ImportError:
AZURE_DEVOPS_AVAILABLE = False
from ..config_loader import get_settings
from ..algo.utils import load_large_diff, clip_tokens
from ..algo.language_handler import is_valid_file
from .git_provider import EDIT_TYPE, FilePatchInfo
class AzureDevopsProvider(GitProvider):
class AzureDevopsProvider:
def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False):
def __init__(
self, pr_url: Optional[str] = None, incremental: Optional[bool] = False
):
if not AZURE_DEVOPS_AVAILABLE:
raise ImportError("Azure DevOps provider is not available. Please install the required dependencies.")
raise ImportError(
"Azure DevOps provider is not available. Please install the required dependencies."
)
self.azure_devops_client = self._get_azure_devops_client()
@ -37,9 +48,123 @@ class AzureDevopsProvider:
if pr_url:
self.set_pr(pr_url)
def publish_code_suggestions(self, code_suggestions: list) -> bool:
"""
Publishes code suggestions as comments on the PR.
"""
post_parameters_list = []
for suggestion in code_suggestions:
body = suggestion['body']
relevant_file = suggestion['relevant_file']
relevant_lines_start = suggestion['relevant_lines_start']
relevant_lines_end = suggestion['relevant_lines_end']
if not relevant_lines_start or relevant_lines_start == -1:
if get_settings().config.verbosity_level >= 2:
get_logger().exception(
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}")
continue
if relevant_lines_end < relevant_lines_start:
if get_settings().config.verbosity_level >= 2:
get_logger().exception(f"Failed to publish code suggestion, "
f"relevant_lines_end is {relevant_lines_end} and "
f"relevant_lines_start is {relevant_lines_start}")
continue
if relevant_lines_end > relevant_lines_start:
post_parameters = {
"body": body,
"path": relevant_file,
"line": relevant_lines_end,
"start_line": relevant_lines_start,
"start_side": "RIGHT",
}
else: # API is different for single line comments
post_parameters = {
"body": body,
"path": relevant_file,
"line": relevant_lines_start,
"side": "RIGHT",
}
post_parameters_list.append(post_parameters)
try:
for post_parameters in post_parameters_list:
comment = Comment(content=post_parameters["body"], comment_type=1)
thread = CommentThread(comments=[comment],
thread_context={
"filePath": post_parameters["path"],
"rightFileStart": {
"line": post_parameters["start_line"],
"offset": 1,
},
"rightFileEnd": {
"line": post_parameters["line"],
"offset": 1,
},
})
self.azure_devops_client.create_thread(
comment_thread=thread,
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num
)
if get_settings().config.verbosity_level >= 2:
get_logger().info(
f"Published code suggestion on {self.pr_num} at {post_parameters['path']}"
)
return True
except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish code suggestion, error: {e}")
return False
def get_pr_description_full(self) -> str:
return self.pr.description
def remove_comment(self, comment):
try:
self.azure_devops_client.delete_comment(
repository_id=self.repo_slug,
pull_request_id=self.pr_num,
thread_id=comment["thread_id"],
comment_id=comment["comment_id"],
project=self.workspace_slug,
)
except Exception as e:
get_logger().exception(f"Failed to remove comment, error: {e}")
def publish_labels(self, pr_types):
try:
for pr_type in pr_types:
self.azure_devops_client.create_pull_request_label(
label={"name": pr_type},
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num,
)
except Exception as e:
get_logger().exception(f"Failed to publish labels, error: {e}")
def get_pr_labels(self):
try:
labels = self.azure_devops_client.get_pull_request_labels(
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num,
)
return [label.name for label in labels]
except Exception as e:
get_logger().exception(f"Failed to get labels, error: {e}")
return []
def is_supported(self, capability: str) -> bool:
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels',
'remove_initial_comment', 'gfm_markdown']:
if capability in [
"get_issue_comments",
"create_inline_comment",
"publish_inline_comments",
]:
return False
return True
@ -49,26 +174,35 @@ class AzureDevopsProvider:
def get_repo_settings(self):
try:
contents = self.azure_devops_client.get_item_content(repository_id=self.repo_slug,
project=self.workspace_slug, download=False,
include_content_metadata=False, include_content=True,
path=".pr_agent.toml")
contents = self.azure_devops_client.get_item_content(
repository_id=self.repo_slug,
project=self.workspace_slug,
download=False,
include_content_metadata=False,
include_content=True,
path=".pr_agent.toml",
)
return contents
except Exception as e:
get_logger().exception("get repo settings error")
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to get repo settings, error: {e}")
return ""
def get_files(self):
files = []
for i in self.azure_devops_client.get_pull_request_commits(project=self.workspace_slug,
for i in self.azure_devops_client.get_pull_request_commits(
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num):
changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug,
repository_id=self.repo_slug, commit_id=i.commit_id)
pull_request_id=self.pr_num,
):
changes_obj = self.azure_devops_client.get_changes(
project=self.workspace_slug,
repository_id=self.repo_slug,
commit_id=i.commit_id,
)
for c in changes_obj.changes:
files.append(c['item']['path'])
files.append(c["item"]["path"])
return list(set(files))
def get_diff_files(self) -> list[FilePatchInfo]:
@ -76,22 +210,27 @@ class AzureDevopsProvider:
base_sha = self.pr.last_merge_target_commit
head_sha = self.pr.last_merge_source_commit
commits = self.azure_devops_client.get_pull_request_commits(project=self.workspace_slug,
commits = self.azure_devops_client.get_pull_request_commits(
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num)
pull_request_id=self.pr_num,
)
diff_files = []
diffs = []
diff_types = {}
for c in commits:
changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug,
repository_id=self.repo_slug, commit_id=c.commit_id)
changes_obj = self.azure_devops_client.get_changes(
project=self.workspace_slug,
repository_id=self.repo_slug,
commit_id=c.commit_id,
)
for i in changes_obj.changes:
if(i['item']['gitObjectType'] == 'tree'):
if i["item"]["gitObjectType"] == "tree":
continue
diffs.append(i['item']['path'])
diff_types[i['item']['path']] = i['changeType']
diffs.append(i["item"]["path"])
diff_types[i["item"]["path"]] = i["changeType"]
diffs = list(set(diffs))
@ -99,49 +238,73 @@ class AzureDevopsProvider:
if not is_valid_file(file):
continue
version = GitVersionDescriptor(version=head_sha.commit_id, version_type='commit')
version = GitVersionDescriptor(
version=head_sha.commit_id, version_type="commit"
)
try:
new_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug,
new_file_content_str = self.azure_devops_client.get_item(
repository_id=self.repo_slug,
path=file,
project=self.workspace_slug,
version_descriptor=version,
download=False,
include_content=True)
include_content=True,
)
new_file_content_str = new_file_content_str.content
except Exception as error:
get_logger().error("Failed to retrieve new file content of %s at version %s. Error: %s", file, version, str(error))
get_logger().error(
"Failed to retrieve new file content of %s at version %s. Error: %s",
file,
version,
str(error),
)
new_file_content_str = ""
edit_type = EDIT_TYPE.MODIFIED
if diff_types[file] == 'add':
if diff_types[file] == "add":
edit_type = EDIT_TYPE.ADDED
elif diff_types[file] == 'delete':
elif diff_types[file] == "delete":
edit_type = EDIT_TYPE.DELETED
elif diff_types[file] == 'rename':
elif diff_types[file] == "rename":
edit_type = EDIT_TYPE.RENAMED
version = GitVersionDescriptor(version=base_sha.commit_id, version_type='commit')
version = GitVersionDescriptor(
version=base_sha.commit_id, version_type="commit"
)
try:
original_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug,
original_file_content_str = self.azure_devops_client.get_item(
repository_id=self.repo_slug,
path=file,
project=self.workspace_slug,
version_descriptor=version,
download=False,
include_content=True)
include_content=True,
)
original_file_content_str = original_file_content_str.content
except Exception as error:
get_logger().error("Failed to retrieve original file content of %s at version %s. Error: %s", file, version, str(error))
get_logger().error(
"Failed to retrieve original file content of %s at version %s. Error: %s",
file,
version,
str(error),
)
original_file_content_str = ""
patch = load_large_diff(file, new_file_content_str, original_file_content_str)
patch = load_large_diff(
file, new_file_content_str, original_file_content_str
)
diff_files.append(FilePatchInfo(original_file_content_str, new_file_content_str,
diff_files.append(
FilePatchInfo(
original_file_content_str,
new_file_content_str,
patch=patch,
filename=file,
edit_type=edit_type))
edit_type=edit_type,
)
)
self.diff_files = diff_files
return diff_files
except Exception as e:
print(f"Error: {str(e)}")
@ -150,67 +313,92 @@ class AzureDevopsProvider:
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
comment = Comment(content=pr_comment)
thread = CommentThread(comments=[comment])
thread_response = self.azure_devops_client.create_thread(comment_thread=thread, project=self.workspace_slug,
thread_response = self.azure_devops_client.create_thread(
comment_thread=thread,
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num)
pull_request_id=self.pr_num,
)
if is_temporary:
self.temp_comments.append({'thread_id': thread_response.id, 'comment_id': comment.id})
self.temp_comments.append(
{"thread_id": thread_response.id, "comment_id": thread_response.comments[0].id}
)
def publish_description(self, pr_title: str, pr_body: str):
try:
updated_pr = GitPullRequest()
updated_pr.title = pr_title
updated_pr.description = pr_body
self.azure_devops_client.update_pull_request(project=self.workspace_slug,
self.azure_devops_client.update_pull_request(
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num,
git_pull_request_to_update=updated_pr)
git_pull_request_to_update=updated_pr,
)
except Exception as e:
get_logger().exception(f"Could not update pull request {self.pr_num} description: {e}")
get_logger().exception(
f"Could not update pull request {self.pr_num} description: {e}"
)
def remove_initial_comment(self):
return "" # not implemented yet
try:
for comment in self.temp_comments:
self.remove_comment(comment)
except Exception as e:
get_logger().exception(f"Failed to remove temp comments, error: {e}")
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
raise NotImplementedError("Azure DevOps provider does not support publishing inline comment yet")
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
raise NotImplementedError("Azure DevOps provider does not support creating inline comments yet")
def publish_inline_comment(
self, body: str, relevant_file: str, relevant_line_in_file: str
):
raise NotImplementedError(
"Azure DevOps provider does not support publishing inline comment yet"
)
def publish_inline_comments(self, comments: list[dict]):
raise NotImplementedError("Azure DevOps provider does not support publishing inline comments yet")
raise NotImplementedError(
"Azure DevOps provider does not support publishing inline comments yet"
)
def get_title(self):
return self.pr.title
def get_languages(self):
languages = []
files = self.azure_devops_client.get_items(project=self.workspace_slug, repository_id=self.repo_slug,
recursion_level="Full", include_content_metadata=True,
include_links=False, download=False)
files = self.azure_devops_client.get_items(
project=self.workspace_slug,
repository_id=self.repo_slug,
recursion_level="Full",
include_content_metadata=True,
include_links=False,
download=False,
)
for f in files:
if f.git_object_type == 'blob':
if f.git_object_type == "blob":
file_name, file_extension = os.path.splitext(f.path)
languages.append(file_extension[1:])
extension_counts = {}
for ext in languages:
if ext != '':
if ext != "":
extension_counts[ext] = extension_counts.get(ext, 0) + 1
total_extensions = sum(extension_counts.values())
extension_percentages = {ext: (count / total_extensions) * 100 for ext, count in extension_counts.items()}
extension_percentages = {
ext: (count / total_extensions) * 100
for ext, count in extension_counts.items()
}
return extension_percentages
def get_pr_branch(self):
pr_info = self.azure_devops_client.get_pull_request_by_id(project=self.workspace_slug,
pull_request_id=self.pr_num)
source_branch = pr_info.source_ref_name.split('/')[-1]
pr_info = self.azure_devops_client.get_pull_request_by_id(
project=self.workspace_slug, pull_request_id=self.pr_num
)
source_branch = pr_info.source_ref_name.split("/")[-1]
return source_branch
def get_pr_description(self):
def get_pr_description(self, *, full: bool = True) -> str:
max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None)
if max_tokens:
return clip_tokens(self.pr.description, max_tokens)
@ -220,7 +408,9 @@ class AzureDevopsProvider:
return 0
def get_issue_comments(self):
raise NotImplementedError("Azure DevOps provider does not support issue comments yet")
raise NotImplementedError(
"Azure DevOps provider does not support issue comments yet"
)
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
return True
@ -228,17 +418,16 @@ class AzureDevopsProvider:
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
return True
def get_issue_comments(self):
raise NotImplementedError("Azure DevOps provider does not support issue comments yet")
@staticmethod
def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
def _parse_pr_url(pr_url: str) -> Tuple[str, str, int]:
parsed_url = urlparse(pr_url)
path_parts = parsed_url.path.strip('/').split('/')
path_parts = parsed_url.path.strip("/").split("/")
if len(path_parts) < 6 or path_parts[4] != 'pullrequest':
raise ValueError("The provided URL does not appear to be a Azure DevOps PR URL")
if len(path_parts) < 6 or path_parts[4] != "pullrequest":
raise ValueError(
"The provided URL does not appear to be a Azure DevOps PR URL"
)
workspace_slug = path_parts[1]
repo_slug = path_parts[3]
@ -249,15 +438,15 @@ class AzureDevopsProvider:
return workspace_slug, repo_slug, pr_number
def _get_azure_devops_client(self):
@staticmethod
def _get_azure_devops_client():
try:
pat = get_settings().azure_devops.pat
org = get_settings().azure_devops.org
except AttributeError as e:
raise ValueError(
"Azure DevOps PAT token is required ") from e
raise ValueError("Azure DevOps PAT token is required ") from e
credentials = BasicAuthentication('', pat)
credentials = BasicAuthentication("", pat)
azure_devops_connection = Connection(base_url=org, creds=credentials)
azure_devops_client = azure_devops_connection.clients.get_git_client()
@ -265,13 +454,25 @@ class AzureDevopsProvider:
def _get_repo(self):
if self.repo is None:
self.repo = self.azure_devops_client.get_repository(project=self.workspace_slug,
repository_id=self.repo_slug)
self.repo = self.azure_devops_client.get_repository(
project=self.workspace_slug, repository_id=self.repo_slug
)
return self.repo
def _get_pr(self):
self.pr = self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug)
self.pr = self.azure_devops_client.get_pull_request_by_id(
pull_request_id=self.pr_num, project=self.workspace_slug
)
return self.pr
def get_commit_messages(self):
return "" # not implemented yet
def get_pr_id(self):
try:
pr_id = f"{self.workspace_slug}/{self.repo_slug}/{self.pr_num}"
return pr_id
except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to get pr id, error: {e}")
return ""

View File

@ -201,8 +201,10 @@ class BitbucketProvider(GitProvider):
get_logger().exception(f"Failed to remove comment, error: {e}")
# funtion to create_inline_comment
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
position, absolute_position = find_line_number_of_relevant_line_in_file(self.get_diff_files(), relevant_file.strip('`'), relevant_line_in_file)
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, absolute_position: int = None):
position, absolute_position = find_line_number_of_relevant_line_in_file(self.get_diff_files(),
relevant_file.strip('`'),
relevant_line_in_file, absolute_position)
if position == -1:
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}")

View File

@ -210,11 +210,14 @@ class BitbucketServerProvider(GitProvider):
pass
# funtion to create_inline_comment
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str,
absolute_position: int = None):
position, absolute_position = find_line_number_of_relevant_line_in_file(
self.get_diff_files(),
relevant_file.strip('`'),
relevant_line_in_file
relevant_line_in_file,
absolute_position
)
if position == -1:
if get_settings().config.verbosity_level >= 2:

View File

@ -229,9 +229,6 @@ class CodeCommitProvider(GitProvider):
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_compared_commit.html
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
raise NotImplementedError("CodeCommit provider does not support creating inline comments yet")
def publish_inline_comments(self, comments: list[dict]):
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")

View File

@ -380,11 +380,6 @@ class GerritProvider(GitProvider):
'Publishing inline comments is not implemented for the gerrit '
'provider')
def create_inline_comment(self, body: str, relevant_file: str,
relevant_line_in_file: str):
raise NotImplementedError(
'Creating inline comments is not implemented for the gerrit '
'provider')
def publish_labels(self, labels):
# Not applicable to the local git provider,

View File

@ -74,15 +74,51 @@ class GitProvider(ABC):
def get_user_description(self) -> str:
description = (self.get_pr_description_full() or "").strip()
description_lowercase = description.lower()
get_logger().info(f"Existing description:\n{description_lowercase}")
# if the existing description wasn't generated by the pr-agent, just return it as-is
if not any(description.startswith(header) for header in ("## PR Type", "## PR Description")):
if not self._is_generated_by_pr_agent(description_lowercase):
get_logger().info(f"Existing description was not generated by the pr-agent")
return description
# if the existing description was generated by the pr-agent, but it doesn't contain the user description,
# if the existing description was generated by the pr-agent, but it doesn't contain a user description,
# return nothing (empty string) because it means there is no user description
if "## User Description:" not in description:
user_description_header = "## **user description**"
if user_description_header not in description_lowercase:
get_logger().info(f"Existing description was generated by the pr-agent, but it doesn't contain a user description")
return ""
# otherwise, extract the original user description from the existing pr-agent description and return it
return description.split("## User Description:", 1)[1].strip()
# user_description_start_position = description_lowercase.find(user_description_header) + len(user_description_header)
# return description[user_description_start_position:].split("\n", 1)[-1].strip()
# the 'user description' is in the beginning. extract and return it
possible_headers = self._possible_headers()
start_position = description_lowercase.find(user_description_header) + len(user_description_header)
end_position = len(description)
for header in possible_headers: # try to clip at the next header
if header != user_description_header and header in description_lowercase:
end_position = min(end_position, description_lowercase.find(header))
if end_position != len(description) and end_position > start_position:
original_user_description = description[start_position:end_position].strip()
if original_user_description.endswith("___"):
original_user_description = original_user_description[:-3].strip()
else:
original_user_description = description.split("___")[0].strip()
if original_user_description.lower().startswith(user_description_header):
original_user_description = original_user_description[len(user_description_header):].strip()
get_logger().info(f"Extracted user description from existing description:\n{original_user_description}")
return original_user_description
def _possible_headers(self):
return ("## **user description**", "## **pr type**", "## **pr description**", "## **pr labels**", "## **type**", "## **description**",
"## **labels**", "### 🤖 generated by pr agent")
def _is_generated_by_pr_agent(self, description_lowercase: str) -> bool:
possible_headers = self._possible_headers()
return any(description_lowercase.startswith(header) for header in possible_headers)
@abstractmethod
def get_repo_settings(self):
@ -106,9 +142,9 @@ class GitProvider(ABC):
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
pass
@abstractmethod
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
pass
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str,
absolute_position: int = None):
raise NotImplementedError("This git provider does not support creating inline comments yet")
@abstractmethod
def publish_inline_comments(self, comments: list[dict]):

View File

@ -1,3 +1,4 @@
import time
import hashlib
from datetime import datetime
from typing import Optional, Tuple
@ -206,8 +207,12 @@ class GithubProvider(GitProvider):
self.publish_inline_comments([self.create_inline_comment(body, relevant_file, relevant_line_in_file)])
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
position, absolute_position = find_line_number_of_relevant_line_in_file(self.diff_files, relevant_file.strip('`'), relevant_line_in_file)
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str,
absolute_position: int = None):
position, absolute_position = find_line_number_of_relevant_line_in_file(self.diff_files,
relevant_file.strip('`'),
relevant_line_in_file,
absolute_position)
if position == -1:
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
@ -217,8 +222,114 @@ class GithubProvider(GitProvider):
path = relevant_file.strip()
return dict(body=body, path=path, position=position) if subject_type == "LINE" else {}
def publish_inline_comments(self, comments: list[dict]):
def publish_inline_comments(self, comments: list[dict], disable_fallback: bool = False):
try:
# publish all comments in a single message
self.pr.create_review(commit=self.last_commit_id, comments=comments)
except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish inline comments")
if (getattr(e, "status", None) == 422
and get_settings().github.publish_inline_comments_fallback_with_verification and not disable_fallback):
pass # continue to try _publish_inline_comments_fallback_with_verification
else:
raise e # will end up with publishing the comments one by one
try:
self._publish_inline_comments_fallback_with_verification(comments)
except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish inline code comments fallback, error: {e}")
raise e
def _publish_inline_comments_fallback_with_verification(self, comments: list[dict]):
"""
Check each inline comment separately against the GitHub API and discard of invalid comments,
then publish all the remaining valid comments in a single review.
For invalid comments, also try removing the suggestion part and posting the comment just on the first line.
"""
verified_comments, invalid_comments = self._verify_code_comments(comments)
# publish as a group the verified comments
if verified_comments:
try:
self.pr.create_review(commit=self.last_commit_id, comments=verified_comments)
except:
pass
# try to publish one by one the invalid comments as a one-line code comment
if invalid_comments and get_settings().github.try_fix_invalid_inline_comments:
fixed_comments_as_one_liner = self._try_fix_invalid_inline_comments(
[comment for comment, _ in invalid_comments])
for comment in fixed_comments_as_one_liner:
try:
self.publish_inline_comments([comment], disable_fallback=True)
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Published invalid comment as a single line comment: {comment}")
except:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to publish invalid comment as a single line comment: {comment}")
def _verify_code_comment(self, comment: dict):
is_verified = False
e = None
try:
# event ="" # By leaving this blank, you set the review action state to PENDING
input = dict(commit_id=self.last_commit_id.sha, comments=[comment])
headers, data = self.pr._requester.requestJsonAndCheck(
"POST", f"{self.pr.url}/reviews", input=input)
pending_review_id = data["id"]
is_verified = True
except Exception as err:
is_verified = False
pending_review_id = None
e = err
if pending_review_id is not None:
try:
self.pr._requester.requestJsonAndCheck("DELETE", f"{self.pr.url}/reviews/{pending_review_id}")
except Exception:
pass
return is_verified, e
def _verify_code_comments(self, comments: list[dict]) -> tuple[list[dict], list[tuple[dict, Exception]]]:
"""Very each comment against the GitHub API and return 2 lists: 1 of verified and 1 of invalid comments"""
verified_comments = []
invalid_comments = []
for comment in comments:
time.sleep(1) # for avoiding secondary rate limit
is_verified, e = self._verify_code_comment(comment)
if is_verified:
verified_comments.append(comment)
else:
invalid_comments.append((comment, e))
return verified_comments, invalid_comments
def _try_fix_invalid_inline_comments(self, invalid_comments: list[dict]) -> list[dict]:
"""
Try fixing invalid comments by removing the suggestion part and setting the comment just on the first line.
Return only comments that have been modified in some way.
This is a best-effort attempt to fix invalid comments, and should be verified accordingly.
"""
import copy
fixed_comments = []
for comment in invalid_comments:
try:
fixed_comment = copy.deepcopy(comment) # avoid modifying the original comment dict for later logging
if "```suggestion" in comment["body"]:
fixed_comment["body"] = comment["body"].split("```suggestion")[0]
if "start_line" in comment:
fixed_comment["line"] = comment["start_line"]
del fixed_comment["start_line"]
if "start_side" in comment:
fixed_comment["side"] = comment["start_side"]
del fixed_comment["start_side"]
if fixed_comment != comment:
fixed_comments.append(fixed_comment)
except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().error(f"Failed to fix inline comment, error: {e}")
return fixed_comments
def publish_code_suggestions(self, code_suggestions: list) -> bool:
"""
@ -262,7 +373,7 @@ class GithubProvider(GitProvider):
post_parameters_list.append(post_parameters)
try:
self.pr.create_review(commit=self.last_commit_id, comments=post_parameters_list)
self.publish_inline_comments(post_parameters_list)
return True
except Exception as e:
if get_settings().config.verbosity_level >= 2:

View File

@ -183,7 +183,7 @@ class GitLabProvider(GitProvider):
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
target_file, target_line_no)
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, absolute_position: int = None):
raise NotImplementedError("Gitlab provider does not support creating inline comments yet")
def create_inline_comments(self, comments: list[dict]):
@ -411,6 +411,9 @@ class GitLabProvider(GitProvider):
def get_pr_labels(self):
return self.mr.labels
def get_repo_labels(self):
return self.gl.projects.get(self.id_project).labels.list()
def get_commit_messages(self):
"""
Retrieves the commit messages of a pull request.
@ -437,11 +440,11 @@ class GitLabProvider(GitProvider):
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
if relevant_line_start == -1:
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads"
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads"
elif relevant_line_end:
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}-L{relevant_line_end}"
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}-L{relevant_line_end}"
else:
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}"
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}"
return link
@ -457,7 +460,7 @@ class GitLabProvider(GitProvider):
if absolute_position != -1:
# link to right file only
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{absolute_position}"
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{absolute_position}"
# # link to diff
# sha_file = hashlib.sha1(relevant_file.encode('utf-8')).hexdigest()

View File

@ -121,9 +121,6 @@ class LocalGitProvider(GitProvider):
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
raise NotImplementedError('Publishing inline comments is not implemented for the local git provider')
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
raise NotImplementedError('Creating inline comments is not implemented for the local git provider')
def publish_inline_comments(self, comments: list[dict]):
raise NotImplementedError('Publishing inline comments is not implemented for the local git provider')

View File

@ -1,4 +1,5 @@
import json
import os
import uvicorn
from fastapi import APIRouter, FastAPI
@ -13,35 +14,55 @@ from starlette_context.middleware import RawContextMiddleware
from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger
from pr_agent.servers.utils import verify_signature
router = APIRouter()
def handle_request(background_tasks: BackgroundTasks, url: str, body: str, log_context: dict):
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")
@router.post("/")
async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
log_context = {"server_type": "bitbucket_server"}
data = await request.json()
get_logger().info(json.dumps(data))
pr_id = data['pullRequest']['id']
repository_name = data['pullRequest']['toRef']['repository']['slug']
project_name = data['pullRequest']['toRef']['repository']['project']['key']
webhook_secret = get_settings().get("BITBUCKET_SERVER.WEBHOOK_SECRET", None)
if webhook_secret:
body_bytes = await request.body()
signature_header = request.headers.get("x-hub-signature", None)
verify_signature(body_bytes, webhook_secret, signature_header)
pr_id = data["pullRequest"]["id"]
repository_name = data["pullRequest"]["toRef"]["repository"]["slug"]
project_name = data["pullRequest"]["toRef"]["repository"]["project"]["key"]
bitbucket_server = get_settings().get("BITBUCKET_SERVER.URL")
pr_url = f"{bitbucket_server}/projects/{project_name}/repos/{repository_name}/pull-requests/{pr_id}"
log_context["api_url"] = pr_url
log_context["event"] = "pull_request"
handle_request(background_tasks, pr_url, "review", log_context)
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
if data["eventKey"] == "pr:opened":
body = "review"
elif data["eventKey"] == "pr:comment:added":
body = data["comment"]["text"]
else:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content=json.dumps({"message": "Unsupported event"}),
)
handle_request(background_tasks, pr_url, body, log_context)
return JSONResponse(
status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"})
)
@router.get("/")
@ -50,15 +71,10 @@ async def root():
def start():
bitbucket_server_url = get_settings().get("BITBUCKET_SERVER.URL", None)
if not bitbucket_server_url:
raise ValueError("BITBUCKET_SERVER.URL is not set")
get_settings().config.git_provider = "bitbucket_server"
middleware = [Middleware(RawContextMiddleware)]
app = FastAPI(middleware=middleware)
app = FastAPI(middleware=[Middleware(RawContextMiddleware)])
app.include_router(router)
uvicorn.run(app, host="0.0.0.0", port=3000)
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "3000")))
if __name__ == '__main__':
if __name__ == "__main__":
start()

View File

@ -82,14 +82,23 @@ async def run_action():
if action in ["opened", "reopened"]:
pr_url = event_payload.get("pull_request", {}).get("url")
if pr_url:
# legacy - supporting both GITHUB_ACTION and GITHUB_ACTION_CONFIG
auto_review = get_setting_or_env("GITHUB_ACTION.AUTO_REVIEW", None)
if auto_review is None:
auto_review = get_setting_or_env("GITHUB_ACTION_CONFIG.AUTO_REVIEW", None)
auto_describe = get_setting_or_env("GITHUB_ACTION.AUTO_DESCRIBE", None)
if auto_describe is None:
auto_describe = get_setting_or_env("GITHUB_ACTION_CONFIG.AUTO_DESCRIBE", None)
auto_improve = get_setting_or_env("GITHUB_ACTION.AUTO_IMPROVE", None)
if auto_improve is None:
auto_improve = get_setting_or_env("GITHUB_ACTION_CONFIG.AUTO_IMPROVE", None)
# invoke by default all three tools
if auto_describe is None or is_true(auto_describe):
await PRDescription(pr_url).run()
if auto_review is None or is_true(auto_review):
await PRReviewer(pr_url).run()
auto_describe = get_setting_or_env("GITHUB_ACTION.AUTO_DESCRIBE", None)
if is_true(auto_describe):
await PRDescription(pr_url).run()
auto_improve = get_setting_or_env("GITHUB_ACTION.AUTO_IMPROVE", None)
if is_true(auto_improve):
if auto_improve is None or is_true(auto_improve):
await PRCodeSuggestions(pr_url).run()
# Handle issue comment event

View File

@ -7,7 +7,6 @@ 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.log import LoggingFormat, get_logger, setup_logger
from pr_agent.servers.help import bot_help_text
setup_logger(fmt=LoggingFormat.JSON)
NOTIFICATION_URL = "https://api.github.com/notifications"
@ -104,8 +103,6 @@ async def polling_loop():
notify=lambda: git_provider.add_eyes_reaction(comment_id)) # noqa E501
if not success:
git_provider.set_pr(pr_url)
git_provider.publish_comment("### How to use PR-Agent\n" +
bot_help_text(user_id))
elif response.status != 304:
print(f"Failed to fetch notifications. Status code: {response.status}")

View File

@ -1,19 +1,328 @@
commands_text = "> **/review**: Request a review of your Pull Request. \n" \
"> **/describe**: Update the PR title and description based on the contents of the PR. \n" \
"> **/improve [--extended]**: Suggest code improvements. Extended mode provides a higher quality feedback. \n" \
"> **/ask \\<QUESTION\\>**: Ask a question about the PR. \n" \
"> **/update_changelog**: Update the changelog based on the PR's contents. \n" \
"> **/add_docs**: Generate docstring for new components introduced in the PR. \n" \
"> **/generate_labels**: Generate labels for the PR based on the PR's contents. \n" \
"> see the [tools guide](https://github.com/Codium-ai/pr-agent/blob/main/docs/TOOLS_GUIDE.md) for more details.\n\n" \
">To edit any configuration parameter from the [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml), add --config_path=new_value. \n" \
">For example: /review --pr_reviewer.extra_instructions=\"focus on the file: ...\" \n" \
">To list the possible configuration parameters, add a **/config** comment. \n" \
class HelpMessage:
@staticmethod
def get_general_commands_text():
commands_text = "> - **/review**: Request a review of your Pull Request. \n" \
"> - **/describe**: Update the PR title and description based on the contents of the PR. \n" \
"> - **/improve [--extended]**: Suggest code improvements. Extended mode provides a higher quality feedback. \n" \
"> - **/ask \\<QUESTION\\>**: Ask a question about the PR. \n" \
"> - **/update_changelog**: Update the changelog based on the PR's contents. \n" \
"> - **/add_docs** 💎: Generate docstring for new components introduced in the PR. \n" \
"> - **/generate_labels** 💎: Generate labels for the PR based on the PR's contents. \n" \
"> - **/analyze** 💎: Automatically analyzes the PR, and presents changes walkthrough for each component. \n\n" \
">See the [tools guide](https://github.com/Codium-ai/pr-agent/blob/main/docs/TOOLS_GUIDE.md) for more details.\n" \
">To list the possible configuration parameters, add a **/config** comment. \n"
return commands_text
def bot_help_text(user: str):
return f"> Tag me in a comment '@{user}' and add one of the following commands: \n" + commands_text
@staticmethod
def get_general_bot_help_text():
output = f"> To invoke the PR-Agent, add a comment using one of the following commands: \n{HelpMessage.get_general_commands_text()} \n"
return output
@staticmethod
def get_review_usage_guide():
output ="**Overview:**\n"
output +="The `review` tool scans the PR code changes, and generates a PR review. The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on any PR.\n"
output +="""\
When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L19) related to the review tool (`pr_reviewer` section), use the following template:
```
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template:
```
[pr_reviewer]
some_config1=...
some_config2=...
```
"""
output +="\n\n<table>"
# extra instructions
output += "<tr><td><details> <summary><strong> Utilizing extra instructions</strong></summary><hr>\n\n"
output += '''\
The `review` tool can be configured with extra instructions, which can be used to guide the model to a feedback tailored to the needs of your project.
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Specify the relevant sub-tool, and the relevant aspects of the PR that you want to emphasize.
Examples for extra instructions:
```
[pr_reviewer] # /review #
extra_instructions="""
In the 'general suggestions' section, emphasize the following:
- Does the code logic cover relevant edge cases?
- Is the code logic clear and easy to understand?
- Is the code logic efficient?
...
"""
```
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
'''
output += "\n\n</details></td></tr>\n\n"
# automation
output += "<tr><td><details> <summary><strong> How to enable\\disable automation</strong></summary><hr>\n\n"
output += """\
- When you first install PR-Agent app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the `review` tool is:
```
pr_commands = ["/review", ...]
```
meaning the `review` tool will run automatically on every PR, with the default configuration.
Edit this field to enable/disable the tool, or to change the used configurations
"""
output += "\n\n</details></td></tr>\n\n"
# # code feedback
# output += "<tr><td><details> <summary><strong> About the 'Code feedback' section</strong></summary><hr>\n\n"
# output+="""\
# The `review` tool provides several type of feedbacks, one of them is code suggestions.
# If you are interested **only** in the code suggestions, it is recommended to use the [`improve`](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md) feature instead, since it dedicated only to code suggestions, and usually gives better results.
# Use the `review` tool if you want to get a more comprehensive feedback, which includes code suggestions as well.
# """
# output += "\n\n</details></td></tr>\n\n"
# auto-labels
output += "<tr><td><details> <summary><strong> Auto-labels</strong></summary><hr>\n\n"
output+="""\
The `review` tool can auto-generate two specific types of labels for a PR:
- a `possible security issue` label, that detects possible [security issues](https://github.com/Codium-ai/pr-agent/blob/tr/user_description/pr_agent/settings/pr_reviewer_prompts.toml#L136) (`enable_review_labels_security` flag)
- a `Review effort [1-5]: x` label, where x is the estimated effort to review the PR (`enable_review_labels_effort` flag)
"""
output += "\n\n</details></td></tr>\n\n"
# extra sub-tools
output += "<tr><td><details> <summary><strong> Extra sub-tools</strong></summary><hr>\n\n"
output += """\
The `review` tool provides a collection of possible feedbacks about a PR.
It is recommended to review the [possible options](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#enabledisable-features), and choose the ones relevant for your use case.
Some of the feature that are disabled by default are quite useful, and should be considered for enabling. For example:
`require_score_review`, `require_soc2_ticket`, and more.
"""
output += "\n\n</details></td></tr>\n\n"
# general
output += "\n\n<tr><td><details> <summary><strong> More PR-Agent commands</strong></summary><hr> \n\n"
output += HelpMessage.get_general_bot_help_text()
output += "\n\n</details></td></tr>\n\n"
output += "</table>"
output += f"\n\nSee the [review usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md) page for a comprehensive guide on using this tool.\n\n"
return output
actions_help_text = "> To invoke the PR-Agent, add a comment using one of the following commands: \n" + \
commands_text
@staticmethod
def get_describe_usage_guide():
output = "**Overview:**\n"
output += "The `describe` tool scans the PR code changes, and generates a description for the PR - title, type, summary, walkthrough and labels. "
output += "The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n"
output += """\
When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L46) related to the describe tool (`pr_description` section), use the following template:
```
/describe --pr_description.some_config1=... --pr_description.some_config2=...
```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template:
```
[pr_description]
some_config1=...
some_config2=...
```
"""
output += "\n\n<table>"
# automation
output += "<tr><td><details> <summary><strong> Enabling\\disabling automation </strong></summary><hr>\n\n"
output += """\
- When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the describe tool is:
```
pr_commands = ["/describe --pr_description.add_original_user_description=true"
"--pr_description.keep_original_user_title=true", ...]
```
meaning the `describe` tool will run automatically on every PR, will keep the original title, and will add the original user description above the generated description.
- Markers are an alternative way to control the generated description, to give maximal control to the user. If you set:
```
pr_commands = ["/describe --pr_description.use_description_markers=true", ...]
```
the tool will replace every marker of the form `pr_agent:marker_name` in the PR description with the relevant content, where `marker_name` is one of the following:
- `type`: the PR type.
- `summary`: the PR summary.
- `walkthrough`: the PR walkthrough.
Note that when markers are enabled, if the original PR description does not contain any markers, the tool will not alter the description at all.
"""
output += "\n\n</details></td></tr>\n\n"
# custom labels
output += "<tr><td><details> <summary><strong> Custom labels </strong></summary><hr>\n\n"
output += """\
The default labels of the `describe` tool are quite generic: [`Bug fix`, `Tests`, `Enhancement`, `Documentation`, `Other`].
If you specify [custom labels](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) in the repo's labels page or via configuration file, you can get tailored labels for your use cases.
Examples for custom labels:
- `Main topic:performance` - pr_agent:The main topic of this PR is performance
- `New endpoint` - pr_agent:A new endpoint was added in this PR
- `SQL query` - pr_agent:A new SQL query was added in this PR
- `Dockerfile changes` - pr_agent:The PR contains changes in the Dockerfile
- ...
The list above is eclectic, and aims to give an idea of different possibilities. Define custom labels that are relevant for your repo and use cases.
Note that Labels are not mutually exclusive, so you can add multiple label categories.
Make sure to provide proper title, and a detailed and well-phrased description for each label, so the tool will know when to suggest it.
"""
output += "\n\n</details></td></tr>\n\n"
# Inline File Walkthrough
output += "<tr><td><details> <summary><strong> Inline File Walkthrough 💎</strong></summary><hr>\n\n"
output += """\
For enhanced user experience, the `describe` tool can add file summaries directly to the "Files changed" tab in the PR page.
This will enable you to quickly understand the changes in each file, while reviewing the code changes (diffs).
To enable inline file summary, set `pr_description.inline_file_summary` in the configuration file, possible values are:
- `'table'`: File changes walkthrough table will be displayed on the top of the "Files changed" tab, in addition to the "Conversation" tab.
- `true`: A collapsable file comment with changes title and a changes summary for each file in the PR.
- `false` (default): File changes walkthrough will be added only to the "Conversation" tab.
"""
# extra instructions
output += "<tr><td><details> <summary><strong> Utilizing extra instructions</strong></summary><hr>\n\n"
output += '''\
The `describe` tool can be configured with extra instructions, to guide the model to a feedback tailored to the needs of your project.
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Notice that the general structure of the description is fixed, and cannot be changed. Extra instructions can change the content or style of each sub-section of the PR description.
Examples for extra instructions:
```
[pr_description]
extra_instructions="""
- The PR title should be in the format: '<PR type>: <title>'
- The title should be short and concise (up to 10 words)
- ...
"""
```
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
'''
output += "\n\n</details></td></tr>\n\n"
# general
output += "\n\n<tr><td><details> <summary><strong> More PR-Agent commands</strong></summary><hr> \n\n"
output += HelpMessage.get_general_bot_help_text()
output += "\n\n</details></td></tr>\n\n"
output += "</table>"
output += f"\n\nSee the [describe usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md) page for a comprehensive guide on using this tool.\n\n"
return output
@staticmethod
def get_ask_usage_guide():
output = "**Overview:**\n"
output += """\
The `ask` tool answers questions about the PR, based on the PR code changes.
It can be invoked manually by commenting on any PR:
```
/ask "..."
```
Note that the tool does not have "memory" of previous questions, and answers each question independently.
"""
output += "\n\n<table>"
# general
output += "\n\n<tr><td><details> <summary><strong> More PR-Agent commands</strong></summary><hr> \n\n"
output += HelpMessage.get_general_bot_help_text()
output += "\n\n</details></td></tr>\n\n"
output += "</table>"
output += f"\n\nSee the [ask usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/ASK.md) page for a comprehensive guide on using this tool.\n\n"
return output
@staticmethod
def get_improve_usage_guide():
output = "**Overview:**\n"
output += "The `improve` tool scans the PR code changes, and automatically generates suggestions for improving the PR code. "
output += "The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n"
output += """\
When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L69) related to the improve tool (`pr_code_suggestions` section), use the following template:
```
/improve --pr_code_suggestions.some_config1=... --pr_code_suggestions.some_config2=...
```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template:
```
[pr_code_suggestions]
some_config1=...
some_config2=...
```
"""
output += "\n\n<table>"
# automation
output += "<tr><td><details> <summary><strong> Enabling\\disabling automation </strong></summary><hr>\n\n"
output += """\
When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the improve tool is:
```
pr_commands = ["/improve --pr_code_suggestions.summarize=true", ...]
```
meaning the `improve` tool will run automatically on every PR, with summarization enabled. Delete this line to disable the tool from running automatically.
"""
output += "\n\n</details></td></tr>\n\n"
# extra instructions
output += "<tr><td><details> <summary><strong> Utilizing extra instructions</strong></summary><hr>\n\n"
output += '''\
Extra instructions are very important for the `improve` tool, since they enable to guide the model to suggestions that are more relevant to the specific needs of the project.
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Specify relevant aspects that you want the model to focus on.
Examples for extra instructions:
```
[pr_code_suggestions] # /improve #
extra_instructions="""
Emphasize the following aspects:
- Does the code logic cover relevant edge cases?
- Is the code logic clear and easy to understand?
- Is the code logic efficient?
...
"""
```
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
'''
output += "\n\n</details></td></tr>\n\n"
# suggestions quality
output += "\n\n<tr><td><details> <summary><strong> A note on code suggestions quality</strong></summary><hr> \n\n"
output += """\
- While the current AI for code is getting better and better (GPT-4), it's not flawless. Not all the suggestions will be perfect, and a user should not accept all of them automatically.
- Suggestions are not meant to be simplistic. 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, or use the [custom suggestions :gem:](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) tool
- With large PRs, best quality will be obtained by using 'improve --extended' mode.
"""
output += "\n\n</details></td></tr>\n\n"\
# general
output += "\n\n<tr><td><details> <summary><strong> More PR-Agent commands</strong></summary><hr> \n\n"
output += HelpMessage.get_general_bot_help_text()
output += "\n\n</details></td></tr>\n\n"
output += "</table>"
output += f"\n\nSee the [improve usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md) page for a more comprehensive guide on using this tool.\n\n"
return output

View File

@ -65,6 +65,11 @@ personal_access_token = ""
# For Bitbucket personal/repository bearer token
bearer_token = ""
[bitbucket_server]
# For Bitbucket Server bearer token
auth_token = ""
webhook_secret = ""
# For Bitbucket app
app_key = ""
base_url = ""

View File

@ -1,5 +1,5 @@
[config]
model="gpt-4" # "gpt-4-1106-preview"
model="gpt-4" # "gpt-4-0125-preview"
fallback_models=["gpt-3.5-turbo-16k"]
git_provider="github"
publish_output=true
@ -7,6 +7,7 @@ publish_output_progress=true
verbosity_level=0 # 0,1,2
use_extra_bad_extensions=false
use_repo_settings_file=true
use_global_settings_file=true
ai_timeout=180
max_description_tokens = 500
max_commits_tokens = 500
@ -22,35 +23,40 @@ require_score_review=false
require_tests_review=true
require_security_review=true
require_estimate_effort_to_review=true
# soc2
require_soc2_ticket=false
soc2_ticket_prompt="Does the PR description include a link to ticket in a project management system (e.g., Jira, Asana, Trello, etc.) ?"
# general options
num_code_suggestions=4
inline_code_comments = false
ask_and_reflect=false
automatic_review=true
#automatic_review=true
remove_previous_review_comment=false
persistent_comment=true
extra_instructions = ""
# review labels
enable_review_labels_security=true
enable_review_labels_effort=false
enable_review_labels_effort=true
# specific configurations for incremental review (/review -i)
require_all_thresholds_for_incremental_review=false
minimal_commits_for_incremental_review=0
minimal_minutes_for_incremental_review=0
enable_help_text=true # Determines whether to include help text in the PR review. Enabled by default.
[pr_description] # /describe #
publish_labels=true
publish_description_as_comment=false
add_original_user_description=false
keep_original_user_title=false
add_original_user_description=true
keep_original_user_title=true
use_bullet_points=true
extra_instructions = ""
enable_pr_type=true
enable_file_walkthrough=false
enable_semantic_files_types=true
final_update_message = true
enable_help_text=true
## changes walkthrough section
enable_semantic_files_types=true
collapsible_file_list='adaptive' # true, false, 'adaptive'
inline_file_summary=false # false, true, 'table'
# markers
use_description_markers=false
include_generated_by_header=true
@ -58,13 +64,17 @@ include_generated_by_header=true
#custom_labels = ['Bug fix', 'Tests', 'Bug fix with tests', 'Enhancement', 'Documentation', 'Other']
[pr_questions] # /ask #
enable_help_text=true
[pr_code_suggestions] # /improve #
num_code_suggestions=4
summarize = false
summarize = true
extra_instructions = ""
rank_suggestions = false
enable_help_text=true
# params for '/improve --extended' mode
auto_extended_mode=false
num_code_suggestions_per_chunk=8
rank_extended_suggestions = true
max_number_of_calls = 5
@ -78,6 +88,17 @@ docs_style = "Sphinx Style" # "Google Style with Args, Returns, Attributes...etc
push_changelog_changes=false
extra_instructions = ""
[pr_analyze] # /analyze #
[pr_test] # /test #
extra_instructions = ""
testing_framework = "" # specify the testing framework you want to use
num_tests=3 # number of tests to generate. max 5.
avoid_mocks=true # if true, the generated tests will prefer to use real objects instead of mocks
file = "" # in case there are several components with the same name, you can specify the relevant file
class_name = "" # in case there are several methods with the same name in the same file, you can specify the relevant class name
enable_help_text=true
[pr_config] # /config #
[github]
@ -85,8 +106,10 @@ extra_instructions = ""
deployment_type = "user"
ratelimit_retries = 5
base_url = "https://api.github.com"
publish_inline_comments_fallback_with_verification = true
try_fix_invalid_inline_comments = true
[github_action]
[github_action_config]
# auto_review = true # set as env var in .github/workflows/pr-agent.yaml
# auto_describe = true # set as env var in .github/workflows/pr-agent.yaml
# auto_improve = true # set as env var in .github/workflows/pr-agent.yaml
@ -103,7 +126,8 @@ duplicate_requests_cache_ttl = 60 # in seconds
handle_pr_actions = ['opened', 'reopened', 'ready_for_review', 'review_requested']
pr_commands = [
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
"/auto_review",
"/review --pr_reviewer.num_code_suggestions=0",
"/improve --pr_code_suggestions.summarize=true",
]
# settings for "pull_request" event with "synchronize" action - used to detect and handle push triggers for new commits
handle_push_trigger = false
@ -164,6 +188,11 @@ polling_interval_seconds = 30
# token to authenticate in the patch server
# patch_server_token = ""
[bitbucket_server]
# URL to the BitBucket Server instance
# url = "https://git.bitbucket.com"
url = ""
[litellm]
#use_client = false
@ -171,9 +200,12 @@ polling_interval_seconds = 30
skip_comments = false
force_update_dataset = false
max_issues_to_scan = 500
vectordb = "pinecone"
[pinecone]
# fill and place in .secrets.toml
#api_key = ...
# environment = "gcp-starter"
[lancedb]
uri = "./lancedb"

View File

@ -5,7 +5,7 @@ Your task is to generate {{ docs_for_language }} for code components in the PR D
Example for the PR Diff format:
======
## src/file1.py
## file: 'src/file1.py'
@@ -12,3 +12,4 @@ def func1():
__new hunk__
@ -18,7 +18,6 @@ __old hunk__
-code line that was removed in the PR
code line2 that remained unchanged in the PR
@@ ... @@ def func2():
__new hunk__
...
@ -26,7 +25,7 @@ __old hunk__
...
## src/file2.py
## file: 'src/file2.py'
...
======
@ -35,7 +34,7 @@ Specific instructions:
- Try to identify edited/added code components (classes/functions/methods...) that are undocumented, and generate {{ docs_for_language }} for each one.
- If there are documented (any type of {{ language }} documentation) code components in the PR, Don't generate {{ docs_for_language }} for them.
- Ignore code components that don't appear fully in the '__new hunk__' section. For example, you must see the component header and body.
- Make sure the {{ docs_for_language }} starts and ends with standart {{ language }} {{ docs_for_language }} signs.
- Make sure the {{ docs_for_language }} starts and ends with standard {{ language }} {{ docs_for_language }} signs.
- The {{ docs_for_language }} should be in standard format.
- Provide the exact line number (inclusive) where the {{ docs_for_language }} should be added.
@ -69,6 +68,7 @@ Code Documentation:
- after
description: |-
The {{ docs_for_language }} placement relative to the relevant line (code component).
For example, in Python the docs are placed after the function signature, but in Java they are placed before.
documentation:
type: string
description: |-

View File

@ -4,19 +4,17 @@ Your task is to provide meaningful and actionable code suggestions, to improve t
Example for the PR Diff format:
======
## src/file1.py
## file: 'src/file1.py'
@@ -12,3 +12,4 @@ def func1():
@@ ... @@ def func1():
__new hunk__
12 code line1 that remained unchanged in the PR
14 +new code line1 added in the PR
15 +new code line2 added in the PR
16 code line2 that remained unchanged in the PR
13 +new code line2 added in the PR
14 code line3 that remained unchanged in the PR
__old hunk__
code line1 that remained unchanged in the PR
-code line that was removed in the PR
code line2 that remained unchanged in the PR
-old code line2 that was removed in the PR
code line3 that remained unchanged in the PR
@@ ... @@ def func2():
__new hunk__
@ -25,20 +23,19 @@ __old hunk__
...
## src/file2.py
## file: 'src/file2.py'
...
======
Specific instructions:
- Provide up to {{ num_code_suggestions }} code suggestions. Try to provide diverse and insightful suggestions.
- Prioritize suggestions that address major problems, issues and bugs in the code. As a second priority, suggestions should focus on best practices, code readability, maintainability, enhancments, performance, and other aspects.
- Don't suggest to add docstring, type hints, or comments.
- Suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+').
- Provide up to {{ num_code_suggestions }} code suggestions. The suggestions should be diverse and insightful.
- The suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+').
- Prioritize suggestions that address major problems, issues and bugs in the PR code. As a second priority, suggestions should focus on enhancement, best practice, performance, maintainability, and other aspects.
- Don't suggest to add docstring, type hints, or comments, or to remove unused imports.
- 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 '__new hunk__' code.
- For each suggestion, make sure to take into consideration also the context, meaning the lines before and after the relevant code.
- Provide the exact line numbers range (inclusive) for each suggestion.
- Assume there is additional relevant code, that is not included in the diff.
- When quoting variables or names from the code, use backticks (`) instead of single quote (').
{%- if extra_instructions %}
@ -49,86 +46,67 @@ Extra instructions from the user:
======
{%- endif %}
The output must be a YAML object equivalent to type $PRCodeSuggestions, according to the following Pydantic definitions:
=====
class CodeSuggestion(BaseModel):
relevant_file: str = Field(description="the relevant file full path")
language: str = Field(description="the code language of the relevant file")
suggestion_content: str = Field(description="an actionable suggestion for meaningfully improving the new code introduced in the PR")
{%- if summarize_mode %}
existing_code: str = Field(description="a short code snippet from a '__new hunk__' section to illustrate the relevant existing code. Don't show the line numbers.")
improved_code: str = Field(description="a short code snippet to illustrate the improved code, after applying the suggestion.")
one_sentence_summary:str = Field(description="a short summary of the suggestion action, in a single sentence. Focus on the 'what'. Be general, and avoid method or variable names.")
{%- else %}
existing_code: str = Field(description="a code snippet, demonstrating the relevant code lines from a '__new hunk__' section. It must be contiguous, correctly formatted and indented, and without line numbers")
improved_code: str = Field(description="a new code snippet, that can be used to replace the relevant lines in '__new hunk__' code. Replacement suggestions should be complete, correctly formatted and indented, and without line numbers")
{%- endif %}
relevant_lines_start: int = Field(description="The relevant line number, from a '__new hunk__' section, where the suggestion starts (inclusive). Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above")
relevant_lines_end: int = Field(description="The relevant line number, from a '__new hunk__' section, where the suggestion ends (inclusive). Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above")
label: str = Field(description="a single label for the suggestion, to help the user understand the suggestion type. For example: 'security', 'bug', 'performance', 'enhancement', 'possible issue', 'best practice', 'maintainability', etc. Other labels are also allowed")
class PRCodeSuggestions(BaseModel):
code_suggestions: List[CodeSuggestion]
=====
You must use the following YAML schema to format your answer:
```yaml
Code suggestions:
type: array
minItems: 1
maxItems: {{ num_code_suggestions }}
uniqueItems: true
items:
relevant file:
type: string
description: the relevant file full path
suggestion content:
type: string
description: |-
a concrete suggestion for meaningfully improving the new PR code.
existing code:
type: string
description: |-
a code snippet showing the relevant code lines from a '__new hunk__' section.
It must be contiguous, correctly formatted and indented, and without line numbers.
relevant lines start:
type: integer
description: |-
The relevant line number from a '__new hunk__' section where the suggestion starts (inclusive).
Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above.
relevant lines end:
type: integer
description: |-
The relevant line number from a '__new hunk__' section where the suggestion ends (inclusive).
Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above.
improved code:
type: string
description: |-
a new code snippet that can be used to replace the relevant lines in '__new hunk__' code.
Replacement suggestions should be complete, correctly formatted and indented, and without line numbers.
```
Example output:
```yaml
Code suggestions:
- relevant file: |-
code_suggestions:
- relevant_file: |-
src/file1.py
suggestion content: |-
language: |-
python
suggestion_content: |-
Add a docstring to func1()
existing code: |-
{%- if summarize_mode %}
existing_code: |-
def func1():
relevant lines start: |-
12
relevant lines end: |-
12
improved code: |-
improved_code: |-
...
one_sentence_summary: |-
...
relevant_lines_start: 12
relevant_lines_end: 12
{%- else %}
existing_code: |-
def func1():
relevant_lines_start: 12
relevant_lines_end: 12
improved_code: |-
...
{%- endif %}
label: |-
...
```
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|-').
Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields.
"""
user="""PR Info:
Title: '{{title}}'
Branch: '{{branch}}'
{%- if description %}
Description:
======
{{ description|trim }}
======
{%- endif %}
{%- if language %}
Main PR language: '{{ language }}'
{%- endif %}
The PR Diff:
======

View File

@ -30,7 +30,7 @@ class Label(str, Enum):
{%- endif %}
class Labels(BaseModel):
labels: List[Label] = Field(min_items=0, description="custom labels that describe the PR. Return the label value, not the name.")
labels: List[Label] = Field(min_items=0, description="choose the relevant custom labels that describe the PR content, and return their keys. Use the value field of the Label object to better understand the label meaning.")
======

View File

@ -1,10 +1,15 @@
[pr_description_prompt]
system="""You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
Your task is to provide a full description for the PR content - title, type, description, and main files walkthrough.
{%- if enable_custom_labels %}
Your task is to provide a full description for the PR content - files walkthrough, title, type, description and labels.
{%- else %}
Your task is to provide a full description for the PR content - files walkthrough, title, type, and description.
{%- endif %}
- Focus on the new PR code (lines starting with '+').
- Keep in mind that the 'Previous title', 'Previous description' and 'Commit messages' sections may be partial, simplistic, non-informative or out of date. Hence, compare them to the PR diff code, and use them only as a reference.
- The generated title and description should prioritize the most significant changes.
- If needed, each YAML output should be in block scalar indicator ('|-')
- When quoting variables or names from the code, use backticks (`) instead of single quote (').
{%- if extra_instructions %}
@ -30,31 +35,25 @@ class PRType(str, Enum):
{%- endif %}
{%- if enable_file_walkthrough %}
class FileWalkthrough(BaseModel):
filename: str = Field(description="the relevant file full path")
changes_in_file: str = Field(description="minimal and concise summary of the changes in the relevant file")
{%- endif %}
{%- if enable_semantic_files_types %}
Class FileDescription(BaseModel):
filename: str = Field(description="the relevant file full path")
changes_summary: str = Field(description="minimal and concise summary of the changes in the relevant file")
language: str = Field(description="the relevant file language")
changes_summary: str = Field(description="concise summary of the changes in the relevant file, in bullet points (1-4 bullet points).")
changes_title: str = Field(description="an informative title for the changes in the files, describing its main theme (5-10 words).")
label: str = Field(description="a single semantic label that represents a type of code changes that occurred in the File. Possible values (partial list): 'bug fix', 'tests', 'enhancement', 'documentation', 'error handling', 'configuration changes', 'dependencies', 'formatting', 'miscellaneous', ...")
{%- endif %}
Class PRDescription(BaseModel):
title: str = Field(description="an informative title for the PR, describing its main theme")
type: List[PRType] = Field(description="one or more types that describe the PR type. Return the label value, not the name.")
description: str = Field(description="an informative and concise description of the PR. {%- if use_bullet_points %} Use bullet points.{% endif %}")
{%- if enable_custom_labels %}
labels: List[Label] = Field(min_items=0, description="custom labels that describe the PR. Return the label value, not the name.")
{%- endif %}
{%- if enable_file_walkthrough %}
main_files_walkthrough: List[FileWalkthrough] = Field(max_items=10)
{%- endif %}
type: List[PRType] = Field(description="one or more types that describe the PR content. Return the label member value (e.g. 'Bug fix', not 'bug_fix')")
{%- if enable_semantic_files_types %}
pr_files[List[FileDescription]] = Field(max_items=15")
pr_files[List[FileDescription]] = Field(max_items=15, description="a list of the files in the PR, and their changes summary.")
{%- endif %}
description: str = Field(description="an informative and concise description of the PR. Use bullet points. Display first the most significant changes.")
title: str = Field(description="an informative title for the PR, describing its main theme")
{%- if enable_custom_labels %}
labels: List[Label] = Field(min_items=0, description="choose the relevant custom labels that describe the PR content, and return their keys. Use the value field of the Label object to better understand the label meaning.")
{%- endif %}
=====
@ -62,33 +61,34 @@ Class PRDescription(BaseModel):
Example output:
```yaml
title: |-
...
type:
- ...
- ...
{%- if enable_custom_labels %}
labels:
- ...
- ...
{%- endif %}
description: |-
...
{%- if enable_file_walkthrough %}
main_files_walkthrough:
- ...
- ...
{%- endif %}
{%- if enable_semantic_files_types %}
pr_files:
- filename: |
...
language: |
...
changes_summary: |
...
changes_title: |
...
label: |
...
...
{%- endif %}
description: |-
...
title: |-
...
{%- if enable_custom_labels %}
labels:
- |
...
- |
...
{%- endif %}
```
Answer should be a valid YAML, and nothing else. Each YAML output MUST be after a newline, with proper indent, and block scalar indicator ('|-')
@ -107,10 +107,7 @@ Previous description:
{%- endif %}
Branch: '{{branch}}'
{%- if language %}
Main PR language: '{{ language }}'
{%- endif %}
{%- if commit_messages_str %}
Commit messages:

View File

@ -1,7 +1,7 @@
[pr_questions_prompt]
system="""You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
system="""You are PR-Reviewer, a language model designed to answer questions about a Git Pull Request (PR).
Your goal is to answer questions\\tasks about the new PR code (lines starting with '+'), and provide feedback.
Your goal is to answer questions\\tasks about the new code introduced in the PR (lines starting with '+' in the 'PR Git Diff' section), and provide feedback.
Be informative, constructive, and give examples. Try to be as specific as possible.
Don't avoid answering the questions. You must answer the questions, as best as you can, without adding any unrelated content.
"""

View File

@ -5,7 +5,7 @@ The review should focus on new code added in the PR diff (lines starting with '+
Example PR Diff:
======
## src/file1.py
## file: 'src/file1.py'
@@ -12,5 +12,5 @@ def func1():
code line 1 that remained unchanged in the PR
@ -14,12 +14,11 @@ code line 2 that remained unchanged in the PR
+code line added in the PR
code line 3 that remained unchanged in the PR
@@ ... @@ def func2():
...
## src/file2.py
## file: 'src/file2.py'
...
======
@ -32,6 +31,7 @@ Code suggestions guidelines:
- 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.
- Suggestions should focus on the new code added in the PR diff (lines starting with '+')
- When quoting variables or names from the code, use backticks (`) instead of single quote (').
{%- endif %}
{%- if extra_instructions %}
@ -114,6 +114,9 @@ PR Feedback:
relevant file:
type: string
description: the relevant file full path
language:
type: string
description: the language of the relevant file
suggestion:
type: string
description: |-
@ -165,6 +168,8 @@ PR Feedback:
Code feedback:
- relevant file: |-
directory/xxx.py
language: |-
python
suggestion: |-
xxx [important]
relevant line: |-
@ -194,10 +199,6 @@ Description:
======
{%- endif %}
{%- if language %}
Main PR language: '{{ language }}'
{%- endif %}
{%- if commit_messages_str %}
Commit messages:

View File

@ -1,10 +1,12 @@
import copy
import textwrap
from functools import partial
from typing import Dict
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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
@ -15,14 +17,15 @@ from pr_agent.log import get_logger
class PRAddDocs:
def __init__(self, pr_url: str, cli_mode=False, args: list = None):
def __init__(self, pr_url: str, cli_mode=False, args: list = None,
ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.git_provider = get_git_provider()(pr_url)
self.main_language = get_main_pr_language(
self.git_provider.get_languages(), self.git_provider.get_files()
)
self.ai_handler = AiHandler()
self.ai_handler = ai_handler()
self.patches_diff = None
self.prediction = None
self.cli_mode = cli_mode

View File

@ -1,20 +1,25 @@
import copy
import textwrap
from functools import partial
from typing import Dict, List
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, get_pr_multi_diffs, 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, replace_code_tags
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
from pr_agent.servers.help import HelpMessage
from pr_agent.tools.pr_description import insert_br_after_x_chars
import difflib
class PRCodeSuggestions:
def __init__(self, pr_url: str, cli_mode=False, args: list = None):
def __init__(self, pr_url: str, cli_mode=False, args: list = None,
ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.git_provider = get_git_provider()(pr_url)
self.main_language = get_main_pr_language(
@ -23,7 +28,7 @@ class PRCodeSuggestions:
# extended mode
try:
self.is_extended = any(["extended" in arg for arg in args])
self.is_extended = self._get_is_extended(args or [])
except:
self.is_extended = False
if self.is_extended:
@ -31,7 +36,7 @@ class PRCodeSuggestions:
else:
num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions
self.ai_handler = AiHandler()
self.ai_handler = ai_handler()
self.patches_diff = None
self.prediction = None
self.cli_mode = cli_mode
@ -42,6 +47,7 @@ class PRCodeSuggestions:
"language": self.main_language,
"diff": "", # empty diff for initial calculation
"num_code_suggestions": num_code_suggestions,
"summarize_mode": get_settings().pr_code_suggestions.summarize,
"extra_instructions": get_settings().pr_code_suggestions.extra_instructions,
"commit_messages_str": self.git_provider.get_commit_messages(),
}
@ -62,21 +68,33 @@ class PRCodeSuggestions:
data = self._prepare_pr_code_suggestions()
else:
data = await retry_with_fallback_models(self._prepare_prediction_extended)
if (not data) or (not 'Code suggestions' in data):
if (not data) or (not 'code_suggestions' in data):
get_logger().info('No code suggestions found for PR.')
return
if (not self.is_extended and get_settings().pr_code_suggestions.rank_suggestions) or \
(self.is_extended and get_settings().pr_code_suggestions.rank_extended_suggestions):
get_logger().info('Ranking Suggestions...')
data['Code suggestions'] = await self.rank_suggestions(data['Code suggestions'])
data['code_suggestions'] = await self.rank_suggestions(data['code_suggestions'])
if get_settings().config.publish_output:
get_logger().info('Pushing PR code suggestions...')
self.git_provider.remove_initial_comment()
if get_settings().pr_code_suggestions.summarize:
if get_settings().pr_code_suggestions.summarize and self.git_provider.is_supported("gfm_markdown"):
get_logger().info('Pushing summarize code suggestions...')
self.publish_summarizes_suggestions(data)
# generate summarized suggestions
pr_body = self.generate_summarized_suggestions(data)
# add usage guide
if get_settings().pr_code_suggestions.enable_help_text:
pr_body += "<hr>\n\n<details> <summary><strong>✨ Usage guide:</strong></summary><hr> \n\n"
pr_body += HelpMessage.get_improve_usage_guide()
pr_body += "\n</details>\n"
self.git_provider.publish_comment(pr_body)
else:
get_logger().info('Pushing inline code suggestions...')
self.push_inline_code_suggestions(data)
@ -106,36 +124,52 @@ class PRCodeSuggestions:
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
system=system_prompt, user=user_prompt)
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"\nAI response:\n{response}")
return response
def _prepare_pr_code_suggestions(self) -> Dict:
review = self.prediction.strip()
data = load_yaml(review)
data = load_yaml(review,
keys_fix_yaml=["relevant_file", "suggestion_content", "existing_code", "improved_code"])
if isinstance(data, list):
data = {'Code suggestions': data}
data = {'code_suggestions': data}
# remove invalid suggestions
suggestion_list = []
for i, suggestion in enumerate(data['code_suggestions']):
if suggestion['existing_code'] != suggestion['improved_code']:
suggestion_list.append(suggestion)
else:
get_logger().debug(
f"Skipping suggestion {i + 1}, because existing code is equal to improved code {suggestion['existing_code']}")
data['code_suggestions'] = suggestion_list
return data
def push_inline_code_suggestions(self, data):
code_suggestions = []
if not data['Code suggestions']:
if not data['code_suggestions']:
get_logger().info('No suggestions found to improve this PR.')
return self.git_provider.publish_comment('No suggestions found to improve this PR.')
for d in data['Code suggestions']:
for d in data['code_suggestions']:
try:
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"suggestion: {d}")
relevant_file = d['relevant file'].strip()
relevant_lines_start = int(d['relevant lines start']) # absolute position
relevant_lines_end = int(d['relevant lines end'])
content = d['suggestion content']
new_code_snippet = d['improved code']
relevant_file = d['relevant_file'].strip()
relevant_lines_start = int(d['relevant_lines_start']) # absolute position
relevant_lines_end = int(d['relevant_lines_end'])
content = d['suggestion_content'].rstrip()
new_code_snippet = d['improved_code'].rstrip()
label = d['label'].strip()
if new_code_snippet:
new_code_snippet = self.dedent_code(relevant_file, relevant_lines_start, new_code_snippet)
body = f"**Suggestion:** {content}\n```suggestion\n" + new_code_snippet + "\n```"
body = f"**Suggestion:** {content} [{label}]\n```suggestion\n" + new_code_snippet + "\n```"
code_suggestions.append({'body': body, 'relevant_file': relevant_file,
'relevant_lines_start': relevant_lines_start,
'relevant_lines_end': relevant_lines_end})
@ -156,6 +190,7 @@ class PRCodeSuggestions:
original_initial_line = None
for file in self.diff_files:
if file.filename.strip() == relevant_file:
if file.head_file: # in bitbucket, head_file is empty. toDo: fix this
original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
break
if original_initial_line:
@ -171,6 +206,16 @@ class PRCodeSuggestions:
return new_code_snippet
def _get_is_extended(self, args: list[str]) -> bool:
"""Check if extended mode should be enabled by the `--extended` flag or automatically according to the configuration"""
if any(["extended" in arg for arg in args]):
get_logger().info("Extended mode is enabled by the `--extended` flag")
return True
if get_settings().pr_code_suggestions.auto_extended_mode:
get_logger().info("Extended mode is enabled automatically based on the configuration toggle")
return True
return False
async def _prepare_prediction_extended(self, model: str) -> dict:
get_logger().info('Getting PR diff...')
patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model,
@ -181,7 +226,7 @@ class PRCodeSuggestions:
for i, patches_diff in enumerate(patches_diff_list):
get_logger().info(f"Processing chunk {i + 1} of {len(patches_diff_list)}")
self.patches_diff = patches_diff
prediction = await self._get_prediction(model)
prediction = await self._get_prediction(model) # toDo: parallelize
prediction_list.append(prediction)
self.prediction_list = prediction_list
@ -189,8 +234,8 @@ class PRCodeSuggestions:
for prediction in prediction_list:
self.prediction = prediction
data_per_chunk = self._prepare_pr_code_suggestions()
if "Code suggestions" in data:
data["Code suggestions"].extend(data_per_chunk["Code suggestions"])
if "code_suggestions" in data:
data["code_suggestions"].extend(data_per_chunk["code_suggestions"])
else:
data.update(data_per_chunk)
self.data = data
@ -208,13 +253,15 @@ class PRCodeSuggestions:
"""
suggestion_list = []
# remove invalid suggestions
for i, suggestion in enumerate(data):
if suggestion['existing code'] != suggestion['improved code']:
if not data:
return suggestion_list
for suggestion in data:
suggestion_list.append(suggestion)
data_sorted = [[]] * len(suggestion_list)
if len(suggestion_list ) == 1:
return suggestion_list
try:
suggestion_str = ""
for i, suggestion in enumerate(suggestion_list):
@ -239,7 +286,13 @@ class PRCodeSuggestions:
data_sorted[importance_order - 1] = suggestion_list[suggestion_number - 1]
if get_settings().pr_code_suggestions.final_clip_factor != 1:
new_len = int(0.5 + len(data_sorted) * get_settings().pr_code_suggestions.final_clip_factor)
max_len = max(
len(data_sorted),
get_settings().pr_code_suggestions.num_code_suggestions,
get_settings().pr_code_suggestions.num_code_suggestions_per_chunk,
)
new_len = int(0.5 + max_len * get_settings().pr_code_suggestions.final_clip_factor)
if new_len < len(data_sorted):
data_sorted = data_sorted[:new_len]
except Exception as e:
if get_settings().config.verbosity_level >= 1:
@ -248,9 +301,13 @@ class PRCodeSuggestions:
return data_sorted
def publish_summarizes_suggestions(self, data: Dict):
def generate_summarized_suggestions(self, data: Dict) -> str:
try:
data_markdown = "## PR Code Suggestions\n\n"
pr_body = "## PR Code Suggestions\n\n"
if len(data.get('code_suggestions', [])) == 0:
pr_body += "No suggestions found to improve this PR."
return pr_body
language_extension_map_org = get_settings().language_extension_map_org
extension_to_language = {}
@ -258,31 +315,78 @@ class PRCodeSuggestions:
for ext in extensions:
extension_to_language[ext] = language
for s in data['Code suggestions']:
try:
extension_s = s['relevant file'].rsplit('.')[-1]
code_snippet_link = self.git_provider.get_line_link(s['relevant file'], s['relevant lines start'],
s['relevant lines end'])
data_markdown += f"\n💡 Suggestion:\n\n**{s['suggestion content']}**\n\n"
if code_snippet_link:
data_markdown += f" File: [{s['relevant file']} ({s['relevant lines start']}-{s['relevant lines end']})]({code_snippet_link})\n\n"
pr_body += "<table>"
header = f"Suggestions"
delta = 77
header += "&nbsp; " * delta
pr_body += f"""<thead><tr><th></th><th>{header}</th></tr></thead>"""
pr_body += """<tbody>"""
suggestions_labels = dict()
# add all suggestions related to each label
for suggestion in data['code_suggestions']:
label = suggestion['label'].strip().strip("'").strip('"')
if label not in suggestions_labels:
suggestions_labels[label] = []
suggestions_labels[label].append(suggestion)
for label, suggestions in suggestions_labels.items():
pr_body += f"""<tr><td><strong>{label}</strong></td>"""
pr_body += f"""<td>"""
# pr_body += f"""<details><summary>{len(suggestions)} suggestions</summary>"""
pr_body += f"""<table>"""
for suggestion in suggestions:
relevant_file = suggestion['relevant_file'].strip()
relevant_lines_start = int(suggestion['relevant_lines_start'])
relevant_lines_end = int(suggestion['relevant_lines_end'])
range_str = ""
if relevant_lines_start == relevant_lines_end:
range_str = f"[{relevant_lines_start}]"
else:
data_markdown += f"File: {s['relevant file']} ({s['relevant lines start']}-{s['relevant lines end']})\n\n"
if self.git_provider.is_supported("gfm_markdown"):
data_markdown += "<details> <summary> Example code:</summary>\n\n"
data_markdown += f"___\n\n"
language_name = "python"
if extension_s and (extension_s in extension_to_language):
language_name = extension_to_language[extension_s]
data_markdown += f"Existing code:\n```{language_name}\n{s['existing code']}\n```\n"
data_markdown += f"Improved code:\n```{language_name}\n{s['improved code']}\n```\n"
if self.git_provider.is_supported("gfm_markdown"):
data_markdown += "</details>\n"
data_markdown += "\n___\n\n"
except Exception as e:
get_logger().error(f"Could not parse suggestion: {s}, error: {e}")
self.git_provider.publish_comment(data_markdown)
range_str = f"[{relevant_lines_start}-{relevant_lines_end}]"
code_snippet_link = self.git_provider.get_line_link(relevant_file, relevant_lines_start,
relevant_lines_end)
# add html table for each suggestion
suggestion_content = suggestion['suggestion_content'].rstrip().rstrip()
suggestion_content = insert_br_after_x_chars(suggestion_content, 90)
# pr_body += f"<tr><td><details><summary>{suggestion_content}</summary>"
existing_code = suggestion['existing_code'].rstrip()+"\n"
improved_code = suggestion['improved_code'].rstrip()+"\n"
diff = difflib.unified_diff(existing_code.split('\n'),
improved_code.split('\n'), n=999)
patch_orig = "\n".join(diff)
patch = "\n".join(patch_orig.splitlines()[5:]).strip('\n')
example_code = ""
example_code += f"```diff\n{patch}\n```\n"
pr_body += f"""<tr><td>"""
suggestion_summary = suggestion['one_sentence_summary'].strip()
if '`' in suggestion_summary:
suggestion_summary = replace_code_tags(suggestion_summary)
suggestion_summary = suggestion_summary + max((77-len(suggestion_summary)), 0)*"&nbsp;"
pr_body += f"""\n\n<details><summary>{suggestion_summary}</summary>\n\n___\n\n"""
pr_body += f"""
**{suggestion_content}**
[{relevant_file} {range_str}]({code_snippet_link})
{example_code}
"""
pr_body += f"</details>"
pr_body += f"</td></tr>"
pr_body += """</table>"""
# pr_body += "</details>"
pr_body += """</td></tr>"""
pr_body += """</tr></tbody></table>"""
return pr_body
except Exception as e:
get_logger().info(f"Failed to publish summarized code suggestions, error: {e}")
return ""

View File

@ -7,7 +7,7 @@ class PRConfig:
"""
The PRConfig class is responsible for listing all configuration options available for the user.
"""
def __init__(self, pr_url: str, args=None):
def __init__(self, pr_url: str, args=None, ai_handler=None):
"""
Initialize the PRConfig object with the necessary attributes and objects to comment on a pull request.

View File

@ -1,10 +1,12 @@
import copy
import re
from functools import partial
from typing import List, Tuple
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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, get_user_labels
@ -12,10 +14,12 @@ 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
from pr_agent.servers.help import HelpMessage
class PRDescription:
def __init__(self, pr_url: str, args: list = None):
def __init__(self, pr_url: str, args: list = None,
ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
"""
Initialize the PRDescription object with the necessary attributes and objects for generating a PR description
using an AI model.
@ -36,7 +40,7 @@ class PRDescription:
get_settings().pr_description.enable_semantic_files_types = False
# Initialize the AI handler
self.ai_handler = AiHandler()
self.ai_handler = ai_handler()
# Initialize the variables dictionary
self.vars = {
@ -45,12 +49,10 @@ 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(),
"enable_custom_labels": get_settings().config.enable_custom_labels,
"custom_labels_class": "", # will be filled if necessary in 'set_custom_labels' function
"enable_file_walkthrough": get_settings().pr_description.enable_file_walkthrough,
"enable_semantic_files_types": get_settings().pr_description.enable_semantic_files_types,
}
@ -84,6 +86,7 @@ class PRDescription:
if self.prediction:
self._prepare_data()
else:
self.git_provider.remove_initial_comment()
return None
if get_settings().pr_description.enable_semantic_files_types:
@ -97,20 +100,34 @@ class PRDescription:
pr_title, pr_body = self._prepare_pr_answer_with_markers()
else:
pr_title, pr_body, = self._prepare_pr_answer()
# Add help text if gfm_markdown is supported
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_description.enable_help_text:
pr_body += "<hr>\n\n<details> <summary><strong>✨ Usage guide:</strong></summary><hr> \n\n"
pr_body += HelpMessage.get_describe_usage_guide()
pr_body += "\n</details>\n"
# final markdown description
full_markdown_description = f"## Title\n\n{pr_title}\n\n___\n{pr_body}"
get_logger().debug(f"full_markdown_description:\n{full_markdown_description}")
if get_settings().config.publish_output:
get_logger().info(f"Pushing answer {self.pr_id}")
if get_settings().pr_description.publish_description_as_comment:
get_logger().info(f"Publishing answer as comment")
self.git_provider.publish_comment(full_markdown_description)
else:
self.git_provider.publish_description(pr_title, pr_body)
# publish labels
if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"):
current_labels = self.git_provider.get_pr_labels()
user_labels = get_user_labels(current_labels)
self.git_provider.publish_labels(pr_labels + user_labels)
# publish description
if get_settings().pr_description.publish_description_as_comment:
get_logger().info(f"Publishing answer as comment")
self.git_provider.publish_comment(full_markdown_description)
else:
self.git_provider.publish_description(pr_title, pr_body)
# publish final update message
if (get_settings().pr_description.final_update_message and
hasattr(self.git_provider, 'pr_url') and self.git_provider.pr_url):
latest_commit_url = self.git_provider.get_latest_commit_url()
@ -124,26 +141,17 @@ class PRDescription:
return ""
async def _prepare_prediction(self, model: str) -> None:
"""
Prepare the AI prediction for the PR description 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.
"""
if get_settings().pr_description.use_description_markers and 'pr_agent:' not in self.user_description:
return None
get_logger().info(f"Getting PR diff {self.pr_id}")
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
if self.patches_diff:
get_logger().info(f"Getting AI prediction {self.pr_id}")
self.prediction = await self._get_prediction(model)
else:
get_logger().error(f"Error getting PR diff {self.pr_id}")
self.prediction = None
async def _get_prediction(self, model: str) -> str:
"""
@ -160,6 +168,7 @@ class PRDescription:
environment = Environment(undefined=StrictUndefined)
set_custom_labels(variables, self.git_provider)
self.variables = 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)
@ -186,6 +195,22 @@ class PRDescription:
if get_settings().pr_description.add_original_user_description and self.user_description:
self.data["User Description"] = self.user_description
# re-order keys
if 'User Description' in self.data:
self.data['User Description'] = self.data.pop('User Description')
if 'title' in self.data:
self.data['title'] = self.data.pop('title')
if 'type' in self.data:
self.data['type'] = self.data.pop('type')
if 'labels' in self.data:
self.data['labels'] = self.data.pop('labels')
if 'description' in self.data:
self.data['description'] = self.data.pop('description')
if 'pr_files' in self.data:
self.data['pr_files'] = self.data.pop('pr_files')
def _prepare_labels(self) -> List[str]:
pr_types = []
@ -201,6 +226,16 @@ class PRDescription:
pr_types = self.data['type']
elif type(self.data['type']) == str:
pr_types = self.data['type'].split(',')
# convert lowercase labels to original case
try:
if "labels_minimal_to_labels_dict" in self.variables:
d: dict = self.variables["labels_minimal_to_labels_dict"]
for i, label_i in enumerate(pr_types):
if label_i in d:
pr_types[i] = d[label_i]
except Exception as e:
get_logger().error(f"Error converting labels to original case {self.pr_id}: {e}")
return pr_types
def _prepare_pr_answer_with_markers(self) -> Tuple[str, str]:
@ -222,16 +257,15 @@ class PRDescription:
summary = f"{ai_header}{ai_summary}"
body = body.replace('pr_agent:summary', summary)
if not re.search(r'<!--\s*pr_agent:walkthrough\s*-->', body):
ai_walkthrough = self.data.get('PR Main Files Walkthrough')
if ai_walkthrough:
walkthrough = str(ai_header)
for file in ai_walkthrough:
filename = file['filename'].replace("'", "`")
description = file['changes in file'].replace("'", "`")
walkthrough += f'- `{filename}`: {description}\n'
body = body.replace('pr_agent:walkthrough', walkthrough)
ai_walkthrough = self.data.get('pr_files')
if ai_walkthrough and not re.search(r'<!--\s*pr_agent:walkthrough\s*-->', body):
try:
walkthrough_gfm = ""
walkthrough_gfm = self.process_pr_files_prediction(walkthrough_gfm, self.file_label_dict)
body = body.replace('pr_agent:walkthrough', walkthrough_gfm)
except Exception as e:
get_logger().error(f"Failing to process walkthrough {self.pr_id}: {e}")
body = body.replace('pr_agent:walkthrough', "")
return title, body
@ -252,7 +286,7 @@ class PRDescription:
if not get_settings().pr_description.enable_pr_type:
self.data.pop('type')
for key, value in self.data.items():
markdown_text += f"## {key}\n\n"
markdown_text += f"## **{key}**\n\n"
markdown_text += f"{value}\n\n"
# Remove the 'PR Title' key from the dictionary
@ -270,10 +304,10 @@ class PRDescription:
for idx, (key, value) in enumerate(self.data.items()):
if key == 'pr_files':
value = self.file_label_dict
key_publish = "PR changes walkthrough"
key_publish = "Changes walkthrough"
else:
key_publish = key.rstrip(':').replace("_", " ").capitalize()
pr_body += f"## {key_publish}\n"
pr_body += f"## **{key_publish}**\n"
if 'walkthrough' in key.lower():
if self.git_provider.is_supported("gfm_markdown"):
pr_body += "<details> <summary>files:</summary>\n\n"
@ -304,43 +338,62 @@ class PRDescription:
try:
filename = file['filename'].replace("'", "`").replace('"', '`')
changes_summary = file['changes_summary']
label = file['label']
changes_title = file['changes_title'].strip()
label = file.get('label')
if label not in self.file_label_dict:
self.file_label_dict[label] = []
self.file_label_dict[label].append((filename, changes_summary))
self.file_label_dict[label].append((filename, changes_title, changes_summary))
except Exception as e:
get_logger().error(f"Error preparing file label dict {self.pr_id}: {e}")
pass
def process_pr_files_prediction(self, pr_body, value):
# logic for using collapsible file list
use_collapsible_file_list = get_settings().pr_description.collapsible_file_list
num_files = 0
if value:
for semantic_label in value.keys():
num_files += len(value[semantic_label])
if use_collapsible_file_list == "adaptive":
use_collapsible_file_list = num_files > 8
if not self.git_provider.is_supported("gfm_markdown"):
get_logger().info(f"Disabling semantic files types for {self.pr_id} since gfm_markdown is not supported")
return pr_body
try:
pr_body += "<table>"
header = f"Relevant files"
delta = 65
header += "&nbsp; " * delta
pr_body += f"""<thead><tr><th></th><th>{header}</th></tr></thead>"""
delta = 77
# header += "&nbsp; " * delta
pr_body += f"""<thead><tr><th></th><th align="left">{header}</th></tr></thead>"""
pr_body += """<tbody>"""
for semantic_label in value.keys():
s_label = semantic_label.strip("'").strip('"')
pr_body += f"""<tr><td><strong>{s_label.capitalize()}</strong></td>"""
list_tuples = value[semantic_label]
if use_collapsible_file_list:
pr_body += f"""<td><details><summary>{len(list_tuples)} files</summary><table>"""
for filename, file_change_description in list_tuples:
else:
pr_body += f"""<td><table>"""
for filename, file_changes_title, file_change_description in list_tuples:
filename = filename.replace("'", "`")
filename_publish = filename.split("/")[-1]
filename_publish = f"{filename_publish}"
if len(filename_publish) < (delta - 5):
filename_publish += "&nbsp; " * ((delta - 5) - len(filename_publish))
file_changes_title_br = insert_br_after_x_chars(file_changes_title, x=(delta - 5),
new_line_char="\n\n")
file_changes_title_extended = file_changes_title_br.strip() + "</code>"
if len(file_changes_title_extended) < (delta - 5):
file_changes_title_extended += "&nbsp; " * ((delta - 5) - len(file_changes_title_extended))
filename_publish = f"<strong>{filename_publish}</strong><dd><code>{file_changes_title_extended}</dd>"
diff_plus_minus = ""
delta_nbsp = ""
diff_files = self.git_provider.diff_files
for f in diff_files:
if f.filename.lower() == filename.lower():
num_plus_lines = f.num_plus_lines
num_minus_lines = f.num_minus_lines
diff_plus_minus += f"+{num_plus_lines}/-{num_minus_lines}"
delta_nbsp = "&nbsp; " * max(0, (8 - len(diff_plus_minus)))
break
# try to add line numbers link to code suggestions
@ -349,23 +402,25 @@ class PRDescription:
filename = filename.strip()
link = self.git_provider.get_line_link(filename, relevant_line_start=-1)
file_change_description = self._insert_br_after_x_chars(file_change_description, x=(delta - 5))
file_change_description_br = insert_br_after_x_chars(file_change_description, x=(delta - 5))
pr_body += f"""
<tr>
<td>
<details>
<summary><strong>{filename_publish}</strong></summary>
<ul>
{filename}<br><br>
<strong>{file_change_description}</strong>
</ul>
<summary>{filename_publish}</summary>
<hr>
{filename}
{file_change_description_br}
</details>
</td>
<td><a href="{link}"> {diff_plus_minus}</a></td>
<td><a href="{link}">{diff_plus_minus}</a>{delta_nbsp}</td>
</tr>
"""
if use_collapsible_file_list:
pr_body += """</table></details></td></tr>"""
else:
pr_body += """</table></td></tr>"""
pr_body += """</tr></tbody></table>"""
except Exception as e:
@ -373,25 +428,48 @@ class PRDescription:
pass
return pr_body
def _insert_br_after_x_chars(self, text, x=70):
def insert_br_after_x_chars(text, x=70, new_line_char="<br> "):
"""
Insert <br> into a string after a word that increases its length above x characters.
"""
if len(text) < x:
return text
words = text.split(' ')
lines = text.splitlines()
words = []
for i,line in enumerate(lines):
words += line.split(' ')
if i<len(lines)-1:
words[-1] += "\n"
# words = text.split(' ')
new_text = ""
current_length = 0
is_inside_code = False
for word in words:
# Check if adding this word exceeds x characters
if current_length + len(word) > x:
new_text += "<br>" # Insert line break
if not is_inside_code:
new_text += f"{new_line_char} " # Insert line break
current_length = 0 # Reset counter
else:
new_text += f"`{new_line_char} `"
# check if inside <code> tag
if word.startswith("`") and not is_inside_code and not word.endswith("`"):
is_inside_code = True
if word.endswith("`"):
is_inside_code = False
# Add the word to the new text
if word.endswith("\n"):
new_text += word
else:
new_text += word + " "
current_length += len(word) + 1 # Add 1 for the space
if word.endswith("\n"):
current_length = 0
return new_text.strip() # Remove trailing space

View File

@ -1,10 +1,12 @@
import copy
import re
from functools import partial
from typing import List, Tuple
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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, get_user_labels
@ -15,7 +17,8 @@ from pr_agent.log import get_logger
class PRGenerateLabels:
def __init__(self, pr_url: str, args: list = None):
def __init__(self, pr_url: str, args: list = None,
ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
"""
Initialize the PRGenerateLabels object with the necessary attributes and objects for generating labels
corresponding to the PR using an AI model.
@ -31,7 +34,7 @@ class PRGenerateLabels:
self.pr_id = self.git_provider.get_pr_id()
# Initialize the AI handler
self.ai_handler = AiHandler()
self.ai_handler = ai_handler()
# Initialize the variables dictionary
self.vars = {
@ -40,7 +43,6 @@ class PRGenerateLabels:
"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(),
"enable_custom_labels": get_settings().config.enable_custom_labels,
@ -133,6 +135,7 @@ class PRGenerateLabels:
environment = Environment(undefined=StrictUndefined)
set_custom_labels(variables, self.git_provider)
self.variables = 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)
@ -168,4 +171,14 @@ class PRGenerateLabels:
elif type(self.data['labels']) == str:
pr_types = self.data['labels'].split(',')
# convert lowercase labels to original case
try:
if "labels_minimal_to_labels_dict" in self.variables:
d: dict = self.variables["labels_minimal_to_labels_dict"]
for i, label_i in enumerate(pr_types):
if label_i in d:
pr_types[i] = d[label_i]
except Exception as e:
get_logger().error(f"Error converting labels to original case {self.pr_id}: {e}")
return pr_types

View File

@ -1,8 +1,10 @@
import copy
from functools import partial
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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.config_loader import get_settings
@ -12,12 +14,13 @@ from pr_agent.log import get_logger
class PRInformationFromUser:
def __init__(self, pr_url: str, args: list = None):
def __init__(self, pr_url: str, args: list = None,
ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
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.ai_handler = AiHandler()
self.ai_handler = ai_handler()
self.vars = {
"title": self.git_provider.pr.title,
"branch": self.git_provider.get_pr_branch(),

View File

@ -1,24 +1,27 @@
import copy
from functools import partial
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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.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
from pr_agent.servers.help import HelpMessage
class PRQuestions:
def __init__(self, pr_url: str, args=None):
def __init__(self, pr_url: str, args=None, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
question_str = self.parse_args(args)
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.ai_handler = AiHandler()
self.ai_handler = ai_handler()
self.question_str = question_str
self.vars = {
"title": self.git_provider.pr.title,
@ -50,6 +53,11 @@ class PRQuestions:
await retry_with_fallback_models(self._prepare_prediction)
get_logger().info('Preparing answer...')
pr_comment = self._prepare_pr_answer()
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_questions.enable_help_text:
pr_comment += "<hr>\n\n<details> <summary><strong>✨ Usage guide:</strong></summary><hr> \n\n"
pr_comment += HelpMessage.get_ask_usage_guide()
pr_comment += "\n</details>\n"
if get_settings().config.publish_output:
get_logger().info('Pushing answer...')
self.git_provider.publish_comment(pr_comment)

View File

@ -1,13 +1,15 @@
import copy
import datetime
from collections import OrderedDict
from functools import partial
from typing import List, Tuple
import yaml
from jinja2 import Environment, StrictUndefined
from yaml import SafeLoader
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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, set_custom_labels, get_user_labels
@ -15,20 +17,23 @@ 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
from pr_agent.log import get_logger
from pr_agent.servers.help import actions_help_text, bot_help_text
from pr_agent.servers.help import HelpMessage
class PRReviewer:
"""
The PRReviewer class is responsible for reviewing a pull request and generating feedback using an AI model.
"""
def __init__(self, pr_url: str, is_answer: bool = False, is_auto: bool = False, args: list = None):
def __init__(self, pr_url: str, is_answer: bool = False, is_auto: bool = False, args: list = None,
ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
"""
Initialize the PRReviewer object with the necessary attributes and objects to review a pull request.
Args:
pr_url (str): The URL of the pull request to be reviewed.
is_answer (bool, optional): Indicates whether the review is being done in answer mode. Defaults to False.
is_auto (bool, optional): Indicates whether the review is being done in automatic mode. Defaults to False.
ai_handler (BaseAiHandler): The AI handler to be used for the review. Defaults to None.
args (list, optional): List of arguments passed to the PRReviewer class. Defaults to None.
"""
self.parse_args(args) # -i command
@ -43,7 +48,7 @@ class PRReviewer:
if self.is_answer and not self.git_provider.is_supported("get_issue_comments"):
raise Exception(f"Answer mode is not supported for {get_settings().config.git_provider} for now")
self.ai_handler = AiHandler()
self.ai_handler = ai_handler()
self.patches_diff = None
self.prediction = None
@ -93,14 +98,7 @@ class PRReviewer:
self.incremental = IncrementalPR(is_incremental)
async def run(self) -> None:
"""
Review the pull request and generate feedback.
"""
try:
if self.is_auto and not get_settings().pr_reviewer.automatic_review:
get_logger().info(f'Automatic review is disabled {self.pr_url}')
return None
if self.incremental.is_incremental and not self._can_run_incremental_review():
return None
@ -110,6 +108,9 @@ class PRReviewer:
self.git_provider.publish_comment("Preparing review...", is_temporary=True)
await retry_with_fallback_models(self._prepare_prediction)
if not self.prediction:
self.git_provider.remove_initial_comment()
return None
get_logger().info('Preparing PR review...')
pr_comment = self._prepare_pr_review()
@ -136,19 +137,14 @@ class PRReviewer:
get_logger().error(f"Failed to review PR: {e}")
async def _prepare_prediction(self, model: str) -> None:
"""
Prepare the AI prediction for the pull request review.
Args:
model: A string representing the AI model to be used for the prediction.
Returns:
None
"""
get_logger().info('Getting PR diff...')
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
if self.patches_diff:
get_logger().info('Getting AI prediction...')
self.prediction = await self._get_prediction(model)
else:
get_logger().error(f"Error getting PR diff")
self.prediction = None
async def _get_prediction(self, model: str) -> str:
"""
@ -244,19 +240,11 @@ class PRReviewer:
data.move_to_end('Incremental PR Review', last=False)
markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown"))
user = self.git_provider.get_user_id()
# Add help text if not in CLI mode
if not get_settings().get("CONFIG.CLI_MODE", False):
markdown_text += "\n### How to use\n"
if self.git_provider.is_supported("gfm_markdown"):
markdown_text += "\n <details> <summary> Instructions</summary>\n\n"
bot_user = "[bot]" if get_settings().github_app.override_deployment_type else get_settings().github_app.bot_user
if user and bot_user not in user:
markdown_text += bot_help_text(user)
else:
markdown_text += actions_help_text
if self.git_provider.is_supported("gfm_markdown"):
# Add help text if gfm_markdown is supported
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_reviewer.enable_help_text:
markdown_text += "<hr>\n\n<details> <summary><strong>✨ Usage guide:</strong></summary><hr> \n\n"
markdown_text += HelpMessage.get_review_usage_guide()
markdown_text += "\n</details>\n"
# Add custom labels from the review prediction (effort, security)
@ -393,9 +381,12 @@ class PRReviewer:
review_labels.append('Possible security concern')
current_labels = self.git_provider.get_pr_labels()
if current_labels:
current_labels_filtered = [label for label in current_labels if
not label.lower().startswith('review effort [1-5]:') and not label.lower().startswith(
'possible security concern')]
else:
current_labels_filtered = []
if current_labels or review_labels:
get_logger().info(f"Setting review labels: {review_labels + current_labels_filtered}")
self.git_provider.publish_labels(review_labels + current_labels_filtered)

View File

@ -19,7 +19,7 @@ MODEL = "text-embedding-ada-002"
class PRSimilarIssue:
def __init__(self, issue_url: str, args: list = None):
def __init__(self, issue_url: str, ai_handler, args: list = None):
if get_settings().config.git_provider != "github":
raise Exception("Only github is supported for similar issue tool")
@ -35,6 +35,7 @@ class PRSimilarIssue:
repo_name_for_index = self.repo_name_for_index = repo_obj.full_name.lower().replace('/', '-').replace('_/', '-')
index_name = self.index_name = "codium-ai-pr-agent-issues"
if get_settings().pr_similar_issue.vectordb == "pinecone":
# assuming pinecone api key and environment are set in secrets file
try:
api_key = get_settings().pinecone.api_key
@ -105,6 +106,69 @@ class PRSimilarIssue:
else:
get_logger().info('No new issues to update')
elif get_settings().pr_similar_issue.vectordb == "lancedb":
import lancedb # import lancedb only if needed
self.db = lancedb.connect(get_settings().lancedb.uri)
self.table = None
run_from_scratch = False
if run_from_scratch: # for debugging
if index_name in self.db.table_names():
get_logger().info('Removing Table...')
self.db.drop_table(index_name)
get_logger().info('Done')
ingest = True
if index_name not in self.db.table_names():
run_from_scratch = True
ingest = False
else:
if get_settings().pr_similar_issue.force_update_dataset:
ingest = True
else:
self.table = self.db[index_name]
res = self.table.search().limit(len(self.table)).where(f"id='example_issue_{repo_name_for_index}'").to_list()
get_logger().info("result: ", res)
if res[0].get("vector"):
ingest = False
if run_from_scratch or ingest: # indexing the entire repo
get_logger().info('Indexing the entire repo...')
get_logger().info('Getting issues...')
issues = list(repo_obj.get_issues(state='all'))
get_logger().info('Done')
self._update_table_with_issues(issues, repo_name_for_index, ingest=ingest)
else: # update table if needed
issues_to_update = []
issues_paginated_list = repo_obj.get_issues(state='all')
counter = 1
for issue in issues_paginated_list:
if issue.pull_request:
continue
issue_str, comments, number = self._process_issue(issue)
issue_key = f"issue_{number}"
issue_id = issue_key + "." + "issue"
res = self.table.search().limit(len(self.table)).where(f"id='{issue_id}'").to_list()
is_new_issue = True
for r in res:
if r['metadata']['repo'] == repo_name_for_index:
is_new_issue = False
break
if is_new_issue:
counter += 1
issues_to_update.append(issue)
else:
break
if issues_to_update:
get_logger().info(f'Updating index with {counter} new issues...')
self._update_table_with_issues(issues_to_update, repo_name_for_index, ingest=True)
else:
get_logger().info('No new issues to update')
async def run(self):
get_logger().info('Getting issue...')
repo_name, original_issue_number = self.git_provider._parse_issue_url(self.issue_url.split('=')[-1])
@ -116,14 +180,18 @@ class PRSimilarIssue:
get_logger().info('Querying...')
res = openai.Embedding.create(input=[issue_str], engine=MODEL)
embeds = [record['embedding'] for record in res['data']]
relevant_issues_number_list = []
relevant_comment_number_list = []
score_list = []
if get_settings().pr_similar_issue.vectordb == "pinecone":
pinecone_index = pinecone.Index(index_name=self.index_name)
res = pinecone_index.query(embeds[0],
top_k=5,
filter={"repo": self.repo_name_for_index},
include_metadata=True).to_dict()
relevant_issues_number_list = []
relevant_comment_number_list = []
score_list = []
for r in res['matches']:
# skip example issue
if 'example_issue_' in r["id"]:
@ -146,8 +214,35 @@ class PRSimilarIssue:
score_list.append(str("{:.2f}".format(r['score'])))
get_logger().info('Done')
elif get_settings().pr_similar_issue.vectordb == "lancedb":
res = self.table.search(embeds[0]).where(f"metadata.repo='{self.repo_name_for_index}'", prefilter=True).to_list()
for r in res:
# skip example issue
if 'example_issue_' in r["id"]:
continue
try:
issue_number = int(r["id"].split('.')[0].split('_')[-1])
except:
get_logger().debug(f"Failed to parse issue number from {r['id']}")
continue
if original_issue_number == issue_number:
continue
if issue_number not in relevant_issues_number_list:
relevant_issues_number_list.append(issue_number)
if 'comment' in r["id"]:
relevant_comment_number_list.append(int(r["id"].split('.')[1].split('_')[-1]))
else:
relevant_comment_number_list.append(-1)
score_list.append(str("{:.2f}".format(1-r['_distance'])))
get_logger().info('Done')
get_logger().info('Publishing response...')
similar_issues_str = "### Similar Issues\n___\n\n"
for i, issue_number_similar in enumerate(relevant_issues_number_list):
issue = self.git_provider.repo_obj.get_issue(issue_number_similar)
title = issue.title
@ -266,6 +361,96 @@ class PRSimilarIssue:
time.sleep(5) # wait for pinecone to finalize upserting before querying
get_logger().info('Done')
def _update_table_with_issues(self, issues_list, repo_name_for_index, ingest=False):
get_logger().info('Processing issues...')
corpus = Corpus()
example_issue_record = Record(
id=f"example_issue_{repo_name_for_index}",
text="example_issue",
metadata=Metadata(repo=repo_name_for_index)
)
corpus.append(example_issue_record)
counter = 0
for issue in issues_list:
if issue.pull_request:
continue
counter += 1
if counter % 100 == 0:
get_logger().info(f"Scanned {counter} issues")
if counter >= self.max_issues_to_scan:
get_logger().info(f"Scanned {self.max_issues_to_scan} issues, stopping")
break
issue_str, comments, number = self._process_issue(issue)
issue_key = f"issue_{number}"
username = issue.user.login
created_at = str(issue.created_at)
if len(issue_str) < 8000 or \
self.token_handler.count_tokens(issue_str) < get_max_tokens(MODEL): # fast reject first
issue_record = Record(
id=issue_key + "." + "issue",
text=issue_str,
metadata=Metadata(repo=repo_name_for_index,
username=username,
created_at=created_at,
level=IssueLevel.ISSUE)
)
corpus.append(issue_record)
if comments:
for j, comment in enumerate(comments):
comment_body = comment.body
num_words_comment = len(comment_body.split())
if num_words_comment < 10 or not isinstance(comment_body, str):
continue
if len(comment_body) < 8000 or \
self.token_handler.count_tokens(comment_body) < MAX_TOKENS[MODEL]:
comment_record = Record(
id=issue_key + ".comment_" + str(j + 1),
text=comment_body,
metadata=Metadata(repo=repo_name_for_index,
username=username, # use issue username for all comments
created_at=created_at,
level=IssueLevel.COMMENT)
)
corpus.append(comment_record)
df = pd.DataFrame(corpus.dict()["documents"])
get_logger().info('Done')
get_logger().info('Embedding...')
openai.api_key = get_settings().openai.key
list_to_encode = list(df["text"].values)
try:
res = openai.Embedding.create(input=list_to_encode, engine=MODEL)
embeds = [record['embedding'] for record in res['data']]
except:
embeds = []
get_logger().error('Failed to embed entire list, embedding one by one...')
for i, text in enumerate(list_to_encode):
try:
res = openai.Embedding.create(input=[text], engine=MODEL)
embeds.append(res['data'][0]['embedding'])
except:
embeds.append([0] * 1536)
df["vector"] = embeds
get_logger().info('Done')
if not ingest:
get_logger().info('Creating table from scratch...')
self.table = self.db.create_table(self.index_name, data=df, mode="overwrite")
time.sleep(15)
else:
get_logger().info('Ingesting in Table...')
if self.index_name not in self.db.table_names():
self.table.add(df)
else:
get_logger().info(f"Table {self.index_name} doesn't exists!")
time.sleep(5)
get_logger().info('Done')
class IssueLevel(str, Enum):
ISSUE = "issue"

View File

@ -1,11 +1,13 @@
import copy
from datetime import date
from functools import partial
from time import sleep
from typing import Tuple
from jinja2 import Environment, StrictUndefined
from pr_agent.algo.ai_handler import AiHandler
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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.config_loader import get_settings
@ -17,7 +19,7 @@ CHANGELOG_LINES = 50
class PRUpdateChangelog:
def __init__(self, pr_url: str, cli_mode=False, args=None):
def __init__(self, pr_url: str, cli_mode=False, args=None, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.git_provider = get_git_provider()(pr_url)
self.main_language = get_main_pr_language(
@ -25,7 +27,7 @@ class PRUpdateChangelog:
)
self.commit_changelog = get_settings().pr_update_changelog.push_changelog_changes
self._get_changlog_file() # self.changelog_file_str
self.ai_handler = AiHandler()
self.ai_handler = ai_handler()
self.patches_diff = None
self.prediction = None
self.cli_mode = cli_mode

View File

@ -17,7 +17,7 @@ maintainers = [
]
description = "CodiumAI PR-Agent is an open-source tool to automatically analyze a pull request and provide several types of feedback"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
keywords = ["ai", "tool", "developer", "review", "agent"]
license = {file = "LICENSE", name = "Apache 2.0 License"}
classifiers = [

View File

@ -1,5 +1,5 @@
aiohttp==3.9.1
atlassian-python-api==3.39.0
atlassian-python-api==3.41.4
azure-devops==7.1.0b3
boto3==1.33.6
dynaconf==3.2.4
@ -14,6 +14,7 @@ msrest==0.7.1
openai==0.27.8
pinecone-client
pinecone-datasets @ git+https://github.com/mrT23/pinecone-datasets.git@main
lancedb==0.3.4
pytest==7.4.0
PyGithub==1.59.*
PyYAML==6.0.1
@ -23,3 +24,4 @@ starlette-context==0.3.6
tiktoken==0.5.2
ujson==5.8.0
uvicorn==0.22.0
# langchain==0.0.349 # uncomment this to support language LangChainOpenAIHandler

View File

@ -71,7 +71,7 @@ class TestConvertToMarkdown:
- 📌 **Type of PR:** Test type\n\
- 🧪 **Relevant tests added:** no\n\
- ✨ **Focused PR:** Yes\n\
- **General PR suggestions:** general suggestion...\n\n\n- <details><summary> 🤖 Code feedback:</summary> - **Code example:**\n - **Before:**\n ```\n Code before\n ```\n - **After:**\n ```\n Code after\n ```\n\n - **Code example:**\n - **Before:**\n ```\n Code before 2\n ```\n - **After:**\n ```\n Code after 2\n ```\n\n</details>\
- **General PR suggestions:** general suggestion...\n\n\n<details><summary> <strong>🤖 Code feedback:</strong></summary> - **Code example:**\n - **Before:**\n ```\n Code before\n ```\n - **After:**\n ```\n Code after\n ```\n\n - **Code example:**\n - **Before:**\n ```\n Code before 2\n ```\n - **After:**\n ```\n Code after 2\n ```\n\n</details>\
"""
assert convert_to_markdown(input_data).strip() == expected_output.strip()

View File

@ -19,6 +19,19 @@ class TestTryFixYaml:
expected_output = {"relevant line": "value: 3"}
assert try_fix_yaml(review_text) == expected_output
# The function extracts YAML snippet
def test_extract_snippet(self):
review_text = '''\
Here is the answer in YAML format:
```yaml
name: John Smith
age: 35
```
'''
expected_output = {'name': 'John Smith', 'age': 35}
assert try_fix_yaml(review_text) == expected_output
# The function removes the last line(s) of the YAML string and successfully parses the YAML string.
def test_remove_last_line(self):
review_text = "key: value\nextra invalid line\n"