mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-14 09:40:39 +08:00
Merge pull request #476 from Codium-ai/tr/improve_inplace
Enhancements and Bug Fixes in Code Suggestions and Line Link Generation
This commit is contained in:
@ -33,6 +33,16 @@ Under the section 'pr_code_suggestions', the [configuration file](./../pr_agent/
|
|||||||
- `max_number_of_calls`: maximum number of chunks. Default is 5.
|
- `max_number_of_calls`: maximum number of chunks. Default is 5.
|
||||||
- `final_clip_factor`: factor to remove suggestions with low confidence. Default is 0.9.
|
- `final_clip_factor`: factor to remove suggestions with low confidence. Default is 0.9.
|
||||||
|
|
||||||
|
#### summarize mode
|
||||||
|
- `summarize`: if set to true, the tool will summarize the PR code changes. Default is false.
|
||||||
|
|
||||||
|
In this mode, instead of presenting commitable suggestions, the different suggestions will be combined into a single compact comment, with a significant smaller PR footprint.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
`/improve --pr_code_suggestions.summarize=true`
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/improved_summerize_open.png width="768"></kbd>
|
||||||
|
|
||||||
#### A note on code suggestions quality
|
#### A note on code suggestions quality
|
||||||
|
|
||||||
|
BIN
pics/improved_summerize_closed.png
Normal file
BIN
pics/improved_summerize_closed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
BIN
pics/improved_summerize_open.png
Normal file
BIN
pics/improved_summerize_open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
@ -58,7 +58,8 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool=True) -> str:
|
|||||||
emoji = emojis.get(key, "")
|
emoji = emojis.get(key, "")
|
||||||
if key.lower() == 'code feedback':
|
if key.lower() == 'code feedback':
|
||||||
if gfm_supported:
|
if gfm_supported:
|
||||||
markdown_text += f"\n\n- **<details><summary> { emoji } Code feedback:**</summary>\n\n"
|
markdown_text += f"\n\n- "
|
||||||
|
markdown_text += f"<details><summary> { emoji } Code feedback:</summary>\n\n"
|
||||||
else:
|
else:
|
||||||
markdown_text += f"\n\n- **{emoji} Code feedback:**\n\n"
|
markdown_text += f"\n\n- **{emoji} Code feedback:**\n\n"
|
||||||
else:
|
else:
|
||||||
|
@ -228,6 +228,10 @@ class BitbucketProvider(GitProvider):
|
|||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||||
|
link = f"{self.pr_url}/#L{relevant_file}T{relevant_line_start}"
|
||||||
|
return link
|
||||||
|
|
||||||
def generate_link_to_relevant_line_number(self, suggestion) -> str:
|
def generate_link_to_relevant_line_number(self, suggestion) -> str:
|
||||||
try:
|
try:
|
||||||
relevant_file = suggestion['relevant file'].strip('`').strip("'")
|
relevant_file = suggestion['relevant file'].strip('`').strip("'")
|
||||||
|
@ -89,6 +89,9 @@ class GitProvider(ABC):
|
|||||||
def get_pr_id(self):
|
def get_pr_id(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
#### comments operations ####
|
#### comments operations ####
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||||
|
@ -501,6 +501,15 @@ class GithubProvider(GitProvider):
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||||
|
sha_file = hashlib.sha256(relevant_file.encode('utf-8')).hexdigest()
|
||||||
|
if relevant_line_end:
|
||||||
|
link = f"https://github.com/{self.repo}/pull/{self.pr_num}/files#diff-{sha_file}R{relevant_line_start}-R{relevant_line_end}"
|
||||||
|
else:
|
||||||
|
link = f"https://github.com/{self.repo}/pull/{self.pr_num}/files#diff-{sha_file}R{relevant_line_start}"
|
||||||
|
return link
|
||||||
|
|
||||||
|
|
||||||
def get_pr_id(self):
|
def get_pr_id(self):
|
||||||
try:
|
try:
|
||||||
pr_id = f"{self.repo}/{self.pr_num}"
|
pr_id = f"{self.repo}/{self.pr_num}"
|
||||||
|
@ -43,7 +43,7 @@ class GitLabProvider(GitProvider):
|
|||||||
self.incremental = incremental
|
self.incremental = incremental
|
||||||
|
|
||||||
def is_supported(self, capability: str) -> bool:
|
def is_supported(self, capability: str) -> bool:
|
||||||
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'gfm_markdown']:
|
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']: # gfm_markdown is supported in gitlab !
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -422,6 +422,14 @@ class GitLabProvider(GitProvider):
|
|||||||
except:
|
except:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||||
|
if relevant_line_end:
|
||||||
|
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}-L{relevant_line_end}"
|
||||||
|
else:
|
||||||
|
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}"
|
||||||
|
return link
|
||||||
|
|
||||||
|
|
||||||
def generate_link_to_relevant_line_number(self, suggestion) -> str:
|
def generate_link_to_relevant_line_number(self, suggestion) -> str:
|
||||||
try:
|
try:
|
||||||
relevant_file = suggestion['relevant file'].strip('`').strip("'")
|
relevant_file = suggestion['relevant file'].strip('`').strip("'")
|
||||||
|
@ -57,6 +57,7 @@ include_generated_by_header=true
|
|||||||
|
|
||||||
[pr_code_suggestions] # /improve #
|
[pr_code_suggestions] # /improve #
|
||||||
num_code_suggestions=4
|
num_code_suggestions=4
|
||||||
|
summarize = false
|
||||||
extra_instructions = ""
|
extra_instructions = ""
|
||||||
rank_suggestions = false
|
rank_suggestions = false
|
||||||
# params for '/improve --extended' mode
|
# params for '/improve --extended' mode
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from jinja2 import Environment, StrictUndefined
|
from jinja2 import Environment, StrictUndefined
|
||||||
|
|
||||||
from pr_agent.algo.ai_handler import AiHandler
|
from pr_agent.algo.ai_handler import AiHandler
|
||||||
@ -55,9 +54,9 @@ class PRCodeSuggestions:
|
|||||||
try:
|
try:
|
||||||
get_logger().info('Generating code suggestions for PR...')
|
get_logger().info('Generating code suggestions for PR...')
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.publish_comment("Preparing review...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing suggestions...", is_temporary=True)
|
||||||
|
|
||||||
get_logger().info('Preparing PR review...')
|
get_logger().info('Preparing PR code suggestions...')
|
||||||
if not self.is_extended:
|
if not self.is_extended:
|
||||||
await retry_with_fallback_models(self._prepare_prediction)
|
await retry_with_fallback_models(self._prepare_prediction)
|
||||||
data = self._prepare_pr_code_suggestions()
|
data = self._prepare_pr_code_suggestions()
|
||||||
@ -73,8 +72,12 @@ class PRCodeSuggestions:
|
|||||||
data['Code suggestions'] = await self.rank_suggestions(data['Code suggestions'])
|
data['Code suggestions'] = await self.rank_suggestions(data['Code suggestions'])
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
get_logger().info('Pushing PR review...')
|
get_logger().info('Pushing PR code suggestions...')
|
||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
|
if get_settings().pr_code_suggestions.summarize:
|
||||||
|
get_logger().info('Pushing summarize code suggestions...')
|
||||||
|
self.publish_summarizes_suggestions(data)
|
||||||
|
else:
|
||||||
get_logger().info('Pushing inline code suggestions...')
|
get_logger().info('Pushing inline code suggestions...')
|
||||||
self.push_inline_code_suggestions(data)
|
self.push_inline_code_suggestions(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -244,4 +247,27 @@ class PRCodeSuggestions:
|
|||||||
|
|
||||||
return data_sorted
|
return data_sorted
|
||||||
|
|
||||||
|
def publish_summarizes_suggestions(self, data: Dict):
|
||||||
|
try:
|
||||||
|
data_markdown = "## PR Code Suggestions\n\n"
|
||||||
|
for s in data['Code suggestions']:
|
||||||
|
code_snippet_link = self.git_provider.get_line_link(s['relevant file'], s['relevant lines start'],
|
||||||
|
s['relevant lines end'])
|
||||||
|
data_markdown += f"\n💡 Suggestion:\n\n**{s['suggestion content']}**\n\n"
|
||||||
|
if code_snippet_link:
|
||||||
|
data_markdown += f" File: [{s['relevant file']} ({s['relevant lines start']}-{s['relevant lines end']})]({code_snippet_link})\n\n"
|
||||||
|
else:
|
||||||
|
data_markdown += f"File: {s['relevant file']} ({s['relevant lines start']}-{s['relevant lines end']})\n\n"
|
||||||
|
if self.git_provider.is_supported("gfm_markdown"):
|
||||||
|
data_markdown += "<details> <summary> Example code:</summary>\n\n"
|
||||||
|
data_markdown += f"___\n\n"
|
||||||
|
data_markdown += f"Existing code:\n```{self.main_language}\n{s['existing code']}\n```\n"
|
||||||
|
data_markdown += f"Improved code:\n```{self.main_language}\n{s['improved code']}\n```\n"
|
||||||
|
if self.git_provider.is_supported("gfm_markdown"):
|
||||||
|
data_markdown += "</details>\n"
|
||||||
|
data_markdown += "\n___\n\n"
|
||||||
|
self.git_provider.publish_comment(data_markdown)
|
||||||
|
except Exception as e:
|
||||||
|
get_logger().info(f"Failed to publish summarized code suggestions, error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ class PRReviewer:
|
|||||||
if not get_settings().get("CONFIG.CLI_MODE", False):
|
if not get_settings().get("CONFIG.CLI_MODE", False):
|
||||||
markdown_text += "\n### How to use\n"
|
markdown_text += "\n### How to use\n"
|
||||||
if self.git_provider.is_supported("gfm_markdown"):
|
if self.git_provider.is_supported("gfm_markdown"):
|
||||||
markdown_text += "\n**<details><summary> Instructions**</summary>\n"
|
markdown_text += "\n <details> <summary> Instructions</summary>\n\n"
|
||||||
bot_user = "[bot]" if get_settings().github_app.override_deployment_type else get_settings().github_app.bot_user
|
bot_user = "[bot]" if get_settings().github_app.override_deployment_type else get_settings().github_app.bot_user
|
||||||
if user and bot_user not in user:
|
if user and bot_user not in user:
|
||||||
markdown_text += bot_help_text(user)
|
markdown_text += bot_help_text(user)
|
||||||
|
@ -71,7 +71,7 @@ class TestConvertToMarkdown:
|
|||||||
- 📌 **Type of PR:** Test type\n\
|
- 📌 **Type of PR:** Test type\n\
|
||||||
- 🧪 **Relevant tests added:** no\n\
|
- 🧪 **Relevant tests added:** no\n\
|
||||||
- ✨ **Focused PR:** Yes\n\
|
- ✨ **Focused PR:** Yes\n\
|
||||||
- **General PR suggestions:** general suggestion...\n\n\n- **<details><summary> 🤖 Code feedback:**</summary>\n\n - **Code example:**\n - **Before:**\n ```\n Code before\n ```\n - **After:**\n ```\n Code after\n ```\n\n - **Code example:**\n - **Before:**\n ```\n Code before 2\n ```\n - **After:**\n ```\n Code after 2\n ```\n\n</details>\
|
- **General PR suggestions:** general suggestion...\n\n\n- <details><summary> 🤖 Code feedback:</summary>\n\n - **Code example:**\n - **Before:**\n ```\n Code before\n ```\n - **After:**\n ```\n Code after\n ```\n\n - **Code example:**\n - **Before:**\n ```\n Code before 2\n ```\n - **After:**\n ```\n Code after 2\n ```\n\n</details>\
|
||||||
"""
|
"""
|
||||||
assert convert_to_markdown(input_data).strip() == expected_output.strip()
|
assert convert_to_markdown(input_data).strip() == expected_output.strip()
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class TestParseCodeSuggestion:
|
|||||||
'before': 'Before 1',
|
'before': 'Before 1',
|
||||||
'after': 'After 1'
|
'after': 'After 1'
|
||||||
}
|
}
|
||||||
expected_output = " **suggestion:** Suggestion 1\n **description:** Description 1\n **before:** Before 1\n **after:** After 1\n\n" # noqa: E501
|
expected_output = ' **suggestion:** Suggestion 1 \n **description:** Description 1 \n **before:** Before 1 \n **after:** After 1 \n\n' # noqa: E501
|
||||||
assert parse_code_suggestion(code_suggestions) == expected_output
|
assert parse_code_suggestion(code_suggestions) == expected_output
|
||||||
|
|
||||||
# Tests that function returns correct output when input dictionary has 'code example' key
|
# Tests that function returns correct output when input dictionary has 'code example' key
|
||||||
@ -74,5 +74,5 @@ class TestParseCodeSuggestion:
|
|||||||
'after': 'After 2'
|
'after': 'After 2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expected_output = " **suggestion:** Suggestion 2\n **description:** Description 2\n - **code example:**\n - **before:**\n ```\n Before 2\n ```\n - **after:**\n ```\n After 2\n ```\n\n" # noqa: E501
|
expected_output = ' **suggestion:** Suggestion 2 \n **description:** Description 2 \n - **code example:**\n - **before:**\n ```\n Before 2\n ```\n - **after:**\n ```\n After 2\n ```\n\n' # noqa: E501
|
||||||
assert parse_code_suggestion(code_suggestions) == expected_output
|
assert parse_code_suggestion(code_suggestions) == expected_output
|
||||||
|
Reference in New Issue
Block a user