Merge remote-tracking branch 'origin/main' into zmeir-publish_inline_comments_single_api_call

This commit is contained in:
Ori Kotek
2023-07-18 11:53:41 +03:00
30 changed files with 122 additions and 40 deletions

View File

@ -1,2 +1,3 @@
venv/ venv/
pr_agent/settings/.secrets.toml pr_agent/settings/.secrets.toml
pics/

View File

@ -39,4 +39,4 @@ We use [tiktoken](https://github.com/openai/tiktoken) to tokenize the patches af
4. If we haven't reached the max token length, add the `deleted files` to the prompt until the prompt reaches the max token length (hard stop), skip the rest of the patches. 4. If we haven't reached the max token length, add the `deleted files` to the prompt until the prompt reaches the max token length (hard stop), skip the rest of the patches.
### Example ### Example
![](./pics/git_patch_logic.png) ![](https://codium.ai/images/git_patch_logic.png)

View File

@ -27,25 +27,25 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review PRs
<h4>Describe:</h4> <h4>Describe:</h4>
<div align="center"> <div align="center">
<p float="center"> <p float="center">
<img src="./pics/describe.gif" width="800"> <img src="https://codium.ai/images/describe.gif" width="800">
</p> </p>
</div> </div>
<h4>Review:</h4> <h4>Review:</h4>
<div align="center"> <div align="center">
<p float="center"> <p float="center">
<img src="./pics/review.gif" width="800"> <img src="https://codium.ai/images/review.gif" width="800">
</p> </p>
</div> </div>
<h4>Ask:</h4> <h4>Ask:</h4>
<div align="center"> <div align="center">
<p float="center"> <p float="center">
<img src="./pics/ask.gif" width="800"> <img src="https://codium.ai/images/ask.gif" width="800">
</p> </p>
</div> </div>
<h4>Improve:</h4> <h4>Improve:</h4>
<div align="center"> <div align="center">
<p float="center"> <p float="center">
<img src="./pics/improve.gif" width="800"> <img src="https://codium.ai/images/improve.gif" width="800">
</p> </p>
</div> </div>
<div align="left"> <div align="left">
@ -64,7 +64,7 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review PRs
Experience GPT-4 powered PR review on your public GitHub repository with our hosted PR-Agent. To try it, just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command. Experience GPT-4 powered PR review on your public GitHub repository with our hosted PR-Agent. To try it, just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command.
![Review generation process](./pics/demo.gif) ![Review generation process](https://codium.ai/images/demo.gif)
To set up your own PR-Agent, see the [Quickstart](#Quickstart) section To set up your own PR-Agent, see the [Quickstart](#Quickstart) section
@ -78,6 +78,7 @@ To set up your own PR-Agent, see the [Quickstart](#Quickstart) section
| | Ask | :white_check_mark: | :white_check_mark: | | | | Ask | :white_check_mark: | :white_check_mark: | |
| | Auto-Description | :white_check_mark: | | | | | Auto-Description | :white_check_mark: | | |
| | Improve Code | :white_check_mark: | :white_check_mark: | | | | Improve Code | :white_check_mark: | :white_check_mark: | |
| | Reflect and Review | :white_check_mark: | | |
| | | | | | | | | | | |
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | | USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Tagging bot | :white_check_mark: | :white_check_mark: | | | | Tagging bot | :white_check_mark: | :white_check_mark: | |
@ -92,6 +93,7 @@ Examples for invoking the different tools via the [CLI](#quickstart):
- **Describe**: python cli.py --pr-url=<pr_url> describe - **Describe**: python cli.py --pr-url=<pr_url> describe
- **Improve**: python cli.py --pr-url=<pr_url> improve - **Improve**: python cli.py --pr-url=<pr_url> improve
- **Ask**: python cli.py --pr-url=<pr_url> ask "Write me a poem about this PR" - **Ask**: python cli.py --pr-url=<pr_url> ask "Write me a poem about this PR"
- **Reflect**: python cli.py --pr-url=<pr_url> reflect
"<pr_url>" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). "<pr_url>" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50).
@ -120,16 +122,17 @@ Here are several ways to install and run PR-Agent:
## Usage and Tools ## Usage and Tools
**PR-Agent** provides four types of interactions ("tools"): `"PR Reviewer"`, `"PR Q&A"`, `"PR Description"` and `"PR Code Sueggestions"`. **PR-Agent** provides five types of interactions ("tools"): `"PR Reviewer"`, `"PR Q&A"`, `"PR Description"`, `"PR Code Sueggestions"` and `"PR Reflect and Review"`.
- The "PR Reviewer" tool automatically analyzes PRs, and provides various types of feedback. - The "PR Reviewer" tool automatically analyzes PRs, and provides various types of feedback.
- The "PR Q&A" tool answers free-text questions about the PR. - The "PR Q&A" tool answers free-text questions about the PR.
- The "PR Description" tool automatically sets the PR Title and body. - The "PR Description" tool automatically sets the PR Title and body.
- The "PR Code Suggestion" tool provide inline code suggestions for the PR that can be applied and committed. - The "PR Code Suggestion" tool provide inline code suggestions for the PR that can be applied and committed.
- The "PR Reflect and Review" tool first initiates a dialog with the user and asks them to reflect on the PR, and then provides a review.
## How it works ## How it works
![PR-Agent Tools](./pics/pr_agent_overview.png) ![PR-Agent Tools](https://codium.ai/images/pr_agent_overview.png)
Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more details on how we convert a code diff to a manageable LLM prompt Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more details on how we convert a code diff to a manageable LLM prompt
@ -138,11 +141,11 @@ Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more detai
- [ ] Support open-source models, as a replacement for openai models. (Note - a minimal requirement for each open-source model is to have 8k+ context, and good support for generating json as an output) - [ ] Support open-source models, as a replacement for openai models. (Note - a minimal requirement for each open-source model is to have 8k+ context, and good support for generating json as an output)
- [x] Support other Git providers, such as Gitlab and Bitbucket. - [x] Support other Git providers, such as Gitlab and Bitbucket.
- [ ] Develop additional logics for handling large PRs, and compressing git patches - [ ] Develop additional logics for handling large PRs, and compressing git patches
- [ ] Dedicated tools and sub-tools for specific programming languages (Python, Javascript, Java, C++, etc)
- [ ] Add additional context to the prompt. For example, repo (or relevant files) summarization, with tools such a [ctags](https://github.com/universal-ctags/ctags) - [ ] Add additional context to the prompt. For example, repo (or relevant files) summarization, with tools such a [ctags](https://github.com/universal-ctags/ctags)
- [ ] Adding more tools. Possible directions: - [ ] Adding more tools. Possible directions:
- [x] PR description - [x] PR description
- [x] Inline code suggestions - [x] Inline code suggestions
- [x] Reflect and review
- [ ] Enforcing CONTRIBUTING.md guidelines - [ ] Enforcing CONTRIBUTING.md guidelines
- [ ] Performance (are there any performance issues) - [ ] Performance (are there any performance issues)
- [ ] Documentation (is the PR properly documented) - [ ] Documentation (is the PR properly documented)

BIN
pics/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

View File

@ -2,8 +2,10 @@ import re
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
from pr_agent.tools.pr_description import PRDescription from pr_agent.tools.pr_description import PRDescription
from pr_agent.tools.pr_information_from_user import PRInformationFromUser
from pr_agent.tools.pr_questions import PRQuestions from pr_agent.tools.pr_questions import PRQuestions
from pr_agent.tools.pr_reviewer import PRReviewer from pr_agent.tools.pr_reviewer import PRReviewer
from pr_agent.config_loader import settings
class PRAgent: class PRAgent:
@ -11,8 +13,13 @@ class PRAgent:
pass pass
async def handle_request(self, pr_url, request) -> bool: async def handle_request(self, pr_url, request) -> bool:
if any(cmd in request for cmd in ["/review", "/review_pr"]): if any(cmd in request for cmd in ["/answer"]):
await PRReviewer(pr_url).review() await PRReviewer(pr_url, is_answer=True).review()
elif any(cmd in request for cmd in ["/review", "/review_pr", "/reflect_and_review"]):
if settings.pr_reviewer.ask_and_reflect or "/reflect_and_review" in request:
await PRInformationFromUser(pr_url).generate_questions()
else:
await PRReviewer(pr_url).review()
elif any(cmd in request for cmd in ["/describe", "/describe_pr"]): elif any(cmd in request for cmd in ["/describe", "/describe_pr"]):
await PRDescription(pr_url).describe() await PRDescription(pr_url).describe()
elif any(cmd in request for cmd in ["/improve", "/improve_code"]): elif any(cmd in request for cmd in ["/improve", "/improve_code"]):

View File

@ -18,19 +18,22 @@ For example:
- cli.py --pr-url=... describe - cli.py --pr-url=... describe
- cli.py --pr-url=... improve - cli.py --pr-url=... improve
- cli.py --pr-url=... ask "write me a poem about this PR" - cli.py --pr-url=... ask "write me a poem about this PR"
- cli.py --pr-url=... reflect
Supported commands: Supported commands:
review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement. review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement.
ask / ask_question [question] - Ask a question about the PR. ask / ask_question [question] - Ask a question about the PR.
describe / describe_pr - Modify the PR title and description based on the PR's contents. describe / describe_pr - Modify the PR title and description based on the PR's contents.
improve / improve_code - Suggest improvements to the code in the PR as pull request comments ready to commit. improve / improve_code - Suggest improvements to the code in the PR as pull request comments ready to commit.
reflect - Ask the PR author questions about the PR.
""") """)
parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', required=True) parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', required=True)
parser.add_argument('command', type=str, help='The', choices=['review', 'review_pr', parser.add_argument('command', type=str, help='The', choices=['review', 'review_pr',
'ask', 'ask_question', 'ask', 'ask_question',
'describe', 'describe_pr', 'describe', 'describe_pr',
'improve', 'improve_code', 'improve', 'improve_code',
'user_questions'], default='review') 'reflect', 'review_after_reflect'],
default='review')
parser.add_argument('rest', nargs=argparse.REMAINDER, default=[]) parser.add_argument('rest', nargs=argparse.REMAINDER, default=[])
args = parser.parse_args() args = parser.parse_args()
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO")) logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
@ -56,10 +59,14 @@ improve / improve_code - Suggest improvements to the code in the PR as pull requ
print(f"Reviewing PR: {args.pr_url}") print(f"Reviewing PR: {args.pr_url}")
reviewer = PRReviewer(args.pr_url, cli_mode=True) reviewer = PRReviewer(args.pr_url, cli_mode=True)
asyncio.run(reviewer.review()) asyncio.run(reviewer.review())
elif command in ['user_questions']: elif command in ['reflect']:
print(f"Asking the PR author questions: {args.pr_url}") print(f"Asking the PR author questions: {args.pr_url}")
reviewer = PRInformationFromUser(args.pr_url) reviewer = PRInformationFromUser(args.pr_url)
asyncio.run(reviewer.generate_questions()) asyncio.run(reviewer.generate_questions())
elif command in ['review_after_reflect']:
print(f"Processing author's answers and sending review: {args.pr_url}")
reviewer = PRReviewer(args.pr_url, cli_mode=True, is_answer=True)
asyncio.run(reviewer.review())
else: else:
print(f"Unknown command: {command}") print(f"Unknown command: {command}")
parser.print_help() parser.print_help()

View File

@ -25,6 +25,11 @@ class BitbucketProvider:
if pr_url: if pr_url:
self.set_pr(pr_url) self.set_pr(pr_url)
def is_supported(self, capability: str) -> bool:
if capability == 'get_issue_comments':
return False
return True
def set_pr(self, pr_url: str): def set_pr(self, pr_url: str):
self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url) self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url)
self.pr = self._get_pr() self.pr = self._get_pr()
@ -74,6 +79,9 @@ class BitbucketProvider:
def get_user_id(self): def get_user_id(self):
return 0 return 0
def get_issue_comments(self):
raise NotImplementedError("Bitbucket provider does not support issue comments yet")
@staticmethod @staticmethod
def _parse_pr_url(pr_url: str) -> Tuple[str, int]: def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
parsed_url = urlparse(pr_url) parsed_url = urlparse(pr_url)

View File

@ -21,6 +21,10 @@ class FilePatchInfo:
class GitProvider(ABC): class GitProvider(ABC):
@abstractmethod
def is_supported(self, capability: str) -> bool:
pass
@abstractmethod @abstractmethod
def get_diff_files(self) -> list[FilePatchInfo]: def get_diff_files(self) -> list[FilePatchInfo]:
pass pass
@ -62,6 +66,10 @@ class GitProvider(ABC):
def get_pr_description(self): def get_pr_description(self):
pass pass
@abstractmethod
def get_issue_comments(self):
pass
def get_main_pr_language(languages, files) -> str: def get_main_pr_language(languages, files) -> str:
""" """

View File

@ -23,6 +23,9 @@ class GithubProvider(GitProvider):
self.set_pr(pr_url) self.set_pr(pr_url)
self.last_commit_id = list(self.pr.get_commits())[-1] self.last_commit_id = list(self.pr.get_commits())[-1]
def is_supported(self, capability: str) -> bool:
return True
def set_pr(self, pr_url: str): def set_pr(self, pr_url: str):
self.repo, self.pr_num = self._parse_pr_url(pr_url) self.repo, self.pr_num = self._parse_pr_url(pr_url)
self.pr = self._get_pr() self.pr = self._get_pr()
@ -172,6 +175,9 @@ class GithubProvider(GitProvider):
notifications = self.github_client.get_user().get_notifications(since=since) notifications = self.github_client.get_user().get_notifications(since=since)
return notifications return notifications
def get_issue_comments(self):
return self.pr.get_issue_comments()
@staticmethod @staticmethod
def _parse_pr_url(pr_url: str) -> Tuple[str, int]: def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
parsed_url = urlparse(pr_url) parsed_url = urlparse(pr_url)

View File

@ -4,6 +4,7 @@ from typing import Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
import gitlab import gitlab
from gitlab import GitlabGetError
from pr_agent.config_loader import settings from pr_agent.config_loader import settings
@ -31,6 +32,11 @@ class GitLabProvider(GitProvider):
self.RE_HUNK_HEADER = re.compile( self.RE_HUNK_HEADER = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)") r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
def is_supported(self, capability: str) -> bool:
if capability == 'get_issue_comments':
return False
return True
@property @property
def pr(self): def pr(self):
'''The GitLab terminology is merge request (MR) instead of pull request (PR)''' '''The GitLab terminology is merge request (MR) instead of pull request (PR)'''
@ -42,7 +48,11 @@ class GitLabProvider(GitProvider):
self.last_diff = self.mr.diffs.list()[-1] self.last_diff = self.mr.diffs.list()[-1]
def _get_pr_file_content(self, file_path: str, branch: str) -> str: def _get_pr_file_content(self, file_path: str, branch: str) -> str:
return self.gl.projects.get(self.id_project).files.get(file_path, branch).decode() try:
return self.gl.projects.get(self.id_project).files.get(file_path, branch).decode()
except GitlabGetError:
# In case of file creation the method returns GitlabGetError (404 file not found). In this case we return an empty string for the diff.
return ''
def get_diff_files(self) -> list[FilePatchInfo]: def get_diff_files(self) -> list[FilePatchInfo]:
diffs = self.mr.changes()['changes'] diffs = self.mr.changes()['changes']
@ -58,8 +68,10 @@ class GitLabProvider(GitProvider):
elif diff['renamed_file']: elif diff['renamed_file']:
edit_type = EDIT_TYPE.RENAMED edit_type = EDIT_TYPE.RENAMED
try: try:
original_file_content_str = bytes.decode(original_file_content_str, 'utf-8') if isinstance(original_file_content_str, bytes):
new_file_content_str = bytes.decode(new_file_content_str, 'utf-8') original_file_content_str = bytes.decode(original_file_content_str, 'utf-8')
if isinstance(new_file_content_str, bytes):
new_file_content_str = bytes.decode(new_file_content_str, 'utf-8')
except UnicodeDecodeError: except UnicodeDecodeError:
logging.warning( logging.warning(
f"Cannot decode file {diff['old_path']} or {diff['new_path']} in merge request {self.id_mr}") f"Cannot decode file {diff['old_path']} or {diff['new_path']} in merge request {self.id_mr}")
@ -203,6 +215,9 @@ class GitLabProvider(GitProvider):
def get_pr_description(self): def get_pr_description(self):
return self.mr.description return self.mr.description
def get_issue_comments(self):
raise NotImplementedError("GitLab provider does not support issue comments yet")
def _parse_merge_request_url(self, merge_request_url: str) -> Tuple[int, int]: def _parse_merge_request_url(self, merge_request_url: str) -> Tuple[int, int]:
parsed_url = urlparse(merge_request_url) parsed_url = urlparse(merge_request_url)

View File

@ -3,9 +3,11 @@ import json
import os import os
import re import re
from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import settings from pr_agent.config_loader import settings
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
from pr_agent.tools.pr_description import PRDescription from pr_agent.tools.pr_description import PRDescription
from pr_agent.tools.pr_information_from_user import PRInformationFromUser
from pr_agent.tools.pr_questions import PRQuestions from pr_agent.tools.pr_questions import PRQuestions
from pr_agent.tools.pr_reviewer import PRReviewer from pr_agent.tools.pr_reviewer import PRReviewer
@ -53,20 +55,7 @@ async def run_action():
pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url", None) pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url", None)
if pr_url: if pr_url:
body = comment_body.strip().lower() body = comment_body.strip().lower()
if any(cmd in body for cmd in ["/review", "/review_pr"]): await PRAgent().handle_request(pr_url, body)
await PRReviewer(pr_url).review()
elif any(cmd in body for cmd in ["/describe", "/describe_pr"]):
await PRDescription(pr_url).describe()
elif any(cmd in body for cmd in ["/improve", "/improve_code"]):
await PRCodeSuggestions(pr_url).suggest()
elif any(cmd in body for cmd in ["/ask", "/ask_question"]):
pattern = r'(/ask|/ask_question)\s*(.*)'
matches = re.findall(pattern, comment_body, re.IGNORECASE)
if matches:
question = matches[0][1]
await PRQuestions(pr_url, question).answer()
else:
print(f"Unknown command: {body}")
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -10,6 +10,7 @@ require_tests_review=true
require_security_review=true require_security_review=true
num_code_suggestions=3 num_code_suggestions=3
inline_code_comments = true inline_code_comments = true
ask_and_reflect=false
[pr_description] [pr_description]
publish_description_as_comment=false publish_description_as_comment=false

View File

@ -1,16 +1,17 @@
[pr_information_from_user_prompt] [pr_information_from_user_prompt]
system="""You are CodiumAI-PR-Reviewer, a language model designed to review git pull requests. system="""You are CodiumAI-PR-Reviewer, a language model designed to review git pull requests.
Given the PR Info and the PR Git Diff, generate 4 questions about the PR for the PR author. Given the PR Info and the PR Git Diff, generate 3 short questions about the PR code for the PR author.
The goal of the questions is to help the language model understand the PR better, so the questions should be insightful, informative, non-trivial, and relevant to the PR. The goal of the questions is to help the language model understand the PR better, so the questions should be insightful, informative, non-trivial, and relevant to the PR.
Prefer yes\\no or multiple choice questions. If you have to ask open-ended questions, make sure they are not too difficult, and can be answered in a sentence or two. You should prefer asking yes\\no questions, or multiple choice questions. Also add at least one open-ended question, but make sure they are not too difficult, and can be answered in a sentence or two.
Example output: Example output:
' '
Questions to better understand the PR: Questions to better understand the PR:
1. ... 1) ...
2. ... 2) ...
... ...
'
""" """
user="""PR Info: user="""PR Info:

View File

@ -26,6 +26,12 @@ You must use the following JSON schema to format your answer:
"description": "yes\\no question: does this PR have relevant tests ?" "description": "yes\\no question: does this PR have relevant tests ?"
}, },
{%- endif %} {%- endif %}
{%- if question_str %}
"Insights from user's answer": {
"type": "string",
"description": "shortly summarize the insights you gained from the user's answers to the questions"
},
{%- endif %}
{%- if require_focused %} {%- if require_focused %}
"Focused PR": { "Focused PR": {
"type": "string", "type": "string",
@ -115,6 +121,16 @@ Description: '{{description}}'
Main language: {{language}} Main language: {{language}}
{%- endif %} {%- endif %}
{%- if question_str %}
######
Here are questions to better understand the PR. Use the answers to provide better feedback.
{{question_str|trim}}
User answers:
{{answer_str|trim}}
######
{%- endif %}
The PR Git Diff: The PR Git Diff:
``` ```

View File

@ -21,7 +21,7 @@ class PRInformationFromUser:
self.vars = { self.vars = {
"title": self.git_provider.pr.title, "title": self.git_provider.pr.title,
"branch": self.git_provider.get_pr_branch(), "branch": self.git_provider.get_pr_branch(),
"description": self.git_provider.get_description(), "description": self.git_provider.get_pr_description(),
"language": self.main_pr_language, "language": self.main_pr_language,
"diff": "", # empty diff for initial calculation "diff": "", # empty diff for initial calculation
} }
@ -35,7 +35,7 @@ class PRInformationFromUser:
async def generate_questions(self): async def generate_questions(self):
logging.info('Generating question to the user...') logging.info('Generating question to the user...')
if settings.config.publish_output: if settings.config.publish_output:
self.git_provider.publish_comment("Preparing answer...", is_temporary=True) self.git_provider.publish_comment("Preparing questions...", is_temporary=True)
logging.info('Getting PR diff...') logging.info('Getting PR diff...')
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler) self.patches_diff = get_pr_diff(self.git_provider, self.token_handler)
logging.info('Getting AI prediction...') logging.info('Getting AI prediction...')
@ -66,6 +66,6 @@ class PRInformationFromUser:
model_output = self.prediction.strip() model_output = self.prediction.strip()
if settings.config.verbosity_level >= 2: if settings.config.verbosity_level >= 2:
logging.info(f"answer_str:\n{model_output}") logging.info(f"answer_str:\n{model_output}")
answer_str = f"{model_output}\n\n Please respond to the question above in the following format:\n\n" + \ answer_str = f"{model_output}\n\n Please respond to the questions above in the following format:\n\n" +\
f"/answer <question_id> <answer>\n\n" + f"Example:\n'\n/answer\n1. Yes, because ...\n2. No, because ...\n'" f"\n>/answer\n>1) ...\n>2) ...\n>...\n"
return answer_str return answer_str

View File

@ -15,12 +15,16 @@ from pr_agent.servers.help import bot_help_text, actions_help_text
class PRReviewer: class PRReviewer:
def __init__(self, pr_url: str, cli_mode=False): def __init__(self, pr_url: str, cli_mode=False, is_answer: bool = False):
self.git_provider = get_git_provider()(pr_url) self.git_provider = get_git_provider()(pr_url)
self.main_language = get_main_pr_language( self.main_language = get_main_pr_language(
self.git_provider.get_languages(), self.git_provider.get_files() self.git_provider.get_languages(), self.git_provider.get_files()
) )
self.is_answer = is_answer
if self.is_answer and not self.git_provider.is_supported("get_issue_comments"):
raise Exception(f"Answer mode is not supported for {settings.config.git_provider} for now")
answer_str = question_str = self._get_user_answers()
self.ai_handler = AiHandler() self.ai_handler = AiHandler()
self.patches_diff = None self.patches_diff = None
self.prediction = None self.prediction = None
@ -35,6 +39,9 @@ class PRReviewer:
"require_security": settings.pr_reviewer.require_security_review, "require_security": settings.pr_reviewer.require_security_review,
"require_focused": settings.pr_reviewer.require_focused_review, "require_focused": settings.pr_reviewer.require_focused_review,
'num_code_suggestions': settings.pr_reviewer.num_code_suggestions, 'num_code_suggestions': settings.pr_reviewer.num_code_suggestions,
#
'question_str': question_str,
'answer_str': answer_str,
} }
self.token_handler = TokenHandler(self.git_provider.pr, self.token_handler = TokenHandler(self.git_provider.pr,
self.vars, self.vars,
@ -142,3 +149,16 @@ class PRReviewer:
if comments: if comments:
self.git_provider.publish_inline_comments(comments) self.git_provider.publish_inline_comments(comments)
def _get_user_answers(self):
answer_str = question_str = ""
if self.is_answer:
discussion_messages = self.git_provider.get_issue_comments()
for message in discussion_messages.reversed:
if "Questions to better understand the PR:" in message.body:
question_str = message.body
elif '/answer' in message.body:
answer_str = message.body
if answer_str and question_str:
break
return question_str, answer_str