From 7a7242030d73d913853b7eb0ee560b9a8c605191 Mon Sep 17 00:00:00 2001 From: Tim Perkins Date: Mon, 4 Sep 2023 12:11:39 -0400 Subject: [PATCH 1/5] Add support for markers in description --- pr_agent/settings/configuration.toml | 3 + pr_agent/tools/pr_description.py | 116 ++++++++++++++++++++------- 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index f8abd555a..e9c59f31b 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -25,9 +25,12 @@ automatic_review=true extra_instructions = "" [pr_description] # /describe # +publish_labels=true publish_description_as_comment=false add_original_user_description=false keep_original_user_title=false +use_description_markers=false +include_generated_by_header=true extra_instructions = "" [pr_questions] # /ask # diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index c45917f4e..585eb2619 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -1,5 +1,6 @@ import copy import json +import re import logging from typing import List, Tuple @@ -28,6 +29,7 @@ def __init__(self, pr_url: str, args: list = None): self.main_pr_language = get_main_pr_language( self.git_provider.get_languages(), self.git_provider.get_files() ) + self.pr_id = f"{self.git_provider.repo}/{self.git_provider.pr_num}" # Initialize the AI handler self.ai_handler = AiHandler() @@ -61,22 +63,34 @@ async def run(self): """ Generates a PR description using an AI model and publishes it to the PR. """ - logging.info('Generating a PR description...') + logging.info(f"Generating a PR description {self.pr_id}") if get_settings().config.publish_output: self.git_provider.publish_comment("Preparing pr description...", is_temporary=True) - + await retry_with_fallback_models(self._prepare_prediction) - - logging.info('Preparing answer...') - pr_title, pr_body, pr_types, markdown_text = self._prepare_pr_answer() - + + logging.info(f"Preparing answer {self.pr_id}") + if self.prediction: + self._prepare_data() + else: + return None + + if get_settings().pr_description.publish_labels: + pr_types = self._prepare_types() + + if get_settings().pr_description.use_description_markers: + logging.info(f"Using description marker replacements {self.pr_id}") + pr_title, pr_body = self._prepare_marker_replacements() + else: + pr_title, pr_body, markdown_text = self._prepare_pr_answer() + if get_settings().config.publish_output: - logging.info('Pushing answer...') + logging.info(f"Pushing answer {self.pr_id}") if get_settings().pr_description.publish_description_as_comment: self.git_provider.publish_comment(markdown_text) else: self.git_provider.publish_description(pr_title, pr_body) - if self.git_provider.is_supported("get_labels"): + if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"): current_labels = self.git_provider.get_labels() if current_labels is None: current_labels = [] @@ -99,9 +113,12 @@ async def _prepare_prediction(self, model: str) -> None: Any exceptions raised by the 'get_pr_diff' and '_get_prediction' functions. """ - logging.info('Getting PR diff...') + if get_settings().pr_description.use_description_markers and 'pr_agent:' not in self.user_description: + return None + + logging.info(f"Getting PR diff {self.pr_id}") self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model) - logging.info('Getting AI prediction...') + logging.info(f"Getting AI prediction {self.pr_id}") self.prediction = await self._get_prediction(model) async def _get_prediction(self, model: str) -> str: @@ -134,34 +151,71 @@ async def _get_prediction(self, model: str) -> str: return response - def _prepare_pr_answer(self) -> Tuple[str, str, List[str], str]: - """ - Prepare the PR description based on the AI prediction data. - Returns: - - title: a string containing the PR title. - - pr_body: a string containing the PR body in a markdown format. - - pr_types: a list of strings containing the PR types. - - markdown_text: a string containing the AI prediction data in a markdown format. used for publishing a comment - """ + def _prepare_data(self): # Load the AI prediction data into a dictionary - data = load_yaml(self.prediction.strip()) + self.data = load_yaml(self.prediction.strip()) if get_settings().pr_description.add_original_user_description and self.user_description: - data["User Description"] = self.user_description + self.data["User Description"] = self.user_description + - # Initialization + def _prepare_types(self) -> List[str]: pr_types = [] # If the 'PR Type' key is present in the dictionary, split its value by comma and assign it to 'pr_types' - if 'PR Type' in data: - if type(data['PR Type']) == list: - pr_types = data['PR Type'] - elif type(data['PR Type']) == str: - pr_types = data['PR Type'].split(',') + if 'PR Type' in self.data: + if type(self.data['PR Type']) == list: + pr_types = self.data['PR Type'] + elif type(self.data['PR Type']) == str: + pr_types = self.data['PR Type'].split(',') + + return pr_types + + def _prepare_marker_replacements(self) -> Tuple[str, str]: + title = self.vars["title"] + body = self.user_description + if get_settings().pr_description.include_generated_by_header: + ai_header = f"### 🤖 Generated by PR Agent at {self.git_provider.last_commit_id.sha}\n\n" + else: + ai_header = "" + + ai_summary = self.data.get('PR Description') + if ai_summary and not re.search(r'', body): + summary = f"{ai_header}{ai_summary}" + body = body.replace('pr_agent:summary', summary) + + if not re.search(r'', body): + ai_walkthrough = self.data.get('PR Main Files Walkthrough') + if ai_walkthrough: + walkthrough = str(ai_header) + for file in ai_walkthrough: + filename = file['filename'].replace("'", "`") + description = file['changes in file'].replace("'", "`") + walkthrough += f'- `{filename}`: {description}\n' + + body = body.replace('pr_agent:walkthrough', walkthrough) + + return title, body + + def _prepare_pr_answer(self) -> Tuple[str, str, str]: + """ + Prepare the PR description based on the AI prediction data. + + Returns: + - title: a string containing the PR title. + - pr_body: a string containing the PR description body in a markdown format. + - markdown_text: a string containing the AI prediction data in a markdown format. used for publishing a comment + """ + + # Iterate over the dictionary items and append the key and value to 'markdown_text' in a markdown format + markdown_text = "" + for key, value in self.data.items(): + markdown_text += f"## {key}\n\n" + markdown_text += f"{value}\n\n" # Remove the 'PR Title' key from the dictionary - ai_title = data.pop('PR Title') + ai_title = self.data.pop('PR Title', self.vars["title"]) if get_settings().pr_description.keep_original_user_title: # Assign the original PR title to the 'title' variable title = self.vars["title"] @@ -172,7 +226,7 @@ def _prepare_pr_answer(self) -> Tuple[str, str, List[str], str]: # Iterate over the remaining dictionary items and append the key and value to 'pr_body' in a markdown format, # except for the items containing the word 'walkthrough' pr_body = "" - for idx, (key, value) in enumerate(data.items()): + for idx, (key, value) in enumerate(self.data.items()): pr_body += f"## {key}:\n" if 'walkthrough' in key.lower(): # for filename, description in value.items(): @@ -185,7 +239,7 @@ def _prepare_pr_answer(self) -> Tuple[str, str, List[str], str]: if type(value) == list: value = ', '.join(v for v in value) pr_body += f"{value}\n" - if idx < len(data) - 1: + if idx < len(self.data) - 1: pr_body += "\n___\n" markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}" @@ -193,4 +247,4 @@ def _prepare_pr_answer(self) -> Tuple[str, str, List[str], str]: if get_settings().config.verbosity_level >= 2: logging.info(f"title:\n{title}\n{pr_body}") - return title, pr_body, pr_types, markdown_text \ No newline at end of file + return title, pr_body, markdown_text From 60e3c81808e1e3579ca626497f1933ff53573568 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 7 Sep 2023 12:10:33 +0300 Subject: [PATCH 2/5] added 'publish_description_as_comment' support --- pr_agent/tools/pr_description.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 585eb2619..6daf60e80 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -82,12 +82,14 @@ async def run(self): logging.info(f"Using description marker replacements {self.pr_id}") pr_title, pr_body = self._prepare_marker_replacements() else: - pr_title, pr_body, markdown_text = self._prepare_pr_answer() + pr_title, pr_body, = self._prepare_pr_answer() + full_markdown_description = f"## Title\n\n{pr_title}\n\n___\n{pr_body}" + if get_settings().config.publish_output: logging.info(f"Pushing answer {self.pr_id}") if get_settings().pr_description.publish_description_as_comment: - self.git_provider.publish_comment(markdown_text) + self.git_provider.publish_comment(full_markdown_description) else: self.git_provider.publish_description(pr_title, pr_body) if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"): @@ -198,7 +200,7 @@ def _prepare_marker_replacements(self) -> Tuple[str, str]: return title, body - def _prepare_pr_answer(self) -> Tuple[str, str, str]: + def _prepare_pr_answer(self) -> Tuple[str, str]: """ Prepare the PR description based on the AI prediction data. @@ -242,9 +244,7 @@ def _prepare_pr_answer(self) -> Tuple[str, str, str]: if idx < len(self.data) - 1: pr_body += "\n___\n" - markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}" - if get_settings().config.verbosity_level >= 2: logging.info(f"title:\n{title}\n{pr_body}") - return title, pr_body, markdown_text + return title, pr_body From 4c83f0bf2fcecf53e166a70dbf01c2233c42dd6a Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 14 Sep 2023 07:47:04 +0300 Subject: [PATCH 3/5] merge --- pr_agent/tools/pr_description.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 60376731a..1f9dfed91 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -253,4 +253,4 @@ def _prepare_pr_answer(self) -> Tuple[str, str]: return title, pr_body - return title, pr_body, pr_types, markdown_text, description \ No newline at end of file + return title, pr_body, pr_types, description \ No newline at end of file From 25acbcb9773770d7ede22a630bf6350aaea8e2e1 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 14 Sep 2023 08:13:00 +0300 Subject: [PATCH 4/5] merge --- pr_agent/tools/pr_description.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 1f9dfed91..26bd16bc1 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -75,17 +75,16 @@ async def run(self): else: return None + pr_labels = [] if get_settings().pr_description.publish_labels: - pr_types = self._prepare_types() + pr_labels = self._prepare_labels() if get_settings().pr_description.use_description_markers: - logging.info(f"Using description marker replacements {self.pr_id}") - pr_title, pr_body = self._prepare_marker_replacements() + pr_title, pr_body = self._prepare_pr_answer_with_markers() else: pr_title, pr_body, = self._prepare_pr_answer() full_markdown_description = f"## Title\n\n{pr_title}\n\n___\n{pr_body}" - if get_settings().config.publish_output: logging.info(f"Pushing answer {self.pr_id}") if get_settings().pr_description.publish_description_as_comment: @@ -96,7 +95,7 @@ async def run(self): current_labels = self.git_provider.get_labels() if current_labels is None: current_labels = [] - self.git_provider.publish_labels(pr_types + current_labels) + self.git_provider.publish_labels(pr_labels + current_labels) self.git_provider.remove_initial_comment() return "" @@ -162,7 +161,7 @@ def _prepare_data(self): self.data["User Description"] = self.user_description - def _prepare_types(self) -> List[str]: + def _prepare_labels(self) -> List[str]: pr_types = [] # If the 'PR Type' key is present in the dictionary, split its value by comma and assign it to 'pr_types' @@ -174,7 +173,8 @@ def _prepare_types(self) -> List[str]: return pr_types - def _prepare_marker_replacements(self) -> Tuple[str, str]: + def _prepare_pr_answer_with_markers(self) -> Tuple[str, str]: + logging.info(f"Using description marker replacements {self.pr_id}") title = self.vars["title"] body = self.user_description if get_settings().pr_description.include_generated_by_header: @@ -207,8 +207,6 @@ def _prepare_pr_answer(self) -> Tuple[str, str]: Returns: - title: a string containing the PR title. - pr_body: a string containing the PR description body in a markdown format. - - markdown_text: a string containing the AI prediction data in a markdown format. used for publishing a comment - - user_description: a string containing the user description """ # Iterate over the dictionary items and append the key and value to 'markdown_text' in a markdown format @@ -245,12 +243,7 @@ def _prepare_pr_answer(self) -> Tuple[str, str]: if idx < len(self.data) - 1: pr_body += "\n___\n" - markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}" - description = data['PR Description'] - if get_settings().config.verbosity_level >= 2: logging.info(f"title:\n{title}\n{pr_body}") - return title, pr_body - - return title, pr_body, pr_types, description \ No newline at end of file + return title, pr_body \ No newline at end of file From cbde37912fb1976c27b151b53c38766178463d69 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 14 Sep 2023 08:20:36 +0300 Subject: [PATCH 5/5] merge --- pr_agent/settings/configuration.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index de4edc6c1..08030bd0d 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -3,7 +3,7 @@ model="gpt-4" fallback_models=["gpt-3.5-turbo-16k"] git_provider="github" publish_output=true -publish_output_progress=true +publish_output_progress=false verbosity_level=0 # 0,1,2 use_extra_bad_extensions=false use_repo_settings_file=true @@ -29,9 +29,10 @@ publish_labels=true publish_description_as_comment=false add_original_user_description=false keep_original_user_title=false +extra_instructions = "" +# markers use_description_markers=false include_generated_by_header=true -extra_instructions = "" [pr_questions] # /ask #