Compare commits
374 Commits
test_best_
...
v0.28
Author | SHA1 | Date | |
---|---|---|---|
7d57edf959 | |||
6950b3ca6b | |||
e422f50cfe | |||
66a667d509 | |||
482cd7c680 | |||
991a866368 | |||
fcd9416129 | |||
255e1d0fc1 | |||
7117e9fe0e | |||
b42841fcc4 | |||
839b6093cb | |||
351b9d9115 | |||
3e11f07f0b | |||
1d57dd7443 | |||
7aa3d12876 | |||
6f6595c343 | |||
b300cfa84d | |||
9a21069075 | |||
85f7b99dea | |||
d6f79f486a | |||
b0ed584821 | |||
05ea699a92 | |||
605eef64e7 | |||
c94aa58ae4 | |||
e20e7c138c | |||
b161672218 | |||
5bc253e1d9 | |||
8495e4d549 | |||
fb324d106c | |||
a4387b5829 | |||
477ebf4926 | |||
fe98779a88 | |||
e14fc7e02d | |||
1bd65934df | |||
88a17848eb | |||
1aab87516e | |||
dd80276f3f | |||
ad17cb4d92 | |||
6efb694945 | |||
dde362bd47 | |||
a9ce909713 | |||
e925f31ac0 | |||
5e7e353670 | |||
52d4312c9a | |||
8ec6067b26 | |||
bc575e5a67 | |||
b087458e33 | |||
6610921bba | |||
3fd15042a6 | |||
4ab2396be0 | |||
a75d430751 | |||
737cf559d9 | |||
fa77828db2 | |||
f506fb1e05 | |||
4ea46d5b25 | |||
ed9fcd0238 | |||
9d3bd7289a | |||
1724a65ab2 | |||
6883ced9e3 | |||
29e28056db | |||
507cd6e675 | |||
dd1a0e51bb | |||
b4e2a32543 | |||
677a54c5a0 | |||
a211175fea | |||
64f52288a1 | |||
4ee1704862 | |||
f5e381e1b2 | |||
2cacaf56b0 | |||
9a574e0caa | |||
0f33750035 | |||
4713175fcf | |||
d16012a568 | |||
01f1599336 | |||
f5bd98a3b9 | |||
1c86af30b6 | |||
0acd5193cb | |||
ffefcb8a04 | |||
35bb2b31e3 | |||
a18f9d00c9 | |||
20d709075c | |||
52c99e3f7b | |||
692bc449be | |||
884b49dd84 | |||
338ec5cae0 | |||
c6e4498653 | |||
c7c411eb63 | |||
222155e4f2 | |||
f9d5e72058 | |||
2619ff3eb3 | |||
15e8167115 | |||
d6ad511511 | |||
1943662946 | |||
c1fb76abcf | |||
121d90a9da | |||
a8935dece3 | |||
fd12191fcf | |||
61b6d1f1a3 | |||
874b7c7da4 | |||
a0d86b532f | |||
4f2551e0a6 | |||
eb5f38b13b | |||
1753bc703e | |||
31de811820 | |||
4c0e371238 | |||
ca286b8dc0 | |||
0c30f084bc | |||
a33bee5805 | |||
f32163d57c | |||
bb24a6f43d | |||
5b267332b9 | |||
30bf7572b0 | |||
b5ce49cbc0 | |||
440d2368a4 | |||
215c10cc8c | |||
7623e1a419 | |||
5e30e190b8 | |||
5447dd2ac6 | |||
214200b816 | |||
fcb7d97640 | |||
224920bdb7 | |||
d3f83f3069 | |||
8e6267b0e6 | |||
9809e2dbd8 | |||
7cf521c001 | |||
e71c0f1805 | |||
8182a4afc0 | |||
3817aa2868 | |||
94a8606d24 | |||
af635650f1 | |||
222f276959 | |||
9a32e94b3e | |||
7c56eee701 | |||
48b3c69c10 | |||
9d1c8312b5 | |||
64e5a87530 | |||
9a9acef0e8 | |||
3ff8f1ff11 | |||
c7f4b87d6f | |||
9db44b5f5f | |||
70a2377ac9 | |||
52a68bcd44 | |||
0a4c02c8b3 | |||
e253f18e7f | |||
d6b6191f90 | |||
de80901284 | |||
dfbd8dad5d | |||
d6f405dd0d | |||
25ba9414fe | |||
d097266c38 | |||
fa1eda967f | |||
c89c0eab8c | |||
93e34703ab | |||
c15ed628db | |||
70f47336d6 | |||
c6a6a2f352 | |||
1dc3db7322 | |||
049fc558a8 | |||
2dc89d0998 | |||
07bbfff4ba | |||
328637b8be | |||
44d9535dbc | |||
7fec17f3ff | |||
cd15f64f11 | |||
d4ac206c46 | |||
444910868e | |||
a24b06b253 | |||
393516f746 | |||
152b111ef2 | |||
56250f5ea8 | |||
7b1df82c05 | |||
05960f2c3f | |||
feb306727e | |||
2a647709c4 | |||
a4cd05e71c | |||
da6ef8c80f | |||
775bfc74eb | |||
ebdbde1bca | |||
e16c6d0b27 | |||
f0b52870a2 | |||
1b35f01aa1 | |||
a0dc9deb30 | |||
7e32a08f00 | |||
84983f3e9d | |||
71451de156 | |||
0e4a1d9ab8 | |||
e7b05732f8 | |||
37083ae354 | |||
020ef212c1 | |||
01cd66f4f5 | |||
af72b45593 | |||
9abb212e83 | |||
e81b0dca30 | |||
d37732c25d | |||
e6b6e28d6b | |||
5bace4ddc6 | |||
b80d7d1189 | |||
b2d8dee00a | |||
ac3dbdf5fc | |||
f143a24879 | |||
347af1dd99 | |||
d91245a9d3 | |||
bfdaac0a05 | |||
183d2965d0 | |||
86647810e0 | |||
56978d9793 | |||
6433e827f4 | |||
c0e78ba522 | |||
45d776a1f7 | |||
6e19e77e5e | |||
1e98d27ab4 | |||
a47d4032b8 | |||
2887d0a7ed | |||
a07f6855cb | |||
237a6ffb5f | |||
5e1cc12df4 | |||
29a350b4f8 | |||
6efcd61087 | |||
c7cafa720e | |||
9de9b397e2 | |||
35059cadf7 | |||
0317951e32 | |||
4edb8b89d1 | |||
a5278bdad2 | |||
3fd586c9bd | |||
717b2fe5f1 | |||
262c1cbc68 | |||
defdaa0e02 | |||
5ca6918943 | |||
cfd813883b | |||
c6d32a4c9f | |||
ae4e99026e | |||
42f493f41e | |||
34d73feb1d | |||
ebc94bbd44 | |||
4fcc7a5f3a | |||
41760ea333 | |||
13128c4c2f | |||
da168151e8 | |||
a132927052 | |||
b52d0726a2 | |||
fc411dc8bc | |||
22f02ac08c | |||
52883fb1b5 | |||
adfc2a6b69 | |||
c4aa13e798 | |||
90575e3f0d | |||
fcbe986ec7 | |||
061fec0d36 | |||
778d00d1a0 | |||
cc8d5a6c50 | |||
62c47f9cb5 | |||
bb31b0c66b | |||
359c963ad1 | |||
130b1ff4fb | |||
605a4b99ad | |||
b989f41b96 | |||
26168a605b | |||
2c37b02aa0 | |||
a2550870c2 | |||
279c6ead8f | |||
c9500cf796 | |||
0f63d8685f | |||
77204faa51 | |||
43fb8ff433 | |||
cd129d8b27 | |||
04aff0d3b2 | |||
be1dd4bd20 | |||
b3b89e7138 | |||
9045723084 | |||
34e22a2c8e | |||
1d784c60cb | |||
deffdbbb89 | |||
b29a19957c | |||
f209bc5323 | |||
32890fec20 | |||
9ddd7c967a | |||
245c3ad523 | |||
43decd9ff2 | |||
3af84aab5b | |||
3633863e90 | |||
69f19f1abd | |||
400146985a | |||
24bf875db6 | |||
9687b4df70 | |||
d8fba02b48 | |||
a6482c37ce | |||
83582bc935 | |||
4ac1e15bae | |||
a48686bde9 | |||
7168d16386 | |||
dc21f71ee2 | |||
7127a030b2 | |||
8f340bce67 | |||
48377e3c81 | |||
7eb26b3220 | |||
32ccb25575 | |||
9aafe18f01 | |||
4df9e03c4c | |||
136c75a5ac | |||
91b0f11cf2 | |||
50740f080b | |||
ad5308c0a3 | |||
f383709629 | |||
4d7fe07c16 | |||
456d5aa233 | |||
d50bfa4b06 | |||
7393887381 | |||
9da4c9d776 | |||
251b4a6395 | |||
d85725c636 | |||
6ef586a617 | |||
43991df2c4 | |||
231d5c20d5 | |||
9052cec391 | |||
33ed6927fc | |||
62aaeb267a | |||
a7bea3b166 | |||
b81d686561 | |||
e5e30f7bd3 | |||
4bfd845877 | |||
e8a80264ca | |||
75f4c7f681 | |||
2277a31661 | |||
805d9cac69 | |||
beffa8dfe4 | |||
a42c5f4f93 | |||
63c98d30d9 | |||
50c52e32c9 | |||
f48c95d113 | |||
2ead9fe4df | |||
4d52715d25 | |||
84d81deac0 | |||
83baac975d | |||
3f6fa5cccb | |||
a7fbd694cc | |||
78b11fca4c | |||
640c1d8dda | |||
dfa98cdfa3 | |||
5518f1ca26 | |||
c4a70f4852 | |||
363ea3eec8 | |||
97cbfe1c2b | |||
68f2198549 | |||
20bbe4187a | |||
2f69c8989c | |||
ed102dc730 | |||
c2bad8ab29 | |||
e9c1c91a4b | |||
eba116fedd | |||
94616a3429 | |||
c1fa22d068 | |||
4f1e4073e4 | |||
b320767c97 | |||
cd5bdf9e59 | |||
e8b8909ab7 | |||
ee8cdcf2b2 | |||
1cb21c6615 | |||
b5f52c5fea | |||
4e481c5088 | |||
7367c00c3d | |||
fb3aa8c96c | |||
983ce131f2 | |||
e571841b5c | |||
c537e9ff66 | |||
fc5454630a | |||
c2ca79da0d | |||
63f1d449ce | |||
0adaddccdf | |||
8a1f07dd6b | |||
8e5d2a0f4d | |||
4e1cae3869 | |||
7be26b665e | |||
664f1938c9 |
38
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: "\U0001FAB2 Bug Report"
|
||||
description: Submit a bug report
|
||||
labels: ["bug"]
|
||||
body:
|
||||
|
||||
- type: dropdown
|
||||
id: information-git-provider
|
||||
attributes:
|
||||
label: Git provider
|
||||
description: 'The problem arises when using:'
|
||||
options:
|
||||
- "Github Cloud"
|
||||
- "Github Enterprise"
|
||||
- "Gitlab"
|
||||
- "Bitbucket Cloud"
|
||||
- "Bitbucket Server"
|
||||
- "Azure"
|
||||
- "Other"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: Please share your system info with us.
|
||||
placeholder: model used, deployment type (action/app/cli/...), etc...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug-details
|
||||
attributes:
|
||||
label: Bug details
|
||||
description: Please describe the problem.
|
||||
placeholder: Describe the problem
|
||||
validations:
|
||||
required: true
|
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
blank_issues_enabled: false
|
||||
version: 0.1
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/qodo-ai/pr-agent/discussions
|
||||
about: GitHub Discussions
|
||||
|
||||
- name: Discord community
|
||||
url: https://discord.com/channels/1057273017547378788/1126104260430528613
|
||||
about: Join our discord community
|
21
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: "\U0001F4A1 Feature request"
|
||||
description: Submit a proposal/request for a new PR-Agent feature
|
||||
labels: ["feature"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature-request
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Feature request
|
||||
description: |
|
||||
Description of the feature proposal.
|
||||
|
||||
- type: textarea
|
||||
id: motivation
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: |
|
||||
Outline the motivation for the proposal.
|
36
.github/ISSUE_TEMPLATE/miscellaneous.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: "❔ General Issue"
|
||||
description: Submit a general issue
|
||||
labels: ["general"]
|
||||
body:
|
||||
|
||||
- type: dropdown
|
||||
id: information-git-provider
|
||||
attributes:
|
||||
label: Git provider (optional)
|
||||
description: 'Git Provider:'
|
||||
options:
|
||||
- "Github Cloud"
|
||||
- "Github Enterprise"
|
||||
- "Gitlab"
|
||||
- "Bitbucket Cloud"
|
||||
- "Bitbucket Server"
|
||||
- "Azure"
|
||||
- "Other"
|
||||
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info (optional)
|
||||
description: Please share your system info with us.
|
||||
placeholder: model used, deployment type (action/app/cli/...), etc...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: issues-details
|
||||
attributes:
|
||||
label: Issues details
|
||||
description: Please share the issues details.
|
||||
placeholder: Describe the issue
|
||||
validations:
|
||||
required: true
|
45
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at tal.r@qodo.ai . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
[Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at
|
||||
[contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/)
|
||||
|
38
CONTRIBUTING.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Contributing to PR-Agent
|
||||
|
||||
Thank you for your interest in contributing to the PR-Agent project!
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository and clone your fork
|
||||
2. Install Python 3.10 or higher
|
||||
3. Install dependencies (`requirements.txt` and `requirements-dev.txt`)
|
||||
4. Create a new branch for your contribution:
|
||||
- For new features: `git checkout -b feature/your-feature-name`
|
||||
- For bug fixes: `git checkout -b fix/issue-description`
|
||||
5. Make your changes
|
||||
6. Write or update tests as needed
|
||||
7. Run tests locally to ensure everything passes
|
||||
8. Commit your changes using conventional commit messages
|
||||
9. Push to your fork and submit a pull request
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
- Keep pull requests focused on a single feature or fix
|
||||
- Follow the existing code style and formatting conventions
|
||||
- Add unit tests for any new functionality using pytest
|
||||
- Ensure test coverage for your changes
|
||||
- Update documentation as needed
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure your PR includes a clear description of the changes
|
||||
2. Link any related issues
|
||||
3. Update the README.md if needed
|
||||
4. Wait for review from maintainers
|
||||
|
||||
## Questions or Need Help?
|
||||
|
||||
- Join our [Discord community](https://discord.com/channels/1057273017547378788/1126104260430528613) for questions and discussions
|
||||
- Check the [documentation](https://qodo-merge-docs.qodo.ai/) for detailed information
|
||||
- Report bugs or request features through [GitHub Issues](https://github.com/qodo-ai/pr-agent/issues)
|
@ -3,7 +3,7 @@ FROM python:3.12 as base
|
||||
WORKDIR /app
|
||||
ADD pyproject.toml .
|
||||
ADD requirements.txt .
|
||||
RUN pip install . && rm pyproject.toml requirements.txt
|
||||
RUN pip install --no-cache-dir . && rm pyproject.toml requirements.txt
|
||||
ENV PYTHONPATH=/app
|
||||
ADD docs docs
|
||||
ADD pr_agent pr_agent
|
||||
|
208
README.md
@ -4,16 +4,22 @@
|
||||
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://codium.ai/images/pr_agent/logo-dark.png" width="330">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://codium.ai/images/pr_agent/logo-light.png" width="330">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://www.qodo.ai/wp-content/uploads/2025/02/PR-Agent-Purple-2.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://www.qodo.ai/wp-content/uploads/2025/02/PR-Agent-Purple-2.png">
|
||||
<img src="https://codium.ai/images/pr_agent/logo-light.png" alt="logo" width="330">
|
||||
|
||||
</picture>
|
||||
<br/>
|
||||
Qode Merge PR-Agent aims to help efficiently review and handle pull requests, by providing AI feedback and suggestions
|
||||
|
||||
[Installation Guide](https://qodo-merge-docs.qodo.ai/installation/) |
|
||||
[Usage Guide](https://qodo-merge-docs.qodo.ai/usage-guide/) |
|
||||
[Tools Guide](https://qodo-merge-docs.qodo.ai/tools/) |
|
||||
[Qodo Merge](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) 💎
|
||||
|
||||
PR-Agent aims to help efficiently review and handle pull requests, by providing AI feedback and suggestions
|
||||
</div>
|
||||
|
||||
[](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl)
|
||||
[](https://chromewebstore.google.com/detail/qodo-merge-ai-powered-cod/ephlnjeghhogofkifjloamocljapahnl)
|
||||
[](https://github.com/apps/qodo-merge-pro/)
|
||||
[](https://github.com/apps/qodo-merge-pro-for-open-source/)
|
||||
[](https://discord.com/channels/1057273017547378788/1126104260430528613)
|
||||
@ -22,72 +28,66 @@ Qode Merge PR-Agent aims to help efficiently review and handle pull requests, by
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### [Documentation](https://pr-agent-docs.codium.ai/)
|
||||
- See the [Installation Guide](https://qodo-merge-docs.qodo.ai/installation/) for instructions on installing Qode Merge PR-Agent on different platforms.
|
||||
[//]: # (### [Documentation](https://qodo-merge-docs.qodo.ai/))
|
||||
|
||||
- See the [Usage Guide](https://qodo-merge-docs.qodo.ai/usage-guide/) for instructions on running Qode Merge PR-Agent tools via different interfaces, such as CLI, PR Comments, or by automatically triggering them when a new PR is opened.
|
||||
[//]: # ()
|
||||
[//]: # (- See the [Installation Guide](https://qodo-merge-docs.qodo.ai/installation/) for instructions on installing PR-Agent on different platforms.)
|
||||
|
||||
- See the [Tools Guide](https://qodo-merge-docs.qodo.ai/tools/) for a detailed description of the different tools, and the available configurations for each tool.
|
||||
[//]: # ()
|
||||
[//]: # (- See the [Usage Guide](https://qodo-merge-docs.qodo.ai/usage-guide/) for instructions on running PR-Agent tools via different interfaces, such as CLI, PR Comments, or by automatically triggering them when a new PR is opened.)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (- See the [Tools Guide](https://qodo-merge-docs.qodo.ai/tools/) for a detailed description of the different tools, and the available configurations for each tool.)
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [News and Updates](#news-and-updates)
|
||||
- [Overview](#overview)
|
||||
- [Example results](#example-results)
|
||||
- [Try it now](#try-it-now)
|
||||
- [PR-Agent Pro 💎](https://pr-agent-docs.codium.ai/overview/pr_agent_pro/)
|
||||
- [Qodo Merge](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/)
|
||||
- [How it works](#how-it-works)
|
||||
- [Why use PR-Agent?](#why-use-pr-agent)
|
||||
|
||||
## News and Updates
|
||||
|
||||
### Feb 28, 2025
|
||||
A new version, v0.27, was released. See release notes [here](https://github.com/qodo-ai/pr-agent/releases/tag/v0.27).
|
||||
|
||||
### Feb 27, 2025
|
||||
- Updated the default model to `o3-mini` for all tools. You can still use the `gpt-4o` as the default model by setting the `model` parameter in the configuration file.
|
||||
- Important updates and bug fixes for Azure DevOps, see [here](https://github.com/qodo-ai/pr-agent/pull/1583)
|
||||
- Added support for adjusting the [response language](https://qodo-merge-docs.qodo.ai/usage-guide/additional_configurations/#language-settings) of the PR-Agent tools.
|
||||
|
||||
### Feb 6, 2025
|
||||
New design for the `/improve` tool:
|
||||
|
||||
<kbd><img src="https://github.com/user-attachments/assets/26506430-550e-469a-adaa-af0a09b70c6d" width="512"></kbd>
|
||||
|
||||
### Jan 25, 2025
|
||||
|
||||
The open-source GitHub organization was updated:
|
||||
`https://github.com/codium-ai/pr-agent` →
|
||||
`https://github.com/qodo-ai/pr-agent`
|
||||
|
||||
The docker should be redirected automatically to the new location.
|
||||
However, if you have any issues, please update the GitHub action docker image from
|
||||
`uses: Codium-ai/pr-agent@main`
|
||||
to
|
||||
`uses: qodo-ai/pr-agent@main`
|
||||
|
||||
|
||||
### Jan 2, 2025
|
||||
|
||||
New tool [/Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) (💎), which converts human code review discussions and feedback into ready-to-commit code changes.
|
||||
|
||||
<kbd><img src="https://codium.ai/images/pr_agent/implement1.png" width="512"></kbd>
|
||||
<kbd><img src="https://www.qodo.ai/images/pr_agent/implement1.png?v=2" width="512"></kbd>
|
||||
|
||||
|
||||
### Jan 1, 2025
|
||||
|
||||
Update logic and [documentation](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#ollama) for running local models via Ollama.
|
||||
|
||||
### December 30, 2024
|
||||
|
||||
Following [feedback](https://research.kudelskisecurity.com/2024/08/29/careful-where-you-code-multiple-vulnerabilities-in-ai-powered-pr-agent/) from the community, we have addressed two vulnerabilities identified in the open-source PR-Agent project. The fixes are now included in the newly released version (v0.26), available as of today.
|
||||
|
||||
### December 25, 2024
|
||||
|
||||
The `review` tool previously included a legacy feature for providing code suggestions (controlled by '--pr_reviewer.num_code_suggestion'). This functionality has been deprecated. Use instead the [`improve`](https://qodo-merge-docs.qodo.ai/tools/improve/) tool, which offers higher quality and more actionable code suggestions.
|
||||
|
||||
### December 2, 2024
|
||||
|
||||
Open-source repositories can now freely use Qodo Merge Pro, and enjoy easy one-click installation using a marketplace [app](https://github.com/apps/qodo-merge-pro-for-open-source).
|
||||
|
||||
<kbd><img src="https://github.com/user-attachments/assets/b0838724-87b9-43b0-ab62-73739a3a855c" width="512"></kbd>
|
||||
|
||||
See [here](https://qodo-merge-docs.qodo.ai/installation/pr_agent_pro/) for more details about installing Qodo Merge Pro for private repositories.
|
||||
|
||||
|
||||
### November 18, 2024
|
||||
|
||||
A new mode was enabled by default for code suggestions - `--pr_code_suggestions.focus_only_on_problems=true`:
|
||||
|
||||
- This option reduces the number of code suggestions received
|
||||
- The suggestions will focus more on identifying and fixing code problems, rather than style considerations like best practices, maintainability, or readability.
|
||||
- The suggestions will be categorized into just two groups: "Possible Issues" and "General".
|
||||
|
||||
Still, if you prefer the previous mode, you can set `--pr_code_suggestions.focus_only_on_problems=false` in the [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/).
|
||||
|
||||
**Example results:**
|
||||
|
||||
Original mode
|
||||
|
||||
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_original_mode.png" width="512"></kbd>
|
||||
|
||||
Focused mode
|
||||
|
||||
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_focused_mode.png" width="512"></kbd>
|
||||
Following feedback from the community, we have addressed two vulnerabilities identified in the open-source PR-Agent project. The [fixes](https://github.com/qodo-ai/pr-agent/pull/1425) are now included in the newly released version (v0.26), available as of today.
|
||||
|
||||
|
||||
## Overview
|
||||
@ -96,25 +96,27 @@ Focused mode
|
||||
Supported commands per platform:
|
||||
|
||||
| | | GitHub | GitLab | Bitbucket | Azure DevOps |
|
||||
|-------|---------------------------------------------------------------------------------------------------------|:--------------------:|:--------------------:|:--------------------:|:------------:|
|
||||
|-------|---------------------------------------------------------------------------------------------------------|:--------------------:|:--------------------:|:---------:|:------------:|
|
||||
| TOOLS | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Ask on code lines](https://pr-agent-docs.codium.ai/tools/ask#ask-lines) | ✅ | ✅ | | |
|
||||
| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | |
|
||||
| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | |
|
||||
| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | |
|
||||
| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | |
|
||||
| | [CI Feedback](https://pr-agent-docs.codium.ai/tools/ci_feedback/) 💎 | ✅ | | | |
|
||||
| | [PR Documentation](https://pr-agent-docs.codium.ai/tools/documentation/) 💎 | ✅ | ✅ | | |
|
||||
| | [Custom Labels](https://pr-agent-docs.codium.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | |
|
||||
| | [Analyze](https://pr-agent-docs.codium.ai/tools/analyze/) 💎 | ✅ | ✅ | | |
|
||||
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | |
|
||||
| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | |
|
||||
| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | |
|
||||
| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | |
|
||||
| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||
| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||
| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | | | | | |
|
||||
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
||||
@ -127,42 +129,42 @@ Supported commands per platform:
|
||||
| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Global and wiki configurations](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR interactive actions](https://www.codium.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | |
|
||||
| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | |
|
||||
| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | |
|
||||
| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | |
|
||||
- 💎 means this feature is available only in [PR-Agent Pro](https://www.codium.ai/pricing/)
|
||||
- 💎 means this feature is available only in [Qodo-Merge](https://www.qodo.ai/pricing/)
|
||||
|
||||
[//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md))
|
||||
___
|
||||
|
||||
‣ **Auto Description ([`/describe`](https://pr-agent-docs.codium.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
|
||||
‣ **Auto Description ([`/describe`](https://qodo-merge-docs.qodo.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
|
||||
\
|
||||
‣ **Auto Review ([`/review`](https://pr-agent-docs.codium.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more.
|
||||
‣ **Auto Review ([`/review`](https://qodo-merge-docs.qodo.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more.
|
||||
\
|
||||
‣ **Code Suggestions ([`/improve`](https://pr-agent-docs.codium.ai/tools/improve/))**: Code suggestions for improving the PR.
|
||||
‣ **Code Suggestions ([`/improve`](https://qodo-merge-docs.qodo.ai/tools/improve/))**: Code suggestions for improving the PR.
|
||||
\
|
||||
‣ **Question Answering ([`/ask ...`](https://pr-agent-docs.codium.ai/tools/ask/))**: Answering free-text questions about the PR.
|
||||
‣ **Question Answering ([`/ask ...`](https://qodo-merge-docs.qodo.ai/tools/ask/))**: Answering free-text questions about the PR.
|
||||
\
|
||||
‣ **Update Changelog ([`/update_changelog`](https://pr-agent-docs.codium.ai/tools/update_changelog/))**: Automatically updating the CHANGELOG.md file with the PR changes.
|
||||
‣ **Update Changelog ([`/update_changelog`](https://qodo-merge-docs.qodo.ai/tools/update_changelog/))**: Automatically updating the CHANGELOG.md file with the PR changes.
|
||||
\
|
||||
‣ **Find Similar Issue ([`/similar_issue`](https://pr-agent-docs.codium.ai/tools/similar_issues/))**: Automatically retrieves and presents similar issues.
|
||||
‣ **Help Docs ([`/help_docs`](https://qodo-merge-docs.qodo.ai/tools/help_docs/))**: Answers a question on any repository by utilizing given documentation.
|
||||
\
|
||||
‣ **Add Documentation 💎 ([`/add_docs`](https://pr-agent-docs.codium.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR.
|
||||
‣ **Add Documentation 💎 ([`/add_docs`](https://qodo-merge-docs.qodo.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR.
|
||||
\
|
||||
‣ **Generate Custom Labels 💎 ([`/generate_labels`](https://pr-agent-docs.codium.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user.
|
||||
‣ **Generate Custom Labels 💎 ([`/generate_labels`](https://qodo-merge-docs.qodo.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user.
|
||||
\
|
||||
‣ **Analyze 💎 ([`/analyze`](https://pr-agent-docs.codium.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component.
|
||||
‣ **Analyze 💎 ([`/analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component.
|
||||
\
|
||||
‣ **Test 💎 ([`/test`](https://pr-agent-docs.codium.ai/tools/test/))**: Generate tests for a selected component, based on the PR code changes.
|
||||
‣ **Test 💎 ([`/test`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generate tests for a selected component, based on the PR code changes.
|
||||
\
|
||||
‣ **Custom Prompt 💎 ([`/custom_prompt`](https://pr-agent-docs.codium.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
|
||||
‣ **Custom Prompt 💎 ([`/custom_prompt`](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
|
||||
\
|
||||
‣ **Generate Tests 💎 ([`/test component_name`](https://pr-agent-docs.codium.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes.
|
||||
‣ **Generate Tests 💎 ([`/test component_name`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes.
|
||||
\
|
||||
‣ **CI Feedback 💎 ([`/checks ci_job`](https://pr-agent-docs.codium.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job.
|
||||
‣ **CI Feedback 💎 ([`/checks ci_job`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job.
|
||||
\
|
||||
‣ **Similar Code 💎 ([`/find_similar_component`](https://pr-agent-docs.codium.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code.
|
||||
‣ **Similar Code 💎 ([`/find_similar_component`](https://qodo-merge-docs.qodo.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code.
|
||||
\
|
||||
‣ **Implement 💎 ([`/implement`](https://qodo-merge-docs.qodo.ai/tools/implement/))**: Generates implementation code from review suggestions.
|
||||
___
|
||||
@ -206,7 +208,7 @@ ___
|
||||
|
||||
## 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.
|
||||
Try the Claude Sonnet 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.
|
||||
For example, add a comment to any pull request with the following text:
|
||||
```
|
||||
@CodiumAI-Agent /review
|
||||
@ -217,22 +219,16 @@ Note that this is a promotional bot, suitable only for initial experimentation.
|
||||
It does not have 'edit' access to your repo, for example, so it cannot update the PR description or add labels (`@CodiumAI-Agent /describe` will publish PR description as a comment). In addition, the bot cannot be used on private repositories, as it does not have access to the files there.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
To set up your own PR-Agent, see the [Installation](https://pr-agent-docs.codium.ai/installation/) section below.
|
||||
Note that when you set your own PR-Agent or use CodiumAI hosted PR-Agent, there is no need to mention `@CodiumAI-Agent ...`. Instead, directly start with the command, e.g., `/ask ...`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 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\GitLab\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.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for a list of features available in PR-Agent Pro.
|
||||
## Qodo Merge 💎
|
||||
[Qodo Merge](https://www.qodo.ai/pricing/) is a hosted version of PR-Agent, provided by Qodo. 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 Qodo Merge app to your GitHub\GitLab\BitBucket repo.
|
||||
2. **Improved privacy** - No data will be stored or used to train models. Qodo Merge will employ zero data retention, and will use an OpenAI account with zero data retention.
|
||||
3. **Improved support** - Qodo Merge 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, Qodo Merge will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for a list of features available in Qodo Merge.
|
||||
|
||||
|
||||
|
||||
@ -240,9 +236,9 @@ See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for a list of
|
||||
|
||||
The following diagram illustrates PR-Agent tools and their flow:
|
||||
|
||||

|
||||

|
||||
|
||||
Check out the [PR Compression strategy](https://pr-agent-docs.codium.ai/core-abilities/#pr-compression-strategy) page for more details on how we convert a code diff to a manageable LLM prompt
|
||||
Check out the [PR Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/#pr-compression-strategy) page for more details on how we convert a code diff to a manageable LLM prompt
|
||||
|
||||
## Why use PR-Agent?
|
||||
|
||||
@ -250,10 +246,10 @@ A reasonable question that can be asked is: `"Why use PR-Agent? What makes it st
|
||||
|
||||
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](https://pr-agent-docs.codium.ai/core-abilities/#pr-compression-strategy) is a core ability that enables to effectively tackle both short and long PRs.
|
||||
- We emphasize **real-life practical usage**. Each tool (review, improve, ask, ...) has a single LLM 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](https://qodo-merge-docs.qodo.ai/core-abilities/#pr-compression-strategy) 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), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2).
|
||||
- We support **multiple git providers** (GitHub, Gitlab, Bitbucket), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT, Claude, Deepseek, ...)
|
||||
|
||||
|
||||
## Data privacy
|
||||
@ -263,24 +259,22 @@ Here are some advantages of PR-Agent:
|
||||
- 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
|
||||
|
||||
### CodiumAI-hosted PR-Agent Pro 💎
|
||||
### Qodo-hosted Qodo Merge 💎
|
||||
|
||||
- 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.
|
||||
- When using Qodo Merge 💎, hosted by Qodo, 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.
|
||||
|
||||
- For certain clients, CodiumAI-hosted PR-Agent Pro will use CodiumAI’s proprietary models — if this is the case, you will be notified.
|
||||
- For certain clients, Qodo-hosted Qodo Merge will use Qodo’s proprietary models — if this is the case, you will be notified.
|
||||
|
||||
- No passive collection of Code and Pull Requests’ data — PR-Agent will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
- No passive collection of Code and Pull Requests’ data — Qodo Merge will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
|
||||
### PR-Agent Chrome extension
|
||||
### Qodo Merge Chrome extension
|
||||
|
||||
- The [PR-Agent Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl) serves solely to modify the visual appearance of a GitHub PR screen. It does not transmit any user's repo or pull request code. Code is only sent for processing when a user submits a GitHub comment that activates a PR-Agent tool, in accordance with the standard privacy policy of PR-Agent.
|
||||
- The [Qodo Merge Chrome extension](https://chromewebstore.google.com/detail/qodo-merge-ai-powered-cod/ephlnjeghhogofkifjloamocljapahnl) serves solely to modify the visual appearance of a GitHub PR screen. It does not transmit any user's repo or pull request code. Code is only sent for processing when a user submits a GitHub comment that activates a PR-Agent tool, in accordance with the standard privacy policy of Qodo-Merge.
|
||||
|
||||
## Links
|
||||
|
||||
[](https://discord.gg/kG35uSHDBc)
|
||||
|
||||
- Discord community: https://discord.gg/kG35uSHDBc
|
||||
- CodiumAI site: https://codium.ai
|
||||
- Blog: https://www.codium.ai/blog/
|
||||
- Troubleshooting: https://www.codium.ai/blog/technical-faq-and-troubleshooting/
|
||||
- Support: support@codium.ai
|
||||
- Qodo site: https://www.qodo.ai/
|
||||
- Blog: https://www.qodo.ai/blog/
|
||||
- Troubleshooting: https://www.qodo.ai/blog/technical-faq-and-troubleshooting/
|
||||
- Support: support@qodo.ai
|
||||
|
62
SECURITY.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Security Policy
|
||||
|
||||
PR-Agent is an open-source tool to help efficiently review and handle pull requests. Qodo Merge is a paid version of PR-Agent, designed for companies and teams that require additional features and capabilities.
|
||||
|
||||
This document describes the security policy of PR-Agent. For Qodo Merge's security policy, see [here](https://qodo-merge-docs.qodo.ai/overview/data_privacy/#qodo-merge).
|
||||
|
||||
## PR-Agent Self-Hosted Solutions
|
||||
|
||||
When using PR-Agent with your OpenAI (or other LLM provider) API key, the security relationship is directly between you and the provider. We do not send your code to Qodo servers.
|
||||
|
||||
Types of [self-hosted solutions](https://qodo-merge-docs.qodo.ai/installation):
|
||||
- Locally
|
||||
- GitHub integration
|
||||
- GitLab integration
|
||||
- BitBucket integration
|
||||
- Azure DevOps integration
|
||||
|
||||
|
||||
## PR-Agent Supported Versions
|
||||
|
||||
This section outlines which versions of PR-Agent are currently supported with security updates.
|
||||
|
||||
### Docker Deployment Options
|
||||
|
||||
#### Latest Version
|
||||
|
||||
For the most recent updates, use our latest Docker image which is automatically built nightly:
|
||||
```yaml
|
||||
uses: qodo-ai/pr-agent@main
|
||||
```
|
||||
|
||||
#### Specific Release Version
|
||||
|
||||
For a fixed version, you can pin your action to a specific release version. Browse available releases at:
|
||||
[PR-Agent Releases](https://github.com/qodo-ai/pr-agent/releases)
|
||||
|
||||
For example, to github action:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: docker://codiumai/pr-agent:0.26-github_action
|
||||
```
|
||||
|
||||
#### Enhanced Security with Docker Digest
|
||||
|
||||
For maximum security, you can specify the Docker image using its digest:
|
||||
```yaml
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: docker://codiumai/pr-agent@sha256:14165e525678ace7d9b51cda8652c2d74abb4e1d76b57c4a6ccaeba84663cc64
|
||||
```
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of PR-Agent seriously. If you discover a security vulnerability, please report it immediately to:
|
||||
|
||||
Email: tal.r@qodo.ai
|
||||
|
||||
Please include a description of the vulnerability, steps to reproduce, and the affected PR-Agent version.
|
@ -4,7 +4,7 @@ WORKDIR /app
|
||||
ADD pyproject.toml .
|
||||
ADD requirements.txt .
|
||||
ADD docs docs
|
||||
RUN pip install . && rm pyproject.toml requirements.txt
|
||||
RUN pip install --no-cache-dir . && rm pyproject.toml requirements.txt
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
FROM base AS github_app
|
||||
@ -33,7 +33,7 @@ CMD ["python", "pr_agent/servers/azuredevops_server_webhook.py"]
|
||||
|
||||
FROM base AS test
|
||||
ADD requirements-dev.txt .
|
||||
RUN pip install -r requirements-dev.txt && rm requirements-dev.txt
|
||||
RUN pip install --no-cache-dir -r requirements-dev.txt && rm requirements-dev.txt
|
||||
ADD pr_agent pr_agent
|
||||
ADD tests tests
|
||||
|
||||
|
@ -5,8 +5,8 @@ RUN yum update -y && \
|
||||
yum clean all
|
||||
|
||||
ADD pyproject.toml requirements.txt .
|
||||
RUN pip install . && rm pyproject.toml
|
||||
RUN pip install mangum==0.17.0
|
||||
RUN pip install --no-cache-dir . && rm pyproject.toml
|
||||
RUN pip install --no-cache-dir mangum==0.17.0
|
||||
COPY pr_agent/ ${LAMBDA_TASK_ROOT}/pr_agent/
|
||||
|
||||
CMD ["pr_agent.servers.serverless.serverless"]
|
||||
|
315
docs/docs/ai_search/index.md
Normal file
@ -0,0 +1,315 @@
|
||||
<div class="search-section">
|
||||
<h1>AI Docs Search</h1>
|
||||
<p class="search-description">
|
||||
Search through our documentation using AI-powered natural language queries.
|
||||
</p>
|
||||
<div class="search-container">
|
||||
<input
|
||||
type="text"
|
||||
id="searchInput"
|
||||
class="search-input"
|
||||
placeholder="Enter your search term..."
|
||||
>
|
||||
<button id="searchButton" class="search-button">Search</button>
|
||||
</div>
|
||||
<div id="spinner" class="spinner-container" style="display: none;">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<div id="results" class="results-container"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
Untitled
|
||||
.search-section {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #666;
|
||||
font-size: 2.125rem;
|
||||
font-weight: normal;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.search-description {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
max-width: 800px;
|
||||
margin: 0; /* Changed from auto to 0 to align left */
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 0 0.875rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9375rem;
|
||||
outline: none;
|
||||
height: 40px; /* Explicit height */
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: #6c63ff;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 0 1.25rem;
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
transition: background-color 0.2s;
|
||||
height: 40px; /* Match the height of search input */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-button:hover {
|
||||
background-color: #1976D2;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #2196F3;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.results-container {
|
||||
margin-top: 2rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
padding: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1.2rem;
|
||||
color: #2196F3;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.result-description {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
padding: 1rem;
|
||||
border: 1px solid #dc3545;
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.markdown-content {
|
||||
line-height: 1.6;
|
||||
color: var(--md-typeset-color);
|
||||
background: var(--md-default-bg-color);
|
||||
border: 1px solid var(--md-default-fg-color--lightest);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
position: relative;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.markdown-content::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 24px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--md-default-bg-color);
|
||||
border-left: 1px solid var(--md-default-fg-color--lightest);
|
||||
border-top: 1px solid var(--md-default-fg-color--lightest);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.markdown-content > *:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.markdown-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-content code {
|
||||
background: var(--md-code-bg-color);
|
||||
color: var(--md-code-fg-color);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
background: var(--md-code-bg-color);
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.markdown-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .markdown-content {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
function displayResults(responseText) {
|
||||
const resultsContainer = document.getElementById('results');
|
||||
const spinner = document.getElementById('spinner');
|
||||
const searchContainer = document.querySelector('.search-container');
|
||||
|
||||
// Hide spinner
|
||||
spinner.style.display = 'none';
|
||||
|
||||
// Scroll to search bar
|
||||
searchContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
try {
|
||||
const results = JSON.parse(responseText);
|
||||
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
headerIds: false,
|
||||
sanitize: false
|
||||
});
|
||||
|
||||
const htmlContent = marked.parse(results.message);
|
||||
|
||||
resultsContainer.className = 'markdown-content';
|
||||
resultsContainer.innerHTML = htmlContent;
|
||||
|
||||
// Scroll after content is rendered
|
||||
setTimeout(() => {
|
||||
const searchContainer = document.querySelector('.search-container');
|
||||
const offset = 55; // Offset from top in pixels
|
||||
const elementPosition = searchContainer.getBoundingClientRect().top;
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error('Error parsing results:', error);
|
||||
resultsContainer.innerHTML = '<div class="error-message">Error processing results</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function performSearch() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const resultsContainer = document.getElementById('results');
|
||||
const spinner = document.getElementById('spinner');
|
||||
const searchTerm = searchInput.value.trim();
|
||||
|
||||
if (!searchTerm) {
|
||||
resultsContainer.innerHTML = '<div class="error-message">Please enter a search term</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Show spinner, clear results
|
||||
spinner.style.display = 'flex';
|
||||
resultsContainer.innerHTML = '';
|
||||
|
||||
try {
|
||||
const data = {
|
||||
"query": searchTerm
|
||||
};
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'accept': 'text/plain',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
|
||||
// const API_ENDPOINT = 'http://0.0.0.0:3000/api/v1/docs_help';
|
||||
const API_ENDPOINT = 'https://help.merge.qodo.ai/api/v1/docs_help';
|
||||
|
||||
const response = await fetch(API_ENDPOINT, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const responseText = await response.text();
|
||||
displayResults(responseText);
|
||||
} catch (error) {
|
||||
spinner.style.display = 'none';
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="error-message">
|
||||
An error occurred while searching. Please try again later.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listeners
|
||||
const searchButton = document.getElementById('searchButton');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
|
||||
if (searchButton) {
|
||||
searchButton.addEventListener('click', performSearch);
|
||||
}
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 263 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 17 KiB |
@ -1,4 +1,4 @@
|
||||
We take your code's security and privacy seriously:
|
||||
|
||||
- The Chrome extension will not send your code to any external servers.
|
||||
- For private repositories, we will first validate the user's identity and permissions. After authentication, we generate responses using the existing Qodo Merge Pro integration.
|
||||
- For private repositories, we will first validate the user's identity and permissions. After authentication, we generate responses using the existing Qodo Merge integration.
|
||||
|
@ -8,7 +8,7 @@ To enable private chat, simply install the Qodo Merge Chrome extension. After in
|
||||
This chat session is **private**, and won't be visible to other users.
|
||||
|
||||
All open-source repositories are supported.
|
||||
For private repositories, you will also need to install Qodo Merge Pro, After installation, make sure to open at least one new PR to fully register your organization. Once done, you can chat with both new and existing PRs across all installed repositories.
|
||||
For private repositories, you will also need to install Qodo Merge. After installation, make sure to open at least one new PR to fully register your organization. Once done, you can chat with both new and existing PRs across all installed repositories.
|
||||
|
||||
#### Context-aware PR chat
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
With a single-click installation you will gain access to a context-aware chat on your pull requests code, a toolbar extension with multiple AI feedbacks, Qodo Merge filters, and additional abilities.
|
||||
|
||||
The extension is powered by top code models like Claude 3.5 Sonnet and GPT4. All the extension's features are free to use on public repositories.
|
||||
The extension is powered by top code models like Claude 3.7 Sonnet and o3-mini. All the extension's features are free to use on public repositories.
|
||||
|
||||
For private repositories, you will need to install [Qodo Merge Pro](https://github.com/apps/qodo-merge-pro){:target="_blank"} in addition to the extension (Quick GitHub app setup with a 14-day free trial. No credit card needed).
|
||||
For a demonstration of how to install Qodo Merge Pro and use it with the Chrome extension, please refer to the tutorial video at the provided [link](https://codium.ai/images/pr_agent/private_repos.mp4){:target="_blank"}.
|
||||
For private repositories, you will need to install [Qodo Merge](https://github.com/apps/qodo-merge-pro){:target="_blank"} in addition to the extension (Quick GitHub app setup with a 14-day free trial. No credit card needed).
|
||||
For a demonstration of how to install Qodo Merge and use it with the Chrome extension, please refer to the tutorial video at the provided [link](https://codium.ai/images/pr_agent/private_repos.mp4){:target="_blank"}.
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/PR-AgentChat.gif" width="768">
|
||||
|
||||
|
69
docs/docs/core-abilities/auto_best_practices.md
Normal file
@ -0,0 +1,69 @@
|
||||
# Auto Best Practices 💎
|
||||
`Supported Git Platforms: GitHub`
|
||||
|
||||
## Overview
|
||||
|
||||
{width=684}
|
||||
|
||||
> Note - enabling a [Wiki](https://qodo-merge-docs.qodo.ai/usage-guide/enabling_a_wiki/) is required for this feature.
|
||||
|
||||
### Finding Code Problems - Exploration Phase
|
||||
|
||||
The `improve` tool identifies potential issues, problems and bugs in Pull Request (PR) code changes.
|
||||
Rather than focusing on minor issues like code style or formatting, the tool intelligently analyzes code to detect meaningful problems.
|
||||
|
||||
The analysis intentionally takes a flexible, _exploratory_ approach to identify meaningful potential issues, allowing the tool to surface relevant code suggestions without being constrained by predefined categories.
|
||||
|
||||
### Tracking Implemented Suggestions
|
||||
|
||||
Qodo Merge features a novel [tracking system](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) that automatically detects when PR authors implement AI-generated code suggestions.
|
||||
All accepted suggestions are aggregated in a repository-specific wiki page called [`.pr_agent_accepted_suggestions`](https://github.com/qodo-ai/pr-agent/wiki/.pr_agent_accepted_suggestions)
|
||||
|
||||
|
||||
### Learning and Applying Auto Best Practices
|
||||
|
||||
Monthly, Qodo Merge analyzes the collection of accepted suggestions to generate repository-specific best practices, stored in [`.pr_agent_auto_best_practices`](https://github.com/qodo-ai/pr-agent/wiki/.pr_agent_auto_best_practices) wiki file.
|
||||
These best practices reflect recurring patterns in accepted code improvements.
|
||||
|
||||
The `improve` tool will incorporate these best practices as an additional analysis layer, checking PR code changes against known patterns of previously accepted improvements.
|
||||
This creates a two-phase analysis:
|
||||
|
||||
1. Open exploration for general code issues
|
||||
2. Targeted checking against established best practices - exploiting the knowledge gained from past suggestions
|
||||
|
||||
By keeping these phases decoupled, the tool remains free to discover new or unseen issues and problems, while also learning from past experiences.
|
||||
|
||||
|
||||
When presenting the suggestions generated by the `improve` tool, Qodo Merge will add a dedicated label for each suggestion generated from the auto best practices - 'Learned best practice':
|
||||
|
||||
{width=684}
|
||||
|
||||
|
||||
## Auto Best Practices vs Custom Best Practices
|
||||
|
||||
Teams and companies can also manually define their own [custom best practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) in Qodo Merge.
|
||||
|
||||
When custom best practices exist, Qodo Merge will still generate an 'auto best practices' wiki file, though it won't be used by the `improve` tool.
|
||||
However, this auto-generated file can still serve two valuable purposes:
|
||||
|
||||
1. It can help enhance your custom best practices with additional insights derived from suggestions your team found valuable enough to implement
|
||||
2. It demonstrates effective patterns for writing AI-friendly best practices
|
||||
|
||||
Even when using custom best practices, we recommend regularly reviewing the auto best practices file to refine your custom rules.
|
||||
|
||||
## Relevant configurations
|
||||
|
||||
```toml
|
||||
[auto_best_practices]
|
||||
# Disable all auto best practices usage or generation
|
||||
enable_auto_best_practices = true
|
||||
|
||||
# Disable usage of auto best practices file in the 'improve' tool
|
||||
utilize_auto_best_practices = true
|
||||
|
||||
# Extra instructions to the auto best practices generation prompt
|
||||
extra_instructions = ""
|
||||
|
||||
# Max number of patterns to be detected
|
||||
max_patterns = 5
|
||||
```
|
@ -1,2 +0,0 @@
|
||||
## Overview
|
||||
TBD
|
57
docs/docs/core-abilities/company_codebase.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Company Codebase 💎
|
||||
`Supported Git Platforms: GitHub`
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
### What is Company Codebase?
|
||||
|
||||
An organized, semantic database that aggregates all your company’s source code into one searchable repository, enabling efficient code discovery and analysis.
|
||||
|
||||
### How does Company Codebase work?
|
||||
|
||||
By indexing your company's code and using Retrieval-Augmented Generation (RAG), it retrieves contextual code segments on demand, improving pull request (PR) insights and accelerating review accuracy.
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
!!! info "Prerequisites"
|
||||
- Database setup and codebase indexing must be completed before proceeding. [Contact support](https://www.qodo.ai/contact/) for assistance.
|
||||
|
||||
### Configuration options
|
||||
|
||||
In order to enable the RAG feature, add the following lines to your configuration file:
|
||||
``` toml
|
||||
[rag_arguments]
|
||||
enable_rag=true
|
||||
```
|
||||
|
||||
!!! example "RAG Arguments Options"
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>enable_rag</b></td>
|
||||
<td>If set to true, codebase enrichment using RAG will be enabled. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>rag_repo_list</b></td>
|
||||
<td>A list of repositories that will be used by the semantic search for RAG. Use `['all']` to consider the entire codebase or a select list or repositories, for example: ['my-org/my-repo', ...]. Default: the repository from which the PR was opened.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
References from the codebase will be shown in a collapsible bookmark, allowing you to easily access relevant code snippets:
|
||||
|
||||
{width=640}
|
||||
|
||||
## Limitations
|
||||
|
||||
### Querying the codebase presents significant challenges:
|
||||
- **Search Method**: RAG uses natural language queries to find semantically relevant code sections
|
||||
- **Result Quality**: No guarantee that RAG results will be useful for all queries
|
||||
- **Scope Recommendation**: To reduce noise, avoid using the whole codebase; focus on PR repository instead
|
||||
|
||||
### This feature has several requirements and restrictions:
|
||||
- **Codebase**: Must be properly indexed for search functionality
|
||||
- **Security**: Requires secure and private indexed codebase implementation
|
||||
- **Deployment**: Only available for Qodo Merge Enterprise plan using single tenant or on-premises setup
|
@ -2,14 +2,15 @@
|
||||
`Supported Git Platforms: GitHub, GitLab, Bitbucket`
|
||||
|
||||
## Overview
|
||||
Qodo Merge PR Agent streamlines code review workflows by seamlessly connecting with multiple ticket management systems.
|
||||
Qodo Merge streamlines code review workflows by seamlessly connecting with multiple ticket management systems.
|
||||
This integration enriches the review process by automatically surfacing relevant ticket information and context alongside code changes.
|
||||
|
||||
## Ticket systems supported
|
||||
**Ticket systems supported**:
|
||||
|
||||
- GitHub
|
||||
- Jira (💎)
|
||||
|
||||
Ticket data fetched:
|
||||
**Ticket data fetched:**
|
||||
|
||||
1. Ticket Title
|
||||
2. Ticket Description
|
||||
@ -26,7 +27,7 @@ Ticket Recognition Requirements:
|
||||
- For Jira tickets, you should follow the instructions in [Jira Integration](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#jira-integration) in order to authenticate with Jira.
|
||||
|
||||
### Describe tool
|
||||
Qodo Merge PR Agent will recognize the ticket and use the ticket content (title, description, labels) to provide additional context for the code changes.
|
||||
Qodo Merge will recognize the ticket and use the ticket content (title, description, labels) to provide additional context for the code changes.
|
||||
By understanding the reasoning and intent behind modifications, the LLM can offer more insightful and relevant code analysis.
|
||||
|
||||
### Review tool
|
||||
@ -46,41 +47,22 @@ If you want to disable this feedback, add the following line to your configurati
|
||||
require_ticket_analysis_review=false
|
||||
```
|
||||
|
||||
## Providers
|
||||
## GitHub Issues Integration
|
||||
|
||||
### Github Issues Integration
|
||||
|
||||
Qodo Merge PR Agent will automatically recognize Github issues mentioned in the PR description and fetch the issue content.
|
||||
Qodo Merge will automatically recognize GitHub issues mentioned in the PR description and fetch the issue content.
|
||||
Examples of valid GitHub issue references:
|
||||
|
||||
- `https://github.com/<ORG_NAME>/<REPO_NAME>/issues/<ISSUE_NUMBER>`
|
||||
- `#<ISSUE_NUMBER>`
|
||||
- `<ORG_NAME>/<REPO_NAME>#<ISSUE_NUMBER>`
|
||||
|
||||
Since Qodo Merge PR Agent is integrated with GitHub, it doesn't require any additional configuration to fetch GitHub issues.
|
||||
Since Qodo Merge is integrated with GitHub, it doesn't require any additional configuration to fetch GitHub issues.
|
||||
|
||||
### Jira Integration 💎
|
||||
## Jira Integration 💎
|
||||
|
||||
We support both Jira Cloud and Jira Server/Data Center.
|
||||
To integrate with Jira, you can link your PR to a ticket using either of these methods:
|
||||
|
||||
**Method 1: Description Reference:**
|
||||
|
||||
Include a ticket reference in your PR description using either the complete URL format https://<JIRA_ORG>.atlassian.net/browse/ISSUE-123 or the shortened ticket ID ISSUE-123.
|
||||
|
||||
**Method 2: Branch Name Detection:**
|
||||
|
||||
Name your branch with the ticket ID as a prefix (e.g., `ISSUE-123-feature-description` or `ISSUE-123/feature-description`).
|
||||
|
||||
!!! note "Jira Base URL"
|
||||
For shortened ticket IDs or branch detection (method 2), you must configure the Jira base URL in your configuration file under the [jira] section:
|
||||
|
||||
```toml
|
||||
[jira]
|
||||
jira_base_url = "https://<JIRA_ORG>.atlassian.net"
|
||||
```
|
||||
|
||||
#### Jira Cloud 💎
|
||||
### Jira Cloud
|
||||
There are two ways to authenticate with Jira Cloud:
|
||||
|
||||
**1) Jira App Authentication**
|
||||
@ -95,7 +77,7 @@ Installation steps:
|
||||
2. After installing the app, you will be redirected to the Qodo Merge registration page. and you will see a success message.<br>
|
||||
{width=384}
|
||||
|
||||
3. Now you can use the Jira integration in Qodo Merge PR Agent.
|
||||
3. Now Qodo Merge will be able to fetch Jira ticket context for your PRs.
|
||||
|
||||
**2) Email/Token Authentication**
|
||||
|
||||
@ -120,45 +102,70 @@ jira_api_email = "YOUR_EMAIL"
|
||||
```
|
||||
|
||||
|
||||
#### Jira Data Center/Server 💎
|
||||
### Jira Data Center/Server
|
||||
|
||||
##### Local App Authentication (For Qodo Merge On-Premise Customers)
|
||||
[//]: # ()
|
||||
[//]: # (##### Local App Authentication (For Qodo Merge On-Premise Customers))
|
||||
|
||||
##### 1. Step 1: Set up an application link in Jira Data Center/Server
|
||||
* Go to Jira Administration > Applications > Application Links > Click on `Create link`
|
||||
[//]: # ()
|
||||
[//]: # (##### 1. Step 1: Set up an application link in Jira Data Center/Server)
|
||||
|
||||
{width=384}
|
||||
* Choose `External application` and set the direction to `Incoming` and then click `Continue`
|
||||
[//]: # (* Go to Jira Administration > Applications > Application Links > Click on `Create link`)
|
||||
|
||||
{width=256}
|
||||
* In the following screen, enter the following details:
|
||||
* Name: `Qodo Merge`
|
||||
* Redirect URL: Enter your Qodo Merge URL followed `https://{QODO_MERGE_ENDPOINT}/register_ticket_provider`
|
||||
* Permission: Select `Read`
|
||||
* Click `Save`
|
||||
[//]: # ()
|
||||
[//]: # ({width=384})
|
||||
|
||||
{width=384}
|
||||
* Copy the `Client ID` and `Client secret` and set them in your `.secrets` file:
|
||||
[//]: # (* Choose `External application` and set the direction to `Incoming` and then click `Continue`)
|
||||
|
||||
{width=256}
|
||||
```toml
|
||||
[jira]
|
||||
jira_app_secret = "..."
|
||||
jira_client_id = "..."
|
||||
```
|
||||
[//]: # ()
|
||||
[//]: # ({width=256})
|
||||
|
||||
##### 2. Step 2: Authenticate with Jira Data Center/Server
|
||||
* Open this URL in your browser: `https://{QODO_MERGE_ENDPOINT}/jira_auth`
|
||||
* Click on link
|
||||
[//]: # (* In the following screen, enter the following details:)
|
||||
|
||||
{width=384}
|
||||
[//]: # ( * Name: `Qodo Merge`)
|
||||
|
||||
* You will be redirected to Jira Data Center/Server, click `Allow`
|
||||
* You will be redirected back to Qodo Merge PR Agent and you will see a success message.
|
||||
[//]: # ( * Redirect URL: Enter your Qodo Merge URL followed `https://{QODO_MERGE_ENDPOINT}/register_ticket_provider`)
|
||||
|
||||
[//]: # ( * Permission: Select `Read`)
|
||||
|
||||
[//]: # ( * Click `Save`)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ({width=384})
|
||||
|
||||
[//]: # (* Copy the `Client ID` and `Client secret` and set them in your `.secrets` file:)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ({width=256})
|
||||
|
||||
[//]: # (```toml)
|
||||
|
||||
[//]: # ([jira])
|
||||
|
||||
[//]: # (jira_app_secret = "...")
|
||||
|
||||
[//]: # (jira_client_id = "...")
|
||||
|
||||
[//]: # (```)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (##### 2. Step 2: Authenticate with Jira Data Center/Server)
|
||||
|
||||
[//]: # (* Open this URL in your browser: `https://{QODO_MERGE_ENDPOINT}/jira_auth`)
|
||||
|
||||
[//]: # (* Click on link)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ({width=384})
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (* You will be redirected to Jira Data Center/Server, click `Allow`)
|
||||
|
||||
[//]: # (* You will be redirected back to Qodo Merge and you will see a success message.)
|
||||
|
||||
|
||||
##### Personal Access Token (PAT) Authentication
|
||||
We also support Personal Access Token (PAT) Authentication method.
|
||||
[//]: # (Personal Access Token (PAT) Authentication)
|
||||
Currently, JIRA integration for Data Center/Server is available via Personal Access Token (PAT) Authentication method
|
||||
|
||||
1. Create a [Personal Access Token (PAT)](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) in your Jira account
|
||||
2. In your Configuration file/Environment variables/Secrets file, add the following lines:
|
||||
@ -168,3 +175,23 @@ We also support Personal Access Token (PAT) Authentication method.
|
||||
jira_base_url = "YOUR_JIRA_BASE_URL" # e.g. https://jira.example.com
|
||||
jira_api_token = "YOUR_API_TOKEN"
|
||||
```
|
||||
|
||||
### How to link a PR to a Jira ticket
|
||||
|
||||
To integrate with Jira, you can link your PR to a ticket using either of these methods:
|
||||
|
||||
**Method 1: Description Reference:**
|
||||
|
||||
Include a ticket reference in your PR description using either the complete URL format https://<JIRA_ORG>.atlassian.net/browse/ISSUE-123 or the shortened ticket ID ISSUE-123.
|
||||
|
||||
**Method 2: Branch Name Detection:**
|
||||
|
||||
Name your branch with the ticket ID as a prefix (e.g., `ISSUE-123-feature-description` or `ISSUE-123/feature-description`).
|
||||
|
||||
!!! note "Jira Base URL"
|
||||
For shortened ticket IDs or branch detection (method 2 for JIRA cloud), you must configure the Jira base URL in your configuration file under the [jira] section:
|
||||
|
||||
```toml
|
||||
[jira]
|
||||
jira_base_url = "https://<JIRA_ORG>.atlassian.net"
|
||||
```
|
||||
|
@ -2,13 +2,14 @@
|
||||
Qodo Merge utilizes a variety of core abilities to provide a comprehensive and efficient code review experience. These abilities include:
|
||||
|
||||
- [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/)
|
||||
- [Auto best practices](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/)
|
||||
- [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/)
|
||||
- [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/)
|
||||
- [Self-reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/)
|
||||
- [Impact evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/)
|
||||
- [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/)
|
||||
- [Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/)
|
||||
- [Code-oriented YAML](https://qodo-merge-docs.qodo.ai/core-abilities/code_oriented_yaml/)
|
||||
- [Company Codebase](https://qodo-merge-docs.qodo.ai/core-abilities/company_codebase/)
|
||||
- [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/)
|
||||
- [Code fine-tuning benchmark](https://qodo-merge-docs.qodo.ai/finetuning_benchmark/)
|
||||
|
||||
@ -18,6 +19,7 @@ Here are some additional technical blogs from Qodo, that delve deeper into the c
|
||||
These resources provide more comprehensive insights into leveraging LLMs for software development.
|
||||
|
||||
### Code Generation and LLMs
|
||||
- [Effective AI code suggestions: less is more](https://www.qodo.ai/blog/effective-code-suggestions-llms-less-is-more/)
|
||||
- [State-of-the-art Code Generation with AlphaCodium – From Prompt Engineering to Flow Engineering](https://www.qodo.ai/blog/qodoflow-state-of-the-art-code-generation-for-code-contests/)
|
||||
- [RAG for a Codebase with 10k Repos](https://www.qodo.ai/blog/rag-for-large-scale-code-repos/)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
## Local and global metadata injection with multi-stage analysis
|
||||
(1)
|
||||
1\.
|
||||
Qodo Merge initially retrieves for each PR the following data:
|
||||
|
||||
- PR title and branch name
|
||||
@ -11,7 +11,7 @@ Qodo Merge initially retrieves for each PR the following data:
|
||||
!!! tip "Tip: Organization-level metadata"
|
||||
In addition to the inputs above, Qodo Merge can incorporate supplementary preferences provided by the user, like [`extra_instructions` and `organization best practices`](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices). This information can be used to enhance the PR analysis.
|
||||
|
||||
(2)
|
||||
2\.
|
||||
By default, the first command that Qodo Merge executes is [`describe`](https://qodo-merge-docs.qodo.ai/tools/describe/), which generates three types of outputs:
|
||||
|
||||
- PR Type (e.g. bug fix, feature, refactor, etc)
|
||||
@ -49,8 +49,8 @@ __old hunk__
|
||||
...
|
||||
```
|
||||
|
||||
(3) The entire PR files that were retrieved are also used to expand and enhance the PR context (see [Dynamic Context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/)).
|
||||
3\. The entire PR files that were retrieved are also used to expand and enhance the PR context (see [Dynamic Context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/)).
|
||||
|
||||
|
||||
(4) All the metadata described above represents several level of cumulative analysis - ranging from hunk level, to file level, to PR level, to organization level.
|
||||
4\. All the metadata described above represents several level of cumulative analysis - ranging from hunk level, to file level, to PR level, to organization level.
|
||||
This comprehensive approach enables Qodo Merge AI models to generate more precise and contextually relevant suggestions and feedback.
|
||||
|
@ -26,7 +26,7 @@ ___
|
||||
|
||||
#### Answer:<span style="display:none;">2</span>
|
||||
|
||||
- Modern AI models, like Claude 3.5 Sonnet and GPT-4, are improving rapidly but remain imperfect. Users should critically evaluate all suggestions rather than accepting them automatically.
|
||||
- Modern AI models, like Claude Sonnet and GPT-4, are improving rapidly but remain imperfect. Users should critically evaluate all suggestions rather than accepting them automatically.
|
||||
- AI errors are rare, but possible. A main value from reviewing the code suggestions lies in their high probability of catching **mistakes or bugs made by the PR author**. We believe it's worth spending 30-60 seconds reviewing suggestions, even if some aren't relevant, as this practice can enhance code quality and prevent bugs in production.
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@ ___
|
||||
|
||||
When you self-host the [open-source](https://github.com/Codium-ai/pr-agent) version, you use your own keys.
|
||||
|
||||
Qodo Merge Pro with SaaS deployment is a hosted version of Qodo Merge, where Qodo manages the infrastructure and the keys.
|
||||
Qodo Merge with SaaS deployment is a hosted version of Qodo Merge, where Qodo manages the infrastructure and the keys.
|
||||
For enterprise customers, on-prem deployment is also available. [Contact us](https://www.codium.ai/contact/#pricing) for more information.
|
||||
___
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Qodo Merge Code Fine-tuning Benchmark
|
||||
|
||||
On coding tasks, the gap between open-source models and top closed-source models such as GPT4 is significant.
|
||||
On coding tasks, the gap between open-source models and top closed-source models such as GPT-4o is significant.
|
||||
<br>
|
||||
In practice, open-source models are unsuitable for most real-world code tasks, and require further fine-tuning to produce acceptable results.
|
||||
|
||||
@ -68,7 +68,7 @@ Here are the prompts, and example outputs, used as input-output pairs to fine-tu
|
||||
|
||||
### Evaluation dataset
|
||||
|
||||
- For each tool, we aggregated 100 additional examples to be used for evaluation. These examples were not used in the training dataset, and were manually selected to represent diverse real-world use-cases.
|
||||
- For each tool, we aggregated 200 additional examples to be used for evaluation. These examples were not used in the training dataset, and were manually selected to represent diverse real-world use-cases.
|
||||
- For each test example, we generated two responses: one from the fine-tuned model, and one from the best code model in the world, `gpt-4-turbo-2024-04-09`.
|
||||
|
||||
- We used a third LLM to judge which response better answers the prompt, and will likely be perceived by a human as better response.
|
||||
|
@ -1,15 +1,16 @@
|
||||
# Overview
|
||||
|
||||
Qodo Merge is an open-source tool to help efficiently review and handle pull requests.
|
||||
[PR-Agent](https://github.com/Codium-ai/pr-agent) is an open-source tool to help efficiently review and handle pull requests.
|
||||
Qodo Merge is a hosted version of PR-Agent, designed for companies and teams that require additional features and capabilities
|
||||
|
||||
- See the [Installation Guide](./installation/index.md) for instructions on installing and running the tool on different git platforms.
|
||||
|
||||
- See the [Usage Guide](./usage-guide/index.md) for instructions on running the Qodo Merge commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
- See the [Usage Guide](./usage-guide/index.md) for instructions on running commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
|
||||
- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools.
|
||||
|
||||
|
||||
## Qodo Merge Docs Smart Search
|
||||
## Docs Smart Search
|
||||
|
||||
To search the documentation site using natural language:
|
||||
|
||||
@ -18,51 +19,45 @@ To search the documentation site using natural language:
|
||||
- A pull request where Qodo Merge is installed
|
||||
- A [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat)
|
||||
|
||||
2) Qodo Merge will respond with an [answer](https://github.com/Codium-ai/pr-agent/pull/1241#issuecomment-2365259334) that includes relevant documentation links.
|
||||
2) The bot will respond with an [answer](https://github.com/Codium-ai/pr-agent/pull/1241#issuecomment-2365259334) that includes relevant documentation links.
|
||||
|
||||
|
||||
## Qodo Merge Features
|
||||
|
||||
Qodo Merge offers extensive pull request functionalities across various git providers.
|
||||
Qodo Merge offers extensive pull request functionalities across various git providers:
|
||||
|
||||
| | | GitHub | GitLab | Bitbucket | Azure DevOps |
|
||||
|-------|---------------------------------------------------------------------------------------------------------|:--------------------:|:--------------------:|:--------------------:|:------------:|
|
||||
| TOOLS | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Ask on code lines](https://pr-agent-docs.codium.ai/tools/ask#ask-lines) | ✅ | ✅ | | |
|
||||
| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | |
|
||||
| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | |
|
||||
| | [CI Feedback](https://pr-agent-docs.codium.ai/tools/ci_feedback/) 💎 | ✅ | | | |
|
||||
| | [PR Documentation](https://pr-agent-docs.codium.ai/tools/documentation/) 💎 | ✅ | ✅ | | |
|
||||
| | [Custom Labels](https://pr-agent-docs.codium.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | |
|
||||
| | [Analyze](https://pr-agent-docs.codium.ai/tools/analyze/) 💎 | ✅ | ✅ | | |
|
||||
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | | GitHub | Gitlab | Bitbucket | Azure DevOps |
|
||||
|-------|-----------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|
|
||||
| TOOLS | Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Incremental | ✅ | | | |
|
||||
| | Ask | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Describe | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Inline file summary](https://qodo-merge-docs.qodo.ai/tools/describe/#inline-file-summary){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | Improve | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Extended | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/#auto-approval) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Custom Prompt](./tools/custom_prompt.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Reflect and Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Update CHANGELOG.md | ✅ | ✅ | ✅ | ️ |
|
||||
| | Find Similar Issue | ✅ | | | ️ |
|
||||
| | [Add PR Documentation](./tools/documentation.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Generate Custom Labels](./tools/describe.md#handle-custom-labels-from-the-repos-labels-page-💎){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Analyze PR Components](./tools/analyze.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | | | | | |
|
||||
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | |
|
||||
| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ |✅| ✅ |✅|
|
||||
| | | | | | |
|
||||
| CORE | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | | | | | ️ |
|
||||
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
||||
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Actions | ✅ | | | ️ |
|
||||
| | | | | |
|
||||
| CORE | PR compression | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Repo language prioritization | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Global and wiki configurations](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR interactive actions](https://www.codium.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | |
|
||||
| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | |
|
||||
| | Multiple models support | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Static code analysis](./core-abilities/static_code_analysis/){:target="_blank"} 💎 | ✅ | ✅ | | |
|
||||
| | [Multiple configuration options](./usage-guide/configuration_options.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
💎 marks a feature available only in [Qodo Merge Pro](https://www.qodo.ai/pricing/){:target="_blank"}
|
||||
💎 marks a feature available only in [Qodo Merge](https://www.codium.ai/pricing/){:target="_blank"}, and not in the open-source version.
|
||||
|
||||
|
||||
## Example Results
|
||||
@ -98,4 +93,4 @@ The following diagram illustrates Qodo Merge tools and their flow:
|
||||
|
||||

|
||||
|
||||
Check out the [core abilities](core-abilities/index.md) page for a comprehensive overview of the variety of core abilities used by Qodo Merge.
|
||||
Check out the [PR Compression strategy](core-abilities/index.md) page for more details on how we convert a code diff to a manageable LLM prompt
|
||||
|
@ -1,5 +1,5 @@
|
||||
## Azure DevOps Pipeline
|
||||
You can use a pre-built Action Docker image to run Qodo Merge as an Azure devops pipeline.
|
||||
You can use a pre-built Action Docker image to run PR-Agent as an Azure devops pipeline.
|
||||
add the following file to your repository under `azure-pipelines.yml`:
|
||||
```yaml
|
||||
# Opt out of CI triggers
|
||||
@ -52,7 +52,7 @@ stages:
|
||||
This script will run Qodo Merge on every new merge request, with the `improve`, `review`, and `describe` commands.
|
||||
Note that you need to export the `azure_devops__pat` and `OPENAI_KEY` variables in the Azure DevOps pipeline settings (Pipelines -> Library -> + Variable group):
|
||||
|
||||
{width=468}
|
||||
{width=468}
|
||||
|
||||
Make sure to give pipeline permissions to the `pr_agent` variable group.
|
||||
|
||||
@ -80,7 +80,7 @@ org = "https://dev.azure.com/YOUR_ORGANIZATION/"
|
||||
# pat = "YOUR_PAT_TOKEN" needed only if using PAT for authentication
|
||||
```
|
||||
|
||||
### Azure DevOps Webhook
|
||||
## Azure DevOps Webhook
|
||||
|
||||
To trigger from an Azure webhook, you need to manually [add a webhook](https://learn.microsoft.com/en-us/azure/devops/service-hooks/services/webhooks?view=azure-devops).
|
||||
Use the "Pull request created" type to trigger a review, or "Pull request commented on" to trigger any supported comment with /<command> <args> comment on the relevant PR. Note that for the "Pull request commented on" trigger, only API v2.0 is supported.
|
||||
|
@ -1,7 +1,7 @@
|
||||
## Run as a Bitbucket Pipeline
|
||||
|
||||
|
||||
You can use the Bitbucket Pipeline system to run Qodo Merge on every pull request open or update.
|
||||
You can use the Bitbucket Pipeline system to run PR-Agent on every pull request open or update.
|
||||
|
||||
1. Add the following file in your repository bitbucket-pipelines.yml
|
||||
|
||||
@ -11,7 +11,7 @@ pipelines:
|
||||
'**':
|
||||
- step:
|
||||
name: PR Agent Review
|
||||
image: python:3.10
|
||||
image: python:3.12
|
||||
services:
|
||||
- docker
|
||||
script:
|
||||
@ -27,10 +27,6 @@ You can get a Bitbucket token for your repository by following Repository Settin
|
||||
Note that comments on a PR are not supported in Bitbucket Pipeline.
|
||||
|
||||
|
||||
## Run using CodiumAI-hosted Bitbucket app 💎
|
||||
|
||||
Please contact visit [Qodo Merge Pro](https://www.codium.ai/pricing/) if you're interested in a hosted BitBucket app solution that provides full functionality including PR reviews and comment handling. It's based on the [bitbucket_app.py](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/git_providers/bitbucket_provider.py) implementation.
|
||||
|
||||
|
||||
## Bitbucket Server and Data Center
|
||||
|
||||
@ -58,7 +54,7 @@ python cli.py --pr_url https://git.onpreminstanceofbitbucket.com/projects/PROJEC
|
||||
|
||||
### Run it as service
|
||||
|
||||
To run Qodo Merge as webhook, build the docker image:
|
||||
To run PR-Agent as webhook, build the docker image:
|
||||
```
|
||||
docker build . -t codiumai/pr-agent:bitbucket_server_webhook --target bitbucket_server_webhook -f docker/Dockerfile
|
||||
docker push codiumai/pr-agent:bitbucket_server_webhook # Push to your Docker repository
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Run as a GitHub Action
|
||||
|
||||
You can use our pre-built Github Action Docker image to run Qodo Merge as a Github Action.
|
||||
You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action.
|
||||
|
||||
1) Add the following file to your repository under `.github/workflows/pr_agent.yml`:
|
||||
|
||||
@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@main
|
||||
uses: qodo-ai/pr-agent@main
|
||||
env:
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -1,5 +1,5 @@
|
||||
## Run as a GitLab Pipeline
|
||||
You can use a pre-built Action Docker image to run Qodo Merge as a GitLab pipeline. This is a simple way to get started with Qodo Merge without setting up your own server.
|
||||
You can use a pre-built Action Docker image to run PR-Agent as a GitLab pipeline. This is a simple way to get started with Qodo Merge without setting up your own server.
|
||||
|
||||
(1) Add the following file to your repository under `.gitlab-ci.yml`:
|
||||
```yaml
|
||||
@ -43,36 +43,47 @@ Note that if your base branches are not protected, don't set the variables as `p
|
||||
|
||||
## Run a GitLab webhook server
|
||||
|
||||
1. From the GitLab workspace or group, create an access token with "Reporter" role ("Developer" if using Pro version of the agent) and "api" scope.
|
||||
1. In GitLab create a new user and give it "Reporter" role ("Developer" if using Pro version of the agent) for the intended group or project.
|
||||
|
||||
2. Generate a random secret for your app, and save it for later. For example, you can use:
|
||||
2. For the user from step 1. generate a `personal_access_token` with `api` access.
|
||||
|
||||
3. Generate a random secret for your app, and save it for later (`shared_secret`). For example, you can use:
|
||||
|
||||
```
|
||||
WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
|
||||
SHARED_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
|
||||
```
|
||||
|
||||
3. Clone this repository:
|
||||
4. Clone this repository:
|
||||
|
||||
```
|
||||
git clone https://github.com/Codium-ai/pr-agent.git
|
||||
git clone https://github.com/qodo-ai/pr-agent.git
|
||||
```
|
||||
|
||||
4. Prepare variables and secrets. Skip this step if you plan on settings these as environment variables when running the agent:
|
||||
5. Prepare variables and secrets. Skip this step if you plan on setting these as environment variables when running the agent:
|
||||
1. In the configuration file/variables:
|
||||
- Set `deployment_type` to "gitlab"
|
||||
- Set `config.git_provider` to "gitlab"
|
||||
|
||||
2. In the secrets file/variables:
|
||||
- Set your AI model key in the respective section
|
||||
- In the [gitlab] section, set `personal_access_token` (with token from step 1) and `shared_secret` (with secret from step 2)
|
||||
- In the [gitlab] section, set `personal_access_token` (with token from step 2) and `shared_secret` (with secret from step 3)
|
||||
|
||||
|
||||
5. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
|
||||
6. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
|
||||
```
|
||||
docker build . -t gitlab_pr_agent --target gitlab_webhook -f docker/Dockerfile
|
||||
docker push codiumai/pr-agent:gitlab_webhook # Push to your Docker repository
|
||||
```
|
||||
|
||||
6. Create a webhook in GitLab. Set the URL to ```http[s]://<PR_AGENT_HOSTNAME>/webhook```, the secret token to the generated secret from step 2, and enable the triggers `push`, `comments` and `merge request events`.
|
||||
7. Set the environmental variables, the method depends on your docker runtime. Skip this step if you included your secrets/configuration directly in the Docker image.
|
||||
|
||||
```
|
||||
"CONFIG.GIT_PROVIDER": "gitlab"
|
||||
"GITLAB.PERSONAL_ACCESS_TOKEN": "<personal_access_token>"
|
||||
"GITLAB.SHARED_SECRET": "<shared_secret>"
|
||||
"GITLAB.URL": "https://gitlab.com"
|
||||
"OPENAI.KEY": "<your_openai_api_key>"
|
||||
```
|
||||
|
||||
8. Create a webhook in your GitLab project. Set the URL to ```http[s]://<PR_AGENT_HOSTNAME>/webhook```, the secret token to the generated secret from step 3, and enable the triggers `push`, `comments` and `merge request events`.
|
||||
|
||||
9. Test your installation by opening a merge request or commenting on a merge request using one of PR Agent's commands.
|
||||
|
||||
7. Test your installation by opening a merge request or commenting on a merge request using one of CodiumAI's commands.
|
||||
boxes
|
||||
|
@ -1,21 +1,17 @@
|
||||
# Installation
|
||||
|
||||
## Self-hosted Qodo Merge
|
||||
If you choose to host your own Qodo Merge, you first need to acquire two tokens:
|
||||
## Self-hosted PR-Agent
|
||||
|
||||
1. An OpenAI key from [here](https://platform.openai.com/api-keys){:target="_blank"}, with access to GPT-4 (or a key for other [language models](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/), if you prefer).
|
||||
2. A GitHub\GitLab\BitBucket personal access token (classic), with the repo scope. [GitHub from [here](https://github.com/settings/tokens){:target="_blank"}]
|
||||
|
||||
There are several ways to use self-hosted Qodo Merge:
|
||||
There are several ways to use self-hosted PR-Agent:
|
||||
|
||||
- [Locally](./locally.md)
|
||||
- [GitHub](./github.md)
|
||||
- [GitLab](./gitlab.md)
|
||||
- [BitBucket](./bitbucket.md)
|
||||
- [Azure DevOps](./azure.md)
|
||||
- [GitHub integration](./github.md)
|
||||
- [GitLab integration](./gitlab.md)
|
||||
- [BitBucket integration](./bitbucket.md)
|
||||
- [Azure DevOps integration](./azure.md)
|
||||
|
||||
## Qodo Merge Pro 💎
|
||||
Qodo Merge Pro, an app hosted by CodiumAI for GitHub\GitLab\BitBucket, is also available.
|
||||
## Qodo Merge 💎
|
||||
Qodo Merge, an app hosted by QodoAI for GitHub\GitLab\BitBucket, is also available.
|
||||
<br>
|
||||
With Qodo Merge Pro, installation is as simple as signing up and adding the Qodo Merge app to your relevant repo.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/installation/pr_agent_pro/) for more details.
|
||||
With Qodo Merge, installation is as simple as adding the Qodo Merge app to your relevant repositories.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/) for more details.
|
||||
|
@ -1,3 +1,77 @@
|
||||
To run PR-Agent locally, you first need to acquire two keys:
|
||||
|
||||
1. An OpenAI key from [here](https://platform.openai.com/api-keys){:target="_blank"}, with access to GPT-4 and o3-mini (or a key for other [language models](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/), if you prefer).
|
||||
2. A personal access token from your Git platform (GitHub, GitLab, BitBucket) with repo scope. GitHub token, for example, can be issued from [here](https://github.com/settings/tokens){:target="_blank"}
|
||||
|
||||
|
||||
## Using Docker image
|
||||
|
||||
A list of the relevant tools can be found in the [tools guide](../tools/ask.md).
|
||||
|
||||
To invoke a tool (for example `review`), you can run PR-Agent directly from the Docker image. Here's how:
|
||||
|
||||
- For GitHub:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
If you are using GitHub enterprise server, you need to specify the custom url as variable.
|
||||
For example, if your GitHub server is at `https://github.mycompany.com`, add the following to the command:
|
||||
```
|
||||
-e GITHUB.BASE_URL=https://github.mycompany.com/api/v3
|
||||
```
|
||||
|
||||
- For GitLab:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
If you have a dedicated GitLab instance, you need to specify the custom url as variable:
|
||||
```
|
||||
-e GITLAB.URL=<your gitlab instance url>
|
||||
```
|
||||
|
||||
- For BitBucket:
|
||||
```
|
||||
docker run --rm -it -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=<pr_url> review
|
||||
```
|
||||
|
||||
For other git providers, update `CONFIG.GIT_PROVIDER` accordingly and check the [`pr_agent/settings/.secrets_template.toml`](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/.secrets_template.toml) file for environment variables expected names and values.
|
||||
|
||||
### Utilizing environment variables
|
||||
|
||||
It is also possible to provide or override the configuration by setting the corresponding environment variables.
|
||||
You can define the corresponding environment variables by following this convention: `<TABLE>__<KEY>=<VALUE>` or `<TABLE>.<KEY>=<VALUE>`.
|
||||
The `<TABLE>` refers to a table/section in a configuration file and `<KEY>=<VALUE>` refers to the key/value pair of a setting in the configuration file.
|
||||
|
||||
For example, suppose you want to run `pr_agent` that connects to a self-hosted GitLab instance similar to an example above.
|
||||
You can define the environment variables in a plain text file named `.env` with the following content:
|
||||
|
||||
```
|
||||
CONFIG__GIT_PROVIDER="gitlab"
|
||||
GITLAB__URL="<your url>"
|
||||
GITLAB__PERSONAL_ACCESS_TOKEN="<your token>"
|
||||
OPENAI__KEY="<your key>"
|
||||
```
|
||||
|
||||
Then, you can run `pr_agent` using Docker with the following command:
|
||||
|
||||
```shell
|
||||
docker run --rm -it --env-file .env codiumai/pr-agent:latest <tool> <tool parameter>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### I get an error when running the Docker image. What should I do?
|
||||
|
||||
If you encounter an error when running the Docker image, it is almost always due to a misconfiguration of api keys or tokens.
|
||||
|
||||
Note that litellm, which is used by pr-agent, sometimes returns non-informative error messages such as `APIError: OpenAIException - Connection error.`
|
||||
Carefully check the api keys and tokens you provided and make sure they are correct.
|
||||
Adjustments may be needed depending on your llm provider.
|
||||
|
||||
For example, for Azure OpenAI, additional keys are [needed](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#azure).
|
||||
Same goes for other providers, make sure to check the [documentation](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#changing-a-model)
|
||||
|
||||
## Using pip package
|
||||
|
||||
Install the package:
|
||||
@ -35,63 +109,6 @@ if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
## Using Docker image
|
||||
|
||||
A list of the relevant tools can be found in the [tools guide](../tools/ask.md).
|
||||
|
||||
To invoke a tool (for example `review`), you can run directly from the Docker image. Here's how:
|
||||
|
||||
- For GitHub:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
If you are using GitHub enterprise server, you need to specify the custom url as variable.
|
||||
For example, if your GitHub server is at `https://github.mycompany.com`, add the following to the command:
|
||||
```
|
||||
-e GITHUB.BASE_URL=https://github.mycompany.com/api/v3
|
||||
```
|
||||
|
||||
- For GitLab:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
If you have a dedicated GitLab instance, you need to specify the custom url as variable:
|
||||
```
|
||||
-e GITLAB.URL=<your gitlab instance url>
|
||||
```
|
||||
|
||||
- For BitBucket:
|
||||
```
|
||||
docker run --rm -it -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=<pr_url> review
|
||||
```
|
||||
|
||||
For other git providers, update `CONFIG.GIT_PROVIDER` accordingly and check the `pr_agent/settings/.secrets_template.toml` file for environment variables expected names and values.
|
||||
The `pr_agent` uses [Dynaconf](https://www.dynaconf.com/) to load settings from configuration files.
|
||||
|
||||
It is also possible to provide or override the configuration by setting the corresponding environment variables.
|
||||
You can define the corresponding environment variables by following this convention: `<TABLE>__<KEY>=<VALUE>` or `<TABLE>.<KEY>=<VALUE>`.
|
||||
The `<TABLE>` refers to a table/section in a configuration file and `<KEY>=<VALUE>` refers to the key/value pair of a setting in the configuration file.
|
||||
|
||||
For example, suppose you want to run `pr_agent` that connects to a self-hosted GitLab instance similar to an example above.
|
||||
You can define the environment variables in a plain text file named `.env` with the following content:
|
||||
|
||||
> Warning: Never commit the `.env` file to version control system as it might contains sensitive credentials!
|
||||
|
||||
```
|
||||
CONFIG__GIT_PROVIDER="gitlab"
|
||||
GITLAB__URL="<your url>"
|
||||
GITLAB__PERSONAL_ACCESS_TOKEN="<your token>"
|
||||
OPENAI__KEY="<your key>"
|
||||
```
|
||||
|
||||
Then, you can run `pr_agent` using Docker with the following command:
|
||||
|
||||
```shell
|
||||
docker run --rm -it --env-file .env codiumai/pr-agent:latest <tool> <tool parameter>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run from source
|
||||
|
||||
|
49
docs/docs/installation/pr_agent.md
Normal file
@ -0,0 +1,49 @@
|
||||
# PR-Agent Installation Guide
|
||||
|
||||
PR-Agent can be deployed in various environments and platforms. Choose the installation method that best suits your needs:
|
||||
|
||||
## 🖥️ Local Installation
|
||||
|
||||
Learn how to run PR-Agent locally using:
|
||||
|
||||
- Docker image
|
||||
- pip package
|
||||
- CLI from source code
|
||||
|
||||
[View Local Installation Guide →](https://qodo-merge-docs.qodo.ai/installation/locally/)
|
||||
|
||||
## 🐙 GitHub Integration
|
||||
|
||||
Set up PR-Agent with GitHub as:
|
||||
|
||||
- GitHub Action
|
||||
- Local GitHub App
|
||||
|
||||
[View GitHub Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/github/)
|
||||
|
||||
## 🦊 GitLab Integration
|
||||
|
||||
Deploy PR-Agent on GitLab as:
|
||||
|
||||
- GitLab pipeline job
|
||||
- Local GitLab webhook server
|
||||
|
||||
[View GitLab Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/gitlab/)
|
||||
|
||||
## 🟦 BitBucket Integration
|
||||
|
||||
Implement PR-Agent in BitBucket as:
|
||||
|
||||
- BitBucket pipeline job
|
||||
- Local BitBucket server
|
||||
|
||||
[View BitBucket Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/bitbucket/)
|
||||
|
||||
## 🔷 Azure DevOps Integration
|
||||
|
||||
Configure PR-Agent with Azure DevOps as:
|
||||
|
||||
- Azure DevOps pipeline job
|
||||
- Local Azure DevOps webhook
|
||||
|
||||
[View Azure DevOps Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/azure/)
|
@ -1,42 +1,44 @@
|
||||
Qodo Merge Pro is a versatile application compatible with GitHub, GitLab, and BitBucket, hosted by QodoAI.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for more details about the benefits of using Qodo Merge Pro.
|
||||
Qodo Merge is a versatile application compatible with GitHub, GitLab, and BitBucket, hosted by QodoAI.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for more details about the benefits of using Qodo Merge.
|
||||
|
||||
A complimentary two-week trial is provided to all new users. Following the trial period, user licenses (seats) are required for continued access.
|
||||
To purchase user licenses, please visit our [pricing page](https://www.qodo.ai/pricing/).
|
||||
Once subscribed, users can seamlessly deploy the application across any of their code repositories.
|
||||
|
||||
## Install Qodo Merge Pro for GitHub
|
||||
## Install Qodo Merge for GitHub
|
||||
|
||||
### GitHub Cloud
|
||||
|
||||
Qodo Merge Pro for GitHub cloud is available for installation through the [GitHub Marketplace](https://github.com/apps/qodo-merge-pro).
|
||||
Qodo Merge for GitHub cloud is available for installation through the [GitHub Marketplace](https://github.com/apps/qodo-merge-pro).
|
||||
|
||||
{width=468}
|
||||
{width=468}
|
||||
|
||||
### GitHub Enterprise Server
|
||||
|
||||
To use Qodo Merge Pro application on your private GitHub Enterprise Server, you will need to contact us for starting an [Enterprise](https://www.codium.ai/pricing/) trial.
|
||||
To use Qodo Merge application on your private GitHub Enterprise Server, you will need to [contact](https://www.qodo.ai/contact/#pricing) Qodo for starting an Enterprise trial.
|
||||
|
||||
### GitHub Open Source Projects
|
||||
|
||||
For open-source projects, Qodo Merge Pro is available for free usage. To install Qodo Merge Pro for your open-source repositories, use the following marketplace [link](https://github.com/apps/qodo-merge-pro-for-open-source).
|
||||
For open-source projects, Qodo Merge is available for free usage. To install Qodo Merge for your open-source repositories, use the following marketplace [link](https://github.com/apps/qodo-merge-pro-for-open-source).
|
||||
|
||||
## Install Qodo Merge Pro for Bitbucket
|
||||
## Install Qodo Merge for Bitbucket
|
||||
|
||||
### Bitbucket Cloud
|
||||
|
||||
Qodo Merge Pro for Bitbucket Cloud is available for installation through the following [link](https://bitbucket.org/site/addons/authorize?addon_key=d6df813252c37258)
|
||||
Qodo Merge for Bitbucket Cloud is available for installation through the following [link](https://bitbucket.org/site/addons/authorize?addon_key=d6df813252c37258)
|
||||
|
||||
{width=468}
|
||||
{width=468}
|
||||
|
||||
### Bitbucket Server
|
||||
|
||||
To use Qodo Merge Pro application on your private Bitbucket Server, you will need to contact us for starting an [Enterprise](https://www.codium.ai/pricing/) trial.
|
||||
To use Qodo Merge application on your private Bitbucket Server, you will need to contact us for starting an [Enterprise](https://www.qodo.ai/pricing/) trial.
|
||||
|
||||
|
||||
## Install Qodo Merge Pro for GitLab (Teams & Enterprise)
|
||||
## Install Qodo Merge for GitLab
|
||||
|
||||
Since GitLab platform does not support apps, installing Qodo Merge Pro for GitLab is a bit more involved, and requires the following steps:
|
||||
### GitLab Cloud
|
||||
|
||||
Since GitLab platform does not support apps, installing Qodo Merge for GitLab is a bit more involved, and requires the following steps:
|
||||
|
||||
#### Step 1
|
||||
|
||||
@ -79,3 +81,7 @@ Enable SSL verification: Check the box.
|
||||
You’re all set!
|
||||
|
||||
Open a new merge request or add a MR comment with one of Qodo Merge’s commands such as /review, /describe or /improve.
|
||||
|
||||
### GitLab Server
|
||||
|
||||
For a trial period of two weeks on your private GitLab Server, the same [installation steps](#gitlab-cloud) as for GitLab Cloud apply. After the trial period, you will need to [contact](https://www.qodo.ai/contact/#pricing) Qodo for moving to an Enterprise account.
|
@ -1,12 +1,12 @@
|
||||
## Self-hosted Qodo Merge
|
||||
## Self-hosted PR-Agent
|
||||
|
||||
- If you self-host Qodo Merge with your OpenAI (or other LLM provider) API key, it is between you and the provider. We don't send your code data to Qodo Merge servers.
|
||||
- If you self-host PR-Agent with your OpenAI (or other LLM provider) API key, it is between you and the provider. We don't send your code data to Qodo servers.
|
||||
|
||||
## Qodo Merge Pro 💎
|
||||
## Qodo Merge 💎
|
||||
|
||||
- When using Qodo Merge 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.
|
||||
- When using Qodo Merge💎, hosted by Qodo, 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.
|
||||
|
||||
- For certain clients, CodiumAI-hosted Qodo Merge Pro will use CodiumAI’s proprietary models. If this is the case, you will be notified.
|
||||
- For certain clients, Qodo Merge will use Qodo’s proprietary models. If this is the case, you will be notified.
|
||||
|
||||
- No passive collection of Code and Pull Requests’ data — Qodo Merge will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
# Overview
|
||||
|
||||
Qodo Merge is an open-source tool to help efficiently review and handle pull requests.
|
||||
|
||||
- See the [Installation Guide](./installation/index.md) for instructions on installing and running the tool on different git platforms.
|
||||
|
||||
- See the [Usage Guide](./usage-guide/index.md) for instructions on running the Qodo Merge commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
|
||||
- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools.
|
||||
|
||||
|
||||
## Qodo Merge Docs Smart Search
|
||||
|
||||
To search the documentation site using natural language:
|
||||
|
||||
1) Comment `/help "your question"` in either:
|
||||
|
||||
- A pull request where Qodo Merge is installed
|
||||
- A [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat)
|
||||
|
||||
2) Qodo Merge will respond with an [answer](https://github.com/Codium-ai/pr-agent/pull/1241#issuecomment-2365259334) that includes relevant documentation links.
|
||||
|
||||
|
||||
## Qodo Merge Features
|
||||
|
||||
Qodo Merge offers extensive pull request functionalities across various git providers.
|
||||
|
||||
| | | GitHub | Gitlab | Bitbucket | Azure DevOps |
|
||||
|-------|-----------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|
|
||||
| TOOLS | Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Incremental | ✅ | | | |
|
||||
| | Ask | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Describe | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Inline file summary](https://qodo-merge-docs.qodo.ai/tools/describe/#inline-file-summary){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | Improve | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Extended | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Custom Prompt](./tools/custom_prompt.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Reflect and Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Update CHANGELOG.md | ✅ | ✅ | ✅ | ️ |
|
||||
| | Find Similar Issue | ✅ | | | ️ |
|
||||
| | [Add PR Documentation](./tools/documentation.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Generate Custom Labels](./tools/describe.md#handle-custom-labels-from-the-repos-labels-page-💎){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Analyze PR Components](./tools/analyze.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | | | | | ️ |
|
||||
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
||||
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Actions | ✅ | | | ️ |
|
||||
| | | | | |
|
||||
| CORE | PR compression | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Repo language prioritization | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Multiple models support | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Incremental PR review | ✅ | | | |
|
||||
| | [Static code analysis](./tools/analyze.md/){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Multiple configuration options](./usage-guide/configuration_options.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
💎 marks a feature available only in [Qodo Merge Pro](https://www.codium.ai/pricing/){:target="_blank"}
|
||||
|
||||
|
||||
## Example Results
|
||||
<hr>
|
||||
|
||||
#### [/describe](https://github.com/Codium-ai/pr-agent/pull/530)
|
||||
<figure markdown="1">
|
||||
{width=512}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
#### [/review](https://github.com/Codium-ai/pr-agent/pull/732#issuecomment-1975099151)
|
||||
<figure markdown="1">
|
||||
{width=512}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
#### [/improve](https://github.com/Codium-ai/pr-agent/pull/732#issuecomment-1975099159)
|
||||
<figure markdown="1">
|
||||
{width=512}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
#### [/generate_labels](https://github.com/Codium-ai/pr-agent/pull/530)
|
||||
<figure markdown="1">
|
||||
{width=300}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
## How it Works
|
||||
|
||||
The following diagram illustrates Qodo Merge tools and their flow:
|
||||
|
||||

|
||||
|
||||
Check out the [PR Compression strategy](core-abilities/index.md) page for more details on how we convert a code diff to a manageable LLM prompt
|
@ -1,37 +1,37 @@
|
||||
### Overview
|
||||
|
||||
[Qodo Merge Pro](https://www.codium.ai/pricing/){:target="_blank"} is a hosted version of open-source [Qodo Merge (PR-Agent)](https://github.com/Codium-ai/pr-agent){:target="_blank"}. A complimentary two-week trial is offered, followed by a monthly subscription fee.
|
||||
Qodo Merge Pro is designed for companies and teams that require additional features and capabilities. It provides the following benefits:
|
||||
[Qodo Merge](https://www.codium.ai/pricing/){:target="_blank"} is a paid, hosted version of open-source [PR-Agent](https://github.com/Codium-ai/pr-agent){:target="_blank"}. A complimentary two-week trial is offered, followed by a monthly subscription fee.
|
||||
Qodo Merge is designed for companies and teams that require additional features and capabilities. It 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 Qodo Merge app to your GitHub\GitLab\BitBucket repo.
|
||||
|
||||
2. **Improved privacy** - No data will be stored or used to train models. Qodo Merge Pro will employ zero data retention, and will use an OpenAI and Claude accounts with zero data retention.
|
||||
2. **Improved privacy** - No data will be stored or used to train models. Qodo Merge will employ zero data retention, and will use an OpenAI and Claude accounts with zero data retention.
|
||||
|
||||
3. **Improved support** - Qodo Merge Pro users will receive priority support, and will be able to request new features and capabilities.
|
||||
3. **Improved support** - Qodo Merge users will receive priority support, and will be able to request new features and capabilities.
|
||||
|
||||
4. **Supporting self-hosted git servers** - Qodo Merge Pro can be installed on GitHub Enterprise Server, GitLab, and BitBucket. For more information, see the [installation guide](https://qodo-merge-docs.qodo.ai/installation/pr_agent_pro/).
|
||||
4. **Supporting self-hosted git servers** - Qodo Merge can be installed on GitHub Enterprise Server, GitLab, and BitBucket. For more information, see the [installation guide](https://qodo-merge-docs.qodo.ai/installation/pr_agent_pro/).
|
||||
|
||||
5. **PR Chat** - Qodo Merge Pro allows you to engage in [private chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) about your pull requests on private repositories.
|
||||
5. **PR Chat** - Qodo Merge allows you to engage in [private chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) about your pull requests on private repositories.
|
||||
|
||||
### Additional features
|
||||
|
||||
Here are some of the additional features and capabilities that Qodo Merge Pro offers:
|
||||
Here are some of the additional features and capabilities that Qodo Merge offers:
|
||||
|
||||
| Feature | Description |
|
||||
|----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [**Model selection**](https://qodo-merge-docs.qodo.ai/usage-guide/PR_agent_pro_models/) | Choose the model that best fits your needs, among top models like `GPT4` and `Claude-Sonnet-3.5`
|
||||
| [**Model selection**](https://qodo-merge-docs.qodo.ai/usage-guide/PR_agent_pro_models/) | Choose the model that best fits your needs, among top models like `Claude Sonnet` and `o3-mini`
|
||||
| [**Global and wiki configuration**](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) | Control configurations for many repositories from a single location; <br>Edit configuration of a single repo without committing code |
|
||||
| [**Apply suggestions**](https://qodo-merge-docs.qodo.ai/tools/improve/#overview) | Generate committable code from the relevant suggestions interactively by clicking on a checkbox |
|
||||
| [**Suggestions impact**](https://qodo-merge-docs.qodo.ai/tools/improve/#assessing-impact) | Automatically mark suggestions that were implemented by the user (either directly in GitHub, or indirectly in the IDE) to enable tracking of the impact of the suggestions |
|
||||
| [**CI feedback**](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) | Automatically analyze failed CI checks on GitHub and provide actionable feedback in the PR conversation, helping to resolve issues quickly |
|
||||
| [**Advanced usage statistics**](https://www.codium.ai/contact/#/) | Qodo Merge Pro offers detailed statistics at user, repository, and company levels, including metrics about Qodo Merge usage, and also general statistics and insights |
|
||||
| [**Advanced usage statistics**](https://www.codium.ai/contact/#/) | Qodo Merge offers detailed statistics at user, repository, and company levels, including metrics about Qodo Merge usage, and also general statistics and insights |
|
||||
| [**Incorporating companies' best practices**](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) | Use the companies' best practices as reference to increase the effectiveness and the relevance of the code suggestions |
|
||||
| [**Interactive triggering**](https://qodo-merge-docs.qodo.ai/tools/analyze/#example-usage) | Interactively apply different tools via the `analyze` command |
|
||||
| [**Custom labels**](https://qodo-merge-docs.qodo.ai/tools/describe/#handle-custom-labels-from-the-repos-labels-page) | Define custom labels for Qodo Merge to assign to the PR |
|
||||
|
||||
### Additional tools
|
||||
|
||||
Here are additional tools that are available only for Qodo Merge Pro users:
|
||||
Here are additional tools that are available only for Qodo Merge users:
|
||||
|
||||
| Feature | Description |
|
||||
|---------------------------------------------------------------------------------------|-------------|
|
||||
@ -46,7 +46,7 @@ Here are additional tools that are available only for Qodo Merge Pro users:
|
||||
|
||||
### Supported languages
|
||||
|
||||
Qodo Merge Pro leverages the world's leading code models - Claude 3.5 Sonnet and GPT-4.
|
||||
Qodo Merge leverages the world's leading code models, such as Claude 3.7 Sonnet and o3-mini.
|
||||
As a result, its primary tools such as `describe`, `review`, and `improve`, as well as the PR-chat feature, support virtually all programming languages.
|
||||
|
||||
For specialized commands that require static code analysis, Qodo Merge Pro offers support for specific languages. For more details about features that require static code analysis, please refer to the [documentation](https://qodo-merge-docs.qodo.ai/tools/analyze/#overview).
|
||||
For specialized commands that require static code analysis, Qodo Merge offers support for specific languages. For more details about features that require static code analysis, please refer to the [documentation](https://qodo-merge-docs.qodo.ai/tools/analyze/#overview).
|
||||
|
@ -14,6 +14,5 @@ An example result:
|
||||
|
||||
{width=750}
|
||||
|
||||
**Notes**
|
||||
|
||||
- Language that are currently supported: Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
!!! note "Language that are currently supported:"
|
||||
Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
|
@ -38,20 +38,20 @@ where `https://real_link_to_image` is the direct link to the image.
|
||||
Note that GitHub has a built-in mechanism of pasting images in comments. However, pasted image does not provide a direct link.
|
||||
To get a direct link to an image, we recommend using the following scheme:
|
||||
|
||||
1) First, post a comment that contains **only** the image:
|
||||
1\. First, post a comment that contains **only** the image:
|
||||
|
||||
{width=512}
|
||||
|
||||
2) Quote reply to that comment:
|
||||
2\. Quote reply to that comment:
|
||||
|
||||
{width=512}
|
||||
|
||||
3) In the screen opened, type the question below the image:
|
||||
3\. In the screen opened, type the question below the image:
|
||||
|
||||
{width=512}
|
||||
{width=512}
|
||||
|
||||
4) Post the comment, and receive the answer:
|
||||
4\. Post the comment, and receive the answer:
|
||||
|
||||
{width=512}
|
||||
|
||||
|
@ -28,7 +28,7 @@ When working from CLI, you need to apply the [configuration changes](#configurat
|
||||
To enable custom labels, you need to apply the [configuration changes](#configuration-options) to the local `.pr_agent.toml` file in your repository.
|
||||
|
||||
#### 3. Handle custom labels from the Repo's labels page 💎
|
||||
> This feature is available only in Qodo Merge Pro
|
||||
> This feature is available only in Qodo Merge
|
||||
|
||||
* 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.
|
||||
|
@ -51,8 +51,8 @@ Results obtained with the prompt above:
|
||||
|
||||
## Configuration options
|
||||
|
||||
`prompt`: the prompt for the tool. It should be a multi-line string.
|
||||
- `prompt`: the prompt for the tool. It should be a multi-line string.
|
||||
|
||||
`num_code_suggestions`: number of code suggestions provided by the 'custom_prompt' tool. Default is 4.
|
||||
- `num_code_suggestions_per_chunk`: number of code suggestions provided by the 'custom_prompt' tool, per chunk. Default is 4.
|
||||
|
||||
`enable_help_text`: if set to true, the tool will display a help text in the comment. Default is true.
|
||||
- `enable_help_text`: if set to true, the tool will display a help text in the comment. Default is true.
|
||||
|
@ -46,7 +46,7 @@ publish_labels = true
|
||||
|
||||
!!! example "Possible configurations"
|
||||
|
||||
<table>
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>publish_labels</b></td>
|
||||
<td>If set to true, the tool will publish labels to the PR. Default is false.</td>
|
||||
@ -95,7 +95,7 @@ publish_labels = true
|
||||
<td><b>enable_help_text</b></td>
|
||||
<td>If set to true, the tool will display a help text in the comment. Default is false.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
|
||||
## Inline file summary 💎
|
||||
@ -143,7 +143,7 @@ The marker `pr_agent:type` will be replaced with the PR type, `pr_agent:summary`
|
||||
|
||||
{width=512}
|
||||
|
||||
→
|
||||
becomes
|
||||
|
||||
{width=512}
|
||||
|
||||
|
@ -27,7 +27,6 @@ You can state a name of a specific component in the PR to get documentation only
|
||||
- `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, C#.
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
!!! note "Notes"
|
||||
- The following languages are currently supported: Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Overview
|
||||
The `help` tool provides a list of all the available tools and their descriptions.
|
||||
For Qodo Merge Pro users, it also enables to trigger each tool by checking the relevant box.
|
||||
For Qodo Merge users, it also enables to trigger each tool by checking the relevant box.
|
||||
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
|
108
docs/docs/tools/help_docs.md
Normal file
@ -0,0 +1,108 @@
|
||||
## Overview
|
||||
|
||||
The `help_docs` tool can answer a free-text question based on a git documentation folder.
|
||||
|
||||
It can be invoked manually by commenting on any PR or Issue:
|
||||
```
|
||||
/help_docs "..."
|
||||
```
|
||||
Or configured to be triggered automatically when a [new issue is opened](#run-as-a-github-action).
|
||||
|
||||
The tool assumes by default that the documentation is located in the root of the repository, at `/docs` folder.
|
||||
However, this can be customized by setting the `docs_path` configuration option:
|
||||
|
||||
```toml
|
||||
[pr_help_docs]
|
||||
repo_url = "" # The repository to use as context
|
||||
docs_path = "docs" # The documentation folder
|
||||
repo_default_branch = "main" # The branch to use in case repo_url overwritten
|
||||
|
||||
```
|
||||
|
||||
See more configuration options in the [Configuration options](#configuration-options) section.
|
||||
|
||||
## Example usage
|
||||
|
||||
[//]: # (#### Asking a question about this repository:)
|
||||
|
||||
[//]: # ({width=512})
|
||||
|
||||
**Asking a question about another repository**
|
||||
|
||||
{width=512}
|
||||
|
||||
**Response**:
|
||||
|
||||
{width=512}
|
||||
|
||||
## Run automatically when a new issue is opened
|
||||
|
||||
You can configure PR-Agent to run `help_docs` automatically on any newly created issue.
|
||||
This can be useful, for example, for providing immediate feedback to users who open issues with questions on open-source projects with extensive documentation.
|
||||
|
||||
Here's how:
|
||||
|
||||
1) Follow the steps depicted under [Run as a Github Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) to create a new workflow, such as:`.github/workflows/help_docs.yml`:
|
||||
|
||||
2) Edit your yaml file to the following:
|
||||
|
||||
```yaml
|
||||
name: Run pr agent on every opened issue, respond to user comments on an issue
|
||||
|
||||
#When the action is triggered
|
||||
on:
|
||||
issues:
|
||||
types: [opened] #New issue
|
||||
|
||||
# Read env. variables
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GIT_REPO_URL: ${{ github.event.repository.clone_url }}
|
||||
ISSUE_URL: ${{ github.event.issue.html_url || github.event.comment.html_url }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body || github.event.comment.body }}
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||
|
||||
# The actual set of actions
|
||||
jobs:
|
||||
issue_agent:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.sender.type != 'Bot' }} #Do not respond to bots
|
||||
|
||||
# Set required permissions
|
||||
permissions:
|
||||
contents: read # For reading repository contents
|
||||
issues: write # For commenting on issues
|
||||
|
||||
steps:
|
||||
- name: Run PR Agent on Issues
|
||||
if: ${{ env.ISSUE_URL != '' }}
|
||||
uses: docker://codiumai/pr-agent:latest
|
||||
with:
|
||||
entrypoint: /bin/bash #Replace invoking cli.py directly with a shell
|
||||
args: |
|
||||
-c "cd /app && \
|
||||
echo 'Running Issue Agent action step on ISSUE_URL=$ISSUE_URL' && \
|
||||
export config__git_provider='github' && \
|
||||
export github__user_token=$GITHUB_TOKEN && \
|
||||
export github__base_url=$GITHUB_API_URL && \
|
||||
export openai__key=$OPENAI_KEY && \
|
||||
python -m pr_agent.cli --issue_url=$ISSUE_URL --pr_help_docs.repo_url="..." --pr_help_docs.docs_path="..." --pr_help_docs.openai_key=$OPENAI_KEY && \help_docs \"$ISSUE_BODY\""
|
||||
```
|
||||
|
||||
3) Following completion of the remaining steps (such as adding secrets and relevant configurations, such as `repo_url` and `docs_path`) merge this change to your main branch.
|
||||
When a new issue is opened, you should see a comment from `github-actions` bot with an auto response, assuming the question is related to the documentation of the repository.
|
||||
---
|
||||
|
||||
|
||||
## Configuration options
|
||||
|
||||
Under the section `pr_help_docs`, the [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L50) contains options to customize the 'help docs' tool:
|
||||
|
||||
- `repo_url`: If not overwritten, will use the repo from where the context came from (issue or PR), otherwise - use the given repo as context.
|
||||
- `repo_default_branch`: The branch to use in case repo_url overwritten, otherwise - has no effect.
|
||||
- `docs_path`: Relative path from root of repository (either the one this PR has been issued for, or above repo url).
|
||||
- `exclude_root_readme`: Whether or not to exclude the root README file for querying the model.
|
||||
- `supported_doc_exts` : Which file extensions should be included for the purpose of querying the model.
|
||||
|
||||
---
|
@ -1,3 +1,5 @@
|
||||
`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
## Overview
|
||||
|
||||
The `implement` tool converts human code review discussions and feedback into ready-to-commit code changes.
|
||||
@ -8,8 +10,9 @@ It leverages LLM technology to transform PR comments and review suggestions into
|
||||
|
||||
### For Reviewers
|
||||
|
||||
Reviewers can request code changes by: <br>
|
||||
1. Selecting the code block to be modified. <br>
|
||||
Reviewers can request code changes by:
|
||||
|
||||
1. Selecting the code block to be modified.
|
||||
2. Adding a comment with the syntax:
|
||||
```
|
||||
/implement <code-change-description>
|
||||
@ -44,7 +47,8 @@ You can reference and implement changes from any comment by:
|
||||
Note that the implementation will occur within the review discussion thread.
|
||||
|
||||
|
||||
**Configuration options** <br>
|
||||
- Use `/implement` to implement code change within and based on the review discussion. <br>
|
||||
- Use `/implement <code-change-description>` inside a review discussion to implement specific instructions. <br>
|
||||
- Use `/implement <link-to-review-comment>` to indirectly call the tool from any comment. <br>
|
||||
**Configuration options**
|
||||
|
||||
- Use `/implement` to implement code change within and based on the review discussion.
|
||||
- Use `/implement <code-change-description>` inside a review discussion to implement specific instructions.
|
||||
- Use `/implement <link-to-review-comment>` to indirectly call the tool from any comment.
|
||||
|
@ -9,8 +9,9 @@ The tool can be triggered automatically every time a new PR is [opened](../usage
|
||||
|
||||
{width=512}
|
||||
|
||||
Note that the `Apply this suggestion` checkbox, which interactively converts a suggestion into a commitable code comment, is available only for Qodo Merge Pro 💎 users.
|
||||
|
||||
!!! note "The following features are available only for Qodo Merge 💎 users:"
|
||||
- The `Apply this suggestion` checkbox, which interactively converts a suggestion into a committable code comment
|
||||
- The `More` checkbox to generate additional suggestions
|
||||
|
||||
## Example usage
|
||||
|
||||
@ -52,9 +53,10 @@ num_code_suggestions_per_chunk = ...
|
||||
- The `pr_commands` lists commands that will be executed automatically when a PR is opened.
|
||||
- The `[pr_code_suggestions]` section contains the configurations for the `improve` tool you want to edit (if any)
|
||||
|
||||
### Assessing Impact 💎
|
||||
### Assessing Impact
|
||||
>`💎 feature`
|
||||
|
||||
Note that Qodo Merge pro tracks two types of implementations:
|
||||
Qodo Merge tracks two types of implementations for tracking implemented suggestions:
|
||||
|
||||
- Direct implementation - when the user directly applies the suggestion by clicking the `Apply` checkbox.
|
||||
- Indirect implementation - when the user implements the suggestion in their IDE environment. In this case, Qodo Merge will utilize, after each commit, a dedicated logic to identify if a suggestion was implemented, and will mark it as implemented.
|
||||
@ -67,10 +69,10 @@ In post-process, Qodo Merge counts the number of suggestions that were implement
|
||||
|
||||
{width=512}
|
||||
|
||||
## Suggestion tracking 💎
|
||||
`Platforms supported: GitHub, GitLab`
|
||||
## Suggestion tracking
|
||||
>`💎 feature. Platforms supported: GitHub, GitLab`
|
||||
|
||||
Qodo Merge employs an novel detection system to automatically [identify](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) AI code suggestions that PR authors have accepted and implemented.
|
||||
Qodo Merge employs a novel detection system to automatically [identify](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) AI code suggestions that PR authors have accepted and implemented.
|
||||
|
||||
Accepted suggestions are also automatically documented in a dedicated wiki page called `.pr_agent_accepted_suggestions`, allowing users to track historical changes, assess the tool's effectiveness, and learn from previously implemented recommendations in the repository.
|
||||
An example [result](https://github.com/Codium-ai/pr-agent/wiki/.pr_agent_accepted_suggestions):
|
||||
@ -101,8 +103,6 @@ The `improve` tool can be further customized by providing additional instruction
|
||||
|
||||
### Extra instructions
|
||||
|
||||
>`Platforms supported: GitHub, GitLab, Bitbucket, Azure DevOps`
|
||||
|
||||
You can use the `extra_instructions` configuration option to give the AI model additional instructions for the `improve` tool.
|
||||
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter.
|
||||
|
||||
@ -118,14 +118,14 @@ extra_instructions="""\
|
||||
```
|
||||
Use triple quotes to write multi-line instructions. Use bullet points or numbers to make the instructions more readable.
|
||||
|
||||
### Best practices 💎
|
||||
### Best practices
|
||||
|
||||
>`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
> `💎 feature. Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
Another option to give additional guidance to the AI model is by creating a `best_practices.md` file, either in your repository's root directory or as a [**wiki page**](https://github.com/Codium-ai/pr-agent/wiki) (we recommend the wiki page, as editing and maintaining it over time is easier).
|
||||
Another option to give additional guidance to the AI model is by creating a `best_practices.md` file in your repository's root directory.
|
||||
This page can contain a list of best practices, coding standards, and guidelines that are specific to your repo/organization.
|
||||
|
||||
The AI model will use this wiki page as a reference, and in case the PR code violates any of the guidelines, it will create additional suggestions, with a dedicated label: `Organization
|
||||
The AI model will use this `best_practices.md` file as a reference, and in case the PR code violates any of the guidelines, it will create additional suggestions, with a dedicated label: `Organization
|
||||
best practice`.
|
||||
|
||||
Example for a python `best_practices.md` content:
|
||||
@ -149,16 +149,16 @@ Tips for writing an effective `best_practices.md` file:
|
||||
- Long files tend to contain generic guidelines already known to AI
|
||||
|
||||
#### Local and global best practices
|
||||
By default, Qodo Merge will look for a local `best_practices.md` wiki file in the root of the relevant local repo.
|
||||
By default, Qodo Merge will look for a local `best_practices.md` in the root of the relevant local repo.
|
||||
|
||||
If you want to enable also a global `best_practices.md` wiki file, set first in the global configuration file:
|
||||
If you want to enable also a global `best_practices.md` file, set first in the global configuration file:
|
||||
|
||||
```toml
|
||||
[best_practices]
|
||||
enable_global_best_practices = true
|
||||
```
|
||||
|
||||
Then, create a `best_practices.md` wiki file in the root of [global](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#global-configuration-file) configuration repository, `pr-agent-settings`.
|
||||
Then, create a `best_practices.md` file in the root of [global](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#global-configuration-file) configuration repository, `pr-agent-settings`.
|
||||
|
||||
#### Best practices for multiple languages
|
||||
For a git organization working with multiple programming languages, you can maintain a centralized global `best_practices.md` file containing language-specific guidelines.
|
||||
@ -191,8 +191,45 @@ And the label will be: `{organization_name} best practice`.
|
||||
|
||||
{width=512}
|
||||
|
||||
### Auto best practices
|
||||
|
||||
### How to combine `extra instructions` and `best practices`
|
||||
>`💎 feature. Platforms supported: GitHub.`
|
||||
|
||||
`Auto best practices` is a novel Qodo Merge capability that:
|
||||
|
||||
1. Identifies recurring patterns from accepted suggestions
|
||||
2. **Automatically** generates [best practices page](https://github.com/qodo-ai/pr-agent/wiki/.pr_agent_auto_best_practices) based on what your team consistently values
|
||||
3. Applies these learned patterns to future code reviews
|
||||
|
||||
This creates an automatic feedback loop where the system continuously learns from your team's choices to provide increasingly relevant suggestions.
|
||||
The system maintains two analysis phases:
|
||||
|
||||
- Open exploration for new issues
|
||||
- Targeted checking against established best practices
|
||||
|
||||
Note that when a [custom best practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) exist, Qodo Merge will still generate an 'auto best practices' wiki file, though it won't use it in the `improve` tool.
|
||||
Learn more about utilizing 'auto best practices' in our [detailed guide](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/).
|
||||
|
||||
#### Relevant configurations
|
||||
|
||||
```toml
|
||||
[auto_best_practices]
|
||||
# Disable all auto best practices usage or generation
|
||||
enable_auto_best_practices = true
|
||||
|
||||
# Disable usage of auto best practices file in the 'improve' tool
|
||||
utilize_auto_best_practices = true
|
||||
|
||||
# Extra instructions to the auto best practices generation prompt
|
||||
extra_instructions = ""
|
||||
|
||||
# Max number of patterns to be detected
|
||||
max_patterns = 5
|
||||
```
|
||||
|
||||
|
||||
### Combining 'extra instructions' and 'best practices'
|
||||
> `💎 feature`
|
||||
|
||||
The `extra instructions` configuration is more related to the `improve` tool prompt. It can be used, for example, to avoid specific suggestions ("Don't suggest to add try-except block", "Ignore changes in toml files", ...) or to emphasize specific aspects or formats ("Answer in Japanese", "Give only short suggestions", ...)
|
||||
|
||||
@ -231,6 +268,8 @@ dual_publishing_score_threshold = x
|
||||
Where x represents the minimum score threshold (>=) for suggestions to be presented as commitable PR comments in addition to the table. Default is -1 (disabled).
|
||||
|
||||
### Self-review
|
||||
> `💎 feature` Platforms supported: GitHub, GitLab
|
||||
|
||||
If you set in a configuration file:
|
||||
```toml
|
||||
[pr_code_suggestions]
|
||||
@ -274,21 +313,74 @@ code_suggestions_self_review_text = "... (your text here) ..."
|
||||
|
||||
To prevent unauthorized approvals, this configuration defaults to false, and cannot be altered through online comments; enabling requires a direct update to the configuration file and a commit to the repository. This ensures that utilizing the feature demands a deliberate documented decision by the repository owner.
|
||||
|
||||
### Auto-approval
|
||||
> `💎 feature. Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
Under specific conditions, Qodo Merge can auto-approve a PR when a specific comment is invoked, or when the PR meets certain criteria.
|
||||
|
||||
**To ensure safety, the auto-approval feature is disabled by default.**
|
||||
To enable auto-approval features, you need to actively set one or both of the following options in a pre-defined _configuration file_:
|
||||
```toml
|
||||
[config]
|
||||
enable_comment_approval = true # For approval via comments
|
||||
enable_auto_approval = true # For criteria-based auto-approval
|
||||
```
|
||||
|
||||
!!! note "Notes"
|
||||
- Note that this specific flag cannot be set with a command line argument, only in the configuration file, committed to the repository.
|
||||
- Enabling auto-approval must be a deliberate decision by the repository owner.
|
||||
|
||||
1\. **Auto-approval by commenting**
|
||||
|
||||
To enable auto-approval by commenting, set in the configuration file:
|
||||
```toml
|
||||
[config]
|
||||
enable_comment_approval = true
|
||||
```
|
||||
|
||||
After enabling, by commenting on a PR:
|
||||
```
|
||||
/review auto_approve
|
||||
```
|
||||
Qodo Merge will automatically approve the PR, and add a comment with the approval.
|
||||
|
||||
2\. **Auto-approval when the PR meets certain criteria**
|
||||
|
||||
To enable auto-approval based on specific criteria, first, you need to enable the top-level flag:
|
||||
```toml
|
||||
[config]
|
||||
enable_auto_approval = true
|
||||
```
|
||||
|
||||
There are two criteria that can be set for auto-approval:
|
||||
|
||||
- **Review effort score**
|
||||
```toml
|
||||
[config]
|
||||
enable_auto_approval = true
|
||||
auto_approve_for_low_review_effort = X # X is a number between 1 to 5
|
||||
```
|
||||
When the [review effort score](https://www.qodo.ai/images/pr_agent/review3.png) is lower or equal to X, the PR will be auto-approved.
|
||||
|
||||
___
|
||||
- **No code suggestions**
|
||||
```toml
|
||||
[config]
|
||||
enable_auto_approval = true
|
||||
auto_approve_for_no_suggestions = true
|
||||
```
|
||||
When no [code suggestion](https://www.qodo.ai/images/pr_agent/code_suggestions_as_comment_closed.png) were found for the PR, the PR will be auto-approved.
|
||||
|
||||
### How many code suggestions are generated?
|
||||
Qodo Merge uses a dynamic strategy to generate code suggestions based on the size of the pull request (PR). Here's how it works:
|
||||
|
||||
1) Chunking large PRs:
|
||||
|
||||
#### 1. Chunking large PRs
|
||||
- Qodo Merge divides large PRs into 'chunks'.
|
||||
- Each chunk contains up to `pr_code_suggestions.max_context_tokens` tokens (default: 14,000).
|
||||
|
||||
|
||||
2) Generating suggestions:
|
||||
|
||||
#### 2. Generating suggestions
|
||||
- For each chunk, Qodo Merge generates up to `pr_code_suggestions.num_code_suggestions_per_chunk` suggestions (default: 4).
|
||||
|
||||
|
||||
This approach has two main benefits:
|
||||
|
||||
- Scalability: The number of suggestions scales with the PR size, rather than being fixed.
|
||||
@ -320,7 +412,7 @@ Note: Chunking is primarily relevant for large PRs. For most PRs (up to 500 line
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>persistent_comment</b></td>
|
||||
<td>If set to true, the improve comment will be persistent, meaning that every new improve request will edit the previous one. Default is false.</td>
|
||||
<td>If set to true, the improve comment will be persistent, meaning that every new improve request will edit the previous one. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>suggestions_score_threshold</b></td>
|
||||
@ -330,6 +422,10 @@ Note: Chunking is primarily relevant for large PRs. For most PRs (up to 500 line
|
||||
<td><b>apply_suggestions_checkbox</b></td>
|
||||
<td> Enable the checkbox to create a committable suggestion. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>enable_more_suggestions_checkbox</b></td>
|
||||
<td> Enable the checkbox to generate more suggestions. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>enable_help_text</b></td>
|
||||
<td>If set to true, the tool will display a help text in the comment. Default is true.</td>
|
||||
@ -344,7 +440,7 @@ Note: Chunking is primarily relevant for large PRs. For most PRs (up to 500 line
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>allow_thumbs_up_down</b></td>
|
||||
<td>If set to true, all code suggestions will have thumbs up and thumbs down buttons, to encourage users to provide feedback on the suggestions. Default is false.</td>
|
||||
<td>If set to true, all code suggestions will have thumbs up and thumbs down buttons, to encourage users to provide feedback on the suggestions. Default is false. Note that this feature is for statistics tracking. It will not affect future feedback from the AI model.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -365,14 +461,12 @@ Note: Chunking is primarily relevant for large PRs. For most PRs (up to 500 line
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## A note on code suggestions quality
|
||||
## Understanding AI Code Suggestions
|
||||
|
||||
- AI models for code are getting better and better (Sonnet-3.5 and GPT-4), but they are not flawless. Not all the suggestions will be perfect, and a user should not accept all of them automatically. Critical reading and judgment are required.
|
||||
- While mistakes of the AI are rare but can happen, a real benefit from the suggestions of the `improve` (and [`review`](https://qodo-merge-docs.qodo.ai/tools/review/)) tool is to catch, with high probability, **mistakes or bugs done by the PR author**, when they happen. So, it's a good practice to spend the needed ~30-60 seconds to review the suggestions, even if not all of them are always relevant.
|
||||
- The hierarchical structure of the suggestions is designed to help the user to _quickly_ understand them, and to decide which ones are relevant and which are not:
|
||||
|
||||
- Only if the `Category` header is relevant, the user should move to the summarized suggestion description
|
||||
- Only if the summarized suggestion description is relevant, the user should click on the collapsible, to read the full suggestion description with a code preview example.
|
||||
|
||||
- In addition, we recommend to use the [`extra_instructions`](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) field to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
- The interactive [PR chat](https://qodo-merge-docs.qodo.ai/chrome-extension/) also provides an easy way to get more tailored suggestions and feedback from the AI model.
|
||||
- **AI Limitations:** AI models for code are getting better and better, but they are not flawless. Not all the suggestions will be perfect, and a user should not accept all of them automatically. Critical reading and judgment are required. Mistakes of the AI are rare but can happen, and it is usually quite easy for a human to spot them.
|
||||
- **Purpose of Suggestions:**
|
||||
- **Self-reflection:** The suggestions aim to enable developers to _self-reflect_ and improve their pull requests. This process can help to identify blind spots, uncover missed edge cases, and enhance code readability and coherency. Even when a specific code suggestion isn't suitable, the underlying issue it highlights often reveals something important that might deserve attention.
|
||||
- **Bug detection:** The suggestions also alert on any _critical bugs_ that may have been identified during the analysis. This provides an additional safety net to catch potential issues before they make it into production. It's perfectly acceptable to implement only the suggestions you find valuable for your specific context.
|
||||
- **Hierarchy:** Presenting the suggestions in a structured hierarchical table enables the user to _quickly_ understand them, and to decide which ones are relevant and which are not.
|
||||
- **Customization:** To guide the model to suggestions that are more relevant to the specific needs of your project, we recommend to use the [`extra_instructions`](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) and [`best practices`](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) fields.
|
||||
- **Interactive usage:** The interactive [PR chat](https://qodo-merge-docs.qodo.ai/chrome-extension/) also provides an easy way to get more tailored suggestions and feedback from the AI model.
|
||||
|
@ -18,9 +18,9 @@ The tool will generate code suggestions for the selected component (if no compon
|
||||
|
||||
{width=768}
|
||||
|
||||
**Notes**
|
||||
- Language that are currently supported by the tool: Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
!!! note "Notes"
|
||||
- Language that are currently supported by the tool: Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
|
||||
## Configuration options
|
||||
- `num_code_suggestions`: number of code suggestions to provide. Default is 4
|
||||
|
@ -9,7 +9,6 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how
|
||||
| **[Code Suggestions (`/improve`](./improve.md))** | Code suggestions for improving the PR |
|
||||
| **[Question Answering (`/ask ...`](./ask.md))** | Answering free-text questions about the PR, or on specific code lines |
|
||||
| **[Update Changelog (`/update_changelog`](./update_changelog.md))** | Automatically updating the CHANGELOG.md file with the PR changes |
|
||||
| **[Find Similar Issue (`/similar_issue`](./similar_issues.md))** | Automatically retrieves and presents similar issues |
|
||||
| **[Help (`/help`](./help.md))** | Provides a list of all the available tools. Also enables to trigger them interactively (💎) |
|
||||
| **💎 [Add Documentation (`/add_docs`](./documentation.md))** | Generates documentation to methods/functions/classes that changed in the PR |
|
||||
| **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user |
|
||||
@ -20,4 +19,4 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how
|
||||
| **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR |
|
||||
| **💎 [CI Feedback (`/checks ci_job`](./ci_feedback.md))** | Automatically generates feedback and analysis for a failed CI job |
|
||||
| **💎 [Implement (`/implement`](./implement.md))** | Generates implementation code from review suggestions |
|
||||
Note that the tools marked with 💎 are available only for Qodo Merge Pro users.
|
||||
Note that the tools marked with 💎 are available only for Qodo Merge users.
|
||||
|
@ -51,7 +51,7 @@ extra_instructions = "..."
|
||||
|
||||
!!! example "General options"
|
||||
|
||||
<table>
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>persistent_comment</b></td>
|
||||
<td>If set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.</td>
|
||||
@ -68,11 +68,11 @@ extra_instructions = "..."
|
||||
<td><b>enable_help_text</b></td>
|
||||
<td>If set to true, the tool will display a help text in the comment. Default is true.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
!!! example "Enable\\disable specific sub-sections"
|
||||
|
||||
<table>
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>require_score_review</b></td>
|
||||
<td>If set to true, the tool will add a section that scores the PR. Default is false.</td>
|
||||
@ -97,13 +97,13 @@ extra_instructions = "..."
|
||||
<td><b>require_ticket_analysis_review</b></td>
|
||||
<td>If set to true, and the PR contains a GitHub or Jira ticket link, the tool will add a section that checks if the PR in fact fulfilled the ticket requirements. Default is true.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
!!! example "Adding PR labels"
|
||||
|
||||
You can enable\disable the `review` tool to add specific labels to the PR:
|
||||
You can enable\disable the `review` tool to add specific labels to the PR:
|
||||
|
||||
<table>
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>enable_review_labels_security</b></td>
|
||||
<td>If set to true, the tool will publish a 'possible security issue' label if it detects a security issue. Default is true.</td>
|
||||
@ -112,22 +112,8 @@ You can enable\disable the `review` tool to add specific labels to the PR:
|
||||
<td><b>enable_review_labels_effort</b></td>
|
||||
<td>If set to true, the tool will publish a 'Review effort [1-5]: x' label. Default is true.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
!!! example "Auto-approval"
|
||||
|
||||
If enabled, the `review` tool can approve a PR when a specific comment, `/review auto_approve`, is invoked.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>enable_auto_approval</b></td>
|
||||
<td>If set to true, the tool will approve the PR when invoked with the 'auto_approve' command. Default is false. This flag can be changed only from a configuration file.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>maximal_review_effort</b></td>
|
||||
<td>Maximal effort level for auto-approval. If the PR's estimated review effort is above this threshold, the auto-approval will not run. Default is 5.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Usage Tips
|
||||
|
||||
@ -179,30 +165,7 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
|
||||
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
|
||||
|
||||
|
||||
!!! tip "Auto-approval"
|
||||
|
||||
Qodo Merge can approve a PR when a specific comment is invoked.
|
||||
|
||||
To ensure safety, the auto-approval feature is disabled by default. To enable auto-approval, you need to actively set in a pre-defined configuration file the following:
|
||||
```
|
||||
[pr_reviewer]
|
||||
enable_auto_approval = true
|
||||
```
|
||||
(this specific flag cannot be set with a command line argument, only in the configuration file, committed to the repository)
|
||||
|
||||
|
||||
After enabling, by commenting on a PR:
|
||||
```
|
||||
/review auto_approve
|
||||
```
|
||||
Qodo Merge will automatically approve the PR, and add a comment with the approval.
|
||||
|
||||
|
||||
You can also enable auto-approval only if the PR meets certain requirements, such as that the `estimated_review_effort` label is equal or below a certain threshold, by adjusting the flag:
|
||||
```
|
||||
[pr_reviewer]
|
||||
maximal_review_effort = 5
|
||||
```
|
||||
|
||||
!!! tip "Code suggestions"
|
||||
|
||||
|
@ -16,14 +16,17 @@ It can be invoked manually by commenting on any PR:
|
||||
|
||||
Note that to perform retrieval, the `similar_issue` tool indexes all the repo previous issues (once).
|
||||
|
||||
### Selecting a Vector Database
|
||||
Configure your preferred database by changing the `pr_similar_issue` parameter in `configuration.toml` file.
|
||||
|
||||
**Select VectorDBs** by changing `pr_similar_issue` parameter in `configuration.toml` file
|
||||
#### Available Options
|
||||
Choose from the following Vector Databases:
|
||||
|
||||
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 Configuration
|
||||
To use Pinecone with the `similar issue` tool, add these credentials to `.secrets.toml` (or set as environment variables):
|
||||
|
||||
```
|
||||
[pinecone]
|
||||
|
@ -17,9 +17,9 @@ The tool will generate tests for the selected component (if no component is stat
|
||||
|
||||
(Example taken from [here](https://github.com/Codium-ai/pr-agent/pull/598#issuecomment-1913679429)):
|
||||
|
||||
**Notes** <br>
|
||||
- The following languages are currently supported: Python, Java, C++, JavaScript, TypeScript, C#. <br>
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
!!! note "Notes"
|
||||
- The following languages are currently supported: Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
|
||||
|
||||
## Configuration options
|
||||
|
@ -1,18 +0,0 @@
|
||||
## Qodo Merge Pro Models
|
||||
|
||||
The default models used by Qodo Merge Pro are a combination of Claude-3.5-sonnet and OpenAI's GPT-4 models.
|
||||
|
||||
Users can configure Qodo Merge Pro to use solely a specific model by editing the [configuration](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) file.
|
||||
|
||||
For example, to restrict Qodo Merge Pro to using only `Claude-3.5-sonnet`, add this setting:
|
||||
|
||||
```
|
||||
[config]
|
||||
model="claude-3-5-sonnet"
|
||||
```
|
||||
|
||||
Or to restrict Qodo Merge Pro to using only `GPT-4o`, add this setting:
|
||||
```
|
||||
[config]
|
||||
model="gpt-4o"
|
||||
```
|
@ -57,16 +57,40 @@ All Qodo Merge tools have a parameter called `extra_instructions`, that enables
|
||||
/update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..."
|
||||
```
|
||||
|
||||
## Working with large PRs
|
||||
## Language Settings
|
||||
|
||||
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 provides 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](../core-abilities/index.md).
|
||||
The default response language for Qodo Merge is **U.S. English**. However, some development teams may prefer to display information in a different language. For example, your team's workflow might improve if PR descriptions and code suggestions are set to your country's native language.
|
||||
|
||||
However, for very large PRs, or in case you want to emphasize quality over speed and cost, there are two possible solutions:
|
||||
1) [Use a model](https://qodo-merge-docs.qodo.ai/usage-guide/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](https://qodo-merge-docs.qodo.ai/tools/improve/) (`/improve --extended`),
|
||||
which divides the PR into chunks, and processes each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur)
|
||||
To configure this, set the `response_language` parameter in the configuration file. This will prompt the model to respond in the specified language. Use a **standard locale code** based on [ISO 3166](https://en.wikipedia.org/wiki/ISO_3166) (country codes) and [ISO 639](https://en.wikipedia.org/wiki/ISO_639) (language codes) to define a language-country pair. See this [comprehensive list of locale codes](https://simplelocalize.io/data/locales/).
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[config]
|
||||
response_language: "it-IT"
|
||||
```
|
||||
|
||||
This will set the response language globally for all the commands to Italian.
|
||||
|
||||
> **Important:** Note that only dynamic text generated by the AI model is translated to the configured language. Static text such as labels and table headers that are not part of the AI models response will remain in US English. In addition, the model you are using must have good support for the specified language.
|
||||
|
||||
[//]: # (## 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 provides 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](../core-abilities/index.md).)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (However, for very large PRs, or in case you want to emphasize quality over speed and cost, there are two possible solutions:)
|
||||
|
||||
[//]: # (1) [Use a model](https://qodo-merge-docs.qodo.ai/usage-guide/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](https://qodo-merge-docs.qodo.ai/tools/improve/) (`/improve --extended`),)
|
||||
|
||||
[//]: # (which divides the PR into chunks, and processes each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur))
|
||||
|
||||
|
||||
|
||||
@ -142,13 +166,11 @@ Qodo Merge allows you to automatically ignore certain PRs based on various crite
|
||||
|
||||
- PRs with specific titles (using regex matching)
|
||||
- PRs between specific branches (using regex matching)
|
||||
- PRs that don't include changes from specific folders (using regex matching)
|
||||
- PRs not from specific folders
|
||||
- PRs containing specific labels
|
||||
- PRs opened by specific users
|
||||
|
||||
### Example usage
|
||||
|
||||
#### Ignoring PRs with specific titles
|
||||
### Ignoring PRs with specific titles
|
||||
|
||||
To ignore PRs with a specific title such as "[Bump]: ...", you can add the following to your `configuration.toml` file:
|
||||
|
||||
@ -159,7 +181,7 @@ ignore_pr_title = ["\\[Bump\\]"]
|
||||
|
||||
Where the `ignore_pr_title` is a list of regex patterns to match the PR title you want to ignore. Default is `ignore_pr_title = ["^\\[Auto\\]", "^Auto"]`.
|
||||
|
||||
#### Ignoring PRs between specific branches
|
||||
### Ignoring PRs between specific branches
|
||||
|
||||
To ignore PRs from specific source or target branches, you can add the following to your `configuration.toml` file:
|
||||
|
||||
@ -172,7 +194,7 @@ ignore_pr_target_branches = ["qa"]
|
||||
Where the `ignore_pr_source_branches` and `ignore_pr_target_branches` are lists of regex patterns to match the source and target branches you want to ignore.
|
||||
They are not mutually exclusive, you can use them together or separately.
|
||||
|
||||
#### Ignoring PRs that don't include changes from specific folders
|
||||
### Ignoring PRs not from specific folders
|
||||
|
||||
To allow only specific folders (often needed in large monorepos), set:
|
||||
|
||||
@ -181,11 +203,11 @@ To allow only specific folders (often needed in large monorepos), set:
|
||||
allow_only_specific_folders=['folder1','folder2']
|
||||
```
|
||||
|
||||
For the configuration above, automatic feedback will only be triggered when the PR changes include files from 'folder1' or 'folder2'
|
||||
For the configuration above, automatic feedback will only be triggered when the PR changes include files where 'folder1' or 'folder2' is in the file path
|
||||
|
||||
#### Ignoring PRs containg specific labels
|
||||
### Ignoring PRs containing specific labels
|
||||
|
||||
To ignore PRs containg specific labels, you can add the following to your `configuration.toml` file:
|
||||
To ignore PRs containing specific labels, you can add the following to your `configuration.toml` file:
|
||||
|
||||
```
|
||||
[config]
|
||||
@ -194,7 +216,7 @@ ignore_pr_labels = ["do-not-merge"]
|
||||
|
||||
Where the `ignore_pr_labels` is a list of labels that when present in the PR, the PR will be ignored.
|
||||
|
||||
#### Ignoring PRs from specific users
|
||||
### Ignoring PRs from specific users
|
||||
|
||||
Qodo Merge automatically identifies and ignores pull requests created by bots using:
|
||||
|
||||
|
@ -7,19 +7,18 @@ Examples of invoking the different tools via the CLI:
|
||||
- **Describe**: `python -m pr_agent.cli --pr_url=<pr_url> describe`
|
||||
- **Improve**: `python -m pr_agent.cli --pr_url=<pr_url> improve`
|
||||
- **Ask**: `python -m pr_agent.cli --pr_url=<pr_url> ask "Write me a poem about this PR"`
|
||||
- **Reflect**: `python -m pr_agent.cli --pr_url=<pr_url> reflect`
|
||||
- **Update Changelog**: `python -m pr_agent.cli --pr_url=<pr_url> update_changelog`
|
||||
|
||||
`<pr_url>` is the url of the relevant PR (for example: [#50](https://github.com/Codium-ai/pr-agent/pull/50)).
|
||||
|
||||
**Notes:**
|
||||
|
||||
(1) in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line:
|
||||
1. in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line:
|
||||
```
|
||||
python -m pr_agent.cli --pr_url=<pr_url> /review --pr_reviewer.extra_instructions="focus on the file: ..."
|
||||
```
|
||||
|
||||
(2) You can print results locally, without publishing them, by setting in `configuration.toml`:
|
||||
2. You can print results locally, without publishing them, by setting in `configuration.toml`:
|
||||
```
|
||||
[config]
|
||||
publish_output=false
|
||||
@ -27,14 +26,9 @@ verbosity_level=2
|
||||
```
|
||||
This is useful for debugging or experimenting with different tools.
|
||||
|
||||
(3)
|
||||
3. **git provider**: The [git_provider](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L5) field in a configuration file determines the GIT provider that will be used by Qodo Merge. Currently, the following providers are supported:
|
||||
`github` **(default)**, `gitlab`, `bitbucket`, `azure`, `codecommit`, `local`, and `gerrit`.
|
||||
|
||||
**git provider**: The [git_provider](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L5) field in a configuration file determines the GIT provider that will be used by Qodo Merge. Currently, the following providers are supported:
|
||||
`
|
||||
"github", "gitlab", "bitbucket", "azure", "codecommit", "local", "gerrit"
|
||||
`
|
||||
|
||||
Default is "github".
|
||||
|
||||
### CLI Health Check
|
||||
To verify that Qodo Merge has been configured correctly, you can run this health check command from the repository root:
|
||||
@ -67,7 +61,6 @@ Commands for invoking the different tools via comments:
|
||||
- **Describe**: `/describe`
|
||||
- **Improve**: `/improve` (or `/improve_code` for bitbucket, since `/improve` is sometimes reserved)
|
||||
- **Ask**: `/ask "..."`
|
||||
- **Reflect**: `/reflect`
|
||||
- **Update Changelog**: `/update_changelog`
|
||||
|
||||
|
||||
@ -95,8 +88,8 @@ When this parameter is set to `true`, Qodo Merge will not run any automatic tool
|
||||
|
||||
### GitHub App
|
||||
|
||||
!!! note "Configurations for Qodo Merge Pro"
|
||||
Qodo Merge Pro for GitHub is an App, hosted by CodiumAI. So all the instructions below are relevant also for Qodo Merge Pro users.
|
||||
!!! note "Configurations for Qodo Merge"
|
||||
Qodo Merge for GitHub is an App, hosted by Qodo. So all the instructions below are relevant also for Qodo Merge users.
|
||||
Same goes for [GitLab webhook](#gitlab-webhook) and [BitBucket App](#bitbucket-app) sections.
|
||||
|
||||
#### GitHub app automatic tools when a new PR is opened
|
||||
@ -173,7 +166,7 @@ If not set, the default configuration is `["opened", "reopened", "ready_for_revi
|
||||
|
||||
`github_action_config.enable_output` are used to enable/disable github actions [output parameter](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions) (default is `true`).
|
||||
Review result is output as JSON to `steps.{step-id}.outputs.review` property.
|
||||
The JSON structure is equivalent to the yaml data structure defined in [pr_reviewer_prompts.toml](https://github.com/idubnori/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml).
|
||||
The JSON structure is equivalent to the yaml data structure defined in [pr_reviewer_prompts.toml](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml).
|
||||
|
||||
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` [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#global-configuration-file) in the root of your repo
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
## Changing a model
|
||||
## Changing a model in PR-Agent
|
||||
|
||||
See [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py) for a list of available models.
|
||||
To use a different model than the default (GPT-4), you need to edit in the [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L2) the fields:
|
||||
@ -30,6 +30,14 @@ model="" # the OpenAI model you've deployed on Azure (e.g. gpt-4o)
|
||||
fallback_models=["..."]
|
||||
```
|
||||
|
||||
Passing custom headers to the underlying LLM Model API can be done by setting extra_headers parameter to litellm.
|
||||
```
|
||||
[litellm]
|
||||
extra_headers='{"projectId": "<authorized projectId >", ...}') #The value of this setting should be a JSON string representing the desired headers, a ValueError is thrown otherwise.
|
||||
```
|
||||
This enables users to pass authorization tokens or API keys, when routing requests through an API management gateway.
|
||||
|
||||
|
||||
### Ollama
|
||||
|
||||
You can run models locally through either [VLLM](https://docs.litellm.ai/docs/providers/vllm) or [Ollama](https://docs.litellm.ai/docs/providers/ollama)
|
||||
@ -46,12 +54,16 @@ duplicate_examples=true # will duplicate the examples in the prompt, to help the
|
||||
api_base = "http://localhost:11434" # or whatever port you're running Ollama on
|
||||
```
|
||||
|
||||
By default, Ollama uses a context window size of 2048 tokens. In most cases this is not enough to cover pr-agent promt and pull-request diff. Context window size can be overridden with the `OLLAMA_CONTEXT_LENGTH` environment variable. For example, to set the default context length to 8K, use: `OLLAMA_CONTEXT_LENGTH=8192 ollama serve`. More information you can find on the [official ollama faq](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-specify-the-context-window-size).
|
||||
|
||||
Please note that the `custom_model_max_tokens` setting should be configured in accordance with the `OLLAMA_CONTEXT_LENGTH`. Failure to do so may result in unexpected model output.
|
||||
|
||||
!!! note "Local models vs commercial models"
|
||||
Qodo Merge is compatible with almost any AI model, but analyzing complex code repositories and pull requests requires a model specifically optimized for code analysis.
|
||||
|
||||
Commercial models such as GPT-4, Claude Sonnet, and Gemini have demonstrated robust capabilities in generating structured output for code analysis tasks with large input. In contrast, most open-source models currently available (as of January 2025) face challenges with these complex tasks.
|
||||
|
||||
Based on our testing, local open-source models are suitable for experimentation and learning purposes, but they are not suitable for production-level code analysis tasks.
|
||||
Based on our testing, local open-source models are suitable for experimentation and learning purposes (mainly for the `ask` command), but they are not suitable for production-level code analysis tasks.
|
||||
|
||||
Hence, for production workflows and real-world usage, we recommend using commercial models.
|
||||
|
||||
@ -185,19 +197,51 @@ key = ...
|
||||
|
||||
(you can obtain a deepseek-chat key from [here](https://platform.deepseek.com))
|
||||
|
||||
|
||||
### DeepInfra
|
||||
|
||||
To use DeepSeek model with DeepInfra, for example, set:
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B"
|
||||
fallback_models = ["deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"]
|
||||
[deepinfra] # in .secrets.toml
|
||||
key = ... # your DeepInfra api key
|
||||
```
|
||||
(you can obtain a DeepInfra key from [here](https://deepinfra.com/dash/api_keys))
|
||||
|
||||
### Custom models
|
||||
|
||||
If the relevant model doesn't appear [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py), you can still use it as a custom model:
|
||||
|
||||
(1) Set the model name in the configuration file:
|
||||
1. Set the model name in the configuration file:
|
||||
```
|
||||
[config]
|
||||
model="custom_model_name"
|
||||
fallback_models=["custom_model_name"]
|
||||
```
|
||||
(2) Set the maximal tokens for the model:
|
||||
2. Set the maximal tokens for the model:
|
||||
```
|
||||
[config]
|
||||
custom_model_max_tokens= ...
|
||||
```
|
||||
(3) Go to [litellm documentation](https://litellm.vercel.app/docs/proxy/quick_start#supported-llms), find the model you want to use, and set the relevant environment variables.
|
||||
3. Go to [litellm documentation](https://litellm.vercel.app/docs/proxy/quick_start#supported-llms), find the model you want to use, and set the relevant environment variables.
|
||||
|
||||
4. Most reasoning models do not support chat-style inputs (`system` and `user` messages) or temperature settings.
|
||||
To bypass chat templates and temperature controls, set `config.custom_reasoning_model = true` in your configuration file.
|
||||
|
||||
## Dedicated parameters
|
||||
|
||||
### OpenAI models
|
||||
|
||||
[config]
|
||||
reasoning_efffort= = "medium" # "low", "medium", "high"
|
||||
|
||||
With the OpenAI models that support reasoning effort (eg: o3-mini), you can specify its reasoning effort via `config` section. The default value is `medium`. You can change it to `high` or `low` based on your usage.
|
||||
|
||||
### Anthropic models
|
||||
|
||||
[config]
|
||||
enable_claude_extended_thinking = false # Set to true to enable extended thinking feature
|
||||
extended_thinking_budget_tokens = 2048
|
||||
extended_thinking_max_output_tokens = 4096
|
||||
|
@ -20,7 +20,7 @@ In terms of precedence, wiki configurations will override local configurations,
|
||||
|
||||
`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
With Qodo Merge Pro, you can set configurations by creating a page called `.pr_agent.toml` in the [wiki](https://github.com/Codium-ai/pr-agent/wiki/pr_agent.toml) of the repo.
|
||||
With Qodo Merge, you can set configurations by creating a page called `.pr_agent.toml` in the [wiki](https://github.com/Codium-ai/pr-agent/wiki/pr_agent.toml) of the repo.
|
||||
The advantage of this method is that it allows to set configurations without needing to commit new content to the repo - just edit the wiki page and **save**.
|
||||
|
||||
|
||||
@ -69,3 +69,29 @@ For example, in the GitHub organization `Codium-ai`:
|
||||
- The file [`https://github.com/Codium-ai/pr-agent-settings/.pr_agent.toml`](https://github.com/Codium-ai/pr-agent-settings/blob/main/.pr_agent.toml) 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`.
|
||||
|
||||
### Bitbucket Organization level configuration file 💎
|
||||
`Relevant platforms: Bitbucket Cloud, Bitbucket Data Center`
|
||||
|
||||
In Bitbucket, there are two levels where you can define a global configuration file:
|
||||
|
||||
* Project-level global configuration:
|
||||
|
||||
Create a repository named `pr-agent-settings` within a specific project. The configuration file in this repository will apply to all repositories under the same project.
|
||||
|
||||
* Organization-level global configuration:
|
||||
|
||||
Create a dedicated project to hold a global configuration file that affects all repositories across all projects in your organization.
|
||||
|
||||
**Setting up organization-level global configuration:**
|
||||
|
||||
1. Create a new project with both the name and key: PR_AGENT_SETTINGS.
|
||||
2. Inside the PR_AGENT_SETTINGS project, create a repository named pr-agent-settings.
|
||||
3. In this repository, add a `.pr_agent.toml` configuration file—structured similarly to the global configuration file described above.
|
||||
4. Optionally, you can add organizational-level [global best practices file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#global-configuration-file).
|
||||
|
||||
Repositories across your entire Bitbucket organization will inherit the configuration from this file.
|
||||
|
||||
!!! note "Note"
|
||||
If both organization-level and project-level global settings are defined, the project-level settings will take precedence over the organization-level configuration. Additionally, parameters from a repository’s local .pr_agent.toml file will always override both global settings.
|
||||
|
||||
|
@ -3,12 +3,11 @@
|
||||
|
||||
For optimal functionality of Qodo Merge, we recommend enabling a wiki for each repository where Qodo Merge is installed. The wiki serves several important purposes:
|
||||
|
||||
**Key Wiki Features:**
|
||||
**Key Wiki Features: 💎**
|
||||
|
||||
- Storing a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#wiki-configuration-file)
|
||||
- Defining a [`best_practices.md`](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) file
|
||||
- Track [accepted suggestions](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking)
|
||||
- Facilitates learning over time by creating an [auto_best_practices.md]() file
|
||||
- Facilitates learning over time by creating an [auto_best_practices.md](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices) file
|
||||
|
||||
|
||||
**Setup Instructions (GitHub):**
|
||||
@ -21,4 +20,13 @@ To enable a wiki for your repository:
|
||||
4. Enable the "Wikis" option by checking the corresponding box
|
||||
5. Return to your repository's main page
|
||||
6. Look for the newly added "Wiki" tab in the top navigation
|
||||
7. Initialize your wiki by clicking "Create the first page" (this step is important - without creating an initial page, the wiki will not be fully functional)
|
||||
7. Initialize your wiki by clicking "Create the first page" and saving (this step is important - without creating an initial page, the wiki will not be fully functional)
|
||||
|
||||
### Why Wiki?
|
||||
|
||||
- Your code (and its derivatives, including accepted code suggestions) is yours. Qodo Merge will never store it on external servers.
|
||||
- Repository changes typically require pull requests, which create overhead and are time-consuming. This process is too cumbersome for auto data aggregation, and is not very convenient even for managing frequently updated content like configuration files.
|
||||
- A repository wiki page provides an ideal balance:
|
||||
- It lives within your repository, making it suitable for code-related documentation
|
||||
- It enables quick updates without the overhead of pull requests
|
||||
- It maintains full Git version control, allowing you to track changes over time.
|
||||
|
@ -24,4 +24,4 @@ It includes information on how to adjust Qodo Merge configurations, define which
|
||||
- [Changing a model](./additional_configurations.md#changing-a-model)
|
||||
- [Patch Extra Lines](./additional_configurations.md#patch-extra-lines)
|
||||
- [Editing the prompts](./additional_configurations.md#editing-the-prompts)
|
||||
- [Qodo Merge Pro Models](./PR_agent_pro_models.md)
|
||||
- [Qodo Merge Models](./qodo_merge_models)
|
||||
|
32
docs/docs/usage-guide/qodo_merge_models.md
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
The default model used by Qodo Merge (March 2025) is Claude Sonnet 3.7.
|
||||
|
||||
### Selecting a Specific Model
|
||||
|
||||
Users can configure Qodo Merge to use a specific model by editing the [configuration](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) file.
|
||||
The models supported by Qodo Merge are:
|
||||
|
||||
- `claude-3-7-sonnet` (default)
|
||||
- `o3-mini`
|
||||
- `gpt-4o`
|
||||
- `deepseek/r1`
|
||||
|
||||
To restrict Qodo Merge to using only `o3-mini`, add this setting:
|
||||
```
|
||||
[config]
|
||||
model="o3-mini"
|
||||
```
|
||||
|
||||
To restrict Qodo Merge to using only `GPT-4o`, add this setting:
|
||||
```
|
||||
[config]
|
||||
model="gpt-4o"
|
||||
```
|
||||
|
||||
To restrict Qodo Merge to using only `deepseek-r1` us-hosted, add this setting:
|
||||
```
|
||||
[config]
|
||||
model="deepseek/r1"
|
||||
```
|
||||
|
||||
|
@ -1,20 +1,16 @@
|
||||
site_name: Qodo Merge (formerly known as PR-Agent)
|
||||
repo_url: https://github.com/Codium-ai/pr-agent
|
||||
repo_name: Codium-ai/pr-agent
|
||||
site_name: Qodo Merge (and open-source PR-Agent)
|
||||
repo_url: https://github.com/qodo-ai/pr-agent
|
||||
repo_name: Qodo-ai/pr-agent
|
||||
|
||||
nav:
|
||||
- Overview:
|
||||
- 'index.md'
|
||||
- 💎 Qodo Merge Pro: 'overview/pr_agent_pro.md'
|
||||
- 💎 Qodo Merge: 'overview/pr_agent_pro.md'
|
||||
- Data Privacy: 'overview/data_privacy.md'
|
||||
- Installation:
|
||||
- 'installation/index.md'
|
||||
- Locally: 'installation/locally.md'
|
||||
- GitHub: 'installation/github.md'
|
||||
- GitLab: 'installation/gitlab.md'
|
||||
- BitBucket: 'installation/bitbucket.md'
|
||||
- Azure DevOps: 'installation/azure.md'
|
||||
- 💎 Qodo Merge Pro: 'installation/pr_agent_pro.md'
|
||||
- PR-Agent: 'installation/pr_agent.md'
|
||||
- 💎 Qodo Merge: 'installation/qodo_merge.md'
|
||||
- Usage Guide:
|
||||
- 'usage-guide/index.md'
|
||||
- Introduction: 'usage-guide/introduction.md'
|
||||
@ -24,7 +20,7 @@ nav:
|
||||
- Managing Mail Notifications: 'usage-guide/mail_notifications.md'
|
||||
- Changing a Model: 'usage-guide/changing_a_model.md'
|
||||
- Additional Configurations: 'usage-guide/additional_configurations.md'
|
||||
- 💎 Qodo Merge Pro Models: 'usage-guide/PR_agent_pro_models'
|
||||
- 💎 Qodo Merge Models: 'usage-guide/qodo_merge_models.md'
|
||||
- Tools:
|
||||
- 'tools/index.md'
|
||||
- Describe: 'tools/describe.md'
|
||||
@ -32,7 +28,7 @@ nav:
|
||||
- Improve: 'tools/improve.md'
|
||||
- Ask: 'tools/ask.md'
|
||||
- Update Changelog: 'tools/update_changelog.md'
|
||||
- Similar Issues: 'tools/similar_issues.md'
|
||||
- Help Docs: 'tools/help_docs.md'
|
||||
- Help: 'tools/help.md'
|
||||
- 💎 Analyze: 'tools/analyze.md'
|
||||
- 💎 Test: 'tools/test.md'
|
||||
@ -46,13 +42,14 @@ nav:
|
||||
- Core Abilities:
|
||||
- 'core-abilities/index.md'
|
||||
- Fetching ticket context: 'core-abilities/fetching_ticket_context.md'
|
||||
- Auto best practices: 'core-abilities/auto_best_practices.md'
|
||||
- Local and global metadata: 'core-abilities/metadata.md'
|
||||
- Dynamic context: 'core-abilities/dynamic_context.md'
|
||||
- Self-reflection: 'core-abilities/self_reflection.md'
|
||||
- Impact evaluation: 'core-abilities/impact_evaluation.md'
|
||||
- Interactivity: 'core-abilities/interactivity.md'
|
||||
- Compression strategy: 'core-abilities/compression_strategy.md'
|
||||
- Code-oriented YAML: 'core-abilities/code_oriented_yaml.md'
|
||||
- Company Codebase: 'core-abilities/company_codebase.md'
|
||||
- Static code analysis: 'core-abilities/static_code_analysis.md'
|
||||
- Code Fine-tuning Benchmark: 'finetuning_benchmark/index.md'
|
||||
- Chrome Extension:
|
||||
@ -61,6 +58,7 @@ nav:
|
||||
- Data Privacy: 'chrome-extension/data_privacy.md'
|
||||
- FAQ:
|
||||
- FAQ: 'faq/index.md'
|
||||
- AI Docs Search: 'ai_search/index.md'
|
||||
# - Code Fine-tuning Benchmark: 'finetuning_benchmark/index.md'
|
||||
|
||||
theme:
|
||||
@ -156,4 +154,4 @@ markdown_extensions:
|
||||
|
||||
|
||||
copyright: |
|
||||
© 2024 <a href="https://www.codium.ai/" target="_blank" rel="noopener">CodiumAI</a>
|
||||
© 2025 <a href="https://www.codium.ai/" target="_blank" rel="noopener">QodoAI</a>
|
||||
|
@ -82,7 +82,7 @@
|
||||
|
||||
<footer class="wrapper">
|
||||
<div class="container">
|
||||
<p class="footer-text">© 2024 <a href="https://www.qodo.ai/" target="_blank" rel="noopener">Qodo</a></p>
|
||||
<p class="footer-text">© 2025 <a href="https://www.qodo.ai/" target="_blank" rel="noopener">Qodo</a></p>
|
||||
<div class="footer-links">
|
||||
<a href="https://qodo-gen-docs.qodo.ai/">Qodo Gen</a>
|
||||
<p>|</p>
|
||||
|
@ -3,6 +3,7 @@ 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.cli_args import CliArgs
|
||||
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
|
||||
@ -12,6 +13,7 @@ from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
||||
from pr_agent.tools.pr_config import PRConfig
|
||||
from pr_agent.tools.pr_description import PRDescription
|
||||
from pr_agent.tools.pr_generate_labels import PRGenerateLabels
|
||||
from pr_agent.tools.pr_help_docs import PRHelpDocs
|
||||
from pr_agent.tools.pr_help_message import PRHelpMessage
|
||||
from pr_agent.tools.pr_line_questions import PR_LineQuestions
|
||||
from pr_agent.tools.pr_questions import PRQuestions
|
||||
@ -38,11 +40,13 @@ command2class = {
|
||||
"similar_issue": PRSimilarIssue,
|
||||
"add_docs": PRAddDocs,
|
||||
"generate_labels": PRGenerateLabels,
|
||||
"help_docs": PRHelpDocs,
|
||||
}
|
||||
|
||||
commands = list(command2class.keys())
|
||||
|
||||
|
||||
|
||||
class PRAgent:
|
||||
def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
|
||||
self.ai_handler = ai_handler # will be initialized in run_action
|
||||
@ -60,30 +64,34 @@ class PRAgent:
|
||||
else:
|
||||
action, *args = request
|
||||
|
||||
forbidden_cli_args = ['enable_auto_approval', 'approve_pr_on_self_review', 'base_url', 'url', 'app_name', 'secret_provider',
|
||||
'git_provider', 'skip_keys', 'openai.key', 'ANALYTICS_FOLDER', 'uri', 'app_id', 'webhook_secret',
|
||||
'bearer_token', 'PERSONAL_ACCESS_TOKEN', 'override_deployment_type', 'private_key',
|
||||
'local_cache_path', 'enable_local_cache', 'jira_base_url', 'api_base', 'api_type', 'api_version',
|
||||
'skip_keys']
|
||||
if args:
|
||||
for arg in args:
|
||||
if arg.startswith('--'):
|
||||
arg_word = arg.lower()
|
||||
arg_word = arg_word.replace('__', '.') # replace double underscore with dot, e.g. --openai__key -> --openai.key
|
||||
for forbidden_arg in forbidden_cli_args:
|
||||
forbidden_arg_word = forbidden_arg.lower()
|
||||
if '.' not in forbidden_arg_word:
|
||||
forbidden_arg_word = '.' + forbidden_arg_word
|
||||
if forbidden_arg_word in arg_word:
|
||||
# validate args
|
||||
is_valid, arg = CliArgs.validate_user_args(args)
|
||||
if not is_valid:
|
||||
get_logger().error(
|
||||
f"CLI argument for param '{forbidden_arg}' is forbidden. Use instead a configuration file."
|
||||
f"CLI argument for param '{arg}' is forbidden. Use instead a configuration file."
|
||||
)
|
||||
return False
|
||||
|
||||
# Update settings from args
|
||||
args = update_settings_from_args(args)
|
||||
|
||||
# Append the response language in the extra instructions
|
||||
response_language = get_settings().config.get('response_language', 'en-us')
|
||||
if response_language.lower() != 'en-us':
|
||||
get_logger().info(f'User has set the response language to: {response_language}')
|
||||
for key in get_settings():
|
||||
setting = get_settings().get(key)
|
||||
if str(type(setting)) == "<class 'dynaconf.utils.boxing.DynaBox'>":
|
||||
if hasattr(setting, 'extra_instructions'):
|
||||
current_extra_instructions = setting.extra_instructions
|
||||
if current_extra_instructions:
|
||||
setting.extra_instructions = current_extra_instructions+ f"\n======\n\nIn addition, Your response MUST be written in the language corresponding to local code: {response_language}. This is crucial."
|
||||
else:
|
||||
setting.extra_instructions = f"Your response MUST be written in the language corresponding to locale code: '{response_language}'. This is crucial."
|
||||
|
||||
action = action.lstrip("/").lower()
|
||||
if action not in command2class:
|
||||
get_logger().error(f"Unknown command: {action}")
|
||||
get_logger().warning(f"Unknown command: {action}")
|
||||
return False
|
||||
with get_logger().contextualize(command=action, pr_url=pr_url):
|
||||
get_logger().info("PR-Agent request handler started", analytics=True)
|
||||
|
@ -26,10 +26,13 @@ MAX_TOKENS = {
|
||||
'o1-preview-2024-09-12': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'o1-2024-12-17': 204800, # 200K, but may be limited by config.max_model_tokens
|
||||
'o1': 204800, # 200K, but may be limited by config.max_model_tokens
|
||||
'o3-mini': 204800, # 200K, but may be limited by config.max_model_tokens
|
||||
'o3-mini-2025-01-31': 204800, # 200K, but may be limited by config.max_model_tokens
|
||||
'claude-instant-1': 100000,
|
||||
'claude-2': 100000,
|
||||
'command-nightly': 4096,
|
||||
'deepseek/deepseek-chat': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'deepseek/deepseek-reasoner': 64000, # 64K, but may be limited by config.max_model_tokens
|
||||
'replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1': 4096,
|
||||
'meta-llama/Llama-2-7b-chat-hf': 4096,
|
||||
'vertex_ai/codechat-bison': 6144,
|
||||
@ -40,13 +43,14 @@ MAX_TOKENS = {
|
||||
'vertex_ai/claude-3-opus@20240229': 100000,
|
||||
'vertex_ai/claude-3-5-sonnet@20240620': 100000,
|
||||
'vertex_ai/claude-3-5-sonnet-v2@20241022': 100000,
|
||||
'vertex_ai/claude-3-7-sonnet@20250219': 200000,
|
||||
'vertex_ai/gemini-1.5-pro': 1048576,
|
||||
'vertex_ai/gemini-1.5-flash': 1048576,
|
||||
'vertex_ai/gemini-2.0-flash-exp': 1048576,
|
||||
'vertex_ai/gemini-2.0-flash': 1048576,
|
||||
'vertex_ai/gemma2': 8200,
|
||||
'gemini/gemini-1.5-pro': 1048576,
|
||||
'gemini/gemini-1.5-flash': 1048576,
|
||||
'gemini/gemini-2.0-flash-exp': 1048576,
|
||||
'gemini/gemini-2.0-flash': 1048576,
|
||||
'codechat-bison': 6144,
|
||||
'codechat-bison-32k': 32000,
|
||||
'anthropic.claude-instant-v1': 100000,
|
||||
@ -55,6 +59,8 @@ MAX_TOKENS = {
|
||||
'anthropic/claude-3-opus-20240229': 100000,
|
||||
'anthropic/claude-3-5-sonnet-20240620': 100000,
|
||||
'anthropic/claude-3-5-sonnet-20241022': 100000,
|
||||
'anthropic/claude-3-7-sonnet-20250219': 200000,
|
||||
'claude-3-7-sonnet-20250219': 200000,
|
||||
'anthropic/claude-3-5-haiku-20241022': 100000,
|
||||
'bedrock/anthropic.claude-instant-v1': 100000,
|
||||
'bedrock/anthropic.claude-v2': 100000,
|
||||
@ -64,7 +70,9 @@ MAX_TOKENS = {
|
||||
'bedrock/anthropic.claude-3-5-haiku-20241022-v1:0': 100000,
|
||||
'bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0': 100000,
|
||||
'bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0': 100000,
|
||||
'bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0': 200000,
|
||||
"bedrock/us.anthropic.claude-3-5-sonnet-20241022-v2:0": 100000,
|
||||
"bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0": 200000,
|
||||
'claude-3-5-sonnet': 100000,
|
||||
'groq/llama3-8b-8192': 8192,
|
||||
'groq/llama3-70b-8192': 8192,
|
||||
@ -79,4 +87,35 @@ MAX_TOKENS = {
|
||||
"watsonx/ibm/granite-13b-chat-v2": 8191,
|
||||
"watsonx/ibm/granite-34b-code-instruct": 8191,
|
||||
"watsonx/mistralai/mistral-large": 32768,
|
||||
"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B": 128000,
|
||||
"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B": 128000,
|
||||
"deepinfra/deepseek-ai/DeepSeek-R1": 128000,
|
||||
}
|
||||
|
||||
USER_MESSAGE_ONLY_MODELS = [
|
||||
"deepseek/deepseek-reasoner",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"o1-preview"
|
||||
]
|
||||
|
||||
NO_SUPPORT_TEMPERATURE_MODELS = [
|
||||
"deepseek/deepseek-reasoner",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"o1",
|
||||
"o1-2024-12-17",
|
||||
"o3-mini",
|
||||
"o3-mini-2025-01-31",
|
||||
"o1-preview"
|
||||
]
|
||||
|
||||
SUPPORT_REASONING_EFFORT_MODELS = [
|
||||
"o3-mini",
|
||||
"o3-mini-2025-01-31"
|
||||
]
|
||||
|
||||
CLAUDE_EXTENDED_THINKING_MODELS = [
|
||||
"anthropic/claude-3-7-sonnet-20250219",
|
||||
"claude-3-7-sonnet-20250219"
|
||||
]
|
||||
|
@ -6,10 +6,12 @@ import requests
|
||||
from litellm import acompletion
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt
|
||||
|
||||
from pr_agent.algo import CLAUDE_EXTENDED_THINKING_MODELS, NO_SUPPORT_TEMPERATURE_MODELS, SUPPORT_REASONING_EFFORT_MODELS, USER_MESSAGE_ONLY_MODELS
|
||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
||||
from pr_agent.algo.utils import get_version
|
||||
from pr_agent.algo.utils import ReasoningEffort, get_version
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import get_logger
|
||||
import json
|
||||
|
||||
OPENAI_RETRIES = 5
|
||||
|
||||
@ -39,11 +41,6 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = get_settings().aws.AWS_ACCESS_KEY_ID
|
||||
os.environ["AWS_SECRET_ACCESS_KEY"] = get_settings().aws.AWS_SECRET_ACCESS_KEY
|
||||
os.environ["AWS_REGION_NAME"] = get_settings().aws.AWS_REGION_NAME
|
||||
if get_settings().get("litellm.use_client"):
|
||||
litellm_token = get_settings().get("litellm.LITELLM_TOKEN")
|
||||
assert litellm_token, "LITELLM_TOKEN is required"
|
||||
os.environ["LITELLM_TOKEN"] = litellm_token
|
||||
litellm.use_client = True
|
||||
if get_settings().get("LITELLM.DROP_PARAMS", None):
|
||||
litellm.drop_params = get_settings().litellm.drop_params
|
||||
if get_settings().get("LITELLM.SUCCESS_CALLBACK", None):
|
||||
@ -94,6 +91,22 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
if get_settings().get("DEEPSEEK.KEY", None):
|
||||
os.environ['DEEPSEEK_API_KEY'] = get_settings().get("DEEPSEEK.KEY")
|
||||
|
||||
# Support deepinfra models
|
||||
if get_settings().get("DEEPINFRA.KEY", None):
|
||||
os.environ['DEEPINFRA_API_KEY'] = get_settings().get("DEEPINFRA.KEY")
|
||||
|
||||
# Models that only use user meessage
|
||||
self.user_message_only_models = USER_MESSAGE_ONLY_MODELS
|
||||
|
||||
# Model that doesn't support temperature argument
|
||||
self.no_support_temperature_models = NO_SUPPORT_TEMPERATURE_MODELS
|
||||
|
||||
# Models that support reasoning effort
|
||||
self.support_reasoning_models = SUPPORT_REASONING_EFFORT_MODELS
|
||||
|
||||
# Models that support extended thinking
|
||||
self.claude_extended_thinking_models = CLAUDE_EXTENDED_THINKING_MODELS
|
||||
|
||||
def prepare_logs(self, response, system, user, resp, finish_reason):
|
||||
response_log = response.dict().copy()
|
||||
response_log['system'] = system
|
||||
@ -106,6 +119,43 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
response_log['main_pr_language'] = 'unknown'
|
||||
return response_log
|
||||
|
||||
def _configure_claude_extended_thinking(self, model: str, kwargs: dict) -> dict:
|
||||
"""
|
||||
Configure Claude extended thinking parameters if applicable.
|
||||
|
||||
Args:
|
||||
model (str): The AI model being used
|
||||
kwargs (dict): The keyword arguments for the model call
|
||||
|
||||
Returns:
|
||||
dict: Updated kwargs with extended thinking configuration
|
||||
"""
|
||||
extended_thinking_budget_tokens = get_settings().config.get("extended_thinking_budget_tokens", 2048)
|
||||
extended_thinking_max_output_tokens = get_settings().config.get("extended_thinking_max_output_tokens", 4096)
|
||||
|
||||
# Validate extended thinking parameters
|
||||
if not isinstance(extended_thinking_budget_tokens, int) or extended_thinking_budget_tokens <= 0:
|
||||
raise ValueError(f"extended_thinking_budget_tokens must be a positive integer, got {extended_thinking_budget_tokens}")
|
||||
if not isinstance(extended_thinking_max_output_tokens, int) or extended_thinking_max_output_tokens <= 0:
|
||||
raise ValueError(f"extended_thinking_max_output_tokens must be a positive integer, got {extended_thinking_max_output_tokens}")
|
||||
if extended_thinking_max_output_tokens < extended_thinking_budget_tokens:
|
||||
raise ValueError(f"extended_thinking_max_output_tokens ({extended_thinking_max_output_tokens}) must be greater than or equal to extended_thinking_budget_tokens ({extended_thinking_budget_tokens})")
|
||||
|
||||
kwargs["thinking"] = {
|
||||
"type": "enabled",
|
||||
"budget_tokens": extended_thinking_budget_tokens
|
||||
}
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Adding max output tokens {extended_thinking_max_output_tokens} to model {model}, extended thinking budget tokens: {extended_thinking_budget_tokens}")
|
||||
kwargs["max_tokens"] = extended_thinking_max_output_tokens
|
||||
|
||||
# temperature may only be set to 1 when thinking is enabled
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info("Temperature may only be set to 1 when thinking is enabled with claude models.")
|
||||
kwargs["temperature"] = 1
|
||||
|
||||
return kwargs
|
||||
|
||||
def add_litellm_callbacks(selfs, kwargs) -> dict:
|
||||
captured_extra = []
|
||||
|
||||
@ -197,13 +247,11 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
messages[1]["content"] = [{"type": "text", "text": messages[1]["content"]},
|
||||
{"type": "image_url", "image_url": {"url": img_path}}]
|
||||
|
||||
# Currently, model OpenAI o1 series does not support a separate system and user prompts
|
||||
O1_MODEL_PREFIX = 'o1'
|
||||
model_type = model.split('/')[-1] if '/' in model else model
|
||||
if model_type.startswith(O1_MODEL_PREFIX):
|
||||
# Currently, some models do not support a separate system and user prompts
|
||||
if model in self.user_message_only_models or get_settings().config.custom_reasoning_model:
|
||||
user = f"{system}\n\n\n{user}"
|
||||
system = ""
|
||||
get_logger().info(f"Using O1 model, combining system and user prompts")
|
||||
get_logger().info(f"Using model {model}, combining system and user prompts")
|
||||
messages = [{"role": "user", "content": user}]
|
||||
kwargs = {
|
||||
"model": model,
|
||||
@ -217,11 +265,26 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
"model": model,
|
||||
"deployment_id": deployment_id,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"timeout": get_settings().config.ai_timeout,
|
||||
"api_base": self.api_base,
|
||||
}
|
||||
|
||||
# Add temperature only if model supports it
|
||||
if model not in self.no_support_temperature_models and not get_settings().config.custom_reasoning_model:
|
||||
# get_logger().info(f"Adding temperature with value {temperature} to model {model}.")
|
||||
kwargs["temperature"] = temperature
|
||||
|
||||
# Add reasoning_effort if model supports it
|
||||
if (model in self.support_reasoning_models):
|
||||
supported_reasoning_efforts = [ReasoningEffort.HIGH.value, ReasoningEffort.MEDIUM.value, ReasoningEffort.LOW.value]
|
||||
reasoning_effort = get_settings().config.reasoning_effort if (get_settings().config.reasoning_effort in supported_reasoning_efforts) else ReasoningEffort.MEDIUM.value
|
||||
get_logger().info(f"Adding reasoning_effort with value {reasoning_effort} to model {model}.")
|
||||
kwargs["reasoning_effort"] = reasoning_effort
|
||||
|
||||
# https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
||||
if (model in self.claude_extended_thinking_models) and get_settings().config.get("enable_claude_extended_thinking", False):
|
||||
kwargs = self._configure_claude_extended_thinking(model, kwargs)
|
||||
|
||||
if get_settings().litellm.get("enable_callbacks", False):
|
||||
kwargs = self.add_litellm_callbacks(kwargs)
|
||||
|
||||
@ -235,6 +298,16 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
if self.repetition_penalty:
|
||||
kwargs["repetition_penalty"] = self.repetition_penalty
|
||||
|
||||
#Added support for extra_headers while using litellm to call underlying model, via a api management gateway, would allow for passing custom headers for security and authorization
|
||||
if get_settings().get("LITELLM.EXTRA_HEADERS", None):
|
||||
try:
|
||||
litellm_extra_headers = json.loads(get_settings().litellm.extra_headers)
|
||||
if not isinstance(litellm_extra_headers, dict):
|
||||
raise ValueError("LITELLM.EXTRA_HEADERS must be a JSON object")
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"LITELLM.EXTRA_HEADERS contains invalid JSON: {str(e)}")
|
||||
kwargs["extra_headers"] = litellm_extra_headers
|
||||
|
||||
get_logger().debug("Prompts", artifact={"system": system, "user": user})
|
||||
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
|
36
pr_agent/algo/cli_args.py
Normal file
@ -0,0 +1,36 @@
|
||||
from base64 import b64decode, encode, b64encode
|
||||
import hashlib
|
||||
|
||||
class CliArgs:
|
||||
@staticmethod
|
||||
def validate_user_args(args: list) -> (bool, str):
|
||||
try:
|
||||
if not args:
|
||||
return True, ""
|
||||
|
||||
# decode forbidden args
|
||||
# b64encode('word'.encode()).decode()
|
||||
_encoded_args = 'c2hhcmVkX3NlY3JldA==:dXNlcg==:c3lzdGVt:ZW5hYmxlX2NvbW1lbnRfYXBwcm92YWw=:ZW5hYmxlX21hbnVhbF9hcHByb3ZhbA==:ZW5hYmxlX2F1dG9fYXBwcm92YWw=:YXBwcm92ZV9wcl9vbl9zZWxmX3Jldmlldw==:YmFzZV91cmw=:dXJs:YXBwX25hbWU=:c2VjcmV0X3Byb3ZpZGVy:Z2l0X3Byb3ZpZGVy:c2tpcF9rZXlz:b3BlbmFpLmtleQ==:QU5BTFlUSUNTX0ZPTERFUg==:dXJp:YXBwX2lk:d2ViaG9va19zZWNyZXQ=:YmVhcmVyX3Rva2Vu:UEVSU09OQUxfQUNDRVNTX1RPS0VO:b3ZlcnJpZGVfZGVwbG95bWVudF90eXBl:cHJpdmF0ZV9rZXk=:bG9jYWxfY2FjaGVfcGF0aA==:ZW5hYmxlX2xvY2FsX2NhY2hl:amlyYV9iYXNlX3VybA==:YXBpX2Jhc2U=:YXBpX3R5cGU=:YXBpX3ZlcnNpb24=:c2tpcF9rZXlz'
|
||||
|
||||
forbidden_cli_args = []
|
||||
for e in _encoded_args.split(':'):
|
||||
forbidden_cli_args.append(b64decode(e).decode())
|
||||
|
||||
# lowercase all forbidden args
|
||||
for i, _ in enumerate(forbidden_cli_args):
|
||||
forbidden_cli_args[i] = forbidden_cli_args[i].lower()
|
||||
if '.' not in forbidden_cli_args[i]:
|
||||
forbidden_cli_args[i] = '.' + forbidden_cli_args[i]
|
||||
|
||||
for arg in args:
|
||||
if arg.startswith('--'):
|
||||
arg_word = arg.lower()
|
||||
arg_word = arg_word.replace('__', '.') # replace double underscore with dot, e.g. --openai__key -> --openai.key
|
||||
for forbidden_arg_word in forbidden_cli_args:
|
||||
if forbidden_arg_word in arg_word:
|
||||
return False, forbidden_arg_word
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
|
@ -9,11 +9,12 @@ from pr_agent.log import get_logger
|
||||
|
||||
|
||||
def extend_patch(original_file_str, patch_str, patch_extra_lines_before=0,
|
||||
patch_extra_lines_after=0, filename: str = "") -> str:
|
||||
patch_extra_lines_after=0, filename: str = "", new_file_str="") -> str:
|
||||
if not patch_str or (patch_extra_lines_before == 0 and patch_extra_lines_after == 0) or not original_file_str:
|
||||
return patch_str
|
||||
|
||||
original_file_str = decode_if_bytes(original_file_str)
|
||||
new_file_str = decode_if_bytes(new_file_str)
|
||||
if not original_file_str:
|
||||
return patch_str
|
||||
|
||||
@ -22,7 +23,7 @@ def extend_patch(original_file_str, patch_str, patch_extra_lines_before=0,
|
||||
|
||||
try:
|
||||
extended_patch_str = process_patch_lines(patch_str, original_file_str,
|
||||
patch_extra_lines_before, patch_extra_lines_after)
|
||||
patch_extra_lines_before, patch_extra_lines_after, new_file_str)
|
||||
except Exception as e:
|
||||
get_logger().warning(f"Failed to extend patch: {e}", artifact={"traceback": traceback.format_exc()})
|
||||
return patch_str
|
||||
@ -52,12 +53,13 @@ def should_skip_patch(filename):
|
||||
return False
|
||||
|
||||
|
||||
def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before, patch_extra_lines_after):
|
||||
def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before, patch_extra_lines_after, new_file_str=""):
|
||||
allow_dynamic_context = get_settings().config.allow_dynamic_context
|
||||
patch_extra_lines_before_dynamic = get_settings().config.max_extra_lines_before_dynamic_context
|
||||
|
||||
original_lines = original_file_str.splitlines()
|
||||
len_original_lines = len(original_lines)
|
||||
file_original_lines = original_file_str.splitlines()
|
||||
file_new_lines = new_file_str.splitlines() if new_file_str else []
|
||||
len_original_lines = len(file_original_lines)
|
||||
patch_lines = patch_str.splitlines()
|
||||
extended_patch_lines = []
|
||||
|
||||
@ -73,12 +75,12 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
|
||||
if match:
|
||||
# finish processing previous hunk
|
||||
if is_valid_hunk and (start1 != -1 and patch_extra_lines_after > 0):
|
||||
delta_lines = [f' {line}' for line in original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]]
|
||||
extended_patch_lines.extend(delta_lines)
|
||||
delta_lines_original = [f' {line}' for line in file_original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]]
|
||||
extended_patch_lines.extend(delta_lines_original)
|
||||
|
||||
section_header, size1, size2, start1, start2 = extract_hunk_headers(match)
|
||||
|
||||
is_valid_hunk = check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1)
|
||||
is_valid_hunk = check_if_hunk_lines_matches_to_file(i, file_original_lines, patch_lines, start1)
|
||||
|
||||
if is_valid_hunk and (patch_extra_lines_before > 0 or patch_extra_lines_after > 0):
|
||||
def _calc_context_limits(patch_lines_before):
|
||||
@ -93,12 +95,15 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
|
||||
extended_size2 = max(extended_size2 - delta_cap, size2)
|
||||
return extended_start1, extended_size1, extended_start2, extended_size2
|
||||
|
||||
if allow_dynamic_context:
|
||||
if allow_dynamic_context and file_new_lines:
|
||||
extended_start1, extended_size1, extended_start2, extended_size2 = \
|
||||
_calc_context_limits(patch_extra_lines_before_dynamic)
|
||||
lines_before = original_lines[extended_start1 - 1:start1 - 1]
|
||||
|
||||
lines_before_original = file_original_lines[extended_start1 - 1:start1 - 1]
|
||||
lines_before_new = file_new_lines[extended_start2 - 1:start2 - 1]
|
||||
found_header = False
|
||||
for i, line, in enumerate(lines_before):
|
||||
if lines_before_original == lines_before_new: # Making sure no changes from a previous hunk
|
||||
for i, line, in enumerate(lines_before_original):
|
||||
if section_header in line:
|
||||
found_header = True
|
||||
# Update start and size in one line each
|
||||
@ -107,6 +112,11 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
|
||||
# get_logger().debug(f"Found section header in line {i} before the hunk")
|
||||
section_header = ''
|
||||
break
|
||||
else:
|
||||
get_logger().debug(f"Extra lines before hunk are different in original and new file - dynamic context",
|
||||
artifact={"lines_before_original": lines_before_original,
|
||||
"lines_before_new": lines_before_new})
|
||||
|
||||
if not found_header:
|
||||
# get_logger().debug(f"Section header not found in the extra lines before the hunk")
|
||||
extended_start1, extended_size1, extended_start2, extended_size2 = \
|
||||
@ -115,11 +125,23 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
|
||||
extended_start1, extended_size1, extended_start2, extended_size2 = \
|
||||
_calc_context_limits(patch_extra_lines_before)
|
||||
|
||||
delta_lines = [f' {line}' for line in original_lines[extended_start1 - 1:start1 - 1]]
|
||||
# check if extra lines before hunk are different in original and new file
|
||||
delta_lines_original = [f' {line}' for line in file_original_lines[extended_start1 - 1:start1 - 1]]
|
||||
if file_new_lines:
|
||||
delta_lines_new = [f' {line}' for line in file_new_lines[extended_start2 - 1:start2 - 1]]
|
||||
if delta_lines_original != delta_lines_new:
|
||||
get_logger().debug(f"Extra lines before hunk are different in original and new file",
|
||||
artifact={"delta_lines_original": delta_lines_original,
|
||||
"delta_lines_new": delta_lines_new})
|
||||
extended_start1 = start1
|
||||
extended_size1 = size1
|
||||
extended_start2 = start2
|
||||
extended_size2 = size2
|
||||
delta_lines_original = []
|
||||
|
||||
# logic to remove section header if its in the extra delta lines (in dynamic context, this is also done)
|
||||
if section_header and not allow_dynamic_context:
|
||||
for line in delta_lines:
|
||||
for line in delta_lines_original:
|
||||
if section_header in line:
|
||||
section_header = '' # remove section header if it is in the extra delta lines
|
||||
break
|
||||
@ -128,12 +150,12 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
|
||||
extended_size1 = size1
|
||||
extended_start2 = start2
|
||||
extended_size2 = size2
|
||||
delta_lines = []
|
||||
delta_lines_original = []
|
||||
extended_patch_lines.append('')
|
||||
extended_patch_lines.append(
|
||||
f'@@ -{extended_start1},{extended_size1} '
|
||||
f'+{extended_start2},{extended_size2} @@ {section_header}')
|
||||
extended_patch_lines.extend(delta_lines) # one to zero based
|
||||
extended_patch_lines.extend(delta_lines_original) # one to zero based
|
||||
continue
|
||||
extended_patch_lines.append(line)
|
||||
except Exception as e:
|
||||
@ -142,15 +164,14 @@ def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before,
|
||||
|
||||
# finish processing last hunk
|
||||
if start1 != -1 and patch_extra_lines_after > 0 and is_valid_hunk:
|
||||
delta_lines = original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]
|
||||
delta_lines_original = file_original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]
|
||||
# add space at the beginning of each extra line
|
||||
delta_lines = [f' {line}' for line in delta_lines]
|
||||
extended_patch_lines.extend(delta_lines)
|
||||
delta_lines_original = [f' {line}' for line in delta_lines_original]
|
||||
extended_patch_lines.extend(delta_lines_original)
|
||||
|
||||
extended_patch_str = '\n'.join(extended_patch_lines)
|
||||
return extended_patch_str
|
||||
|
||||
|
||||
def check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1):
|
||||
"""
|
||||
Check if the hunk lines match the original file content. We saw cases where the hunk header line doesn't match the original file content, and then
|
||||
@ -160,8 +181,18 @@ def check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1):
|
||||
try:
|
||||
if i + 1 < len(patch_lines) and patch_lines[i + 1][0] == ' ': # an existing line in the file
|
||||
if patch_lines[i + 1].strip() != original_lines[start1 - 1].strip():
|
||||
# check if different encoding is needed
|
||||
original_line = original_lines[start1 - 1].strip()
|
||||
for encoding in ['iso-8859-1', 'latin-1', 'ascii', 'utf-16']:
|
||||
try:
|
||||
if original_line.encode(encoding).decode().strip() == patch_lines[i + 1].strip():
|
||||
get_logger().info(f"Detected different encoding in hunk header line {start1}, needed encoding: {encoding}")
|
||||
return False # we still want to avoid extending the hunk. But we don't want to log an error
|
||||
except:
|
||||
pass
|
||||
|
||||
is_valid_hunk = False
|
||||
get_logger().error(
|
||||
get_logger().info(
|
||||
f"Invalid hunk in PR, line {start1} in hunk header doesn't match the original file content")
|
||||
except:
|
||||
pass
|
||||
@ -254,7 +285,7 @@ def handle_patch_deletions(patch: str, original_file_content_str: str,
|
||||
return patch
|
||||
|
||||
|
||||
def convert_to_hunks_with_lines_numbers(patch: str, file) -> str:
|
||||
def decouple_and_convert_to_hunks_with_lines_numbers(patch: str, file) -> str:
|
||||
"""
|
||||
Convert a given patch string into a string with line numbers for each hunk, indicating the new and old content of
|
||||
the file.
|
||||
@ -286,11 +317,17 @@ __old hunk__
|
||||
line6
|
||||
...
|
||||
"""
|
||||
|
||||
# Add a header for the file
|
||||
if file:
|
||||
# if the file was deleted, return a message indicating that the file was deleted
|
||||
if hasattr(file, 'edit_type') and file.edit_type == EDIT_TYPE.DELETED:
|
||||
return f"\n\n## file '{file.filename.strip()}' was deleted\n"
|
||||
return f"\n\n## File '{file.filename.strip()}' was deleted\n"
|
||||
|
||||
patch_with_lines_str = f"\n\n## File: '{file.filename.strip()}'\n"
|
||||
else:
|
||||
patch_with_lines_str = ""
|
||||
|
||||
patch_lines = patch.splitlines()
|
||||
RE_HUNK_HEADER = re.compile(
|
||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||
@ -363,7 +400,7 @@ __old hunk__
|
||||
return patch_with_lines_str.rstrip()
|
||||
|
||||
|
||||
def extract_hunk_lines_from_patch(patch: str, file_name, line_start, line_end, side) -> tuple[str, str]:
|
||||
def extract_hunk_lines_from_patch(patch: str, file_name, line_start, line_end, side, remove_trailing_chars: bool = True) -> tuple[str, str]:
|
||||
try:
|
||||
patch_with_lines_str = f"\n\n## File: '{file_name.strip()}'\n\n"
|
||||
selected_lines = ""
|
||||
@ -411,4 +448,8 @@ def extract_hunk_lines_from_patch(patch: str, file_name, line_start, line_end, s
|
||||
get_logger().error(f"Failed to extract hunk lines from patch: {e}", artifact={"traceback": traceback.format_exc()})
|
||||
return "", ""
|
||||
|
||||
return patch_with_lines_str.rstrip(), selected_lines.rstrip()
|
||||
if remove_trailing_chars:
|
||||
patch_with_lines_str = patch_with_lines_str.rstrip()
|
||||
selected_lines = selected_lines.rstrip()
|
||||
|
||||
return patch_with_lines_str, selected_lines
|
||||
|
@ -19,6 +19,12 @@ def is_valid_file(filename:str, bad_extensions=None) -> bool:
|
||||
bad_extensions = get_settings().bad_extensions.default
|
||||
if get_settings().config.use_extra_bad_extensions:
|
||||
bad_extensions += get_settings().bad_extensions.extra
|
||||
|
||||
auto_generated_files = ['package-lock.json', 'yarn.lock', 'composer.lock', 'Gemfile.lock', 'poetry.lock']
|
||||
for forbidden_file in auto_generated_files:
|
||||
if filename.endswith(forbidden_file):
|
||||
return False
|
||||
|
||||
return filename.split('.')[-1] not in bad_extensions
|
||||
|
||||
|
||||
@ -41,6 +47,7 @@ def sort_files_by_main_languages(languages: Dict, files: list):
|
||||
|
||||
# filter out files bad extensions
|
||||
files_filtered = filter_bad_extensions(files)
|
||||
|
||||
# sort files by their extension, put the files that are in the main extension first
|
||||
# and the rest files after, map languages_sorted to their respective files
|
||||
files_sorted = []
|
||||
|
@ -7,7 +7,8 @@ from github import RateLimitExceededException
|
||||
|
||||
from pr_agent.algo.file_filter import filter_ignored
|
||||
from pr_agent.algo.git_patch_processing import (
|
||||
convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions)
|
||||
extend_patch, handle_patch_deletions,
|
||||
decouple_and_convert_to_hunks_with_lines_numbers)
|
||||
from pr_agent.algo.language_handler import sort_files_by_main_languages
|
||||
from pr_agent.algo.token_handler import TokenHandler
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
@ -50,22 +51,11 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
PATCH_EXTRA_LINES_AFTER = cap_and_log_extra_lines(PATCH_EXTRA_LINES_AFTER, "after")
|
||||
|
||||
try:
|
||||
diff_files_original = git_provider.get_diff_files()
|
||||
diff_files = git_provider.get_diff_files()
|
||||
except RateLimitExceededException as e:
|
||||
get_logger().error(f"Rate limit exceeded for git provider API. original message {e}")
|
||||
raise
|
||||
|
||||
diff_files = filter_ignored(diff_files_original)
|
||||
if diff_files != diff_files_original:
|
||||
try:
|
||||
get_logger().info(f"Filtered out {len(diff_files_original) - len(diff_files)} files")
|
||||
new_names = set([a.filename for a in diff_files])
|
||||
orig_names = set([a.filename for a in diff_files_original])
|
||||
get_logger().info(f"Filtered out files: {orig_names - new_names}")
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
# get pr languages
|
||||
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
||||
if pr_languages:
|
||||
@ -155,21 +145,11 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
def get_pr_diff_multiple_patchs(git_provider: GitProvider, token_handler: TokenHandler, model: str,
|
||||
add_line_numbers_to_hunks: bool = False, disable_extra_lines: bool = False):
|
||||
try:
|
||||
diff_files_original = git_provider.get_diff_files()
|
||||
diff_files = git_provider.get_diff_files()
|
||||
except RateLimitExceededException as e:
|
||||
get_logger().error(f"Rate limit exceeded for git provider API. original message {e}")
|
||||
raise
|
||||
|
||||
diff_files = filter_ignored(diff_files_original)
|
||||
if diff_files != diff_files_original:
|
||||
try:
|
||||
get_logger().info(f"Filtered out {len(diff_files_original) - len(diff_files)} files")
|
||||
new_names = set([a.filename for a in diff_files])
|
||||
orig_names = set([a.filename for a in diff_files_original])
|
||||
get_logger().info(f"Filtered out files: {orig_names - new_names}")
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# get pr languages
|
||||
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
||||
if pr_languages:
|
||||
@ -195,21 +175,24 @@ def pr_generate_extended_diff(pr_languages: list,
|
||||
for lang in pr_languages:
|
||||
for file in lang['files']:
|
||||
original_file_content_str = file.base_file
|
||||
new_file_content_str = file.head_file
|
||||
patch = file.patch
|
||||
if not patch:
|
||||
continue
|
||||
|
||||
# extend each patch with extra lines of context
|
||||
extended_patch = extend_patch(original_file_content_str, patch,
|
||||
patch_extra_lines_before, patch_extra_lines_after, file.filename)
|
||||
patch_extra_lines_before, patch_extra_lines_after, file.filename,
|
||||
new_file_str=new_file_content_str)
|
||||
if not extended_patch:
|
||||
get_logger().warning(f"Failed to extend patch for file: {file.filename}")
|
||||
continue
|
||||
|
||||
if add_line_numbers_to_hunks:
|
||||
full_extended_patch = convert_to_hunks_with_lines_numbers(extended_patch, file)
|
||||
full_extended_patch = decouple_and_convert_to_hunks_with_lines_numbers(extended_patch, file)
|
||||
else:
|
||||
full_extended_patch = f"\n\n## File: '{file.filename.strip()}'\n{extended_patch.rstrip()}\n"
|
||||
extended_patch = extended_patch.replace('\n@@ ', '\n\n@@ ') # add extra line before each hunk
|
||||
full_extended_patch = f"\n\n## File: '{file.filename.strip()}'\n\n{extended_patch.strip()}\n"
|
||||
|
||||
# add AI-summary metadata to the patch
|
||||
if file.ai_file_summary and get_settings().get("config.enable_ai_metadata", False):
|
||||
@ -252,7 +235,7 @@ def pr_generate_compressed_diff(top_langs: list, token_handler: TokenHandler, mo
|
||||
continue
|
||||
|
||||
if convert_hunks_to_line_numbers:
|
||||
patch = convert_to_hunks_with_lines_numbers(patch, file)
|
||||
patch = decouple_and_convert_to_hunks_with_lines_numbers(patch, file)
|
||||
|
||||
## add AI-summary metadata to the patch (disabled, since we are in the compressed diff)
|
||||
# if file.ai_file_summary and get_settings().config.get('config.is_auto_command', False):
|
||||
@ -384,7 +367,8 @@ def _get_all_deployments(all_models: List[str]) -> List[str]:
|
||||
def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
token_handler: TokenHandler,
|
||||
model: str,
|
||||
max_calls: int = 5) -> List[str]:
|
||||
max_calls: int = 5,
|
||||
add_line_numbers: bool = True) -> List[str]:
|
||||
"""
|
||||
Retrieves the diff files from a Git provider, sorts them by main language, and generates patches for each file.
|
||||
The patches are split into multiple groups based on the maximum number of tokens allowed for the given model.
|
||||
@ -407,8 +391,6 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
get_logger().error(f"Rate limit exceeded for git provider API. original message {e}")
|
||||
raise
|
||||
|
||||
diff_files = filter_ignored(diff_files)
|
||||
|
||||
# Sort files by main language
|
||||
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
||||
|
||||
@ -425,7 +407,8 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
|
||||
# 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,
|
||||
pr_languages, token_handler,
|
||||
add_line_numbers_to_hunks=add_line_numbers,
|
||||
patch_extra_lines_before=PATCH_EXTRA_LINES_BEFORE,
|
||||
patch_extra_lines_after=PATCH_EXTRA_LINES_AFTER)
|
||||
|
||||
@ -454,7 +437,12 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
if patch is None:
|
||||
continue
|
||||
|
||||
patch = convert_to_hunks_with_lines_numbers(patch, file)
|
||||
# Add line numbers and metadata to the patch
|
||||
if add_line_numbers:
|
||||
patch = decouple_and_convert_to_hunks_with_lines_numbers(patch, file)
|
||||
else:
|
||||
patch = f"\n\n## File: '{file.filename.strip()}'\n\n{patch.strip()}\n"
|
||||
|
||||
# add AI-summary metadata to the patch
|
||||
if file.ai_file_summary and get_settings().get("config.enable_ai_metadata", False):
|
||||
patch = add_ai_summary_top_patch(file, patch)
|
||||
@ -502,7 +490,7 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
# Add the last chunk
|
||||
if patches:
|
||||
final_diff = "\n".join(patches)
|
||||
final_diff_list.append(final_diff)
|
||||
final_diff_list.append(final_diff.strip())
|
||||
|
||||
return final_diff_list
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
from threading import Lock
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
from math import ceil
|
||||
from tiktoken import encoding_for_model, get_encoding
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
@ -76,7 +77,35 @@ class TokenHandler:
|
||||
get_logger().error(f"Error in _get_system_user_tokens: {e}")
|
||||
return 0
|
||||
|
||||
def count_tokens(self, patch: str) -> int:
|
||||
def calc_claude_tokens(self, patch):
|
||||
try:
|
||||
import anthropic
|
||||
from pr_agent.algo import MAX_TOKENS
|
||||
client = anthropic.Anthropic(api_key=get_settings(use_context=False).get('anthropic.key'))
|
||||
MaxTokens = MAX_TOKENS[get_settings().config.model]
|
||||
|
||||
# Check if the content size is too large (9MB limit)
|
||||
if len(patch.encode('utf-8')) > 9_000_000:
|
||||
get_logger().warning(
|
||||
"Content too large for Anthropic token counting API, falling back to local tokenizer"
|
||||
)
|
||||
return MaxTokens
|
||||
|
||||
response = client.messages.count_tokens(
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
system="system",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": patch
|
||||
}],
|
||||
)
|
||||
return response.input_tokens
|
||||
|
||||
except Exception as e:
|
||||
get_logger().error( f"Error in Anthropic token counting: {e}")
|
||||
return MaxTokens
|
||||
|
||||
def count_tokens(self, patch: str, force_accurate=False) -> int:
|
||||
"""
|
||||
Counts the number of tokens in a given patch string.
|
||||
|
||||
@ -86,4 +115,22 @@ class TokenHandler:
|
||||
Returns:
|
||||
The number of tokens in the patch string.
|
||||
"""
|
||||
return len(self.encoder.encode(patch, disallowed_special=()))
|
||||
encoder_estimate = len(self.encoder.encode(patch, disallowed_special=()))
|
||||
if not force_accurate:
|
||||
return encoder_estimate
|
||||
#else, need to provide an accurate estimation:
|
||||
|
||||
model = get_settings().config.model.lower()
|
||||
if force_accurate and 'claude' in model and get_settings(use_context=False).get('anthropic.key'):
|
||||
return self.calc_claude_tokens(patch) # API call to Anthropic for accurate token counting for Claude models
|
||||
#else: Non Anthropic provided model
|
||||
|
||||
import re
|
||||
model_is_from_o_series = re.match(r"^o[1-9](-mini|-preview)?$", model)
|
||||
if ('gpt' in get_settings().config.model.lower() or model_is_from_o_series) and get_settings(use_context=False).get('openai.key'):
|
||||
return encoder_estimate
|
||||
#else: Model is neither an OpenAI, nor an Anthropic model - therefore, cannot provide an accurate token count and instead, return a higher number as best effort.
|
||||
|
||||
elbow_factor = 1 + get_settings().get('config.model_token_count_estimate_factor', 0)
|
||||
get_logger().warning(f"{model}'s expected token count cannot be accurately estimated. Using {elbow_factor} of encoder output as best effort estimate")
|
||||
return ceil(elbow_factor * encoder_estimate)
|
||||
|
@ -50,6 +50,11 @@ class PRReviewHeader(str, Enum):
|
||||
REGULAR = "## PR Reviewer Guide"
|
||||
INCREMENTAL = "## Incremental PR Reviewer Guide"
|
||||
|
||||
class ReasoningEffort(str, Enum):
|
||||
HIGH = "high"
|
||||
MEDIUM = "medium"
|
||||
LOW = "low"
|
||||
|
||||
|
||||
class PRDescriptionHeader(str, Enum):
|
||||
CHANGES_WALKTHROUGH = "### **Changes walkthrough** 📝"
|
||||
@ -245,7 +250,7 @@ def convert_to_markdown_v2(output_data: dict,
|
||||
if gfm_supported:
|
||||
if reference_link is not None and len(reference_link) > 0:
|
||||
if relevant_lines_str:
|
||||
issue_str = f"<details><summary><a href='{reference_link}'><strong>{issue_header}</strong></a>\n\n{issue_content}</summary>\n\n{relevant_lines_str}\n\n</details>"
|
||||
issue_str = f"<details><summary><a href='{reference_link}'><strong>{issue_header}</strong></a>\n\n{issue_content}\n</summary>\n\n{relevant_lines_str}\n\n</details>"
|
||||
else:
|
||||
issue_str = f"<a href='{reference_link}'><strong>{issue_header}</strong></a><br>{issue_content}"
|
||||
else:
|
||||
@ -316,47 +321,98 @@ def extract_relevant_lines_str(end_line, files, relevant_file, start_line, deden
|
||||
|
||||
def ticket_markdown_logic(emoji, markdown_text, value, gfm_supported) -> str:
|
||||
ticket_compliance_str = ""
|
||||
final_compliance_level = -1
|
||||
compliance_emoji = ''
|
||||
# Track compliance levels across all tickets
|
||||
all_compliance_levels = []
|
||||
|
||||
if isinstance(value, list):
|
||||
for v in value:
|
||||
ticket_url = v.get('ticket_url', '').strip()
|
||||
compliance_level = v.get('overall_compliance_level', '').strip()
|
||||
# add emojis, if 'Fully compliant' ✅, 'Partially compliant' 🔶, or 'Not compliant' ❌
|
||||
if compliance_level.lower() == 'fully compliant':
|
||||
# compliance_level = '✅ Fully compliant'
|
||||
final_compliance_level = 2 if final_compliance_level == -1 else 1
|
||||
elif compliance_level.lower() == 'partially compliant':
|
||||
# compliance_level = '🔶 Partially compliant'
|
||||
final_compliance_level = 1
|
||||
elif compliance_level.lower() == 'not compliant':
|
||||
# compliance_level = '❌ Not compliant'
|
||||
final_compliance_level = 0 if final_compliance_level < 1 else 1
|
||||
|
||||
# explanation = v.get('compliance_analysis', '').strip()
|
||||
for ticket_analysis in value:
|
||||
try:
|
||||
ticket_url = ticket_analysis.get('ticket_url', '').strip()
|
||||
explanation = ''
|
||||
fully_compliant_str = v.get('fully_compliant_requirements', '').strip()
|
||||
not_compliant_str = v.get('not_compliant_requirements', '').strip()
|
||||
ticket_compliance_level = '' # Individual ticket compliance
|
||||
fully_compliant_str = ticket_analysis.get('fully_compliant_requirements', '').strip()
|
||||
not_compliant_str = ticket_analysis.get('not_compliant_requirements', '').strip()
|
||||
requires_further_human_verification = ticket_analysis.get('requires_further_human_verification',
|
||||
'').strip()
|
||||
|
||||
if not fully_compliant_str and not not_compliant_str:
|
||||
get_logger().debug(f"Ticket compliance has no requirements",
|
||||
artifact={'ticket_url': ticket_url})
|
||||
continue
|
||||
|
||||
# Calculate individual ticket compliance level
|
||||
if fully_compliant_str:
|
||||
explanation += f"Fully compliant requirements:\n{fully_compliant_str}\n\n"
|
||||
if not_compliant_str:
|
||||
explanation += f"Not compliant requirements:\n{not_compliant_str}\n\n"
|
||||
|
||||
ticket_compliance_str += f"\n\n**[{ticket_url.split('/')[-1]}]({ticket_url}) - {compliance_level}**\n\n{explanation}\n\n"
|
||||
if final_compliance_level == 2:
|
||||
compliance_level = '✅'
|
||||
elif final_compliance_level == 1:
|
||||
compliance_level = '🔶'
|
||||
ticket_compliance_level = 'Partially compliant'
|
||||
else:
|
||||
compliance_level = '❌'
|
||||
if not requires_further_human_verification:
|
||||
ticket_compliance_level = 'Fully compliant'
|
||||
else:
|
||||
ticket_compliance_level = 'PR Code Verified'
|
||||
elif not_compliant_str:
|
||||
ticket_compliance_level = 'Not compliant'
|
||||
|
||||
# Store the compliance level for aggregation
|
||||
if ticket_compliance_level:
|
||||
all_compliance_levels.append(ticket_compliance_level)
|
||||
|
||||
# build compliance string
|
||||
if fully_compliant_str:
|
||||
explanation += f"Compliant requirements:\n\n{fully_compliant_str}\n\n"
|
||||
if not_compliant_str:
|
||||
explanation += f"Non-compliant requirements:\n\n{not_compliant_str}\n\n"
|
||||
if requires_further_human_verification:
|
||||
explanation += f"Requires further human verification:\n\n{requires_further_human_verification}\n\n"
|
||||
ticket_compliance_str += f"\n\n**[{ticket_url.split('/')[-1]}]({ticket_url}) - {ticket_compliance_level}**\n\n{explanation}\n\n"
|
||||
|
||||
# for debugging
|
||||
if requires_further_human_verification:
|
||||
get_logger().debug(f"Ticket compliance requires further human verification",
|
||||
artifact={'ticket_url': ticket_url,
|
||||
'requires_further_human_verification': requires_further_human_verification,
|
||||
'compliance_level': ticket_compliance_level})
|
||||
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to process ticket compliance: {e}")
|
||||
continue
|
||||
|
||||
# Calculate overall compliance level and emoji
|
||||
if all_compliance_levels:
|
||||
if all(level == 'Fully compliant' for level in all_compliance_levels):
|
||||
compliance_level = 'Fully compliant'
|
||||
compliance_emoji = '✅'
|
||||
elif all(level == 'PR Code Verified' for level in all_compliance_levels):
|
||||
compliance_level = 'PR Code Verified'
|
||||
compliance_emoji = '✅'
|
||||
elif any(level == 'Not compliant' for level in all_compliance_levels):
|
||||
# If there's a mix of compliant and non-compliant tickets
|
||||
if any(level in ['Fully compliant', 'PR Code Verified'] for level in all_compliance_levels):
|
||||
compliance_level = 'Partially compliant'
|
||||
compliance_emoji = '🔶'
|
||||
else:
|
||||
compliance_level = 'Not compliant'
|
||||
compliance_emoji = '❌'
|
||||
elif any(level == 'Partially compliant' for level in all_compliance_levels):
|
||||
compliance_level = 'Partially compliant'
|
||||
compliance_emoji = '🔶'
|
||||
else:
|
||||
compliance_level = 'PR Code Verified'
|
||||
compliance_emoji = '✅'
|
||||
|
||||
# Set extra statistics outside the ticket loop
|
||||
get_settings().set('config.extra_statistics', {'compliance_level': compliance_level})
|
||||
|
||||
# editing table row for ticket compliance analysis
|
||||
if gfm_supported:
|
||||
markdown_text += f"<tr><td>\n\n"
|
||||
markdown_text += f"**{emoji} Ticket compliance analysis {compliance_level}**\n\n"
|
||||
markdown_text += f"**{emoji} Ticket compliance analysis {compliance_emoji}**\n\n"
|
||||
markdown_text += ticket_compliance_str
|
||||
markdown_text += f"</td></tr>\n"
|
||||
else:
|
||||
markdown_text += f"### {emoji} Ticket compliance analysis {compliance_level}\n\n"
|
||||
markdown_text += ticket_compliance_str+"\n\n"
|
||||
markdown_text += f"### {emoji} Ticket compliance analysis {compliance_emoji}\n\n"
|
||||
markdown_text += ticket_compliance_str + "\n\n"
|
||||
|
||||
return markdown_text
|
||||
|
||||
|
||||
@ -648,12 +704,14 @@ def _fix_key_value(key: str, value: str):
|
||||
|
||||
|
||||
def load_yaml(response_text: str, keys_fix_yaml: List[str] = [], first_key="", last_key="") -> dict:
|
||||
response_text_original = copy.deepcopy(response_text)
|
||||
response_text = response_text.strip('\n').removeprefix('```yaml').rstrip().removesuffix('```')
|
||||
try:
|
||||
data = yaml.safe_load(response_text)
|
||||
except Exception as e:
|
||||
get_logger().warning(f"Initial failure to parse AI prediction: {e}")
|
||||
data = try_fix_yaml(response_text, keys_fix_yaml=keys_fix_yaml, first_key=first_key, last_key=last_key)
|
||||
data = try_fix_yaml(response_text, keys_fix_yaml=keys_fix_yaml, first_key=first_key, last_key=last_key,
|
||||
response_text_original=response_text_original)
|
||||
if not data:
|
||||
get_logger().error(f"Failed to parse AI prediction after fallbacks",
|
||||
artifact={'response_text': response_text})
|
||||
@ -667,7 +725,8 @@ def load_yaml(response_text: str, keys_fix_yaml: List[str] = [], first_key="", l
|
||||
def try_fix_yaml(response_text: str,
|
||||
keys_fix_yaml: List[str] = [],
|
||||
first_key="",
|
||||
last_key="",) -> dict:
|
||||
last_key="",
|
||||
response_text_original="") -> dict:
|
||||
response_text_lines = response_text.split('\n')
|
||||
|
||||
keys_yaml = ['relevant line:', 'suggestion content:', 'relevant file:', 'existing code:', 'improved code:']
|
||||
@ -684,11 +743,13 @@ def try_fix_yaml(response_text: str,
|
||||
get_logger().info(f"Successfully parsed AI prediction after adding |-\n")
|
||||
return data
|
||||
except:
|
||||
get_logger().info(f"Failed to parse AI prediction after adding |-\n")
|
||||
pass
|
||||
|
||||
# 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 not snippet:
|
||||
snippet = re.search(snippet_pattern, response_text_original) # before we removed the "```"
|
||||
if snippet:
|
||||
snippet_text = snippet.group()
|
||||
try:
|
||||
@ -728,9 +789,19 @@ def try_fix_yaml(response_text: str,
|
||||
except:
|
||||
pass
|
||||
|
||||
# fifth fallback - try to remove leading '+' (sometimes added by AI for 'existing code' and 'improved code')
|
||||
response_text_lines_copy = response_text_lines.copy()
|
||||
for i in range(0, len(response_text_lines_copy)):
|
||||
if response_text_lines_copy[i].startswith('+'):
|
||||
response_text_lines_copy[i] = ' ' + response_text_lines_copy[i][1:]
|
||||
try:
|
||||
data = yaml.safe_load('\n'.join(response_text_lines_copy))
|
||||
get_logger().info(f"Successfully parsed AI prediction after removing leading '+'")
|
||||
return data
|
||||
except:
|
||||
pass
|
||||
|
||||
# fifth fallback - try to remove last lines
|
||||
data = {}
|
||||
# sixth fallback - try to remove last lines
|
||||
for i in range(1, len(response_text_lines)):
|
||||
response_text_lines_tmp = '\n'.join(response_text_lines[:-i])
|
||||
try:
|
||||
|
@ -22,6 +22,7 @@ def set_parser():
|
||||
- cli.py --pr_url=... ask "write me a poem about this PR"
|
||||
- cli.py --pr_url=... reflect
|
||||
- cli.py --issue_url=... similar_issue
|
||||
- cli.py --pr_url/--issue_url= help_docs [<asked question>]
|
||||
|
||||
Supported commands:
|
||||
- review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement.
|
||||
@ -41,6 +42,8 @@ def set_parser():
|
||||
|
||||
- generate_labels
|
||||
|
||||
- help_docs - Ask a question, from either an issue or PR context, on a given repo (current context or a different one)
|
||||
|
||||
|
||||
Configuration:
|
||||
To edit any configuration parameter from 'configuration.toml', just add -config_path=<value>.
|
||||
|
@ -19,22 +19,23 @@ global_settings = Dynaconf(
|
||||
"settings/pr_questions_prompts.toml",
|
||||
"settings/pr_line_questions_prompts.toml",
|
||||
"settings/pr_description_prompts.toml",
|
||||
"settings/pr_code_suggestions_prompts.toml",
|
||||
"settings/pr_code_suggestions_reflect_prompts.toml",
|
||||
"settings/pr_sort_code_suggestions_prompts.toml",
|
||||
"settings/code_suggestions/pr_code_suggestions_prompts.toml",
|
||||
"settings/code_suggestions/pr_code_suggestions_prompts_not_decoupled.toml",
|
||||
"settings/code_suggestions/pr_code_suggestions_reflect_prompts.toml",
|
||||
"settings/pr_information_from_user_prompts.toml",
|
||||
"settings/pr_update_changelog_prompts.toml",
|
||||
"settings/pr_custom_labels.toml",
|
||||
"settings/pr_add_docs.toml",
|
||||
"settings/custom_labels.toml",
|
||||
"settings/pr_help_prompts.toml",
|
||||
"settings/pr_help_docs_prompts.toml",
|
||||
"settings/.secrets.toml",
|
||||
"settings_prod/.secrets.toml",
|
||||
]]
|
||||
)
|
||||
|
||||
|
||||
def get_settings():
|
||||
def get_settings(use_context=False):
|
||||
"""
|
||||
Retrieves the current settings.
|
||||
|
||||
|
@ -183,6 +183,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
return True
|
||||
|
||||
def set_pr(self, pr_url: str):
|
||||
self.pr_url = pr_url
|
||||
self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url)
|
||||
self.pr = self._get_pr()
|
||||
|
||||
@ -382,7 +383,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
get_logger().debug(f"Skipping publish_comment for temporary comment: {pr_comment}")
|
||||
return None
|
||||
comment = Comment(content=pr_comment)
|
||||
thread = CommentThread(comments=[comment], thread_context=thread_context, status=5)
|
||||
thread = CommentThread(comments=[comment], thread_context=thread_context, status=1)
|
||||
thread_response = self.azure_devops_client.create_thread(
|
||||
comment_thread=thread,
|
||||
project=self.workspace_slug,
|
||||
@ -614,8 +615,11 @@ class AzureDevopsProvider(GitProvider):
|
||||
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}")
|
||||
get_logger().info(f"Failed to get pr id, error: {e}")
|
||||
return ""
|
||||
|
||||
def publish_file_comments(self, file_comments: list) -> bool:
|
||||
pass
|
||||
|
||||
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||
return self.pr_url+f"?_a=files&path={relevant_file}"
|
||||
|
@ -30,12 +30,15 @@ class BitbucketProvider(GitProvider):
|
||||
):
|
||||
s = requests.Session()
|
||||
try:
|
||||
bearer = context.get("bitbucket_bearer_token", None)
|
||||
self.bearer_token = bearer = context.get("bitbucket_bearer_token", None)
|
||||
if not bearer and get_settings().get("BITBUCKET.BEARER_TOKEN", None):
|
||||
self.bearer_token = bearer = get_settings().get("BITBUCKET.BEARER_TOKEN", None)
|
||||
s.headers["Authorization"] = f"Bearer {bearer}"
|
||||
except Exception:
|
||||
self.bearer_token = get_settings().get("BITBUCKET.BEARER_TOKEN", None)
|
||||
s.headers[
|
||||
"Authorization"
|
||||
] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}'
|
||||
] = f'Bearer {self.bearer_token}'
|
||||
s.headers["Content-Type"] = "application/json"
|
||||
self.headers = s.headers
|
||||
self.bitbucket_client = Cloud(session=s)
|
||||
@ -67,6 +70,37 @@ class BitbucketProvider(GitProvider):
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def get_git_repo_url(self, pr_url: str=None) -> str: #bitbucket does not support issue url, so ignore param
|
||||
try:
|
||||
parsed_url = urlparse(self.pr_url)
|
||||
return f"{parsed_url.scheme}://{parsed_url.netloc}/{self.workspace_slug}/{self.repo_slug}.git"
|
||||
except Exception as e:
|
||||
get_logger().exception(f"url is not a valid merge requests url: {self.pr_url}")
|
||||
return ""
|
||||
|
||||
# Given a git repo url, return prefix and suffix of the provider in order to view a given file belonging to that repo.
|
||||
# Example: git clone git clone https://bitbucket.org/codiumai/pr-agent.git and branch: main -> prefix: "https://bitbucket.org/codiumai/pr-agent/src/main", suffix: ""
|
||||
# In case git url is not provided, provider will use PR context (which includes branch) to determine the prefix and suffix.
|
||||
def get_canonical_url_parts(self, repo_git_url:str=None, desired_branch:str=None) -> Tuple[str, str]:
|
||||
scheme_and_netloc = None
|
||||
if repo_git_url:
|
||||
parsed_git_url = urlparse(repo_git_url)
|
||||
scheme_and_netloc = parsed_git_url.scheme + "://" + parsed_git_url.netloc
|
||||
repo_path = parsed_git_url.path.split('.git')[0][1:] #/<workspace>/<repo>.git -> <workspace>/<repo>
|
||||
if repo_path.count('/') != 1:
|
||||
get_logger().error(f"repo_git_url is not a valid git repo url: {repo_git_url}")
|
||||
return ("", "")
|
||||
workspace_name, project_name = repo_path.split('/')
|
||||
else:
|
||||
desired_branch = self.get_pr_branch()
|
||||
parsed_pr_url = urlparse(self.pr_url)
|
||||
scheme_and_netloc = parsed_pr_url.scheme + "://" + parsed_pr_url.netloc
|
||||
workspace_name, project_name = (self.workspace_slug, self.repo_slug)
|
||||
prefix = f"{scheme_and_netloc}/{workspace_name}/{project_name}/src/{desired_branch}"
|
||||
suffix = "" #None
|
||||
return (prefix, suffix)
|
||||
|
||||
|
||||
def publish_code_suggestions(self, code_suggestions: list) -> bool:
|
||||
"""
|
||||
Publishes code suggestions as comments on the PR.
|
||||
@ -457,7 +491,7 @@ class BitbucketProvider(GitProvider):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
|
||||
def _parse_pr_url(pr_url: str) -> Tuple[str, int, int]:
|
||||
parsed_url = urlparse(pr_url)
|
||||
|
||||
if "bitbucket.org" not in parsed_url.netloc:
|
||||
@ -559,3 +593,21 @@ class BitbucketProvider(GitProvider):
|
||||
# bitbucket does not support labels
|
||||
def get_pr_labels(self, update=False):
|
||||
pass
|
||||
#Clone related
|
||||
def _prepare_clone_url_with_token(self, repo_url_to_clone: str) -> str | None:
|
||||
if "bitbucket.org" not in repo_url_to_clone:
|
||||
get_logger().error("Repo URL is not a valid bitbucket URL.")
|
||||
return None
|
||||
bearer_token = self.bearer_token
|
||||
if not bearer_token:
|
||||
get_logger().error("No bearer token provided. Returning None")
|
||||
return None
|
||||
|
||||
#For example: For repo: https://bitbucket.org/codiumai/pr-agent-tests.git
|
||||
#clone url will be: https://x-token-auth:<token>@bitbucket.org/codiumai/pr-agent-tests.git
|
||||
(scheme, base_url) = repo_url_to_clone.split("bitbucket.org")
|
||||
if not all([scheme, base_url]):
|
||||
get_logger().error(f"repo_url_to_clone: {repo_url_to_clone} is not a valid bitbucket URL.")
|
||||
return None
|
||||
clone_url = f"{scheme}x-token-auth:{bearer_token}@bitbucket.org{base_url}"
|
||||
return clone_url
|
||||
|
@ -7,6 +7,8 @@ from urllib.parse import quote_plus, urlparse
|
||||
|
||||
from atlassian.bitbucket import Bitbucket
|
||||
from requests.exceptions import HTTPError
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
from ..algo.git_patch_processing import decode_if_bytes
|
||||
from ..algo.language_handler import is_valid_file
|
||||
@ -34,7 +36,7 @@ class BitbucketServerProvider(GitProvider):
|
||||
self.incremental = incremental
|
||||
self.diff_files = None
|
||||
self.bitbucket_pull_request_api_url = pr_url
|
||||
|
||||
self.bearer_token = get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN", None)
|
||||
self.bitbucket_server_url = self._parse_bitbucket_server(url=pr_url)
|
||||
self.bitbucket_client = bitbucket_client or Bitbucket(url=self.bitbucket_server_url,
|
||||
token=get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN",
|
||||
@ -47,6 +49,35 @@ class BitbucketServerProvider(GitProvider):
|
||||
if pr_url:
|
||||
self.set_pr(pr_url)
|
||||
|
||||
def get_git_repo_url(self, pr_url: str=None) -> str: #bitbucket server does not support issue url, so ignore param
|
||||
try:
|
||||
parsed_url = urlparse(self.pr_url)
|
||||
return f"{parsed_url.scheme}://{parsed_url.netloc}/scm/{self.workspace_slug.lower()}/{self.repo_slug.lower()}.git"
|
||||
except Exception as e:
|
||||
get_logger().exception(f"url is not a valid merge requests url: {self.pr_url}")
|
||||
return ""
|
||||
|
||||
# Given a git repo url, return prefix and suffix of the provider in order to view a given file belonging to that repo.
|
||||
# Example: https://bitbucket.dev.my_inc.com/scm/my_work/my_repo.git and branch: my_branch -> prefix: "https://bitbucket.dev.my_inc.com/projects/MY_WORK/repos/my_repo/browse/src", suffix: "?at=refs%2Fheads%2Fmy_branch"
|
||||
# In case git url is not provided, provider will use PR context (which includes branch) to determine the prefix and suffix.
|
||||
def get_canonical_url_parts(self, repo_git_url:str=None, desired_branch:str=None) -> Tuple[str, str]:
|
||||
workspace_name = None
|
||||
project_name = None
|
||||
if not repo_git_url:
|
||||
desired_branch = self.get_pr_branch()
|
||||
workspace_name = self.workspace_slug
|
||||
project_name = self.repo_slug
|
||||
elif '.git' in repo_git_url and 'scm/' in repo_git_url:
|
||||
repo_path = repo_git_url.split('.git')[0].split('scm/')[-1]
|
||||
if repo_path.count('/') == 1: # Has to have the form <workspace>/<repo>
|
||||
workspace_name, project_name = repo_path.split('/')
|
||||
if not workspace_name or not project_name:
|
||||
get_logger().error(f"workspace_name or project_name not found in context, either git url: {repo_git_url} or uninitialized workspace/project.")
|
||||
return ("", "")
|
||||
prefix = f"{self.bitbucket_server_url}/projects/{workspace_name}/repos/{project_name}/browse"
|
||||
suffix = f"?at=refs%2Fheads%2F{desired_branch}"
|
||||
return (prefix, suffix)
|
||||
|
||||
def get_repo_settings(self):
|
||||
try:
|
||||
content = self.bitbucket_client.get_content_of_file(self.workspace_slug, self.repo_slug, ".pr_agent.toml", self.get_pr_branch())
|
||||
@ -234,7 +265,7 @@ class BitbucketServerProvider(GitProvider):
|
||||
new_file_content_str = self.get_file(file_path, head_sha)
|
||||
new_file_content_str = decode_if_bytes(new_file_content_str)
|
||||
|
||||
patch = load_large_diff(file_path, new_file_content_str, original_file_content_str)
|
||||
patch = load_large_diff(file_path, new_file_content_str, original_file_content_str, show_warning=False)
|
||||
|
||||
diff_files.append(
|
||||
FilePatchInfo(
|
||||
@ -481,3 +512,28 @@ class BitbucketServerProvider(GitProvider):
|
||||
|
||||
def _get_merge_base(self):
|
||||
return f"rest/api/latest/projects/{self.workspace_slug}/repos/{self.repo_slug}/pull-requests/{self.pr_num}/merge-base"
|
||||
# Clone related
|
||||
def _prepare_clone_url_with_token(self, repo_url_to_clone: str) -> str | None:
|
||||
if 'bitbucket.' not in repo_url_to_clone:
|
||||
get_logger().error("Repo URL is not a valid bitbucket URL.")
|
||||
return None
|
||||
bearer_token = self.bearer_token
|
||||
if not bearer_token:
|
||||
get_logger().error("No bearer token provided. Returning None")
|
||||
return None
|
||||
# Return unmodified URL as the token is passed via HTTP headers in _clone_inner, as seen below.
|
||||
return repo_url_to_clone
|
||||
|
||||
#Overriding the shell command, since for some reason usage of x-token-auth doesn't work, as mentioned here:
|
||||
# https://stackoverflow.com/questions/56760396/cloning-bitbucket-server-repo-with-access-tokens
|
||||
def _clone_inner(self, repo_url: str, dest_folder: str, operation_timeout_in_seconds: int=None):
|
||||
bearer_token = self.bearer_token
|
||||
if not bearer_token:
|
||||
#Shouldn't happen since this is checked in _prepare_clone, therefore - throwing an exception.
|
||||
raise RuntimeError(f"Bearer token is required!")
|
||||
|
||||
cli_args = shlex.split(f"git clone -c http.extraHeader='Authorization: Bearer {bearer_token}' "
|
||||
f"--filter=blob:none --depth 1 {repo_url} {dest_folder}")
|
||||
|
||||
subprocess.run(cli_args, check=True, # check=True will raise an exception if the command fails
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=operation_timeout_in_seconds)
|
||||
|
@ -1,6 +1,9 @@
|
||||
from abc import ABC, abstractmethod
|
||||
# enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED)
|
||||
from typing import Optional
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from pr_agent.algo.types import FilePatchInfo
|
||||
from pr_agent.algo.utils import Range, process_description
|
||||
@ -14,6 +17,75 @@ class GitProvider(ABC):
|
||||
def is_supported(self, capability: str) -> bool:
|
||||
pass
|
||||
|
||||
#Given a url (issues or PR/MR) - get the .git repo url to which they belong. Needs to be implemented by the provider.
|
||||
def get_git_repo_url(self, issues_or_pr_url: str) -> str:
|
||||
get_logger().warning("Not implemented! Returning empty url")
|
||||
return ""
|
||||
|
||||
# Given a git repo url, return prefix and suffix of the provider in order to view a given file belonging to that repo. Needs to be implemented by the provider.
|
||||
# For example: For a git: https://git_provider.com/MY_PROJECT/MY_REPO.git and desired branch: <MY_BRANCH> then it should return ('https://git_provider.com/projects/MY_PROJECT/repos/MY_REPO/.../<MY_BRANCH>', '?=<SOME HEADER>')
|
||||
# so that to properly view the file: docs/readme.md -> <PREFIX>/docs/readme.md<SUFFIX> -> https://git_provider.com/projects/MY_PROJECT/repos/MY_REPO/<MY_BRANCH>/docs/readme.md?=<SOME HEADER>)
|
||||
def get_canonical_url_parts(self, repo_git_url:str, desired_branch:str) -> Tuple[str, str]:
|
||||
get_logger().warning("Not implemented! Returning empty prefix and suffix")
|
||||
return ("", "")
|
||||
|
||||
|
||||
#Clone related API
|
||||
#An object which ensures deletion of a cloned repo, once it becomes out of scope.
|
||||
# Example usage:
|
||||
# with TemporaryDirectory() as tmp_dir:
|
||||
# returned_obj: GitProvider.ScopedClonedRepo = self.git_provider.clone(self.repo_url, tmp_dir, remove_dest_folder=False)
|
||||
# print(returned_obj.path) #Use returned_obj.path.
|
||||
# #From this point, returned_obj.path may be deleted at any point and therefore must not be used.
|
||||
class ScopedClonedRepo(object):
|
||||
def __init__(self, dest_folder):
|
||||
self.path = dest_folder
|
||||
|
||||
def __del__(self):
|
||||
if self.path and os.path.exists(self.path):
|
||||
shutil.rmtree(self.path, ignore_errors=True)
|
||||
|
||||
#Method to allow implementors to manipulate the repo url to clone (such as embedding tokens in the url string). Needs to be implemented by the provider.
|
||||
def _prepare_clone_url_with_token(self, repo_url_to_clone: str) -> str | None:
|
||||
get_logger().warning("Not implemented! Returning None")
|
||||
return None
|
||||
|
||||
# Does a shallow clone, using a forked process to support a timeout guard.
|
||||
# In case operation has failed, it is expected to throw an exception as this method does not return a value.
|
||||
def _clone_inner(self, repo_url: str, dest_folder: str, operation_timeout_in_seconds: int=None) -> None:
|
||||
#The following ought to be equivalent to:
|
||||
# #Repo.clone_from(repo_url, dest_folder)
|
||||
# , but with throwing an exception upon timeout.
|
||||
# Note: This can only be used in context that supports using pipes.
|
||||
subprocess.run([
|
||||
"git", "clone",
|
||||
"--filter=blob:none",
|
||||
"--depth", "1",
|
||||
repo_url, dest_folder
|
||||
], check=True, # check=True will raise an exception if the command fails
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=operation_timeout_in_seconds)
|
||||
|
||||
CLONE_TIMEOUT_SEC = 20
|
||||
# Clone a given url to a destination folder. If successful, returns an object that wraps the destination folder,
|
||||
# deleting it once it is garbage collected. See: GitProvider.ScopedClonedRepo for more details.
|
||||
def clone(self, repo_url_to_clone: str, dest_folder: str, remove_dest_folder: bool = True,
|
||||
operation_timeout_in_seconds: int=CLONE_TIMEOUT_SEC) -> ScopedClonedRepo|None:
|
||||
returned_obj = None
|
||||
clone_url = self._prepare_clone_url_with_token(repo_url_to_clone)
|
||||
if not clone_url:
|
||||
get_logger().error("Clone failed: Unable to obtain url to clone.")
|
||||
return returned_obj
|
||||
try:
|
||||
if remove_dest_folder and os.path.exists(dest_folder) and os.path.isdir(dest_folder):
|
||||
shutil.rmtree(dest_folder)
|
||||
self._clone_inner(clone_url, dest_folder, operation_timeout_in_seconds)
|
||||
returned_obj = GitProvider.ScopedClonedRepo(dest_folder)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Clone failed: Could not clone url.",
|
||||
artifact={"error": str(e), "url": clone_url, "dest_folder": dest_folder})
|
||||
finally:
|
||||
return returned_obj
|
||||
|
||||
@abstractmethod
|
||||
def get_files(self) -> list:
|
||||
pass
|
||||
|
@ -5,11 +5,13 @@ import itertools
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from github import AppAuthentication, Auth, Github
|
||||
from github.Issue import Issue
|
||||
from github import AppAuthentication, Auth, Github, GithubException
|
||||
from retry import retry
|
||||
from starlette_context import context
|
||||
|
||||
@ -41,6 +43,7 @@ class GithubProvider(GitProvider):
|
||||
self.repo = None
|
||||
self.pr_num = None
|
||||
self.pr = None
|
||||
self.issue_main = None
|
||||
self.github_user_id = None
|
||||
self.diff_files = None
|
||||
self.git_files = None
|
||||
@ -50,9 +53,29 @@ class GithubProvider(GitProvider):
|
||||
self.pr_commits = list(self.pr.get_commits())
|
||||
self.last_commit_id = self.pr_commits[-1]
|
||||
self.pr_url = self.get_pr_url() # pr_url for github actions can be as api.github.com, so we need to get the url from the pr object
|
||||
else:
|
||||
elif pr_url and 'issue' in pr_url: #url is an issue
|
||||
self.issue_main = self._get_issue_handle(pr_url)
|
||||
else: #Instantiated the provider without a PR / Issue
|
||||
self.pr_commits = None
|
||||
|
||||
def _get_issue_handle(self, issue_url) -> Optional[Issue]:
|
||||
repo_name, issue_number = self._parse_issue_url(issue_url)
|
||||
if not repo_name or not issue_number:
|
||||
get_logger().error(f"Given url: {issue_url} is not a valid issue.")
|
||||
return None
|
||||
# else: Check if can get a valid Repo handle:
|
||||
try:
|
||||
repo_obj = self.github_client.get_repo(repo_name)
|
||||
if not repo_obj:
|
||||
get_logger().error(f"Given url: {issue_url}, belonging to owner/repo: {repo_name} does "
|
||||
f"not have a valid repository: {self.get_git_repo_url(issue_url)}")
|
||||
return None
|
||||
# else: Valid repo handle:
|
||||
return repo_obj.get_issue(issue_number)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to get an issue object for issue: {issue_url}, belonging to owner/repo: {repo_name}")
|
||||
return None
|
||||
|
||||
def get_incremental_commits(self, incremental=IncrementalPR(False)):
|
||||
self.incremental = incremental
|
||||
if self.incremental.is_incremental:
|
||||
@ -62,6 +85,63 @@ class GithubProvider(GitProvider):
|
||||
def is_supported(self, capability: str) -> bool:
|
||||
return True
|
||||
|
||||
def _get_owner_and_repo_path(self, given_url: str) -> str:
|
||||
try:
|
||||
repo_path = None
|
||||
if 'issues' in given_url:
|
||||
repo_path, _ = self._parse_issue_url(given_url)
|
||||
elif 'pull' in given_url:
|
||||
repo_path, _ = self._parse_pr_url(given_url)
|
||||
elif given_url.endswith('.git'):
|
||||
parsed_url = urlparse(given_url)
|
||||
repo_path = (parsed_url.path.split('.git')[0])[1:] # /<owner>/<repo>.git -> <owner>/<repo>
|
||||
if not repo_path:
|
||||
get_logger().error(f"url is neither an issues url nor a pr url nor a valid git url: {given_url}. Returning empty result.")
|
||||
return ""
|
||||
return repo_path
|
||||
except Exception as e:
|
||||
get_logger().exception(f"unable to parse url: {given_url}. Returning empty result.")
|
||||
return ""
|
||||
|
||||
def get_git_repo_url(self, issues_or_pr_url: str) -> str:
|
||||
repo_path = self._get_owner_and_repo_path(issues_or_pr_url) #Return: <OWNER>/<REPO>
|
||||
if not repo_path or repo_path not in issues_or_pr_url:
|
||||
get_logger().error(f"Unable to retrieve owner/path from url: {issues_or_pr_url}")
|
||||
return ""
|
||||
return f"{self.base_url_html}/{repo_path}.git" #https://github.com / <OWNER>/<REPO>.git
|
||||
|
||||
# Given a git repo url, return prefix and suffix of the provider in order to view a given file belonging to that repo.
|
||||
# Example: https://github.com/qodo-ai/pr-agent.git and branch: v0.8 -> prefix: "https://github.com/qodo-ai/pr-agent/blob/v0.8", suffix: ""
|
||||
# In case git url is not provided, provider will use PR context (which includes branch) to determine the prefix and suffix.
|
||||
def get_canonical_url_parts(self, repo_git_url:str, desired_branch:str) -> Tuple[str, str]:
|
||||
owner = None
|
||||
repo = None
|
||||
scheme_and_netloc = None
|
||||
|
||||
if repo_git_url or self.issue_main: #Either user provided an external git url, which may be different than what this provider was initialized with, or an issue:
|
||||
desired_branch = desired_branch if repo_git_url else self.issue_main.repository.default_branch
|
||||
html_url = repo_git_url if repo_git_url else self.issue_main.html_url
|
||||
parsed_git_url = urlparse(html_url)
|
||||
scheme_and_netloc = parsed_git_url.scheme + "://" + parsed_git_url.netloc
|
||||
repo_path = self._get_owner_and_repo_path(html_url)
|
||||
if repo_path.count('/') == 1: #Has to have the form <owner>/<repo>
|
||||
owner, repo = repo_path.split('/')
|
||||
else:
|
||||
get_logger().error(f"Invalid repo_path: {repo_path} from url: {html_url}")
|
||||
return ("", "")
|
||||
|
||||
if (not owner or not repo) and self.repo: #"else" - User did not provide an external git url, or not an issue, use self.repo object
|
||||
owner, repo = self.repo.split('/')
|
||||
scheme_and_netloc = self.base_url_html
|
||||
desired_branch = self.get_pr_branch()
|
||||
if not all([scheme_and_netloc, owner, repo]): #"else": Not invoked from a PR context,but no provided git url for context
|
||||
get_logger().error(f"Unable to get canonical url parts since missing context (PR or explicit git url)")
|
||||
return ("", "")
|
||||
|
||||
prefix = f"{scheme_and_netloc}/{owner}/{repo}/blob/{desired_branch}"
|
||||
suffix = "" # github does not add a suffix
|
||||
return (prefix, suffix)
|
||||
|
||||
def get_pr_url(self) -> str:
|
||||
return self.pr.html_url
|
||||
|
||||
@ -289,10 +369,19 @@ class GithubProvider(GitProvider):
|
||||
self.publish_persistent_comment_full(pr_comment, initial_header, update_header, name, final_update_message)
|
||||
|
||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||
if not self.pr and not self.issue_main:
|
||||
get_logger().error("Cannot publish a comment if missing PR/Issue context")
|
||||
return None
|
||||
|
||||
if is_temporary and not get_settings().config.publish_output_progress:
|
||||
get_logger().debug(f"Skipping publish_comment for temporary comment: {pr_comment}")
|
||||
return None
|
||||
pr_comment = self.limit_output_characters(pr_comment, self.max_comment_chars)
|
||||
|
||||
# In case this is an issue, can publish the comment on the issue.
|
||||
if self.issue_main:
|
||||
return self.issue_main.create_comment(pr_comment)
|
||||
|
||||
response = self.pr.create_issue_comment(pr_comment)
|
||||
if hasattr(response, "user") and hasattr(response.user, "login"):
|
||||
self.github_user_id = response.user.login
|
||||
@ -475,8 +564,17 @@ class GithubProvider(GitProvider):
|
||||
return False
|
||||
|
||||
def edit_comment(self, comment, body: str):
|
||||
try:
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
comment.edit(body=body)
|
||||
except GithubException as e:
|
||||
if hasattr(e, "status") and e.status == 403:
|
||||
# Log as warning for permission-related issues (usually due to polling)
|
||||
get_logger().warning(
|
||||
"Failed to edit github comment due to permission restrictions",
|
||||
artifact={"error": e})
|
||||
else:
|
||||
get_logger().exception(f"Failed to edit github comment", artifact={"error": e})
|
||||
|
||||
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
try:
|
||||
@ -667,11 +765,11 @@ class GithubProvider(GitProvider):
|
||||
def _parse_issue_url(self, issue_url: str) -> Tuple[str, int]:
|
||||
parsed_url = urlparse(issue_url)
|
||||
|
||||
if 'github.com' not in parsed_url.netloc:
|
||||
raise ValueError("The provided URL is not a valid GitHub URL")
|
||||
if parsed_url.path.startswith('/api/v3'): #Check if came from github app
|
||||
parsed_url = urlparse(issue_url.replace("/api/v3", ""))
|
||||
|
||||
path_parts = parsed_url.path.strip('/').split('/')
|
||||
if 'api.github.com' in parsed_url.netloc:
|
||||
if 'api.github.com' in parsed_url.netloc or '/api/v3' in issue_url: #Check if came from github app
|
||||
if len(path_parts) < 5 or path_parts[3] != 'issues':
|
||||
raise ValueError("The provided URL does not appear to be a GitHub ISSUE URL")
|
||||
repo_name = '/'.join(path_parts[1:3])
|
||||
@ -693,9 +791,9 @@ class GithubProvider(GitProvider):
|
||||
return repo_name, issue_number
|
||||
|
||||
def _get_github_client(self):
|
||||
deployment_type = get_settings().get("GITHUB.DEPLOYMENT_TYPE", "user")
|
||||
|
||||
if deployment_type == 'app':
|
||||
self.deployment_type = get_settings().get("GITHUB.DEPLOYMENT_TYPE", "user")
|
||||
self.auth = None
|
||||
if self.deployment_type == 'app':
|
||||
try:
|
||||
private_key = get_settings().github.private_key
|
||||
app_id = get_settings().github.app_id
|
||||
@ -705,16 +803,19 @@ class GithubProvider(GitProvider):
|
||||
raise ValueError("GitHub app installation ID is required when using GitHub app deployment")
|
||||
auth = AppAuthentication(app_id=app_id, private_key=private_key,
|
||||
installation_id=self.installation_id)
|
||||
return Github(app_auth=auth, base_url=self.base_url)
|
||||
|
||||
if deployment_type == 'user':
|
||||
self.auth = auth
|
||||
elif self.deployment_type == 'user':
|
||||
try:
|
||||
token = get_settings().github.user_token
|
||||
except AttributeError as e:
|
||||
raise ValueError(
|
||||
"GitHub token is required when using user deployment. See: "
|
||||
"https://github.com/Codium-ai/pr-agent#method-2-run-from-source") from e
|
||||
return Github(auth=Auth.Token(token), base_url=self.base_url)
|
||||
self.auth = Auth.Token(token)
|
||||
if self.auth:
|
||||
return Github(auth=self.auth, base_url=self.base_url)
|
||||
else:
|
||||
raise ValueError("Could not authenticate to GitHub")
|
||||
|
||||
def _get_repo(self):
|
||||
if hasattr(self, 'repo_obj') and \
|
||||
@ -878,6 +979,84 @@ class GithubProvider(GitProvider):
|
||||
except:
|
||||
return ""
|
||||
|
||||
def fetch_sub_issues(self, issue_url):
|
||||
"""
|
||||
Fetch sub-issues linked to the given GitHub issue URL using GraphQL via PyGitHub.
|
||||
"""
|
||||
sub_issues = set()
|
||||
|
||||
# Extract owner, repo, and issue number from URL
|
||||
parts = issue_url.rstrip("/").split("/")
|
||||
owner, repo, issue_number = parts[-4], parts[-3], parts[-1]
|
||||
|
||||
try:
|
||||
# Gets Issue ID from Issue Number
|
||||
query = f"""
|
||||
query {{
|
||||
repository(owner: "{owner}", name: "{repo}") {{
|
||||
issue(number: {issue_number}) {{
|
||||
id
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
response_tuple = self.github_client._Github__requester.requestJson("POST", "/graphql",
|
||||
input={"query": query})
|
||||
|
||||
# Extract the JSON response from the tuple and parses it
|
||||
if isinstance(response_tuple, tuple) and len(response_tuple) == 3:
|
||||
response_json = json.loads(response_tuple[2])
|
||||
else:
|
||||
get_logger().error(f"Unexpected response format: {response_tuple}")
|
||||
return sub_issues
|
||||
|
||||
|
||||
issue_id = response_json.get("data", {}).get("repository", {}).get("issue", {}).get("id")
|
||||
|
||||
if not issue_id:
|
||||
get_logger().warning(f"Issue ID not found for {issue_url}")
|
||||
return sub_issues
|
||||
|
||||
# Fetch Sub-Issues
|
||||
sub_issues_query = f"""
|
||||
query {{
|
||||
node(id: "{issue_id}") {{
|
||||
... on Issue {{
|
||||
subIssues(first: 10) {{
|
||||
nodes {{
|
||||
url
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
sub_issues_response_tuple = self.github_client._Github__requester.requestJson("POST", "/graphql", input={
|
||||
"query": sub_issues_query})
|
||||
|
||||
# Extract the JSON response from the tuple and parses it
|
||||
if isinstance(sub_issues_response_tuple, tuple) and len(sub_issues_response_tuple) == 3:
|
||||
sub_issues_response_json = json.loads(sub_issues_response_tuple[2])
|
||||
else:
|
||||
get_logger().error("Unexpected sub-issues response format", artifact={"response": sub_issues_response_tuple})
|
||||
return sub_issues
|
||||
|
||||
if not sub_issues_response_json.get("data", {}).get("node", {}).get("subIssues"):
|
||||
get_logger().error("Invalid sub-issues response structure")
|
||||
return sub_issues
|
||||
|
||||
nodes = sub_issues_response_json.get("data", {}).get("node", {}).get("subIssues", {}).get("nodes", [])
|
||||
get_logger().info(f"Github Sub-issues fetched: {len(nodes)}", artifact={"nodes": nodes})
|
||||
|
||||
for sub_issue in nodes:
|
||||
if "url" in sub_issue:
|
||||
sub_issues.add(sub_issue["url"])
|
||||
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to fetch sub-issues. Error: {e}")
|
||||
|
||||
return sub_issues
|
||||
|
||||
def auto_approve(self) -> bool:
|
||||
try:
|
||||
res = self.pr.create_review(event="APPROVE")
|
||||
@ -976,3 +1155,37 @@ class GithubProvider(GitProvider):
|
||||
get_logger().error(f"Failed to process patch for committable comment, error: {e}")
|
||||
return code_suggestions_copy
|
||||
|
||||
#Clone related
|
||||
def _prepare_clone_url_with_token(self, repo_url_to_clone: str) -> str | None:
|
||||
scheme = "https://"
|
||||
|
||||
#For example, to clone:
|
||||
#https://github.com/Codium-ai/pr-agent-pro.git
|
||||
#Need to embed inside the github token:
|
||||
#https://<token>@github.com/Codium-ai/pr-agent-pro.git
|
||||
|
||||
github_token = self.auth.token
|
||||
github_base_url = self.base_url_html
|
||||
if not all([github_token, github_base_url]):
|
||||
get_logger().error("Either missing auth token or missing base url")
|
||||
return None
|
||||
if scheme not in github_base_url:
|
||||
get_logger().error(f"Base url: {github_base_url} is missing prefix: {scheme}")
|
||||
return None
|
||||
github_com = github_base_url.split(scheme)[1] # e.g. 'github.com' or github.<org>.com
|
||||
if not github_com:
|
||||
get_logger().error(f"Base url: {github_base_url} has an empty base url")
|
||||
return None
|
||||
if github_com not in repo_url_to_clone:
|
||||
get_logger().error(f"url to clone: {repo_url_to_clone} does not contain {github_com}")
|
||||
return None
|
||||
repo_full_name = repo_url_to_clone.split(github_com)[-1]
|
||||
if not repo_full_name:
|
||||
get_logger().error(f"url to clone: {repo_url_to_clone} is malformed")
|
||||
return None
|
||||
|
||||
clone_url = scheme
|
||||
if self.deployment_type == 'app':
|
||||
clone_url += "git:"
|
||||
clone_url += f"{github_token}@{github_com}{repo_full_name}"
|
||||
return clone_url
|
||||
|
@ -57,6 +57,43 @@ class GitLabProvider(GitProvider):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_project_path_from_pr_or_issue_url(self, pr_or_issue_url: str) -> str:
|
||||
repo_project_path = None
|
||||
if 'issues' in pr_or_issue_url:
|
||||
#replace 'issues' with 'merge_requests', since gitlab provider does not support issue urls, just to get the git repo url:
|
||||
pr_or_issue_url = pr_or_issue_url.replace('issues', 'merge_requests')
|
||||
if 'merge_requests' in pr_or_issue_url:
|
||||
repo_project_path, _ = self._parse_merge_request_url(pr_or_issue_url)
|
||||
if not repo_project_path:
|
||||
get_logger().error(f"url is not a valid merge requests url: {pr_or_issue_url}")
|
||||
return ""
|
||||
return repo_project_path
|
||||
|
||||
def get_git_repo_url(self, issues_or_pr_url: str) -> str:
|
||||
provider_url = issues_or_pr_url
|
||||
repo_path = self._get_project_path_from_pr_or_issue_url(provider_url)
|
||||
if not repo_path or repo_path not in issues_or_pr_url:
|
||||
get_logger().error(f"Unable to retrieve project path from url: {issues_or_pr_url}")
|
||||
return ""
|
||||
return f"{issues_or_pr_url.split(repo_path)[0]}{repo_path}.git"
|
||||
|
||||
# Given a git repo url, return prefix and suffix of the provider in order to view a given file belonging to that repo.
|
||||
# Example: https://gitlab.com/codiumai/pr-agent.git and branch: t1 -> prefix: "https://gitlab.com/codiumai/pr-agent/-/blob/t1", suffix: "?ref_type=heads"
|
||||
# In case git url is not provided, provider will use PR context (which includes branch) to determine the prefix and suffix.
|
||||
def get_canonical_url_parts(self, repo_git_url:str=None, desired_branch:str=None) -> Tuple[str, str]:
|
||||
repo_path = ""
|
||||
if not repo_git_url and not self.pr_url:
|
||||
get_logger().error("Cannot get canonical URL parts: missing either context PR URL or a repo GIT URL")
|
||||
return ("", "")
|
||||
if not repo_git_url: #Use PR url as context
|
||||
repo_path = self._get_project_path_from_pr_or_issue_url(self.pr_url)
|
||||
desired_branch = self.get_pr_branch()
|
||||
else: #Use repo git url
|
||||
repo_path = repo_git_url.split('.git')[0].split('.com/')[-1]
|
||||
prefix = f"{self.gitlab_url}/{repo_path}/-/blob/{desired_branch}"
|
||||
suffix = "?ref_type=heads" # gitlab cloud adds this suffix. gitlab server does not, but it is harmless.
|
||||
return (prefix, suffix)
|
||||
|
||||
@property
|
||||
def pr(self):
|
||||
'''The GitLab terminology is merge request (MR) instead of pull request (PR)'''
|
||||
@ -181,7 +218,13 @@ class GitLabProvider(GitProvider):
|
||||
get_logger().exception(f"Could not update merge request {self.id_mr} description: {e}")
|
||||
|
||||
def get_latest_commit_url(self):
|
||||
try:
|
||||
return self.mr.commits().next().web_url
|
||||
except StopIteration: # no commits
|
||||
return ""
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Could not get latest commit URL: {e}")
|
||||
return ""
|
||||
|
||||
def get_comment_url(self, comment):
|
||||
return f"{self.mr.web_url}#note_{comment.id}"
|
||||
@ -591,3 +634,24 @@ class GitLabProvider(GitProvider):
|
||||
get_logger().info(f"Failed adding line link, error: {e}")
|
||||
|
||||
return ""
|
||||
#Clone related
|
||||
def _prepare_clone_url_with_token(self, repo_url_to_clone: str) -> str | None:
|
||||
if "gitlab." not in repo_url_to_clone:
|
||||
get_logger().error(f"Repo URL: {repo_url_to_clone} is not a valid gitlab URL.")
|
||||
return None
|
||||
(scheme, base_url) = repo_url_to_clone.split("gitlab.")
|
||||
access_token = self.gl.oauth_token
|
||||
if not all([scheme, access_token, base_url]):
|
||||
get_logger().error(f"Either no access token found, or repo URL: {repo_url_to_clone} "
|
||||
f"is missing prefix: {scheme} and/or base URL: {base_url}.")
|
||||
return None
|
||||
|
||||
#Note that the ""official"" method found here:
|
||||
# https://docs.gitlab.com/user/profile/personal_access_tokens/#clone-repository-using-personal-access-token
|
||||
# requires a username, which may not be applicable.
|
||||
# The following solution is taken from: https://stackoverflow.com/questions/25409700/using-gitlab-token-to-clone-without-authentication/35003812#35003812
|
||||
# For example: For repo url: https://gitlab.codium-inc.com/qodo/autoscraper.git
|
||||
# Then to clone one will issue: 'git clone https://oauth2:<access token>@gitlab.codium-inc.com/qodo/autoscraper.git'
|
||||
|
||||
clone_url = f"{scheme}oauth2:{access_token}@gitlab.{base_url}"
|
||||
return clone_url
|
||||
|
@ -33,20 +33,16 @@ azure_devops_server = get_settings().get("azure_devops_server")
|
||||
WEBHOOK_USERNAME = azure_devops_server.get("webhook_username")
|
||||
WEBHOOK_PASSWORD = azure_devops_server.get("webhook_password")
|
||||
|
||||
def handle_request(
|
||||
background_tasks: BackgroundTasks, url: str, body: str, log_context: dict
|
||||
async def handle_request_comment( url: str, body: str, log_context: dict
|
||||
):
|
||||
log_context["action"] = body
|
||||
log_context["api_url"] = url
|
||||
|
||||
async def inner():
|
||||
try:
|
||||
with get_logger().contextualize(**log_context):
|
||||
await PRAgent().handle_request(url, body)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to handle webhook: {e}")
|
||||
|
||||
background_tasks.add_task(inner)
|
||||
get_logger().exception(f"Failed to handle webhook", artifact={"url": url, "body": body}, error=str(e))
|
||||
|
||||
|
||||
# currently only basic auth is supported with azure webhooks
|
||||
@ -68,6 +64,9 @@ async def _perform_commands_azure(commands_conf: str, agent: PRAgent, api_url: s
|
||||
get_logger().info(f"Auto feedback is disabled, skipping auto commands for PR {api_url=}", **log_context)
|
||||
return
|
||||
commands = get_settings().get(f"azure_devops_server.{commands_conf}")
|
||||
if not commands:
|
||||
return
|
||||
|
||||
get_settings().set("config.is_auto_command", True)
|
||||
for command in commands:
|
||||
try:
|
||||
@ -83,12 +82,7 @@ async def _perform_commands_azure(commands_conf: str, agent: PRAgent, api_url: s
|
||||
get_logger().error(f"Failed to perform command {command}: {e}")
|
||||
|
||||
|
||||
@router.post("/", dependencies=[Depends(authorize)])
|
||||
async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
log_context = {"server_type": "azure_devops_server"}
|
||||
data = await request.json()
|
||||
get_logger().info(json.dumps(data))
|
||||
|
||||
async def handle_request_azure(data, log_context):
|
||||
actions = []
|
||||
if data["eventType"] == "git.pullrequest.created":
|
||||
# API V1 (latest)
|
||||
@ -96,7 +90,10 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
log_context["event"] = data["eventType"]
|
||||
log_context["api_url"] = pr_url
|
||||
await _perform_commands_azure("pr_commands", PRAgent(), pr_url, log_context)
|
||||
return
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
content=jsonable_encoder({"message": "webhook triggered successfully"})
|
||||
)
|
||||
elif data["eventType"] == "ms.vss-code.git-pullrequest-comment-event" and "content" in data["resource"]["comment"]:
|
||||
if available_commands_rgx.match(data["resource"]["comment"]["content"]):
|
||||
if(data["resourceVersion"] == "2.0"):
|
||||
@ -124,7 +121,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
|
||||
for action in actions:
|
||||
try:
|
||||
handle_request(background_tasks, pr_url, action, log_context)
|
||||
await handle_request_comment(pr_url, action, log_context)
|
||||
except Exception as e:
|
||||
get_logger().error("Azure DevOps Trigger failed. Error:" + str(e))
|
||||
return JSONResponse(
|
||||
@ -135,6 +132,18 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder({"message": "webhook triggered successfully"})
|
||||
)
|
||||
|
||||
@router.post("/", dependencies=[Depends(authorize)])
|
||||
async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
log_context = {"server_type": "azure_devops_server"}
|
||||
data = await request.json()
|
||||
# get_logger().info(json.dumps(data))
|
||||
|
||||
background_tasks.add_task(handle_request_azure, data, log_context)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder({"message": "webhook triggered successfully"})
|
||||
)
|
||||
|
||||
@router.get("/")
|
||||
async def root():
|
||||
return {"status": "ok"}
|
||||
|
@ -81,7 +81,7 @@ async def run_action():
|
||||
get_logger().info(f"github action: failed to apply repo settings: {e}")
|
||||
|
||||
# Handle pull request opened event
|
||||
if GITHUB_EVENT_NAME == "pull_request":
|
||||
if GITHUB_EVENT_NAME == "pull_request" or GITHUB_EVENT_NAME == "pull_request_target":
|
||||
action = event_payload.get("action")
|
||||
|
||||
# Retrieve the list of actions from the configuration
|
||||
|
@ -64,7 +64,7 @@ async def get_body(request):
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception as e:
|
||||
get_logger().error("Error parsing request body", e)
|
||||
get_logger().error("Error parsing request body", artifact={'error': e})
|
||||
raise HTTPException(status_code=400, detail="Error parsing request body") from e
|
||||
webhook_secret = getattr(get_settings().github, 'webhook_secret', None)
|
||||
if webhook_secret:
|
||||
@ -107,7 +107,7 @@ async def handle_comments_on_pr(body: Dict[str, Any],
|
||||
comment_body = handle_line_comments(body, comment_body)
|
||||
disable_eyes = True
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to handle line comments: {e}")
|
||||
get_logger().error("Failed to get log context", artifact={'error': e})
|
||||
else:
|
||||
return {}
|
||||
log_context["api_url"] = api_url
|
||||
@ -233,7 +233,7 @@ def get_log_context(body, event, action, build_number):
|
||||
"request_id": uuid.uuid4().hex, "build_number": build_number, "app_name": app_name,
|
||||
"repo": repo, "git_org": git_org, "installation_id": installation_id}
|
||||
except Exception as e:
|
||||
get_logger().error("Failed to get log context", e)
|
||||
get_logger().error(f"Failed to get log context", artifact={'error': e})
|
||||
log_context = {}
|
||||
return log_context, sender, sender_id, sender_type
|
||||
|
||||
@ -310,16 +310,20 @@ async def handle_request(body: Dict[str, Any], event: str):
|
||||
event: The GitHub event type (e.g. "pull_request", "issue_comment", etc.).
|
||||
"""
|
||||
action = body.get("action") # "created", "opened", "reopened", "ready_for_review", "review_requested", "synchronize"
|
||||
get_logger().debug(f"Handling request with event: {event}, action: {action}")
|
||||
if not action:
|
||||
get_logger().debug(f"No action found in request body, exiting handle_request")
|
||||
return {}
|
||||
agent = PRAgent()
|
||||
log_context, sender, sender_id, sender_type = get_log_context(body, event, action, build_number)
|
||||
|
||||
# logic to ignore PRs opened by bot, PRs with specific titles, labels, source branches, or target branches
|
||||
if is_bot_user(sender, sender_type) and 'check_run' not in body:
|
||||
get_logger().debug(f"Request ignored: bot user detected")
|
||||
return {}
|
||||
if action != 'created' and 'check_run' not in body:
|
||||
if not should_process_pr_logic(body):
|
||||
get_logger().debug(f"Request ignored: PR logic filtering")
|
||||
return {}
|
||||
|
||||
if 'check_run' in body: # handle failed checks
|
||||
|
@ -130,12 +130,14 @@ async def is_valid_notification(notification, headers, handled_ids, session, use
|
||||
artifact={"comment": comment_body})
|
||||
return True, handled_ids, comment, comment_body, pr_url, user_tag
|
||||
|
||||
get_logger().error(f"Failed to fetch comments for PR: {pr_url}")
|
||||
get_logger().warning(f"Failed to fetch comments for PR: {pr_url}",
|
||||
artifact={"comments": comments})
|
||||
return False, handled_ids
|
||||
|
||||
return False, handled_ids
|
||||
except Exception as e:
|
||||
get_logger().error(f"Error processing notification: {e}", artifact={"traceback": traceback.format_exc()})
|
||||
get_logger().exception(f"Error processing polling notification",
|
||||
artifact={"notification": notification, "error": e})
|
||||
return False, handled_ids
|
||||
|
||||
|
||||
|
@ -25,29 +25,6 @@ router = APIRouter()
|
||||
secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None
|
||||
|
||||
|
||||
async def get_mr_url_from_commit_sha(commit_sha, gitlab_token, project_id):
|
||||
try:
|
||||
import requests
|
||||
headers = {
|
||||
'Private-Token': f'{gitlab_token}'
|
||||
}
|
||||
# API endpoint to find MRs containing the commit
|
||||
gitlab_url = get_settings().get("GITLAB.URL", 'https://gitlab.com')
|
||||
response = requests.get(
|
||||
f'{gitlab_url}/api/v4/projects/{project_id}/repository/commits/{commit_sha}/merge_requests',
|
||||
headers=headers
|
||||
)
|
||||
merge_requests = response.json()
|
||||
if merge_requests and response.status_code == 200:
|
||||
pr_url = merge_requests[0]['web_url']
|
||||
return pr_url
|
||||
else:
|
||||
get_logger().info(f"No merge requests found for commit: {commit_sha}")
|
||||
return None
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to get MR url from commit sha: {e}")
|
||||
return None
|
||||
|
||||
async def handle_request(api_url: str, body: str, log_context: dict, sender_id: str):
|
||||
log_context["action"] = body
|
||||
log_context["event"] = "pull_request" if body == "/review" else "comment"
|
||||
@ -94,6 +71,31 @@ def is_bot_user(data) -> bool:
|
||||
get_logger().error(f"Failed 'is_bot_user' logic: {e}")
|
||||
return False
|
||||
|
||||
def is_draft(data) -> bool:
|
||||
try:
|
||||
if 'draft' in data.get('object_attributes', {}):
|
||||
return data['object_attributes']['draft']
|
||||
|
||||
# for gitlab server version before 16
|
||||
elif 'Draft:' in data.get('object_attributes', {}).get('title'):
|
||||
return True
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed 'is_draft' logic: {e}")
|
||||
return False
|
||||
|
||||
def is_draft_ready(data) -> bool:
|
||||
try:
|
||||
if 'draft' in data.get('changes', {}):
|
||||
if data['changes']['draft']['previous'] == 'true' and data['changes']['draft']['current'] == 'false':
|
||||
return True
|
||||
|
||||
# for gitlab server version before 16
|
||||
elif 'title' in data.get('changes', {}):
|
||||
if 'Draft:' in data['changes']['title']['previous'] and 'Draft:' not in data['changes']['title']['current']:
|
||||
return True
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed 'is_draft_ready' logic: {e}")
|
||||
return False
|
||||
|
||||
def should_process_pr_logic(data) -> bool:
|
||||
try:
|
||||
@ -190,22 +192,48 @@ async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
# ignore bot users
|
||||
if is_bot_user(data):
|
||||
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
|
||||
if data.get('event_type') != 'note': # not a comment
|
||||
|
||||
log_context["sender"] = sender
|
||||
if data.get('object_kind') == 'merge_request':
|
||||
# ignore MRs based on title, labels, source and target branches
|
||||
if not should_process_pr_logic(data):
|
||||
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
log_context["sender"] = sender
|
||||
if data.get('object_kind') == 'merge_request' and data['object_attributes'].get('action') in ['open', 'reopen']:
|
||||
title = data['object_attributes'].get('title')
|
||||
url = data['object_attributes'].get('url')
|
||||
draft = data['object_attributes'].get('draft')
|
||||
object_attributes = data.get('object_attributes', {})
|
||||
if object_attributes.get('action') in ['open', 'reopen']:
|
||||
url = object_attributes.get('url')
|
||||
get_logger().info(f"New merge request: {url}")
|
||||
if draft:
|
||||
if is_draft(data):
|
||||
get_logger().info(f"Skipping draft MR: {url}")
|
||||
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
await _perform_commands_gitlab("pr_commands", PRAgent(), url, log_context, data)
|
||||
|
||||
# for push event triggered merge requests
|
||||
elif object_attributes.get('action') == 'update' and object_attributes.get('oldrev'):
|
||||
url = object_attributes.get('url')
|
||||
get_logger().info(f"New merge request: {url}")
|
||||
if is_draft(data):
|
||||
get_logger().info(f"Skipping draft MR: {url}")
|
||||
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
commands_on_push = get_settings().get(f"gitlab.push_commands", {})
|
||||
handle_push_trigger = get_settings().get(f"gitlab.handle_push_trigger", False)
|
||||
if not commands_on_push or not handle_push_trigger:
|
||||
get_logger().info("Push event, but no push commands found or push trigger is disabled")
|
||||
return JSONResponse(status_code=status.HTTP_200_OK,
|
||||
content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
get_logger().debug(f'A push event has been received: {url}')
|
||||
await _perform_commands_gitlab("push_commands", PRAgent(), url, log_context, data)
|
||||
|
||||
# for draft to ready triggered merge requests
|
||||
elif object_attributes.get('action') == 'update' and is_draft_ready(data):
|
||||
url = object_attributes.get('url')
|
||||
get_logger().info(f"Draft MR is ready: {url}")
|
||||
|
||||
# same as open MR
|
||||
await _perform_commands_gitlab("pr_commands", PRAgent(), url, log_context, data)
|
||||
|
||||
elif data.get('object_kind') == 'note' and data.get('event_type') == 'note': # comment on MR
|
||||
if 'merge_request' in data:
|
||||
mr = data['merge_request']
|
||||
@ -217,29 +245,6 @@ async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||
body = handle_ask_line(body, data)
|
||||
|
||||
await handle_request(url, body, log_context, sender_id)
|
||||
elif data.get('object_kind') == 'push' and data.get('event_name') == 'push':
|
||||
try:
|
||||
project_id = data['project_id']
|
||||
commit_sha = data['checkout_sha']
|
||||
url = await get_mr_url_from_commit_sha(commit_sha, gitlab_token, project_id)
|
||||
if not url:
|
||||
get_logger().info(f"No MR found for commit: {commit_sha}")
|
||||
return JSONResponse(status_code=status.HTTP_200_OK,
|
||||
content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
# we need first to apply_repo_settings
|
||||
apply_repo_settings(url)
|
||||
commands_on_push = get_settings().get(f"gitlab.push_commands", {})
|
||||
handle_push_trigger = get_settings().get(f"gitlab.handle_push_trigger", False)
|
||||
if not commands_on_push or not handle_push_trigger:
|
||||
get_logger().info("Push event, but no push commands found or push trigger is disabled")
|
||||
return JSONResponse(status_code=status.HTTP_200_OK,
|
||||
content=jsonable_encoder({"message": "success"}))
|
||||
|
||||
get_logger().debug(f'A push event has been received: {url}')
|
||||
await _perform_commands_gitlab("push_commands", PRAgent(), url, log_context, data)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to handle push event: {e}")
|
||||
|
||||
background_tasks.add_task(inner, request_json)
|
||||
end_time = datetime.now()
|
||||
|
@ -6,6 +6,7 @@ class HelpMessage:
|
||||
"> - **/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" \
|
||||
"> - **/help_docs \\<QUESTION\\>**: Given a path to documentation (either for this repository or for a given one), ask a question. \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" \
|
||||
@ -201,3 +202,17 @@ some_config2=...
|
||||
output += f"\n\nSee the improve [usage page](https://pr-agent-docs.codium.ai/tools/improve/) for a comprehensive guide on using this tool.\n\n"
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_help_docs_usage_guide():
|
||||
output = "**Overview:**\n"
|
||||
output += """\
|
||||
The help docs tool, named `help_docs`, answers a question based on a given relative path of documentation, either from the repository of this merge request or from a given one."
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
/help_docs "..."
|
||||
```
|
||||
"""
|
||||
output += f"\n\nSee the [help_docs usage](https://pr-agent-docs.codium.ai/tools/help_docs/) page for a comprehensive guide on using this tool.\n\n"
|
||||
return output
|
||||
|
@ -78,9 +78,6 @@ webhook_secret = ""
|
||||
app_key = ""
|
||||
base_url = ""
|
||||
|
||||
[litellm]
|
||||
LITELLM_TOKEN = "" # see https://docs.litellm.ai/docs/debugging/hosted_debugging for details and instructions on how to get a token
|
||||
|
||||
[azure_devops]
|
||||
# For Azure devops personal access token
|
||||
org = ""
|
||||
@ -94,3 +91,6 @@ pat = ""
|
||||
|
||||
[deepseek]
|
||||
key = ""
|
||||
|
||||
[deepinfra]
|
||||
key = ""
|
||||
|
@ -30,7 +30,7 @@ __old hunk__
|
||||
@@ ... @@ def func2():
|
||||
__new hunk__
|
||||
unchanged code line4
|
||||
+new code line5 removed
|
||||
+new code line5 added
|
||||
unchanged code line6
|
||||
|
||||
## File: 'src/file2.py'
|
||||
@ -66,7 +66,7 @@ Specific guidelines for generating code suggestions:
|
||||
- Do not suggest to change packages version, add missing import statement, or declare undefined variable.
|
||||
{%- endif %}
|
||||
- When mentioning code elements (variables, names, or files) in your response, surround them with backticks (`). For example: "verify that `user_id` is..."
|
||||
- Note that you only see changed code segments (diff hunks in a PR), not the entire codebase. Avoid suggestions that might duplicate existing functionality or questioning code elements (like variables declerations or import statements) that may be defined elsewhere in the codebase.
|
||||
- Note that you only see changed code segments (diff hunks in a PR), not the entire codebase. Avoid suggestions that might duplicate existing functionality or questioning code elements (like variables declarations or import statements) that may be defined elsewhere in the codebase.
|
||||
|
||||
{%- if extra_instructions %}
|
||||
|
||||
@ -83,14 +83,14 @@ The output must be a YAML object equivalent to type $PRCodeSuggestions, accordin
|
||||
class CodeSuggestion(BaseModel):
|
||||
relevant_file: str = Field(description="Full path of the relevant file")
|
||||
language: str = Field(description="Programming language used by the relevant file")
|
||||
existing_code: str = Field(description="A short code snippet, from a '__new hunk__' section after the PR changes, that the suggestion aims to enhance or fix. Include only complete code lines. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.")
|
||||
suggestion_content: str = Field(description="An actionable suggestion to enhance, improve or fix the new code introduced in the PR. Don't present here actual code snippets, just the suggestion. Be short and concise")
|
||||
existing_code: str = Field(description="A short code snippet from a '__new hunk__' section that the suggestion aims to enhance or fix. Include only complete code lines. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.")
|
||||
improved_code: str = Field(description="A refined code snippet that replaces the 'existing_code' snippet after implementing the suggestion.")
|
||||
one_sentence_summary: str = Field(description="A concise, single-sentence overview (up to 6 words) of the suggested improvement. Focus on the 'what'. Be general, and avoid method or variable names.")
|
||||
{%- if not focus_only_on_problems %}
|
||||
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', 'typo'. Other relevant labels are also acceptable.")
|
||||
{%- else %}
|
||||
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'critical bug', 'general'. The 'general' section should be used for suggestions that address a major issue, but are necessarily on a critical level.")
|
||||
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'critical bug', 'general'. The 'general' section should be used for suggestions that address a major issue, but are not necessarily on a critical level.")
|
||||
{%- endif %}
|
||||
|
||||
|
||||
@ -106,10 +106,10 @@ code_suggestions:
|
||||
src/file1.py
|
||||
language: |
|
||||
python
|
||||
suggestion_content: |
|
||||
...
|
||||
existing_code: |
|
||||
...
|
||||
suggestion_content: |
|
||||
...
|
||||
improved_code: |
|
||||
...
|
||||
one_sentence_summary: |
|
||||
@ -145,10 +145,10 @@ code_suggestions:
|
||||
src/file1.py
|
||||
language: |
|
||||
python
|
||||
suggestion_content: |
|
||||
...
|
||||
existing_code: |
|
||||
...
|
||||
suggestion_content: |
|
||||
...
|
||||
improved_code: |
|
||||
...
|
||||
one_sentence_summary: |
|
@ -0,0 +1,158 @@
|
||||
[pr_code_suggestions_prompt_not_decoupled]
|
||||
system="""You are PR-Reviewer, an AI specializing in Pull Request (PR) code analysis and suggestions.
|
||||
{%- if not focus_only_on_problems %}
|
||||
Your task is to examine the provided code diff, focusing on new code (lines prefixed with '+'), and offer concise, actionable suggestions to fix possible bugs and problems, and enhance code quality and performance.
|
||||
{%- else %}
|
||||
Your task is to examine the provided code diff, focusing on new code (lines prefixed with '+'), and offer concise, actionable suggestions to fix critical bugs and problems.
|
||||
{%- endif %}
|
||||
|
||||
|
||||
The PR code diff will be in the following structured format:
|
||||
======
|
||||
## File: 'src/file1.py'
|
||||
{%- if is_ai_metadata %}
|
||||
### AI-generated changes summary:
|
||||
* ...
|
||||
* ...
|
||||
{%- endif %}
|
||||
|
||||
@@ ... @@ def func1():
|
||||
unchanged code line0
|
||||
unchanged code line1
|
||||
+new code line2
|
||||
-removed code line2
|
||||
unchanged code line3
|
||||
|
||||
@@ ... @@ def func2():
|
||||
...
|
||||
|
||||
|
||||
## File: 'src/file2.py'
|
||||
...
|
||||
======
|
||||
The diff structure above uses line prefixes to show changes:
|
||||
'+' → new line code added
|
||||
'-' → line code removed
|
||||
' ' → unchanged context lines
|
||||
{%- if is_ai_metadata %}
|
||||
|
||||
When available, an AI-generated summary will precede each file's diff, with a high-level overview of the changes. Note that this summary may not be fully accurate or complete.
|
||||
{%- endif %}
|
||||
|
||||
|
||||
Specific guidelines for generating code suggestions:
|
||||
{%- if not focus_only_on_problems %}
|
||||
- Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions.
|
||||
{%- else %}
|
||||
- Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions. Return less suggestions if no pertinent ones are applicable.
|
||||
{%- endif %}
|
||||
- Focus your suggestions ONLY on improving the new code introduced in the PR (lines starting with '+' in the diff). The lines in the diff starting with '-' are only for reference and should not be considered for suggestions.
|
||||
{%- if not focus_only_on_problems %}
|
||||
- Prioritize suggestions that address potential issues, critical problems, and bugs in the PR code. Avoid repeating changes already implemented in the PR. If no pertinent suggestions are applicable, return an empty list.
|
||||
- Don't suggest to add docstring, type hints, or comments, to remove unused imports, or to use more specific exception types.
|
||||
{%- else %}
|
||||
- Only give suggestions that address critical problems and bugs in the PR code. If no relevant suggestions are applicable, return an empty list.
|
||||
- DO NOT suggest the following:
|
||||
- change packages version
|
||||
- add missing import statement
|
||||
- declare undefined variable
|
||||
- use more specific exception types
|
||||
{%- endif %}
|
||||
- When mentioning code elements (variables, names, or files) in your response, surround them with backticks (`). For example: "verify that `user_id` is..."
|
||||
- Note that you only see changed code segments (diff hunks in a PR), not the entire codebase. Avoid suggestions that might duplicate existing functionality or questioning code elements (like variables declarations or import statements) that may be defined elsewhere in the codebase.
|
||||
|
||||
{%- if extra_instructions %}
|
||||
|
||||
|
||||
Extra user-provided instructions (should be addressed with high priority):
|
||||
======
|
||||
{{ extra_instructions }}
|
||||
======
|
||||
{%- 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="Full path of the relevant file")
|
||||
language: str = Field(description="Programming language used by the relevant file")
|
||||
existing_code: str = Field(description="A short code snippet from the final state of the PR diff, that the suggestion aims to enhance or fix. Include only complete code lines, preserving all indentation, newlines, and original formatting. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.")
|
||||
suggestion_content: str = Field(description="An actionable suggestion to enhance, improve or fix the new code introduced in the PR. Don't present here actual code snippets, just the suggestion. Be short and concise")
|
||||
improved_code: str = Field(description="A refined code snippet that replaces the 'existing_code' snippet after implementing the suggestion.")
|
||||
one_sentence_summary: str = Field(description="A concise, single-sentence overview (up to 6 words) of the suggested improvement. Focus on the 'what'. Be general, and avoid method or variable names.")
|
||||
{%- if not focus_only_on_problems %}
|
||||
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', 'typo'. Other relevant labels are also acceptable.")
|
||||
{%- else %}
|
||||
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'critical bug', 'general'. The 'general' section should be used for suggestions that address a major issue, but are not necessarily on a critical level.")
|
||||
{%- endif %}
|
||||
|
||||
|
||||
class PRCodeSuggestions(BaseModel):
|
||||
code_suggestions: List[CodeSuggestion]
|
||||
=====
|
||||
|
||||
|
||||
Example output:
|
||||
```yaml
|
||||
code_suggestions:
|
||||
- relevant_file: |
|
||||
src/file1.py
|
||||
language: |
|
||||
python
|
||||
existing_code: |
|
||||
...
|
||||
suggestion_content: |
|
||||
...
|
||||
improved_code: |
|
||||
...
|
||||
one_sentence_summary: |
|
||||
...
|
||||
label: |
|
||||
...
|
||||
```
|
||||
|
||||
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|').
|
||||
"""
|
||||
|
||||
user="""--PR Info--
|
||||
|
||||
Title: '{{title}}'
|
||||
|
||||
{%- if date %}
|
||||
|
||||
Today's Date: {{date}}
|
||||
{%- endif %}
|
||||
|
||||
The PR Diff:
|
||||
======
|
||||
{{ diff_no_line_numbers|trim }}
|
||||
======
|
||||
|
||||
{%- if duplicate_prompt_examples %}
|
||||
|
||||
|
||||
Example output:
|
||||
```yaml
|
||||
code_suggestions:
|
||||
- relevant_file: |
|
||||
src/file1.py
|
||||
language: |
|
||||
python
|
||||
existing_code: |
|
||||
...
|
||||
suggestion_content: |
|
||||
...
|
||||
improved_code: |
|
||||
...
|
||||
one_sentence_summary: |
|
||||
...
|
||||
label: |
|
||||
...
|
||||
```
|
||||
(replace '...' with actual content)
|
||||
{%- endif %}
|
||||
|
||||
|
||||
Response (should be a valid YAML, and nothing else):
|
||||
```yaml
|
||||
"""
|
@ -2,7 +2,7 @@
|
||||
system="""You are an AI language model specialized in reviewing and evaluating code suggestions for a Pull Request (PR).
|
||||
Your task is to analyze a PR code diff and evaluate a set of AI-generated code suggestions. These suggestions aim to address potential bugs and problems, and enhance the new code introduced in the PR.
|
||||
|
||||
Examine each suggestion meticulously, assessing its quality, relevance, and accuracy within the context of PR. Keep in mind that the suggestions may vary in their correctness and accuracy. Your evaluation should be based on a thorough comparison between each suggestion and the actual PR code diff.
|
||||
Examine each suggestion meticulously, assessing its quality, relevance, and accuracy within the context of PR. Keep in mind that the suggestions may vary in their correctness, accuracy and impact.
|
||||
Consider the following components of each suggestion:
|
||||
1. 'one_sentence_summary' - A brief summary of the suggestion's purpose
|
||||
2. 'suggestion_content' - The detailed suggestion content, explaining the proposed modification
|
||||
@ -31,9 +31,11 @@ Key guidelines for evaluation:
|
||||
|
||||
Additional scoring considerations:
|
||||
- If the suggestion is not actionable, and only asks the user to verify or ensure a change, reduce its score by 1-2 points.
|
||||
- Error handling or type checking suggestions should not receive a score above 8 (and may be lower).
|
||||
- Assign a score of 0 to suggestions aiming at:
|
||||
- Adding docstring, type hints, or comments
|
||||
- Remove unused imports or variables
|
||||
- Add missing import statements
|
||||
- Using more specific exception types.
|
||||
|
||||
|
||||
@ -82,8 +84,8 @@ The output must be a YAML object equivalent to type $PRCodeSuggestionsFeedback,
|
||||
class CodeSuggestionFeedback(BaseModel):
|
||||
suggestion_summary: str = Field(description="Repeated from the input")
|
||||
relevant_file: str = Field(description="Repeated from the input")
|
||||
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 beginning of the relevant 'existing code' snippet")
|
||||
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 end of the relevant 'existing code' snippet")
|
||||
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 added '__new hunk__' line numbers, and correspond to the first line of the relevant 'existing code' snippet.")
|
||||
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 added '__new hunk__' line numbers, and correspond to the end of the relevant 'existing code' snippet")
|
||||
suggestion_score: int = Field(description="Evaluate the suggestion and assign a score from 0 to 10. Give 0 if the suggestion is wrong. For valid suggestions, score from 1 (lowest impact/importance) to 10 (highest impact/importance).")
|
||||
why: str = Field(description="Briefly explain the score given in 1-2 sentences, focusing on the suggestion's impact, relevance, and accuracy.")
|
||||
|
@ -1,8 +1,15 @@
|
||||
# Important: This file contains all available configuration options.
|
||||
# Do not copy this entire file to your repository configuration.
|
||||
# Your repository configuration should only include options you wish to override from the defaults.
|
||||
#
|
||||
# Use this page: 'https://qodo-merge-docs.qodo.ai/ai_search/' to ask questions about the configuration options.
|
||||
|
||||
[config]
|
||||
# models
|
||||
model="gpt-4o-2024-11-20"
|
||||
fallback_models=["gpt-4o-2024-08-06"]
|
||||
model="o3-mini"
|
||||
fallback_models=["gpt-4o-2024-11-20"]
|
||||
#model_weak="gpt-4o-mini-2024-07-18" # optional, a weaker model to use for some easier tasks
|
||||
model_token_count_estimate_factor=0.3 # factor to increase the token count estimate, in order to reduce likelihood of model failure due to too many tokens.
|
||||
# CLI
|
||||
git_provider="github"
|
||||
publish_output=true
|
||||
@ -17,6 +24,8 @@ use_global_settings_file=true
|
||||
disable_auto_feedback = false
|
||||
ai_timeout=120 # 2minutes
|
||||
skip_keys = []
|
||||
custom_reasoning_model = false # when true, disables system messages and temperature controls for models that don't support chat-style inputs
|
||||
response_language="en-US" # Language locales code for PR responses in ISO 3166 and ISO 639 format (e.g., "en-US", "it-IT", "zh-CN", ...)
|
||||
# token limits
|
||||
max_description_tokens = 500
|
||||
max_commits_tokens = 500
|
||||
@ -47,6 +56,16 @@ ignore_pr_authors = [] # authors to ignore from PR agent when an PR is created
|
||||
#
|
||||
is_auto_command = false # will be auto-set to true if the command is triggered by an automation
|
||||
enable_ai_metadata = false # will enable adding ai metadata
|
||||
reasoning_effort = "medium" # "low", "medium", "high"
|
||||
# auto approval 💎
|
||||
enable_auto_approval=false # Set to true to enable auto-approval of PRs under certain conditions
|
||||
auto_approve_for_low_review_effort=-1 # -1 to disable, [1-5] to set the threshold for auto-approval
|
||||
auto_approve_for_no_suggestions=false # If true, the PR will be auto-approved if there are no suggestions
|
||||
# extended thinking for Claude reasoning models
|
||||
enable_claude_extended_thinking = false # Set to true to enable extended thinking feature
|
||||
extended_thinking_budget_tokens = 2048
|
||||
extended_thinking_max_output_tokens = 4096
|
||||
|
||||
|
||||
[pr_reviewer] # /review #
|
||||
# enable/disable features
|
||||
@ -69,10 +88,6 @@ minimal_commits_for_incremental_review=0
|
||||
minimal_minutes_for_incremental_review=0
|
||||
enable_intro_text=true
|
||||
enable_help_text=false # Determines whether to include help text in the PR review. Enabled by default.
|
||||
# auto approval
|
||||
enable_auto_approval=false
|
||||
maximal_review_effort=5
|
||||
|
||||
|
||||
[pr_description] # /describe #
|
||||
publish_labels=false
|
||||
@ -90,7 +105,7 @@ publish_description_as_comment_persistent=true
|
||||
## changes walkthrough section
|
||||
enable_semantic_files_types=true
|
||||
collapsible_file_list='adaptive' # true, false, 'adaptive'
|
||||
collapsible_file_list_threshold=8
|
||||
collapsible_file_list_threshold=6
|
||||
inline_file_summary=false # false, true, 'table'
|
||||
# markers
|
||||
use_description_markers=false
|
||||
@ -122,6 +137,9 @@ max_history_len=4
|
||||
apply_suggestions_checkbox=true
|
||||
# suggestions scoring
|
||||
suggestions_score_threshold=0 # [0-10]| recommend not to set this value above 8, since above it may clip highly relevant suggestions
|
||||
new_score_mechanism=true
|
||||
new_score_mechanism_th_high=9
|
||||
new_score_mechanism_th_medium=7
|
||||
# params for '/improve --extended' mode
|
||||
auto_extended_mode=true
|
||||
num_code_suggestions_per_chunk=4
|
||||
@ -129,6 +147,7 @@ max_number_of_calls = 3
|
||||
parallel_calls = true
|
||||
|
||||
final_clip_factor = 0.8
|
||||
decouple_hunks = false
|
||||
# self-review checkbox
|
||||
demand_code_suggestions_self_review=false # add a checkbox for the author to self-review the code suggestions
|
||||
code_suggestions_self_review_text= "**Author self-review**: I have reviewed the PR code suggestions, and addressed the relevant ones."
|
||||
@ -194,6 +213,14 @@ num_retrieved_snippets=5
|
||||
|
||||
[pr_config] # /config #
|
||||
|
||||
[pr_help_docs]
|
||||
repo_url = "" #If not overwritten, will use the repo from where the context came from (issue or PR)
|
||||
repo_default_branch = "main"
|
||||
docs_path = "docs"
|
||||
exclude_root_readme = false
|
||||
supported_doc_exts = [".md", ".mdx", ".rst"]
|
||||
enable_help_text=false
|
||||
|
||||
[github]
|
||||
# The type of deployment to create. Valid values are 'app' or 'user'.
|
||||
deployment_type = "user"
|
||||
@ -308,7 +335,24 @@ number_of_results = 5
|
||||
|
||||
[lancedb]
|
||||
uri = "./lancedb"
|
||||
|
||||
[best_practices]
|
||||
content = ""
|
||||
organization_name = ""
|
||||
max_lines_allowed = 800
|
||||
enable_global_best_practices = false
|
||||
|
||||
[auto_best_practices]
|
||||
enable_auto_best_practices = true # public - general flag to disable all auto best practices usage
|
||||
utilize_auto_best_practices = true # public - disable usage of auto best practices in the 'improve' tool
|
||||
extra_instructions = "" # public - extra instructions to the auto best practices generation prompt
|
||||
content = ""
|
||||
max_patterns = 5 # max number of patterns to be detected
|
||||
|
||||
|
||||
[azure_devops_server]
|
||||
pr_commands = [
|
||||
"/describe",
|
||||
"/review",
|
||||
"/improve",
|
||||
]
|
||||
|
77
pr_agent/settings/pr_help_docs_prompts.toml
Normal file
@ -0,0 +1,77 @@
|
||||
[pr_help_docs_prompts]
|
||||
system="""You are Doc-helper, a language model designed to answer questions about a documentation website for a given repository.
|
||||
You will receive a question, a repository url and the full documentation content for that repository (either as markdown or as restructred text).
|
||||
Your goal is to provide the best answer to the question using the documentation provided.
|
||||
|
||||
Additional instructions:
|
||||
- Be short and concise in your answers. Give examples if needed.
|
||||
- Answer only questions that are related to the documentation website content. If the question is completely unrelated to the documentation, return an empty response.
|
||||
|
||||
|
||||
The output must be a YAML object equivalent to type $DocHelper, according to the following Pydantic definitions:
|
||||
=====
|
||||
class relevant_section(BaseModel):
|
||||
file_name: str = Field(description="The name of the relevant file")
|
||||
relevant_section_header_string: str = Field(description="The exact text of the relevant markdown/restructured text section heading from the relevant file (starting with '#', '##', etc.). Return empty string if the entire file is the relevant section, or if the relevant section has no heading")
|
||||
|
||||
class DocHelper(BaseModel):
|
||||
user_question: str = Field(description="The user's question")
|
||||
response: str = Field(description="The response to the user's question")
|
||||
relevant_sections: List[relevant_section] = Field(description="A list of the relevant markdown/restructured text sections in the documentation that answer the user's question, ordered by importance (most relevant first)")
|
||||
question_is_relevant: int = Field(description="Return 1 if the question is somewhat relevant to documentation. 0 - otherwise")
|
||||
=====
|
||||
|
||||
|
||||
Example output:
|
||||
```yaml
|
||||
user_question: |
|
||||
...
|
||||
response: |
|
||||
...
|
||||
relevant_sections:
|
||||
- file_name: "src/file1.py"
|
||||
relevant_section_header_string: |
|
||||
...
|
||||
- ...
|
||||
question_is_relevant: |
|
||||
1
|
||||
"""
|
||||
|
||||
user="""\
|
||||
Documentation url: '{{ docs_url| trim }}'
|
||||
-----
|
||||
|
||||
|
||||
User's Question:
|
||||
=====
|
||||
{{ question|trim }}
|
||||
=====
|
||||
|
||||
|
||||
Documentation website content:
|
||||
=====
|
||||
{{ snippets|trim }}
|
||||
=====
|
||||
|
||||
|
||||
Reminder: The output must be a YAML object equivalent to type $DocHelper, similar to the following example output:
|
||||
=====
|
||||
Example output:
|
||||
```yaml
|
||||
user_question: |
|
||||
...
|
||||
response: |
|
||||
...
|
||||
relevant_sections:
|
||||
- file_name: "src/file1.py"
|
||||
relevant_section_header_string: |
|
||||
...
|
||||
- ...
|
||||
question_is_relevant: |
|
||||
1
|
||||
=====
|
||||
|
||||
|
||||
Response (should be a valid YAML, and nothing else).
|
||||
```yaml
|
||||
"""
|
@ -13,7 +13,7 @@ The output must be a YAML object equivalent to type $DocHelper, according to the
|
||||
=====
|
||||
class relevant_section(BaseModel):
|
||||
file_name: str = Field(description="The name of the relevant file")
|
||||
relevant_section_header_string: str = Field(description="From the relevant file, exact text of the relevant section heading. If no markdown heading is relevant, return empty string")
|
||||
relevant_section_header_string: str = Field(description="The exact text of the relevant markdown section heading from the relevant file (starting with '#', '##', etc.). Return empty string if the entire file is the relevant section, or if the relevant section has no heading")
|
||||
|
||||
class DocHelper(BaseModel):
|
||||
user_question: str = Field(description="The user's question")
|
||||
|
@ -66,7 +66,7 @@ class SubPR(BaseModel):
|
||||
|
||||
class KeyIssuesComponentLink(BaseModel):
|
||||
relevant_file: str = Field(description="The full file path of the relevant file")
|
||||
issue_header: str = Field(description="One or two word title for the the issue. For example: 'Possible Bug', etc.")
|
||||
issue_header: str = Field(description="One or two word title for the issue. For example: 'Possible Bug', etc.")
|
||||
issue_content: str = Field(description="A short and concise summary of what should be further inspected and validated during the PR review process for this issue. Do not reference line numbers in this field.")
|
||||
start_line: int = Field(description="The start line that corresponds to this issue in the relevant file")
|
||||
end_line: int = Field(description="The end line that corresponds to this issue in the relevant file")
|
||||
@ -75,10 +75,10 @@ class KeyIssuesComponentLink(BaseModel):
|
||||
|
||||
class TicketCompliance(BaseModel):
|
||||
ticket_url: str = Field(description="Ticket URL or ID")
|
||||
ticket_requirements: str = Field(description="Repeat, in your own words, all ticket requirements, in bullet points")
|
||||
fully_compliant_requirements: str = Field(description="A list, in bullet points, of which requirements are met by the PR code. Don't explain how the requirements are met, just list them shortly. Can be empty")
|
||||
not_compliant_requirements: str = Field(description="A list, in bullet points, of which requirements are not met by the PR code. Don't explain how the requirements are not met, just list them shortly. Can be empty")
|
||||
overall_compliance_level: str = Field(description="Overall give this PR one of these three values in relation to the ticket: 'Fully compliant', 'Partially compliant', or 'Not compliant'")
|
||||
ticket_requirements: str = Field(description="Repeat, in your own words (in bullet points), all the requirements, sub-tasks, DoD, and acceptance criteria raised by the ticket")
|
||||
fully_compliant_requirements: str = Field(description="Bullet-point list of items from the 'ticket_requirements' section above that are fulfilled by the PR code. Don't explain how the requirements are met, just list them shortly. Can be empty")
|
||||
not_compliant_requirements: str = Field(description="Bullet-point list of items from the 'ticket_requirements' section above that are not fulfilled by the PR code. Don't explain how the requirements are not met, just list them shortly. Can be empty")
|
||||
requires_further_human_verification: str = Field(description="Bullet-point list of items from the 'ticket_requirements' section above that cannot be assessed through code review alone, are unclear, or need further human review (e.g., browser testing, UI checks). Leave empty if all 'ticket_requirements' were marked as fully compliant or not compliant")
|
||||
{%- endif %}
|
||||
|
||||
class Review(BaseModel):
|
||||
@ -187,6 +187,10 @@ Ticket Description:
|
||||
|
||||
|
||||
--PR Info--
|
||||
{%- if date %}
|
||||
|
||||
Today's Date: {{date}}
|
||||
{%- endif %}
|
||||
|
||||
Title: '{{title}}'
|
||||
|
||||
|
@ -1,46 +0,0 @@
|
||||
[pr_sort_code_suggestions_prompt]
|
||||
system="""
|
||||
"""
|
||||
|
||||
user="""You are given a list of code suggestions to improve a Git Pull Request (PR):
|
||||
======
|
||||
{{ suggestion_str|trim }}
|
||||
======
|
||||
|
||||
Your task is to sort the code suggestions by their order of importance, and return a list with sorting order.
|
||||
The sorting order is a list of pairs, where each pair contains the index of the suggestion in the original list.
|
||||
Rank the suggestions based on their importance to improving the PR, with critical issues first and minor issues last.
|
||||
|
||||
You must use the following YAML schema to format your answer:
|
||||
```yaml
|
||||
Sort Order:
|
||||
type: array
|
||||
maxItems: {{ suggestion_list|length }}
|
||||
uniqueItems: true
|
||||
items:
|
||||
suggestion number:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: {{ suggestion_list|length }}
|
||||
importance order:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: {{ suggestion_list|length }}
|
||||
```
|
||||
|
||||
Example output:
|
||||
```yaml
|
||||
Sort Order:
|
||||
- suggestion number: 1
|
||||
importance order: 2
|
||||
- suggestion number: 2
|
||||
importance order: 3
|
||||
- suggestion number: 3
|
||||
importance order: 1
|
||||
```
|
||||
|
||||
Make sure to output a valid YAML. Use multi-line block scalar ('|') if needed.
|
||||
Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields.
|
||||
Response (should be a valid YAML, and nothing else):
|
||||
```yaml
|
||||
"""
|
@ -10,14 +10,16 @@ from typing import Dict, List
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from pr_agent.algo import MAX_TOKENS
|
||||
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.git_patch_processing import decouple_and_convert_to_hunks_with_lines_numbers
|
||||
from pr_agent.algo.pr_processing import (add_ai_metadata_to_diff_files,
|
||||
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 (ModelType, load_yaml, replace_code_tags,
|
||||
show_relevant_configurations)
|
||||
show_relevant_configurations, get_max_tokens, clip_tokens)
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import (AzureDevopsProvider, GithubProvider,
|
||||
GitLabProvider, get_git_provider,
|
||||
@ -45,14 +47,8 @@ class PRCodeSuggestions:
|
||||
get_settings().config.max_model_tokens_original = get_settings().config.max_model_tokens
|
||||
get_settings().config.max_model_tokens = MAX_CONTEXT_TOKENS_IMPROVE
|
||||
|
||||
# extended mode
|
||||
try:
|
||||
self.is_extended = self._get_is_extended(args or [])
|
||||
except:
|
||||
self.is_extended = False
|
||||
num_code_suggestions = int(get_settings().pr_code_suggestions.num_code_suggestions_per_chunk)
|
||||
|
||||
|
||||
self.ai_handler = ai_handler()
|
||||
self.ai_handler.main_pr_language = self.main_language
|
||||
self.patches_diff = None
|
||||
@ -85,12 +81,18 @@ class PRCodeSuggestions:
|
||||
"date": datetime.now().strftime('%Y-%m-%d'),
|
||||
'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False),
|
||||
}
|
||||
|
||||
if get_settings().pr_code_suggestions.get("decouple_hunks", True):
|
||||
self.pr_code_suggestions_prompt_system = get_settings().pr_code_suggestions_prompt.system
|
||||
self.pr_code_suggestions_prompt_user = get_settings().pr_code_suggestions_prompt.user
|
||||
else:
|
||||
self.pr_code_suggestions_prompt_system = get_settings().pr_code_suggestions_prompt_not_decoupled.system
|
||||
self.pr_code_suggestions_prompt_user = get_settings().pr_code_suggestions_prompt_not_decoupled.user
|
||||
|
||||
self.token_handler = TokenHandler(self.git_provider.pr,
|
||||
self.vars,
|
||||
self.pr_code_suggestions_prompt_system,
|
||||
get_settings().pr_code_suggestions_prompt.user)
|
||||
self.pr_code_suggestions_prompt_user)
|
||||
|
||||
self.progress = f"## Generating PR code suggestions\n\n"
|
||||
self.progress += f"""\nWork in progress ...<br>\n<img src="https://codium.ai/images/pr_agent/dual_ball_loading-crop.gif" width=48>"""
|
||||
@ -115,10 +117,10 @@ class PRCodeSuggestions:
|
||||
else:
|
||||
self.git_provider.publish_comment("Preparing suggestions...", is_temporary=True)
|
||||
|
||||
# call the model to get the suggestions, and self-reflect on them
|
||||
if not self.is_extended:
|
||||
data = await retry_with_fallback_models(self._prepare_prediction, model_type=ModelType.REGULAR)
|
||||
else:
|
||||
# # call the model to get the suggestions, and self-reflect on them
|
||||
# if not self.is_extended:
|
||||
# data = await retry_with_fallback_models(self._prepare_prediction, model_type=ModelType.REGULAR)
|
||||
# else:
|
||||
data = await retry_with_fallback_models(self._prepare_prediction_extended, model_type=ModelType.REGULAR)
|
||||
if not data:
|
||||
data = {"code_suggestions": []}
|
||||
@ -466,6 +468,8 @@ class PRCodeSuggestions:
|
||||
suggestion["score"] = 7
|
||||
suggestion["score_why"] = ""
|
||||
|
||||
suggestion = self.validate_one_liner_suggestion_not_repeating_code(suggestion)
|
||||
|
||||
# if the before and after code is the same, clear one of them
|
||||
try:
|
||||
if suggestion['existing_code'] == suggestion['improved_code']:
|
||||
@ -621,15 +625,32 @@ 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
|
||||
def validate_one_liner_suggestion_not_repeating_code(self, suggestion):
|
||||
try:
|
||||
existing_code = suggestion.get('existing_code', '').strip()
|
||||
if '...' in existing_code:
|
||||
return suggestion
|
||||
new_code = suggestion.get('improved_code', '').strip()
|
||||
|
||||
relevant_file = suggestion.get('relevant_file', '').strip()
|
||||
diff_files = self.git_provider.get_diff_files()
|
||||
for file in diff_files:
|
||||
if file.filename.strip() == relevant_file:
|
||||
# protections
|
||||
if not file.head_file:
|
||||
get_logger().info(f"head_file is empty")
|
||||
return suggestion
|
||||
head_file = file.head_file
|
||||
base_file = file.base_file
|
||||
if existing_code in base_file and existing_code not in head_file and new_code in head_file:
|
||||
suggestion["score"] = 0
|
||||
get_logger().warning(
|
||||
f"existing_code is in the base file but not in the head file, setting score to 0",
|
||||
artifact={"suggestion": suggestion})
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Error validating one-liner suggestion", artifact={"error": e})
|
||||
|
||||
return suggestion
|
||||
|
||||
def remove_line_numbers(self, patches_diff_list: List[str]) -> List[str]:
|
||||
# create a copy of the patches_diff_list, without line numbers for '__new hunk__' sections
|
||||
@ -654,11 +675,31 @@ class PRCodeSuggestions:
|
||||
return patches_diff_list
|
||||
|
||||
async def _prepare_prediction_extended(self, model: str) -> dict:
|
||||
self.patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model,
|
||||
max_calls=get_settings().pr_code_suggestions.max_number_of_calls)
|
||||
# get PR diff
|
||||
if get_settings().pr_code_suggestions.decouple_hunks:
|
||||
self.patches_diff_list = get_pr_multi_diffs(self.git_provider,
|
||||
self.token_handler,
|
||||
model,
|
||||
max_calls=get_settings().pr_code_suggestions.max_number_of_calls,
|
||||
add_line_numbers=True) # decouple hunk with line numbers
|
||||
self.patches_diff_list_no_line_numbers = self.remove_line_numbers(self.patches_diff_list) # decouple hunk
|
||||
|
||||
# create a copy of the patches_diff_list, without line numbers for '__new hunk__' sections
|
||||
self.patches_diff_list_no_line_numbers = self.remove_line_numbers(self.patches_diff_list)
|
||||
else:
|
||||
# non-decoupled hunks
|
||||
self.patches_diff_list_no_line_numbers = get_pr_multi_diffs(self.git_provider,
|
||||
self.token_handler,
|
||||
model,
|
||||
max_calls=get_settings().pr_code_suggestions.max_number_of_calls,
|
||||
add_line_numbers=False)
|
||||
self.patches_diff_list = await self.convert_to_decoupled_with_line_numbers(
|
||||
self.patches_diff_list_no_line_numbers, model)
|
||||
if not self.patches_diff_list:
|
||||
# fallback to decoupled hunks
|
||||
self.patches_diff_list = get_pr_multi_diffs(self.git_provider,
|
||||
self.token_handler,
|
||||
model,
|
||||
max_calls=get_settings().pr_code_suggestions.max_number_of_calls,
|
||||
add_line_numbers=True) # decouple hunk with line numbers
|
||||
|
||||
if self.patches_diff_list:
|
||||
get_logger().info(f"Number of PR chunk calls: {len(self.patches_diff_list)}")
|
||||
@ -699,6 +740,42 @@ class PRCodeSuggestions:
|
||||
self.data = data = None
|
||||
return data
|
||||
|
||||
async def convert_to_decoupled_with_line_numbers(self, patches_diff_list_no_line_numbers, model) -> List[str]:
|
||||
with get_logger().contextualize(sub_feature='convert_to_decoupled_with_line_numbers'):
|
||||
try:
|
||||
patches_diff_list = []
|
||||
for patch_prompt in patches_diff_list_no_line_numbers:
|
||||
file_prefix = "## File: "
|
||||
patches = patch_prompt.strip().split(f"\n{file_prefix}")
|
||||
patches_new = copy.deepcopy(patches)
|
||||
for i in range(len(patches_new)):
|
||||
if i == 0:
|
||||
prefix = patches_new[i].split("\n@@")[0].strip()
|
||||
else:
|
||||
prefix = file_prefix + patches_new[i].split("\n@@")[0][1:]
|
||||
prefix = prefix.strip()
|
||||
patches_new[i] = prefix + '\n\n' + decouple_and_convert_to_hunks_with_lines_numbers(patches_new[i],
|
||||
file=None).strip()
|
||||
patches_new[i] = patches_new[i].strip()
|
||||
patch_final = "\n\n\n".join(patches_new)
|
||||
if model in MAX_TOKENS:
|
||||
max_tokens_full = MAX_TOKENS[
|
||||
model] # note - here we take the actual max tokens, without any reductions. we do aim to get the full documentation website in the prompt
|
||||
else:
|
||||
max_tokens_full = get_max_tokens(model)
|
||||
delta_output = 2000
|
||||
token_count = self.token_handler.count_tokens(patch_final)
|
||||
if token_count > max_tokens_full - delta_output:
|
||||
get_logger().warning(
|
||||
f"Token count {token_count} exceeds the limit {max_tokens_full - delta_output}. clipping the tokens")
|
||||
patch_final = clip_tokens(patch_final, max_tokens_full - delta_output)
|
||||
patches_diff_list.append(patch_final)
|
||||
return patches_diff_list
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Error converting to decoupled with line numbers",
|
||||
artifact={'patches_diff_list_no_line_numbers': patches_diff_list_no_line_numbers})
|
||||
return []
|
||||
|
||||
def generate_summarized_suggestions(self, data: Dict) -> str:
|
||||
try:
|
||||
pr_body = "## PR Code Suggestions ✨\n\n"
|
||||
@ -720,7 +797,7 @@ class PRCodeSuggestions:
|
||||
header = f"Suggestion"
|
||||
delta = 66
|
||||
header += " " * delta
|
||||
pr_body += f"""<thead><tr><td>Category</td><td align=left>{header}</td><td align=center>Score</td></tr>"""
|
||||
pr_body += f"""<thead><tr><td><strong>Category</strong></td><td align=left><strong>{header}</strong></td><td align=center><strong>Impact</strong></td></tr>"""
|
||||
pr_body += """<tbody>"""
|
||||
suggestions_labels = dict()
|
||||
# add all suggestions related to each label
|
||||
@ -740,7 +817,7 @@ class PRCodeSuggestions:
|
||||
counter_suggestions = 0
|
||||
for label, suggestions in suggestions_labels.items():
|
||||
num_suggestions = len(suggestions)
|
||||
pr_body += f"""<tr><td rowspan={num_suggestions}><strong>{label.capitalize()}</strong></td>\n"""
|
||||
pr_body += f"""<tr><td rowspan={num_suggestions}>{label.capitalize()}</td>\n"""
|
||||
for i, suggestion in enumerate(suggestions):
|
||||
|
||||
relevant_file = suggestion['relevant_file'].strip()
|
||||
@ -794,14 +871,19 @@ class PRCodeSuggestions:
|
||||
|
||||
{example_code.rstrip()}
|
||||
"""
|
||||
if suggestion.get('score_why'):
|
||||
pr_body += f"<details><summary>Suggestion importance[1-10]: {suggestion['score']}</summary>\n\n"
|
||||
pr_body += f"Why: {suggestion['score_why']}\n\n"
|
||||
pr_body += f"__\n\nWhy: {suggestion['score_why']}\n\n"
|
||||
pr_body += f"</details>"
|
||||
|
||||
pr_body += f"</details>"
|
||||
|
||||
# # add another column for 'score'
|
||||
pr_body += f"</td><td align=center>{suggestion['score']}\n\n"
|
||||
score_int = int(suggestion.get('score', 0))
|
||||
score_str = f"{score_int}"
|
||||
if get_settings().pr_code_suggestions.new_score_mechanism:
|
||||
score_str = self.get_score_str(score_int)
|
||||
pr_body += f"</td><td align=center>{score_str}\n\n"
|
||||
|
||||
pr_body += f"</td></tr>"
|
||||
counter_suggestions += 1
|
||||
@ -814,6 +896,16 @@ class PRCodeSuggestions:
|
||||
get_logger().info(f"Failed to publish summarized code suggestions, error: {e}")
|
||||
return ""
|
||||
|
||||
def get_score_str(self, score: int) -> str:
|
||||
th_high = get_settings().pr_code_suggestions.get('new_score_mechanism_th_high', 9)
|
||||
th_medium = get_settings().pr_code_suggestions.get('new_score_mechanism_th_medium', 7)
|
||||
if score >= th_high:
|
||||
return "High"
|
||||
elif score >= th_medium:
|
||||
return "Medium"
|
||||
else: # score < 7
|
||||
return "Low"
|
||||
|
||||
async def self_reflect_on_suggestions(self,
|
||||
suggestion_list: List,
|
||||
patches_diff: str,
|
||||
|
@ -41,7 +41,8 @@ class PRConfig:
|
||||
skip_keys = ['ai_disclaimer', 'ai_disclaimer_title', 'ANALYTICS_FOLDER', 'secret_provider', "skip_keys", "app_id", "redirect",
|
||||
'trial_prefix_message', 'no_eligible_message', 'identity_provider', 'ALLOWED_REPOS',
|
||||
'APP_NAME', 'PERSONAL_ACCESS_TOKEN', 'shared_secret', 'key', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'user_token',
|
||||
'private_key', 'private_key_id', 'client_id', 'client_secret', 'token', 'bearer_token']
|
||||
'private_key', 'private_key_id', 'client_id', 'client_secret', 'token', 'bearer_token', 'jira_api_token','webhook_secret']
|
||||
partial_skip_keys = ['key', 'secret', 'token', 'private']
|
||||
extra_skip_keys = get_settings().config.get('config.skip_keys', [])
|
||||
if extra_skip_keys:
|
||||
skip_keys.extend(extra_skip_keys)
|
||||
@ -57,6 +58,8 @@ class PRConfig:
|
||||
for key, value in configs.items():
|
||||
if key.lower() in skip_keys_lower:
|
||||
continue
|
||||
if any(skip_key in key.lower() for skip_key in partial_skip_keys):
|
||||
continue
|
||||
markdown_text += f"\n{header.lower()}.{key.lower()} = {repr(value) if isinstance(value, str) else value}"
|
||||
markdown_text += " "
|
||||
markdown_text += "\n```"
|
||||
|
@ -134,8 +134,18 @@ class PRDescription:
|
||||
pr_body += "<hr>\n\n<details> <summary><strong>✨ Describe tool usage guide:</strong></summary><hr> \n\n"
|
||||
pr_body += HelpMessage.get_describe_usage_guide()
|
||||
pr_body += "\n</details>\n"
|
||||
elif get_settings().pr_description.enable_help_comment:
|
||||
pr_body += '\n\n___\n\n> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull request to receive relevant information'
|
||||
elif get_settings().pr_description.enable_help_comment and self.git_provider.is_supported("gfm_markdown"):
|
||||
if isinstance(self.git_provider, GithubProvider):
|
||||
pr_body += ('\n\n___\n\n> <details> <summary> Need help?</summary><li>Type <code>/help how to ...</code> '
|
||||
'in the comments thread for any questions about PR-Agent usage.</li><li>Check out the '
|
||||
'<a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> '
|
||||
'for more information.</li></details>')
|
||||
else: # gitlab
|
||||
pr_body += ("\n\n___\n\n<details><summary>Need help?</summary>- Type <code>/help how to ...</code> in the comments "
|
||||
"thread for any questions about PR-Agent usage.<br>- Check out the "
|
||||
"<a href='https://qodo-merge-docs.qodo.ai/usage-guide/'>documentation</a> for more information.</details>")
|
||||
# elif get_settings().pr_description.enable_help_comment:
|
||||
# pr_body += '\n\n___\n\n> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull request to receive relevant information'
|
||||
|
||||
# Output the relevant configurations if enabled
|
||||
if get_settings().get('config', {}).get('output_relevant_configurations', False):
|
||||
@ -318,7 +328,10 @@ class PRDescription:
|
||||
original_prediction_dict = {"pr_files": original_prediction_loaded}
|
||||
else:
|
||||
original_prediction_dict = original_prediction_loaded
|
||||
filenames_predicted = [file['filename'].strip() for file in original_prediction_dict.get('pr_files', [])]
|
||||
if original_prediction_dict:
|
||||
filenames_predicted = [file.get('filename', '').strip() for file in original_prediction_dict.get('pr_files', [])]
|
||||
else:
|
||||
filenames_predicted = []
|
||||
|
||||
# extend the prediction with additional files not included in the original prediction
|
||||
pr_files = self.git_provider.get_diff_files()
|
||||
@ -358,8 +371,12 @@ class PRDescription:
|
||||
if counter_extra_files > 0:
|
||||
get_logger().info(f"Adding {counter_extra_files} unprocessed extra files to table prediction")
|
||||
prediction_extra_dict = load_yaml(prediction_extra, keys_fix_yaml=self.keys_fix)
|
||||
if isinstance(original_prediction_dict, dict) and isinstance(prediction_extra_dict, dict):
|
||||
if original_prediction_dict and isinstance(original_prediction_dict, dict) and \
|
||||
isinstance(prediction_extra_dict, dict) and "pr_files" in prediction_extra_dict:
|
||||
if "pr_files" in original_prediction_dict:
|
||||
original_prediction_dict["pr_files"].extend(prediction_extra_dict["pr_files"])
|
||||
else:
|
||||
original_prediction_dict["pr_files"] = prediction_extra_dict["pr_files"]
|
||||
new_yaml = yaml.dump(original_prediction_dict)
|
||||
if load_yaml(new_yaml, keys_fix_yaml=self.keys_fix):
|
||||
prediction = new_yaml
|
||||
@ -368,7 +385,7 @@ class PRDescription:
|
||||
|
||||
return prediction
|
||||
except Exception as e:
|
||||
get_logger().error(f"Error extending uncovered files {self.pr_id}: {e}")
|
||||
get_logger().exception(f"Error extending uncovered files {self.pr_id}", artifact={"error": e})
|
||||
return original_prediction
|
||||
|
||||
|
||||
@ -673,8 +690,9 @@ class PRDescription:
|
||||
filename = filename.strip()
|
||||
link = self.git_provider.get_line_link(filename, relevant_line_start=-1)
|
||||
if (not link or not diff_plus_minus) and ('additional files' not in filename.lower()):
|
||||
get_logger().warning(f"Error getting line link for '{filename}'")
|
||||
continue
|
||||
# get_logger().warning(f"Error getting line link for '{filename}'")
|
||||
link = ""
|
||||
# continue
|
||||
|
||||
# Add file data to the PR body
|
||||
file_change_description_br = insert_br_after_x_chars(file_change_description, x=(delta - 5))
|
||||
|
370
pr_agent/tools/pr_help_docs.py
Normal file
@ -0,0 +1,370 @@
|
||||
import copy
|
||||
from functools import partial
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from pr_agent.algo import MAX_TOKENS
|
||||
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 retry_with_fallback_models
|
||||
from pr_agent.algo.token_handler import TokenHandler
|
||||
from pr_agent.algo.utils import clip_tokens, get_max_tokens, load_yaml, ModelType
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import get_git_provider_with_context
|
||||
from pr_agent.log import get_logger
|
||||
from pr_agent.servers.help import HelpMessage
|
||||
|
||||
|
||||
#Common code that can be called from similar tools:
|
||||
def modify_answer_section(ai_response: str) -> str | None:
|
||||
# Gets the model's answer and relevant sources section, repacing the heading of the answer section with:
|
||||
# :bulb: Auto-generated documentation-based answer:
|
||||
"""
|
||||
For example: The following input:
|
||||
|
||||
### Question: \nThe following general issue was asked by a user: Title: How does one request to re-review a PR? More Info: I cannot seem to find to do this.
|
||||
### Answer:\nAccording to the documentation, one needs to invoke the command: /review
|
||||
#### Relevant Sources...
|
||||
|
||||
Should become:
|
||||
|
||||
### :bulb: Auto-generated documentation-based answer:\n
|
||||
According to the documentation, one needs to invoke the command: /review
|
||||
#### Relevant Sources...
|
||||
"""
|
||||
model_answer_and_relevant_sections_in_response \
|
||||
= extract_model_answer_and_relevant_sources(ai_response)
|
||||
if model_answer_and_relevant_sections_in_response is not None:
|
||||
cleaned_question_with_answer = "### :bulb: Auto-generated documentation-based answer:\n"
|
||||
cleaned_question_with_answer += model_answer_and_relevant_sections_in_response
|
||||
return cleaned_question_with_answer
|
||||
get_logger().warning(f"Either no answer section found, or that section is malformed: {ai_response}")
|
||||
return None
|
||||
|
||||
def extract_model_answer_and_relevant_sources(ai_response: str) -> str | None:
|
||||
# It is assumed that the input contains several sections with leading "### ",
|
||||
# where the answer is the last one of them having the format: "### Answer:\n"), since the model returns the answer
|
||||
# AFTER the user question. By splitting using the string: "### Answer:\n" and grabbing the last part,
|
||||
# the model answer is guaranteed to be in that last part, provided it is followed by a "#### Relevant Sources:\n\n".
|
||||
# (for more details, see here: https://github.com/Codium-ai/pr-agent-pro/blob/main/pr_agent/tools/pr_help_message.py#L173)
|
||||
"""
|
||||
For example:
|
||||
### Question: \nHow does one request to re-review a PR?\n\n
|
||||
### Answer:\nAccording to the documentation, one needs to invoke the command: /review\n\n
|
||||
#### Relevant Sources:\n\n...
|
||||
|
||||
The answer part is: "According to the documentation, one needs to invoke the command: /review\n\n"
|
||||
followed by "Relevant Sources:\n\n".
|
||||
"""
|
||||
if "### Answer:\n" in ai_response:
|
||||
model_answer_and_relevant_sources_sections_in_response = ai_response.split("### Answer:\n")[-1]
|
||||
# Split such part by "Relevant Sources" section to contain only the model answer:
|
||||
if "#### Relevant Sources:\n\n" in model_answer_and_relevant_sources_sections_in_response:
|
||||
model_answer_section_in_response \
|
||||
= model_answer_and_relevant_sources_sections_in_response.split("#### Relevant Sources:\n\n")[0]
|
||||
get_logger().info(f"Found model answer: {model_answer_section_in_response}")
|
||||
return model_answer_and_relevant_sources_sections_in_response \
|
||||
if len(model_answer_section_in_response) > 0 else None
|
||||
get_logger().warning(f"Either no answer section found, or that section is malformed: {ai_response}")
|
||||
return None
|
||||
|
||||
def get_maximal_text_input_length_for_token_count_estimation():
|
||||
model = get_settings().config.model
|
||||
if 'claude-3-7-sonnet' in model.lower():
|
||||
return 900000 #Claude API for token estimation allows maximal text input of 900K chars
|
||||
return math.inf #Otherwise, no known limitation on input text just for token estimation
|
||||
|
||||
# Load documentation files to memory, decorating them with a header to mark where each file begins,
|
||||
# as to help the LLM to give a better answer.
|
||||
def aggregate_documentation_files_for_prompt_contents(base_path: str, doc_files: List[str]) -> Optional[str]:
|
||||
docs_prompt = ""
|
||||
for file in doc_files:
|
||||
try:
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Skip files with no text content
|
||||
if not re.search(r'[a-zA-Z]', content):
|
||||
continue
|
||||
file_path = str(file).replace(str(base_path), '')
|
||||
docs_prompt += f"\n==file name==\n\n{file_path}\n\n==file content==\n\n{content.strip()}\n=========\n\n"
|
||||
except Exception as e:
|
||||
get_logger().warning(f"Error while reading the file {file}: {e}")
|
||||
continue
|
||||
if not docs_prompt:
|
||||
get_logger().error("Couldn't find any usable documentation files. Returning None.")
|
||||
return None
|
||||
return docs_prompt
|
||||
|
||||
def format_markdown_q_and_a_response(question_str: str, response_str: str, relevant_sections: List[Dict[str, str]],
|
||||
supported_suffixes: List[str], base_url_prefix: str, base_url_suffix: str="") -> str:
|
||||
base_url_prefix = base_url_prefix.strip('/') #Sanitize base_url_prefix
|
||||
answer_str = ""
|
||||
answer_str += f"### Question: \n{question_str}\n\n"
|
||||
answer_str += f"### Answer:\n{response_str.strip()}\n\n"
|
||||
answer_str += f"#### Relevant Sources:\n\n"
|
||||
for section in relevant_sections:
|
||||
file = section.get('file_name').strip()
|
||||
ext = [suffix for suffix in supported_suffixes if file.endswith(suffix)]
|
||||
if not ext:
|
||||
get_logger().warning(f"Unsupported file extension: {file}")
|
||||
continue
|
||||
if str(section['relevant_section_header_string']).strip():
|
||||
markdown_header = format_markdown_header(section['relevant_section_header_string'])
|
||||
if base_url_prefix:
|
||||
answer_str += f"> - {base_url_prefix}/{file}{base_url_suffix}#{markdown_header}\n"
|
||||
else:
|
||||
answer_str += f"> - {base_url_prefix}/{file}{base_url_suffix}\n"
|
||||
return answer_str
|
||||
|
||||
def format_markdown_header(header: str) -> str:
|
||||
try:
|
||||
# First, strip common characters from both ends
|
||||
cleaned = header.strip('# 💎\n')
|
||||
|
||||
# Define all characters to be removed/replaced in a single pass
|
||||
replacements = {
|
||||
"'": '',
|
||||
"`": '',
|
||||
'(': '',
|
||||
')': '',
|
||||
',': '',
|
||||
'.': '',
|
||||
'?': '',
|
||||
'!': '',
|
||||
' ': '-'
|
||||
}
|
||||
|
||||
# Compile regex pattern for characters to remove
|
||||
pattern = re.compile('|'.join(map(re.escape, replacements.keys())))
|
||||
|
||||
# Perform replacements in a single pass and convert to lowercase
|
||||
return pattern.sub(lambda m: replacements[m.group()], cleaned).lower()
|
||||
except Exception:
|
||||
get_logger().exception(f"Error while formatting markdown header", artifacts={'header': header})
|
||||
return ""
|
||||
|
||||
def clean_markdown_content(content: str) -> str:
|
||||
"""
|
||||
Remove hidden comments and unnecessary elements from markdown content to reduce size.
|
||||
|
||||
Args:
|
||||
content: The original markdown content
|
||||
|
||||
Returns:
|
||||
Cleaned markdown content
|
||||
"""
|
||||
# Remove HTML comments
|
||||
content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
|
||||
|
||||
# Remove frontmatter (YAML between --- or +++ delimiters)
|
||||
content = re.sub(r'^---\s*\n.*?\n---\s*\n', '', content, flags=re.DOTALL)
|
||||
content = re.sub(r'^\+\+\+\s*\n.*?\n\+\+\+\s*\n', '', content, flags=re.DOTALL)
|
||||
|
||||
# Remove excessive blank lines (more than 2 consecutive)
|
||||
content = re.sub(r'\n{3,}', '\n\n', content)
|
||||
|
||||
# Remove HTML tags that are often used for styling only
|
||||
content = re.sub(r'<div.*?>|</div>|<span.*?>|</span>', '', content, flags=re.DOTALL)
|
||||
|
||||
# Remove image alt text which can be verbose
|
||||
content = re.sub(r'!\[(.*?)\]', '![]', content)
|
||||
|
||||
# Remove images completely
|
||||
content = re.sub(r'!\[.*?\]\(.*?\)', '', content)
|
||||
|
||||
# Remove simple HTML tags but preserve content between them
|
||||
content = re.sub(r'<(?!table|tr|td|th|thead|tbody)([a-zA-Z][a-zA-Z0-9]*)[^>]*>(.*?)</\1>',
|
||||
r'\2', content, flags=re.DOTALL)
|
||||
return content.strip()
|
||||
|
||||
class PredictionPreparator:
|
||||
def __init__(self, ai_handler, vars, system_prompt, user_prompt):
|
||||
self.ai_handler = ai_handler
|
||||
variables = copy.deepcopy(vars)
|
||||
environment = Environment(undefined=StrictUndefined)
|
||||
self.system_prompt = environment.from_string(system_prompt).render(variables)
|
||||
self.user_prompt = environment.from_string(user_prompt).render(variables)
|
||||
|
||||
async def __call__(self, model: str) -> str:
|
||||
try:
|
||||
response, finish_reason = await self.ai_handler.chat_completion(
|
||||
model=model, temperature=get_settings().config.temperature, system=self.system_prompt, user=self.user_prompt)
|
||||
return response
|
||||
except Exception as e:
|
||||
get_logger().error(f"Error while preparing prediction: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
class PRHelpDocs(object):
|
||||
def __init__(self, ctx_url, ai_handler:partial[BaseAiHandler,] = LiteLLMAIHandler, args: Tuple[str]=None, return_as_string: bool=False):
|
||||
self.ctx_url = ctx_url
|
||||
self.question = args[0] if args else None
|
||||
self.return_as_string = return_as_string
|
||||
self.repo_url_given_explicitly = True
|
||||
self.repo_url = get_settings().get('PR_HELP_DOCS.REPO_URL', '')
|
||||
self.repo_desired_branch = get_settings().get('PR_HELP_DOCS.REPO_DEFAULT_BRANCH', 'main') #Ignored if self.repo_url is empty
|
||||
self.include_root_readme_file = not(get_settings()['PR_HELP_DOCS.EXCLUDE_ROOT_README'])
|
||||
self.supported_doc_exts = get_settings()['PR_HELP_DOCS.SUPPORTED_DOC_EXTS']
|
||||
self.docs_path = get_settings()['PR_HELP_DOCS.DOCS_PATH']
|
||||
|
||||
retrieved_settings = [self.include_root_readme_file, self.supported_doc_exts, self.docs_path]
|
||||
if any([setting is None for setting in retrieved_settings]):
|
||||
raise Exception(f"One of the settings is invalid: {retrieved_settings}")
|
||||
|
||||
self.git_provider = get_git_provider_with_context(ctx_url)
|
||||
if not self.git_provider:
|
||||
raise Exception(f"No git provider found at {ctx_url}")
|
||||
if not self.repo_url:
|
||||
self.repo_url_given_explicitly = False
|
||||
get_logger().debug(f"No explicit repo url provided, deducing it from type: {self.git_provider.__class__.__name__} "
|
||||
f"context url: {self.ctx_url}")
|
||||
self.repo_url = self.git_provider.get_git_repo_url(self.ctx_url)
|
||||
if not self.repo_url:
|
||||
raise Exception(f"Unable to deduce repo url from type: {self.git_provider.__class__.__name__} url: {self.ctx_url}")
|
||||
get_logger().debug(f"deduced repo url: {self.repo_url}")
|
||||
self.repo_desired_branch = None #Inferred from the repo provider.
|
||||
|
||||
self.ai_handler = ai_handler()
|
||||
self.vars = {
|
||||
"docs_url": self.repo_url,
|
||||
"question": self.question,
|
||||
"snippets": "",
|
||||
}
|
||||
self.token_handler = TokenHandler(None,
|
||||
self.vars,
|
||||
get_settings().pr_help_docs_prompts.system,
|
||||
get_settings().pr_help_docs_prompts.user)
|
||||
|
||||
async def run(self):
|
||||
if not self.question:
|
||||
get_logger().warning('No question provided. Will do nothing.')
|
||||
return None
|
||||
|
||||
try:
|
||||
# Clone the repository and gather relevant documentation files.
|
||||
docs_prompt = None
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
get_logger().debug(f"About to clone repository: {self.repo_url} to temporary directory: {tmp_dir}...")
|
||||
returned_cloned_repo_root = self.git_provider.clone(self.repo_url, tmp_dir, remove_dest_folder=False)
|
||||
if not returned_cloned_repo_root:
|
||||
raise Exception(f"Failed to clone {self.repo_url} to {tmp_dir}")
|
||||
|
||||
get_logger().debug(f"About to gather relevant documentation files...")
|
||||
doc_files = []
|
||||
if self.include_root_readme_file:
|
||||
for root, _, files in os.walk(returned_cloned_repo_root.path):
|
||||
# Only look at files in the root directory, not subdirectories
|
||||
if root == returned_cloned_repo_root.path:
|
||||
for file in files:
|
||||
if file.lower().startswith("readme."):
|
||||
doc_files.append(os.path.join(root, file))
|
||||
abs_docs_path = os.path.join(returned_cloned_repo_root.path, self.docs_path)
|
||||
if os.path.exists(abs_docs_path):
|
||||
doc_files.extend(self._find_all_document_files_matching_exts(abs_docs_path,
|
||||
ignore_readme=(self.docs_path=='.')))
|
||||
if not doc_files:
|
||||
get_logger().warning(f"No documentation files found matching file extensions: "
|
||||
f"{self.supported_doc_exts} under repo: {self.repo_url} path: {self.docs_path}")
|
||||
return None
|
||||
|
||||
get_logger().info(f'Answering a question inside context {self.ctx_url} for repo: {self.repo_url}'
|
||||
f' using the following documentation files: ', artifacts={'doc_files': doc_files})
|
||||
|
||||
docs_prompt = aggregate_documentation_files_for_prompt_contents(returned_cloned_repo_root.path, doc_files)
|
||||
if not docs_prompt:
|
||||
get_logger().warning(f"Error reading one of the documentation files. Returning with no result...")
|
||||
return None
|
||||
docs_prompt_to_send_to_model = docs_prompt
|
||||
|
||||
# Estimate how many tokens will be needed. Trim in case of exceeding limit.
|
||||
# Firstly, check if text needs to be trimmed, as some models fail to return the estimated token count if the input text is too long.
|
||||
max_allowed_txt_input = get_maximal_text_input_length_for_token_count_estimation()
|
||||
if len(docs_prompt_to_send_to_model) >= max_allowed_txt_input:
|
||||
get_logger().warning(f"Text length: {len(docs_prompt_to_send_to_model)} exceeds the current returned limit of {max_allowed_txt_input} just for token count estimation. Trimming the text...")
|
||||
docs_prompt_to_send_to_model = docs_prompt_to_send_to_model[:max_allowed_txt_input]
|
||||
# Then, count the tokens in the prompt. If the count exceeds the limit, trim the text.
|
||||
token_count = self.token_handler.count_tokens(docs_prompt_to_send_to_model, force_accurate=True)
|
||||
get_logger().debug(f"Estimated token count of documentation to send to model: {token_count}")
|
||||
model = get_settings().config.model
|
||||
if model in MAX_TOKENS:
|
||||
max_tokens_full = MAX_TOKENS[model] # note - here we take the actual max tokens, without any reductions. we do aim to get the full documentation website in the prompt
|
||||
else:
|
||||
max_tokens_full = get_max_tokens(model)
|
||||
delta_output = 5000 #Elbow room to reduce chance of exceeding token limit or model paying less attention to prompt guidelines.
|
||||
if token_count > max_tokens_full - delta_output:
|
||||
docs_prompt_to_send_to_model = clean_markdown_content(docs_prompt_to_send_to_model) #Reduce unnecessary text/images/etc.
|
||||
get_logger().info(f"Token count {token_count} exceeds the limit {max_tokens_full - delta_output}. Attempting to clip text to fit within the limit...")
|
||||
docs_prompt_to_send_to_model = clip_tokens(docs_prompt_to_send_to_model, max_tokens_full - delta_output,
|
||||
num_input_tokens=token_count)
|
||||
self.vars['snippets'] = docs_prompt_to_send_to_model.strip()
|
||||
|
||||
# Run the AI model and extract sections from its response
|
||||
response = await retry_with_fallback_models(PredictionPreparator(self.ai_handler, self.vars,
|
||||
get_settings().pr_help_docs_prompts.system,
|
||||
get_settings().pr_help_docs_prompts.user),
|
||||
model_type=ModelType.REGULAR)
|
||||
response_yaml = load_yaml(response)
|
||||
if not response_yaml:
|
||||
get_logger().exception("Failed to parse the AI response.", artifacts={'response': response})
|
||||
raise Exception(f"Failed to parse the AI response.")
|
||||
response_str = response_yaml.get('response')
|
||||
relevant_sections = response_yaml.get('relevant_sections')
|
||||
if not response_str or not relevant_sections:
|
||||
get_logger().exception("Failed to extract response/relevant sections.",
|
||||
artifacts={'response_str': response_str, 'relevant_sections': relevant_sections})
|
||||
raise Exception(f"Failed to extract response/relevant sections.")
|
||||
|
||||
# Format the response as markdown
|
||||
canonical_url_prefix, canonical_url_suffix = self.git_provider.get_canonical_url_parts(repo_git_url=self.repo_url if self.repo_url_given_explicitly else None,
|
||||
desired_branch=self.repo_desired_branch)
|
||||
answer_str = format_markdown_q_and_a_response(self.question, response_str, relevant_sections, self.supported_doc_exts, canonical_url_prefix, canonical_url_suffix)
|
||||
if answer_str:
|
||||
#Remove the question phrase and replace with light bulb and a heading mentioning this is an automated answer:
|
||||
answer_str = modify_answer_section(answer_str)
|
||||
# For PR help docs, we return the answer string instead of publishing it
|
||||
if answer_str and self.return_as_string:
|
||||
if int(response_yaml.get('question_is_relevant', '1')) == 0:
|
||||
get_logger().warning(f"Chat help docs answer would be ignored due to an invalid question.",
|
||||
artifacts={'answer_str': answer_str})
|
||||
return ""
|
||||
get_logger().info(f"Chat help docs answer", artifacts={'answer_str': answer_str})
|
||||
return answer_str
|
||||
|
||||
# Publish the answer
|
||||
if not answer_str or int(response_yaml.get('question_is_relevant', '1')) == 0:
|
||||
get_logger().info(f"No answer found")
|
||||
return ""
|
||||
|
||||
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_help_docs.enable_help_text:
|
||||
answer_str += "<hr>\n\n<details> <summary><strong>💡 Tool usage guide:</strong></summary><hr> \n\n"
|
||||
answer_str += HelpMessage.get_help_docs_usage_guide()
|
||||
answer_str += "\n</details>\n"
|
||||
|
||||
if get_settings().config.publish_output:
|
||||
self.git_provider.publish_comment(answer_str)
|
||||
else:
|
||||
get_logger().info("Answer:", artifacts={'answer_str': answer_str})
|
||||
|
||||
except:
|
||||
get_logger().exception('failed to provide answer to given user question as a result of a thrown exception (see above)')
|
||||
|
||||
|
||||
def _find_all_document_files_matching_exts(self, abs_docs_path: str, ignore_readme=False) -> List[str]:
|
||||
matching_files = []
|
||||
|
||||
# Ensure extensions don't have leading dots and are lowercase
|
||||
dotless_extensions = [ext.lower().lstrip('.') for ext in self.supported_doc_exts]
|
||||
|
||||
# Walk through directory and subdirectories
|
||||
for root, _, files in os.walk(abs_docs_path):
|
||||
for file in files:
|
||||
if ignore_readme and root == abs_docs_path and file.lower() in [f"readme.{ext}" for ext in dotless_extensions]:
|
||||
continue
|
||||
# Check if file has one of the specified extensions
|
||||
if any(file.lower().endswith(f'.{ext}') for ext in dotless_extensions):
|
||||
matching_files.append(os.path.join(root, file))
|
||||
return matching_files
|