diff --git a/CHANGELOG.md b/CHANGELOG.md index 52927c7c..3d594a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2023-07-30 + +### Enhanced +- Added the ability to modify any configuration parameter from 'configuration.toml' on-the-fly. +- Updated the command line interface and bot commands to accept configuration changes as arguments. +- Improved the PR agent to handle additional arguments for each action. + ## 2023-07-28 ### Improved diff --git a/pr_agent/agent/pr_agent.py b/pr_agent/agent/pr_agent.py index 9f336f64..1a00c977 100644 --- a/pr_agent/agent/pr_agent.py +++ b/pr_agent/agent/pr_agent.py @@ -16,16 +16,16 @@ class PRAgent: async def handle_request(self, pr_url, request) -> bool: action, *args = request.strip().split() if any(cmd == action for cmd in ["/answer"]): - await PRReviewer(pr_url, is_answer=True).review() + await PRReviewer(pr_url, is_answer=True, args=args).review() elif any(cmd == action 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() + await PRInformationFromUser(pr_url, args=args).generate_questions() else: await PRReviewer(pr_url, args=args).review() elif any(cmd == action for cmd in ["/describe", "/describe_pr"]): await PRDescription(pr_url, args=args).describe() elif any(cmd == action for cmd in ["/improve", "/improve_code"]): - await PRCodeSuggestions(pr_url).suggest() + await PRCodeSuggestions(pr_url, args=args).suggest() elif any(cmd == action for cmd in ["/ask", "/ask_question"]): await PRQuestions(pr_url, args=args).answer() elif any(cmd == action for cmd in ["/update_changelog"]): diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 23bf8dfd..c31556f1 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -1,4 +1,5 @@ from __future__ import annotations +from typing import List import difflib from datetime import datetime @@ -211,3 +212,46 @@ def load_large_diff(file, new_file_content_str: str, original_file_content_str: except Exception: pass return patch + + +def update_settings_from_args(args: List[str]) -> None: + """ + Update the settings of the Dynaconf object based on the arguments passed to the function. + + Args: + args: A list of arguments passed to the function. + Example args: ['--pr_code_suggestions.extra_instructions="be funny', + '--pr_code_suggestions.num_code_suggestions=3'] + + Returns: + None + + Raises: + ValueError: If the argument is not in the correct format. + + """ + if args: + for arg in args: + try: + arg = arg.strip('-').strip() + vals = arg.split('=') + if len(vals) != 2: + raise ValueError(f'Invalid argument format: {arg}') + key, value = vals + keys = key.split('.') + d = settings + for i, k in enumerate(keys[:-1]): + if k not in d: + raise ValueError(f'Invalid setting: {key}') + d = d[k] + if keys[-1] not in d: + raise ValueError(f'Invalid setting: {key}') + if isinstance(d[keys[-1]], bool): + d[keys[-1]] = value.lower() in ("yes", "true", "t", "1") + else: + d[keys[-1]] = type(d[keys[-1]])(value) + logging.info(f'Updated setting {key} to: "{value}"') + except ValueError as e: + logging.error(str(e)) + except Exception as e: + logging.error(f'Failed to parse argument {arg}: {e}') \ No newline at end of file diff --git a/pr_agent/cli.py b/pr_agent/cli.py index 2f0a3a28..b0173ace 100644 --- a/pr_agent/cli.py +++ b/pr_agent/cli.py @@ -29,6 +29,9 @@ describe / describe_pr - Modify the PR title and description based on the PR's c 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. update_changelog - Update the changelog based on the PR's contents. + +To edit any configuration parameter from 'configuration.toml', just add -config_path=. +For example: '- cli.py --pr-url=... review --pr_reviewer.extra_instructions="focus on the file: ..."' """) 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', @@ -79,7 +82,7 @@ def _handle_describe_command(pr_url: str, rest: list): def _handle_improve_command(pr_url: str, rest: list): print(f"PR code suggestions: {pr_url}") - reviewer = PRCodeSuggestions(pr_url) + reviewer = PRCodeSuggestions(pr_url, args=rest) asyncio.run(reviewer.suggest()) @@ -97,7 +100,7 @@ def _handle_reflect_command(pr_url: str, rest: list): def _handle_review_after_reflect_command(pr_url: str, rest: list): print(f"Processing author's answers and sending review: {pr_url}") - reviewer = PRReviewer(pr_url, cli_mode=True, is_answer=True) + reviewer = PRReviewer(pr_url, cli_mode=True, is_answer=True, args=rest) asyncio.run(reviewer.review()) def _handle_update_changelog(pr_url: str, rest: list): diff --git a/pr_agent/config_loader.py b/pr_agent/config_loader.py index 69d20d88..5716fe8a 100644 --- a/pr_agent/config_loader.py +++ b/pr_agent/config_loader.py @@ -19,7 +19,7 @@ settings = Dynaconf( "settings/pr_description_prompts.toml", "settings/pr_code_suggestions_prompts.toml", "settings/pr_information_from_user_prompts.toml", - "settings/pr_update_changelog.toml", + "settings/pr_update_changelog_prompts.toml", "settings_prod/.secrets.toml" ]] ) diff --git a/pr_agent/servers/help.py b/pr_agent/servers/help.py index af5350d2..0631e8f6 100644 --- a/pr_agent/servers/help.py +++ b/pr_agent/servers/help.py @@ -1,11 +1,10 @@ commands_text = "> **/review [-i]**: Request a review of your Pull Request. For an incremental review, which only " \ "considers changes since the last review, include the '-i' option.\n" \ - "> **/describe [-c]**: Modify the PR title and description based on the contents of the PR. " \ - "To get the description as comment instead of modifying the PR description, " \ - "include the '-c' option.\n" \ - "> **/improve**: Suggest improvements to the code in the PR. " \ - "These will be provided as pull request comments, ready to commit.\n" \ - "> **/ask \\**: Pose a question about the PR.\n" + "> **/describe**: Modify the PR title and description based on the contents of the PR.\n" \ + "> **/improve**: Suggest improvements to the code in the PR. \n" \ + "> **/ask \\**: Pose a question about the PR.\n\n" \ + "To edit any configuration parameter from 'configuration.toml', just add -config_path=. " \ + "For example: '/review --pr_reviewer.extra_instructions=\"focus on the file: ...\"'" \ def bot_help_text(user: str): diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 58f4ba32..5d9c0846 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -7,7 +7,7 @@ publish_output_progress=true verbosity_level=0 # 0,1,2 use_extra_bad_extensions=false -[pr_reviewer] +[pr_reviewer] # /review # require_focused_review=true require_score_review=false require_tests_review=true @@ -15,17 +15,21 @@ require_security_review=true num_code_suggestions=0 inline_code_comments = true ask_and_reflect=false +extra_instructions = "" -[pr_description] +[pr_description] # /describe # publish_description_as_comment=false +extra_instructions = "" -[pr_questions] +[pr_questions] # /ask # -[pr_code_suggestions] +[pr_code_suggestions] # /improve # num_code_suggestions=4 +extra_instructions = "" -[pr_update_changelog] +[pr_update_changelog] # /update_changelog # push_changelog_changes=false +extra_instructions = "" [github] # The type of deployment to create. Valid values are 'app' or 'user'. diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index d4aa24a2..b3a63f9a 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -9,6 +9,12 @@ Your task is to provide meaningfull non-trivial code suggestions to improve the - Make sure not to provide suggestions repeating modifications already implemented in the new PR code (the '+' lines). - Don't output line numbers in the 'improved code' snippets. +{%- if extra_instructions %} + +Extra instructions from the user: +{{ extra_instructions }} +{% endif %} + You must use the following JSON schema to format your answer: ```json { diff --git a/pr_agent/settings/pr_description_prompts.toml b/pr_agent/settings/pr_description_prompts.toml index 8c8df966..16b6bfde 100644 --- a/pr_agent/settings/pr_description_prompts.toml +++ b/pr_agent/settings/pr_description_prompts.toml @@ -3,6 +3,12 @@ system="""You are CodiumAI-PR-Reviewer, a language model designed to review git Your task is to provide full description of the PR content. - Make sure not to focus the new PR code (the '+' lines). +{%- if extra_instructions %} + +Extra instructions from the user: +{{ extra_instructions }} +{% endif %} + You must use the following JSON schema to format your answer: ```json { diff --git a/pr_agent/settings/pr_reviewer_prompts.toml b/pr_agent/settings/pr_reviewer_prompts.toml index 97d12366..87cfa29f 100644 --- a/pr_agent/settings/pr_reviewer_prompts.toml +++ b/pr_agent/settings/pr_reviewer_prompts.toml @@ -8,6 +8,12 @@ Your task is to provide constructive and concise feedback for the PR, and also p - Make sure not to provide suggestions repeating modifications already implemented in the new PR code (the '+' lines). {%- endif %} +{%- if extra_instructions %} + +Extra instructions from the user: +{{ extra_instructions }} +{% endif %} + You must use the following JSON schema to format your answer: ```json { diff --git a/pr_agent/settings/pr_update_changelog.toml b/pr_agent/settings/pr_update_changelog_prompts.toml similarity index 90% rename from pr_agent/settings/pr_update_changelog.toml rename to pr_agent/settings/pr_update_changelog_prompts.toml index 91413010..f12b6c65 100644 --- a/pr_agent/settings/pr_update_changelog.toml +++ b/pr_agent/settings/pr_update_changelog_prompts.toml @@ -4,6 +4,12 @@ Your task is to update the CHANGELOG.md file of the project, to shortly summariz - The output should match the existing CHANGELOG.md format, style and conventions, so it will look like a natural part of the file. For example, if previous changes were summarized in a single line, you should do the same. - Don't repeat previous changes. Generate only new content, that is not already in the CHANGELOG.md file. - Be general, and avoid specific details, files, etc. The output should be minimal, no more than 3-4 short lines. Ignore non-relevant subsections. + +{%- if extra_instructions %} + +Extra instructions from the user: +{{ extra_instructions }} +{%- endif %} """ user="""PR Info: diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index 6ec80ec0..3c1477cc 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -8,19 +8,21 @@ from jinja2 import Environment, StrictUndefined from pr_agent.algo.ai_handler import AiHandler from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.token_handler import TokenHandler -from pr_agent.algo.utils import try_fix_json +from pr_agent.algo.utils import try_fix_json, update_settings_from_args from pr_agent.config_loader import settings from pr_agent.git_providers import BitbucketProvider, get_git_provider from pr_agent.git_providers.git_provider import get_main_pr_language class PRCodeSuggestions: - def __init__(self, pr_url: str, cli_mode=False): + def __init__(self, pr_url: str, cli_mode=False, args: list = None): self.git_provider = get_git_provider()(pr_url) self.main_language = get_main_pr_language( self.git_provider.get_languages(), self.git_provider.get_files() ) + update_settings_from_args(args) + self.ai_handler = AiHandler() self.patches_diff = None self.prediction = None @@ -31,7 +33,8 @@ class PRCodeSuggestions: "description": self.git_provider.get_pr_description(), "language": self.main_language, "diff": "", # empty diff for initial calculation - 'num_code_suggestions': settings.pr_code_suggestions.num_code_suggestions, + "num_code_suggestions": settings.pr_code_suggestions.num_code_suggestions, + "extra_instructions": settings.pr_code_suggestions.extra_instructions, } self.token_handler = TokenHandler(self.git_provider.pr, self.vars, @@ -137,3 +140,4 @@ class PRCodeSuggestions: logging.info(f"Could not dedent code snippet for file {relevant_file}, error: {e}") return new_code_snippet + diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index aa2d2ce1..fe78cae8 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -8,6 +8,7 @@ from jinja2 import Environment, StrictUndefined from pr_agent.algo.ai_handler import AiHandler from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.token_handler import TokenHandler +from pr_agent.algo.utils import update_settings_from_args from pr_agent.config_loader import settings from pr_agent.git_providers import get_git_provider from pr_agent.git_providers.git_provider import get_main_pr_language @@ -21,8 +22,8 @@ class PRDescription: pr_url (str): The URL of the pull request. args (list, optional): List of arguments passed to the PRDescription class. Defaults to None. """ - self.parse_args(args) - + update_settings_from_args(args) + # Initialize the git provider and main PR language self.git_provider = get_git_provider()(pr_url) self.main_pr_language = get_main_pr_language( @@ -39,6 +40,7 @@ class PRDescription: "description": self.git_provider.get_pr_description(), "language": self.main_pr_language, "diff": "", # empty diff for initial calculation + "extra_instructions": settings.pr_description.extra_instructions, } # Initialize the token handler @@ -53,22 +55,6 @@ class PRDescription: self.patches_diff = None self.prediction = None - def parse_args(self, args: List[str]) -> None: - """ - Parse the arguments passed to the PRDescription class and set the 'publish_description_as_comment' attribute accordingly. - - Args: - args: A list of arguments passed to the PRReviewer class. - - Returns: - None - """ - self.publish_description_as_comment = settings.pr_description.publish_description_as_comment - if args and len(args) >= 1: - arg = args[0] - if arg == "-c": - self.publish_description_as_comment = True - async def describe(self): """ Generates a PR description using an AI model and publishes it to the PR. @@ -84,7 +70,7 @@ class PRDescription: if settings.config.publish_output: logging.info('Pushing answer...') - if self.publish_description_as_comment: + if settings.pr_description.publish_description_as_comment: self.git_provider.publish_comment(markdown_text) else: self.git_provider.publish_description(pr_title, pr_body) diff --git a/pr_agent/tools/pr_information_from_user.py b/pr_agent/tools/pr_information_from_user.py index 463b434e..feeb0e31 100644 --- a/pr_agent/tools/pr_information_from_user.py +++ b/pr_agent/tools/pr_information_from_user.py @@ -14,7 +14,7 @@ from pr_agent.git_providers.git_provider import get_main_pr_language class PRInformationFromUser: - def __init__(self, pr_url: str): + def __init__(self, pr_url: str, args: list = None): self.git_provider = get_git_provider()(pr_url) self.main_pr_language = get_main_pr_language( self.git_provider.get_languages(), self.git_provider.get_files() diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index ec4b8f03..8e2343d0 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -9,7 +9,7 @@ from jinja2 import Environment, StrictUndefined from pr_agent.algo.ai_handler import AiHandler from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.token_handler import TokenHandler -from pr_agent.algo.utils import convert_to_markdown, try_fix_json +from pr_agent.algo.utils import convert_to_markdown, try_fix_json, update_settings_from_args from pr_agent.config_loader import settings from pr_agent.git_providers import get_git_provider from pr_agent.git_providers.git_provider import get_main_pr_language, IncrementalPR @@ -30,7 +30,8 @@ class PRReviewer: is_answer (bool, optional): Indicates whether the review is being done in answer mode. Defaults to False. args (list, optional): List of arguments passed to the PRReviewer class. Defaults to None. """ - self.parse_args(args) + update_settings_from_args(args) + self.parse_args(args) # -i command self.git_provider = get_git_provider()(pr_url, incremental=self.incremental) self.main_language = get_main_pr_language( @@ -60,6 +61,7 @@ class PRReviewer: 'num_code_suggestions': settings.pr_reviewer.num_code_suggestions, 'question_str': question_str, 'answer_str': answer_str, + "extra_instructions": settings.pr_reviewer.extra_instructions, } self.token_handler = TokenHandler( diff --git a/pr_agent/tools/pr_update_changelog.py b/pr_agent/tools/pr_update_changelog.py index 1b06c381..a4f93978 100644 --- a/pr_agent/tools/pr_update_changelog.py +++ b/pr_agent/tools/pr_update_changelog.py @@ -10,6 +10,7 @@ from pr_agent.algo.ai_handler import AiHandler from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.token_handler import TokenHandler from pr_agent.config_loader import settings +from pr_agent.algo.utils import update_settings_from_args from pr_agent.git_providers import get_git_provider, GithubProvider from pr_agent.git_providers.git_provider import get_main_pr_language @@ -23,7 +24,8 @@ class PRUpdateChangelog: self.main_language = get_main_pr_language( self.git_provider.get_languages(), self.git_provider.get_files() ) - self.commit_changelog = self._parse_args(args, settings) + update_settings_from_args(args) + self.commit_changelog = settings.pr_update_changelog.push_changelog_changes self._get_changlog_file() # self.changelog_file_str self.ai_handler = AiHandler() self.patches_diff = None @@ -37,6 +39,7 @@ class PRUpdateChangelog: "diff": "", # empty diff for initial calculation "changelog_file_str": self.changelog_file_str, "today": date.today(), + "extra_instructions": settings.pr_update_changelog.extra_instructions, } self.token_handler = TokenHandler(self.git_provider.pr, self.vars, @@ -95,7 +98,7 @@ class PRUpdateChangelog: if not self.commit_changelog: answer += "\n\n\n>to commit the new content to the CHANGELOG.md file, please type:" \ - "\n>'/update_changelog -commit'\n" + "\n>'/update_changelog --pr_update_changelog.push_changelog_changes=true'\n" if settings.config.verbosity_level >= 2: logging.info(f"answer:\n{answer}") @@ -137,19 +140,6 @@ Example: """ return example_changelog - def _parse_args(self, args, setting): - commit_changelog = False - if args and len(args) >= 1: - try: - if args[0] == "-commit": - commit_changelog = True - except: - pass - else: - commit_changelog = setting.pr_update_changelog.push_changelog_changes - - return commit_changelog - def _get_changlog_file(self): try: self.changelog_file = self.git_provider.repo_obj.get_contents("CHANGELOG.md",