mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-02 11:50:37 +08:00
Refactor logging statements for better readability and debugging
This commit is contained in:
@ -113,6 +113,8 @@ class LiteLLMAIHandler(BaseAiHandler):
|
|||||||
}
|
}
|
||||||
if self.aws_bedrock_client:
|
if self.aws_bedrock_client:
|
||||||
kwargs["aws_bedrock_client"] = self.aws_bedrock_client
|
kwargs["aws_bedrock_client"] = self.aws_bedrock_client
|
||||||
|
|
||||||
|
get_logger().debug(f"Prompts", system=system, user=user)
|
||||||
response = await acompletion(**kwargs)
|
response = await acompletion(**kwargs)
|
||||||
except (APIError, Timeout, TryAgain) as e:
|
except (APIError, Timeout, TryAgain) as e:
|
||||||
get_logger().error("Error during OpenAI inference: ", e)
|
get_logger().error("Error during OpenAI inference: ", e)
|
||||||
@ -127,7 +129,7 @@ class LiteLLMAIHandler(BaseAiHandler):
|
|||||||
raise TryAgain
|
raise TryAgain
|
||||||
resp = response["choices"][0]['message']['content']
|
resp = response["choices"][0]['message']['content']
|
||||||
finish_reason = response["choices"][0]["finish_reason"]
|
finish_reason = response["choices"][0]["finish_reason"]
|
||||||
usage = response.get("usage")
|
# usage = response.get("usage")
|
||||||
get_logger().info("AI response", response=resp, messages=messages, finish_reason=finish_reason,
|
|
||||||
model=model, usage=usage)
|
get_logger().debug(f"\nAI response:\n{resp}", full_response=response)
|
||||||
return resp, finish_reason
|
return resp, finish_reason
|
@ -50,15 +50,29 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s
|
|||||||
PATCH_EXTRA_LINES = get_settings().config.patch_extra_lines
|
PATCH_EXTRA_LINES = get_settings().config.patch_extra_lines
|
||||||
|
|
||||||
try:
|
try:
|
||||||
diff_files = git_provider.get_diff_files()
|
diff_files_original = git_provider.get_diff_files()
|
||||||
except RateLimitExceededException as e:
|
except RateLimitExceededException as e:
|
||||||
get_logger().error(f"Rate limit exceeded for git provider API. original message {e}")
|
get_logger().error(f"Rate limit exceeded for git provider API. original message {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
diff_files = filter_ignored(diff_files)
|
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
|
# get pr languages
|
||||||
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
||||||
|
if pr_languages:
|
||||||
|
try:
|
||||||
|
get_logger().info(f"PR main language: {pr_languages[0]['language']}")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
# generate a standard diff string, with patch extension
|
# generate a standard diff string, with patch extension
|
||||||
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
||||||
@ -66,9 +80,13 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s
|
|||||||
|
|
||||||
# if we are under the limit, return the full diff
|
# if we are under the limit, return the full diff
|
||||||
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < get_max_tokens(model):
|
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < get_max_tokens(model):
|
||||||
|
get_logger().info(f"Tokens: {total_tokens}, total tokens under limit: {get_max_tokens(model)}, "
|
||||||
|
f"returning full diff.")
|
||||||
return "\n".join(patches_extended)
|
return "\n".join(patches_extended)
|
||||||
|
|
||||||
# if we are over the limit, start pruning
|
# if we are over the limit, start pruning
|
||||||
|
get_logger().info(f"Tokens: {total_tokens}, total tokens over limit: {get_max_tokens(model)}, "
|
||||||
|
f"pruning diff.")
|
||||||
patches_compressed, modified_file_names, deleted_file_names, added_file_names = \
|
patches_compressed, modified_file_names, deleted_file_names, added_file_names = \
|
||||||
pr_generate_compressed_diff(pr_languages, token_handler, model, add_line_numbers_to_hunks)
|
pr_generate_compressed_diff(pr_languages, token_handler, model, add_line_numbers_to_hunks)
|
||||||
|
|
||||||
@ -82,6 +100,11 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s
|
|||||||
if deleted_file_names:
|
if deleted_file_names:
|
||||||
deleted_list_str = DELETED_FILES_ + "\n".join(deleted_file_names)
|
deleted_list_str = DELETED_FILES_ + "\n".join(deleted_file_names)
|
||||||
final_diff = final_diff + "\n\n" + deleted_list_str
|
final_diff = final_diff + "\n\n" + deleted_list_str
|
||||||
|
try:
|
||||||
|
get_logger().debug(f"After pruning, added_list_str: {added_list_str}, modified_list_str: {modified_list_str}, "
|
||||||
|
f"deleted_list_str: {deleted_list_str}")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
return final_diff
|
return final_diff
|
||||||
|
|
||||||
|
|
||||||
@ -225,11 +248,10 @@ async def retry_with_fallback_models(f: Callable, model_type: ModelType = ModelT
|
|||||||
# try each (model, deployment_id) pair until one is successful, otherwise raise exception
|
# try each (model, deployment_id) pair until one is successful, otherwise raise exception
|
||||||
for i, (model, deployment_id) in enumerate(zip(all_models, all_deployments)):
|
for i, (model, deployment_id) in enumerate(zip(all_models, all_deployments)):
|
||||||
try:
|
try:
|
||||||
if get_settings().config.verbosity_level >= 2:
|
get_logger().debug(
|
||||||
get_logger().debug(
|
f"Generating prediction with {model}"
|
||||||
f"Generating prediction with {model}"
|
f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
|
||||||
f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
|
)
|
||||||
)
|
|
||||||
get_settings().set("openai.deployment_id", deployment_id)
|
get_settings().set("openai.deployment_id", deployment_id)
|
||||||
return await f(model)
|
return await f(model)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -433,7 +433,7 @@ def get_user_labels(current_labels: List[str] = None):
|
|||||||
continue
|
continue
|
||||||
user_labels.append(label)
|
user_labels.append(label)
|
||||||
if user_labels:
|
if user_labels:
|
||||||
get_logger().info(f"Keeping user labels: {user_labels}")
|
get_logger().debug(f"Keeping user labels: {user_labels}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().exception(f"Failed to get user labels: {e}")
|
get_logger().exception(f"Failed to get user labels: {e}")
|
||||||
return current_labels
|
return current_labels
|
||||||
|
@ -6,7 +6,8 @@ from pr_agent.agent.pr_agent import PRAgent, commands
|
|||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
from pr_agent.log import setup_logger
|
from pr_agent.log import setup_logger
|
||||||
|
|
||||||
setup_logger()
|
log_level = os.environ.get("LOG_LEVEL", "INFO")
|
||||||
|
setup_logger(log_level)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,9 +57,12 @@ class GitProvider(ABC):
|
|||||||
return description
|
return description
|
||||||
|
|
||||||
def get_user_description(self) -> str:
|
def get_user_description(self) -> str:
|
||||||
|
if hasattr(self, 'user_description') and not (self.user_description is None):
|
||||||
|
return self.user_description
|
||||||
|
|
||||||
description = (self.get_pr_description_full() or "").strip()
|
description = (self.get_pr_description_full() or "").strip()
|
||||||
description_lowercase = description.lower()
|
description_lowercase = description.lower()
|
||||||
get_logger().info(f"Existing description:\n{description_lowercase}")
|
get_logger().debug(f"Existing description", description=description_lowercase)
|
||||||
|
|
||||||
# if the existing description wasn't generated by the pr-agent, just return it as-is
|
# if the existing description wasn't generated by the pr-agent, just return it as-is
|
||||||
if not self._is_generated_by_pr_agent(description_lowercase):
|
if not self._is_generated_by_pr_agent(description_lowercase):
|
||||||
@ -93,7 +96,9 @@ class GitProvider(ABC):
|
|||||||
if original_user_description.lower().startswith(user_description_header):
|
if original_user_description.lower().startswith(user_description_header):
|
||||||
original_user_description = original_user_description[len(user_description_header):].strip()
|
original_user_description = original_user_description[len(user_description_header):].strip()
|
||||||
|
|
||||||
get_logger().info(f"Extracted user description from existing description:\n{original_user_description}")
|
get_logger().info(f"Extracted user description from existing description",
|
||||||
|
description=original_user_description)
|
||||||
|
self.user_description = original_user_description
|
||||||
return original_user_description
|
return original_user_description
|
||||||
|
|
||||||
def _possible_headers(self):
|
def _possible_headers(self):
|
||||||
|
@ -38,7 +38,7 @@ def apply_repo_settings(pr_url):
|
|||||||
section_dict[key] = value
|
section_dict[key] = value
|
||||||
get_settings().unset(section)
|
get_settings().unset(section)
|
||||||
get_settings().set(section, section_dict, merge=False)
|
get_settings().set(section, section_dict, merge=False)
|
||||||
get_logger().info(f"Applying repo settings for section {section}, contents: {contents}")
|
get_logger().info(f"Applying repo settings:\n{new_settings.as_dict()}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().exception("Failed to apply repo settings", e)
|
get_logger().exception("Failed to apply repo settings", e)
|
||||||
finally:
|
finally:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ def setup_logger(level: str = "INFO", fmt: LoggingFormat = LoggingFormat.CONSOLE
|
|||||||
if type(level) is not int:
|
if type(level) is not int:
|
||||||
level = logging.INFO
|
level = logging.INFO
|
||||||
|
|
||||||
if fmt == LoggingFormat.JSON:
|
if fmt == LoggingFormat.JSON and os.getenv("LOG_SANE", "0").lower() == "0": # better debugging github_app
|
||||||
logger.remove(None)
|
logger.remove(None)
|
||||||
logger.add(
|
logger.add(
|
||||||
sys.stdout,
|
sys.stdout,
|
||||||
@ -29,7 +30,7 @@ def setup_logger(level: str = "INFO", fmt: LoggingFormat = LoggingFormat.CONSOLE
|
|||||||
colorize=False,
|
colorize=False,
|
||||||
serialize=True,
|
serialize=True,
|
||||||
)
|
)
|
||||||
elif fmt == LoggingFormat.CONSOLE:
|
elif fmt == LoggingFormat.CONSOLE: # does not print the 'extra' fields
|
||||||
logger.remove(None)
|
logger.remove(None)
|
||||||
logger.add(sys.stdout, level=level, colorize=True)
|
logger.add(sys.stdout, level=level, colorize=True)
|
||||||
|
|
||||||
|
@ -208,15 +208,15 @@ async def handle_request(body: Dict[str, Any], event: str):
|
|||||||
|
|
||||||
# handle comments on PRs
|
# handle comments on PRs
|
||||||
if action == 'created':
|
if action == 'created':
|
||||||
get_logger().debug(f'Request body:\n{body}')
|
get_logger().debug(f'Request body', body=body)
|
||||||
await handle_comments_on_pr(body, event, sender, action, log_context, agent)
|
await handle_comments_on_pr(body, event, sender, action, log_context, agent)
|
||||||
# handle new PRs
|
# handle new PRs
|
||||||
elif event == 'pull_request' and action != 'synchronize':
|
elif event == 'pull_request' and action != 'synchronize':
|
||||||
get_logger().debug(f'Request body:\n{body}')
|
get_logger().debug(f'Request body', body=body)
|
||||||
await handle_new_pr_opened(body, event, sender, action, log_context, agent)
|
await handle_new_pr_opened(body, event, sender, action, log_context, agent)
|
||||||
# handle pull_request event with synchronize action - "push trigger" for new commits
|
# handle pull_request event with synchronize action - "push trigger" for new commits
|
||||||
elif event == 'pull_request' and action == 'synchronize':
|
elif event == 'pull_request' and action == 'synchronize':
|
||||||
get_logger().debug(f'Request body:\n{body}')
|
get_logger().debug(f'Request body', body=body)
|
||||||
await handle_push_trigger_for_new_commits(body, event, sender, action, log_context, agent)
|
await handle_push_trigger_for_new_commits(body, event, sender, action, log_context, agent)
|
||||||
else:
|
else:
|
||||||
get_logger().info(f"event {event=} action {action=} does not require any handling")
|
get_logger().info(f"event {event=} action {action=} does not require any handling")
|
||||||
|
@ -115,6 +115,7 @@ excluded_checks_list=["lint"] # list of checks to exclude, for example: ["check1
|
|||||||
persistent_comment=true
|
persistent_comment=true
|
||||||
enable_help_text=true
|
enable_help_text=true
|
||||||
|
|
||||||
|
[pr_help] # /help #
|
||||||
|
|
||||||
[pr_config] # /config #
|
[pr_config] # /config #
|
||||||
|
|
||||||
|
@ -72,6 +72,9 @@ class PRCodeSuggestions:
|
|||||||
async def run(self):
|
async def run(self):
|
||||||
try:
|
try:
|
||||||
get_logger().info('Generating code suggestions for PR...')
|
get_logger().info('Generating code suggestions for PR...')
|
||||||
|
relevant_configs = {'pr_code_suggestions': dict(get_settings().pr_code_suggestions),
|
||||||
|
'config': dict(get_settings().config)}
|
||||||
|
get_logger().debug("Relevant configs", configs=relevant_configs)
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
if self.git_provider.is_supported("gfm_markdown"):
|
if self.git_provider.is_supported("gfm_markdown"):
|
||||||
@ -79,7 +82,6 @@ class PRCodeSuggestions:
|
|||||||
else:
|
else:
|
||||||
self.git_provider.publish_comment("Preparing suggestions...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing suggestions...", is_temporary=True)
|
||||||
|
|
||||||
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, ModelType.TURBO)
|
await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO)
|
||||||
data = self._prepare_pr_code_suggestions()
|
data = self._prepare_pr_code_suggestions()
|
||||||
@ -97,13 +99,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 code suggestions...')
|
|
||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
if get_settings().pr_code_suggestions.summarize and self.git_provider.is_supported("gfm_markdown"):
|
if get_settings().pr_code_suggestions.summarize and self.git_provider.is_supported("gfm_markdown"):
|
||||||
get_logger().info('Pushing summarize code suggestions...')
|
|
||||||
|
|
||||||
# generate summarized suggestions
|
# generate summarized suggestions
|
||||||
pr_body = self.generate_summarized_suggestions(data)
|
pr_body = self.generate_summarized_suggestions(data)
|
||||||
|
get_logger().debug(f"PR output", suggestions=pr_body)
|
||||||
|
|
||||||
# add usage guide
|
# add usage guide
|
||||||
if get_settings().pr_code_suggestions.enable_help_text:
|
if get_settings().pr_code_suggestions.enable_help_text:
|
||||||
@ -117,7 +118,6 @@ class PRCodeSuggestions:
|
|||||||
self.git_provider.publish_comment(pr_body)
|
self.git_provider.publish_comment(pr_body)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
get_logger().info('Pushing inline code suggestions...')
|
|
||||||
self.push_inline_code_suggestions(data)
|
self.push_inline_code_suggestions(data)
|
||||||
if self.progress_response:
|
if self.progress_response:
|
||||||
self.progress_response.delete()
|
self.progress_response.delete()
|
||||||
@ -127,15 +127,17 @@ class PRCodeSuggestions:
|
|||||||
self.progress_response.delete()
|
self.progress_response.delete()
|
||||||
|
|
||||||
async def _prepare_prediction(self, model: str):
|
async def _prepare_prediction(self, model: str):
|
||||||
get_logger().info('Getting PR diff...')
|
self.patches_diff = get_pr_diff(self.git_provider,
|
||||||
patches_diff = get_pr_diff(self.git_provider,
|
|
||||||
self.token_handler,
|
self.token_handler,
|
||||||
model,
|
model,
|
||||||
add_line_numbers_to_hunks=True,
|
add_line_numbers_to_hunks=True,
|
||||||
disable_extra_lines=True)
|
disable_extra_lines=True)
|
||||||
|
if self.patches_diff:
|
||||||
get_logger().info('Getting AI prediction...')
|
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
||||||
self.prediction = await self._get_prediction(model, patches_diff)
|
self.prediction = await self._get_prediction(model, self.patches_diff)
|
||||||
|
else:
|
||||||
|
get_logger().error(f"Error getting PR diff")
|
||||||
|
self.prediction = None
|
||||||
|
|
||||||
async def _get_prediction(self, model: str, patches_diff: str):
|
async def _get_prediction(self, model: str, patches_diff: str):
|
||||||
variables = copy.deepcopy(self.vars)
|
variables = copy.deepcopy(self.vars)
|
||||||
@ -143,15 +145,10 @@ class PRCodeSuggestions:
|
|||||||
environment = Environment(undefined=StrictUndefined)
|
environment = Environment(undefined=StrictUndefined)
|
||||||
system_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.system).render(variables)
|
system_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.system).render(variables)
|
||||||
user_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.user).render(variables)
|
user_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.user).render(variables)
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
|
||||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
|
||||||
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
||||||
system=system_prompt, user=user_prompt)
|
system=system_prompt, user=user_prompt)
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nAI response:\n{response}")
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _prepare_pr_code_suggestions(self) -> Dict:
|
def _prepare_pr_code_suggestions(self) -> Dict:
|
||||||
@ -185,8 +182,6 @@ class PRCodeSuggestions:
|
|||||||
|
|
||||||
for d in data['code_suggestions']:
|
for d in data['code_suggestions']:
|
||||||
try:
|
try:
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"suggestion: {d}")
|
|
||||||
relevant_file = d['relevant_file'].strip()
|
relevant_file = d['relevant_file'].strip()
|
||||||
relevant_lines_start = int(d['relevant_lines_start']) # absolute position
|
relevant_lines_start = int(d['relevant_lines_start']) # absolute position
|
||||||
relevant_lines_end = int(d['relevant_lines_end'])
|
relevant_lines_end = int(d['relevant_lines_end'])
|
||||||
@ -202,8 +197,7 @@ class PRCodeSuggestions:
|
|||||||
'relevant_lines_start': relevant_lines_start,
|
'relevant_lines_start': relevant_lines_start,
|
||||||
'relevant_lines_end': relevant_lines_end})
|
'relevant_lines_end': relevant_lines_end})
|
||||||
except Exception:
|
except Exception:
|
||||||
if get_settings().config.verbosity_level >= 2:
|
get_logger().info(f"Could not parse suggestion: {d}")
|
||||||
get_logger().info(f"Could not parse suggestion: {d}")
|
|
||||||
|
|
||||||
is_successful = self.git_provider.publish_code_suggestions(code_suggestions)
|
is_successful = self.git_provider.publish_code_suggestions(code_suggestions)
|
||||||
if not is_successful:
|
if not is_successful:
|
||||||
@ -229,8 +223,7 @@ class PRCodeSuggestions:
|
|||||||
if delta_spaces > 0:
|
if delta_spaces > 0:
|
||||||
new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n')
|
new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if get_settings().config.verbosity_level >= 2:
|
get_logger().error(f"Could not dedent code snippet for file {relevant_file}, error: {e}")
|
||||||
get_logger().info(f"Could not dedent code snippet for file {relevant_file}, error: {e}")
|
|
||||||
|
|
||||||
return new_code_snippet
|
return new_code_snippet
|
||||||
|
|
||||||
@ -245,32 +238,33 @@ class PRCodeSuggestions:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def _prepare_prediction_extended(self, model: str) -> dict:
|
async def _prepare_prediction_extended(self, model: str) -> dict:
|
||||||
get_logger().info('Getting PR diff...')
|
self.patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model,
|
||||||
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)
|
max_calls=get_settings().pr_code_suggestions.max_number_of_calls)
|
||||||
|
if self.patches_diff_list:
|
||||||
|
get_logger().debug(f"PR diff", diff=self.patches_diff_list)
|
||||||
|
|
||||||
# parallelize calls to AI:
|
# parallelize calls to AI:
|
||||||
if get_settings().pr_code_suggestions.parallel_calls:
|
if get_settings().pr_code_suggestions.parallel_calls:
|
||||||
get_logger().info('Getting multi AI predictions in parallel...')
|
prediction_list = await asyncio.gather(*[self._get_prediction(model, patches_diff) for patches_diff in self.patches_diff_list])
|
||||||
prediction_list = await asyncio.gather(*[self._get_prediction(model, patches_diff) for patches_diff in patches_diff_list])
|
self.prediction_list = prediction_list
|
||||||
self.prediction_list = prediction_list
|
|
||||||
else:
|
|
||||||
get_logger().info('Getting multi AI predictions...')
|
|
||||||
prediction_list = []
|
|
||||||
for i, patches_diff in enumerate(patches_diff_list):
|
|
||||||
get_logger().info(f"Processing chunk {i + 1} of {len(patches_diff_list)}")
|
|
||||||
prediction = await self._get_prediction(model, patches_diff)
|
|
||||||
prediction_list.append(prediction)
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
for prediction in prediction_list:
|
|
||||||
self.prediction = prediction
|
|
||||||
data_per_chunk = self._prepare_pr_code_suggestions()
|
|
||||||
if "code_suggestions" in data:
|
|
||||||
data["code_suggestions"].extend(data_per_chunk["code_suggestions"])
|
|
||||||
else:
|
else:
|
||||||
data.update(data_per_chunk)
|
prediction_list = []
|
||||||
self.data = data
|
for i, patches_diff in enumerate(self.patches_diff_list):
|
||||||
|
prediction = await self._get_prediction(model, patches_diff)
|
||||||
|
prediction_list.append(prediction)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for prediction in prediction_list:
|
||||||
|
self.prediction = prediction
|
||||||
|
data_per_chunk = self._prepare_pr_code_suggestions()
|
||||||
|
if "code_suggestions" in data:
|
||||||
|
data["code_suggestions"].extend(data_per_chunk["code_suggestions"])
|
||||||
|
else:
|
||||||
|
data.update(data_per_chunk)
|
||||||
|
self.data = data
|
||||||
|
else:
|
||||||
|
get_logger().error(f"Error getting PR diff")
|
||||||
|
self.data = data = None
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def rank_suggestions(self, data: List) -> List:
|
async def rank_suggestions(self, data: List) -> List:
|
||||||
@ -305,9 +299,7 @@ class PRCodeSuggestions:
|
|||||||
system_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.system).render(
|
system_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.system).render(
|
||||||
variables)
|
variables)
|
||||||
user_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.user).render(variables)
|
user_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.user).render(variables)
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
|
||||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
|
||||||
response, finish_reason = await self.ai_handler.chat_completion(model=model, system=system_prompt,
|
response, finish_reason = await self.ai_handler.chat_completion(model=model, system=system_prompt,
|
||||||
user=user_prompt)
|
user=user_prompt)
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class PRDescription:
|
|||||||
|
|
||||||
if get_settings().pr_description.enable_semantic_files_types and not self.git_provider.is_supported(
|
if get_settings().pr_description.enable_semantic_files_types and not self.git_provider.is_supported(
|
||||||
"gfm_markdown"):
|
"gfm_markdown"):
|
||||||
get_logger().debug(f"Disabling semantic files types for {self.pr_id}")
|
get_logger().debug(f"Disabling semantic files types for {self.pr_id}, gfm_markdown not supported.")
|
||||||
get_settings().pr_description.enable_semantic_files_types = False
|
get_settings().pr_description.enable_semantic_files_types = False
|
||||||
|
|
||||||
# Initialize the AI handler
|
# Initialize the AI handler
|
||||||
@ -56,10 +56,8 @@ class PRDescription:
|
|||||||
"custom_labels_class": "", # will be filled if necessary in 'set_custom_labels' function
|
"custom_labels_class": "", # will be filled if necessary in 'set_custom_labels' function
|
||||||
"enable_semantic_files_types": get_settings().pr_description.enable_semantic_files_types,
|
"enable_semantic_files_types": get_settings().pr_description.enable_semantic_files_types,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.user_description = self.git_provider.get_user_description()
|
self.user_description = self.git_provider.get_user_description()
|
||||||
|
|
||||||
|
|
||||||
# Initialize the token handler
|
# Initialize the token handler
|
||||||
self.token_handler = TokenHandler(
|
self.token_handler = TokenHandler(
|
||||||
self.git_provider.pr,
|
self.git_provider.pr,
|
||||||
@ -68,33 +66,32 @@ class PRDescription:
|
|||||||
get_settings().pr_description_prompt.user,
|
get_settings().pr_description_prompt.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Initialize patches_diff and prediction attributes
|
# Initialize patches_diff and prediction attributes
|
||||||
self.patches_diff = None
|
self.patches_diff = None
|
||||||
self.prediction = None
|
self.prediction = None
|
||||||
|
self.file_label_dict = None
|
||||||
self.COLLAPSIBLE_FILE_LIST_THRESHOLD = 8
|
self.COLLAPSIBLE_FILE_LIST_THRESHOLD = 8
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""
|
|
||||||
Generates a PR description using an AI model and publishes it to the PR.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
get_logger().info(f"Generating a PR description {self.pr_id}")
|
get_logger().info(f"Generating a PR description for pr_id: {self.pr_id}")
|
||||||
|
relevant_configs = {'pr_description': dict(get_settings().pr_description),
|
||||||
|
'config': dict(get_settings().config)}
|
||||||
|
get_logger().debug("Relevant configs", configs=relevant_configs)
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.publish_comment("Preparing PR description...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing PR description...", is_temporary=True)
|
||||||
|
|
||||||
await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO) # turbo model because larger context
|
await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO) # turbo model because larger context
|
||||||
|
|
||||||
get_logger().info(f"Preparing answer {self.pr_id}")
|
|
||||||
if self.prediction:
|
if self.prediction:
|
||||||
self._prepare_data()
|
self._prepare_data()
|
||||||
else:
|
else:
|
||||||
|
get_logger().error(f"Error getting AI prediction {self.pr_id}")
|
||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if get_settings().pr_description.enable_semantic_files_types:
|
if get_settings().pr_description.enable_semantic_files_types:
|
||||||
self._prepare_file_labels()
|
self.file_label_dict = self._prepare_file_labels()
|
||||||
|
|
||||||
pr_labels = []
|
pr_labels = []
|
||||||
if get_settings().pr_description.publish_labels:
|
if get_settings().pr_description.publish_labels:
|
||||||
@ -104,6 +101,7 @@ class PRDescription:
|
|||||||
pr_title, pr_body = self._prepare_pr_answer_with_markers()
|
pr_title, pr_body = self._prepare_pr_answer_with_markers()
|
||||||
else:
|
else:
|
||||||
pr_title, pr_body, = self._prepare_pr_answer()
|
pr_title, pr_body, = self._prepare_pr_answer()
|
||||||
|
get_logger().debug(f"PR output", title=pr_title, body=pr_body)
|
||||||
|
|
||||||
# Add help text if gfm_markdown is supported
|
# Add help text if gfm_markdown is supported
|
||||||
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_description.enable_help_text:
|
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_description.enable_help_text:
|
||||||
@ -111,26 +109,21 @@ class PRDescription:
|
|||||||
pr_body += HelpMessage.get_describe_usage_guide()
|
pr_body += HelpMessage.get_describe_usage_guide()
|
||||||
pr_body += "\n</details>\n"
|
pr_body += "\n</details>\n"
|
||||||
elif get_settings().pr_description.enable_help_comment:
|
elif get_settings().pr_description.enable_help_comment:
|
||||||
pr_body +="\n\n___\n\n> ✨ **PR-Agent usage**:"
|
pr_body += "\n\n___\n\n> ✨ **PR-Agent usage**:"
|
||||||
pr_body +="\n>Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions\n\n"
|
pr_body += "\n>Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions\n\n"
|
||||||
|
|
||||||
|
|
||||||
# final markdown description
|
|
||||||
full_markdown_description = f"## Title\n\n{pr_title}\n\n___\n{pr_body}"
|
|
||||||
# get_logger().debug(f"full_markdown_description:\n{full_markdown_description}")
|
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
get_logger().info(f"Pushing answer {self.pr_id}")
|
|
||||||
|
|
||||||
# publish labels
|
# publish labels
|
||||||
if get_settings().pr_description.publish_labels and 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_pr_labels()
|
original_labels = self.git_provider.get_pr_labels()
|
||||||
user_labels = get_user_labels(current_labels)
|
get_logger().debug(f"original labels", labels=original_labels)
|
||||||
|
user_labels = get_user_labels(original_labels)
|
||||||
|
get_logger().debug(f"published labels:\n{pr_labels + user_labels}")
|
||||||
self.git_provider.publish_labels(pr_labels + user_labels)
|
self.git_provider.publish_labels(pr_labels + user_labels)
|
||||||
|
|
||||||
# publish description
|
# publish description
|
||||||
if get_settings().pr_description.publish_description_as_comment:
|
if get_settings().pr_description.publish_description_as_comment:
|
||||||
get_logger().info(f"Publishing answer as comment")
|
full_markdown_description = f"## Title\n\n{pr_title}\n\n___\n{pr_body}"
|
||||||
self.git_provider.publish_comment(full_markdown_description)
|
self.git_provider.publish_comment(full_markdown_description)
|
||||||
else:
|
else:
|
||||||
self.git_provider.publish_description(pr_title, pr_body)
|
self.git_provider.publish_description(pr_title, pr_body)
|
||||||
@ -152,10 +145,9 @@ class PRDescription:
|
|||||||
if get_settings().pr_description.use_description_markers and 'pr_agent:' not in self.user_description:
|
if get_settings().pr_description.use_description_markers and 'pr_agent:' not in self.user_description:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
get_logger().info(f"Getting PR diff {self.pr_id}")
|
|
||||||
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
||||||
if self.patches_diff:
|
if self.patches_diff:
|
||||||
get_logger().info(f"Getting AI prediction {self.pr_id}")
|
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
||||||
self.prediction = await self._get_prediction(model)
|
self.prediction = await self._get_prediction(model)
|
||||||
else:
|
else:
|
||||||
get_logger().error(f"Error getting PR diff {self.pr_id}")
|
get_logger().error(f"Error getting PR diff {self.pr_id}")
|
||||||
@ -180,10 +172,6 @@ class PRDescription:
|
|||||||
system_prompt = environment.from_string(get_settings().pr_description_prompt.system).render(variables)
|
system_prompt = environment.from_string(get_settings().pr_description_prompt.system).render(variables)
|
||||||
user_prompt = environment.from_string(get_settings().pr_description_prompt.user).render(variables)
|
user_prompt = environment.from_string(get_settings().pr_description_prompt.user).render(variables)
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
|
||||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
|
||||||
|
|
||||||
response, finish_reason = await self.ai_handler.chat_completion(
|
response, finish_reason = await self.ai_handler.chat_completion(
|
||||||
model=model,
|
model=model,
|
||||||
temperature=0.2,
|
temperature=0.2,
|
||||||
@ -191,9 +179,6 @@ class PRDescription:
|
|||||||
user=user_prompt
|
user=user_prompt
|
||||||
)
|
)
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nAI response:\n{response}")
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _prepare_data(self):
|
def _prepare_data(self):
|
||||||
@ -335,25 +320,23 @@ class PRDescription:
|
|||||||
if idx < len(self.data) - 1:
|
if idx < len(self.data) - 1:
|
||||||
pr_body += "\n\n___\n\n"
|
pr_body += "\n\n___\n\n"
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"title:\n{title}\n{pr_body}")
|
|
||||||
|
|
||||||
return title, pr_body
|
return title, pr_body
|
||||||
|
|
||||||
def _prepare_file_labels(self):
|
def _prepare_file_labels(self):
|
||||||
self.file_label_dict = {}
|
file_label_dict = {}
|
||||||
for file in self.data['pr_files']:
|
for file in self.data['pr_files']:
|
||||||
try:
|
try:
|
||||||
filename = file['filename'].replace("'", "`").replace('"', '`')
|
filename = file['filename'].replace("'", "`").replace('"', '`')
|
||||||
changes_summary = file['changes_summary']
|
changes_summary = file['changes_summary']
|
||||||
changes_title = file['changes_title'].strip()
|
changes_title = file['changes_title'].strip()
|
||||||
label = file.get('label')
|
label = file.get('label')
|
||||||
if label not in self.file_label_dict:
|
if label not in file_label_dict:
|
||||||
self.file_label_dict[label] = []
|
file_label_dict[label] = []
|
||||||
self.file_label_dict[label].append((filename, changes_title, changes_summary))
|
file_label_dict[label].append((filename, changes_title, changes_summary))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().error(f"Error preparing file label dict {self.pr_id}: {e}")
|
get_logger().error(f"Error preparing file label dict {self.pr_id}: {e}")
|
||||||
pass
|
pass
|
||||||
|
return file_label_dict
|
||||||
|
|
||||||
def process_pr_files_prediction(self, pr_body, value):
|
def process_pr_files_prediction(self, pr_body, value):
|
||||||
# logic for using collapsible file list
|
# logic for using collapsible file list
|
||||||
@ -366,7 +349,6 @@ class PRDescription:
|
|||||||
use_collapsible_file_list = num_files > self.COLLAPSIBLE_FILE_LIST_THRESHOLD
|
use_collapsible_file_list = num_files > self.COLLAPSIBLE_FILE_LIST_THRESHOLD
|
||||||
|
|
||||||
if not self.git_provider.is_supported("gfm_markdown"):
|
if not self.git_provider.is_supported("gfm_markdown"):
|
||||||
get_logger().info(f"Disabling semantic files types for {self.pr_id} since gfm_markdown is not supported")
|
|
||||||
return pr_body
|
return pr_body
|
||||||
try:
|
try:
|
||||||
pr_body += "<table>"
|
pr_body += "<table>"
|
||||||
|
@ -10,6 +10,10 @@ class PRHelpMessage:
|
|||||||
async def run(self):
|
async def run(self):
|
||||||
try:
|
try:
|
||||||
get_logger().info('Getting PR Help Message...')
|
get_logger().info('Getting PR Help Message...')
|
||||||
|
relevant_configs = {'pr_help': dict(get_settings().pr_help),
|
||||||
|
'config': dict(get_settings().config)}
|
||||||
|
get_logger().debug("Relevant configs", configs=relevant_configs)
|
||||||
|
|
||||||
pr_comment = "## PR Agent Walkthrough\n\n"
|
pr_comment = "## PR Agent Walkthrough\n\n"
|
||||||
pr_comment += "🤖 Welcome to the PR Agent, an AI-powered tool for automated pull request analysis, feedback, suggestions and more."""
|
pr_comment += "🤖 Welcome to the PR Agent, an AI-powered tool for automated pull request analysis, feedback, suggestions and more."""
|
||||||
pr_comment += "\n\nHere is a list of tools you can use to interact with the PR Agent:\n"
|
pr_comment += "\n\nHere is a list of tools you can use to interact with the PR Agent:\n"
|
||||||
|
@ -48,27 +48,35 @@ class PRQuestions:
|
|||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
get_logger().info('Answering a PR question...')
|
get_logger().info('Answering a PR question...')
|
||||||
|
relevant_configs = {'pr_questions': dict(get_settings().pr_questions),
|
||||||
|
'config': dict(get_settings().config)}
|
||||||
|
get_logger().debug("Relevant configs", configs=relevant_configs)
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.publish_comment("Preparing answer...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing answer...", is_temporary=True)
|
||||||
await retry_with_fallback_models(self._prepare_prediction)
|
await retry_with_fallback_models(self._prepare_prediction)
|
||||||
get_logger().info('Preparing answer...')
|
|
||||||
pr_comment = self._prepare_pr_answer()
|
pr_comment = self._prepare_pr_answer()
|
||||||
|
get_logger().debug(f"PR output", answer=pr_comment)
|
||||||
|
|
||||||
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_questions.enable_help_text:
|
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_questions.enable_help_text:
|
||||||
pr_comment += "<hr>\n\n<details> <summary><strong>✨ Ask tool usage guide:</strong></summary><hr> \n\n"
|
pr_comment += "<hr>\n\n<details> <summary><strong>✨ Ask tool usage guide:</strong></summary><hr> \n\n"
|
||||||
pr_comment += HelpMessage.get_ask_usage_guide()
|
pr_comment += HelpMessage.get_ask_usage_guide()
|
||||||
pr_comment += "\n</details>\n"
|
pr_comment += "\n</details>\n"
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
get_logger().info('Pushing answer...')
|
|
||||||
self.git_provider.publish_comment(pr_comment)
|
self.git_provider.publish_comment(pr_comment)
|
||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
async def _prepare_prediction(self, model: str):
|
async def _prepare_prediction(self, model: str):
|
||||||
get_logger().info('Getting PR diff...')
|
|
||||||
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
||||||
get_logger().info('Getting AI prediction...')
|
if self.patches_diff:
|
||||||
self.prediction = await self._get_prediction(model)
|
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
||||||
|
self.prediction = await self._get_prediction(model)
|
||||||
|
else:
|
||||||
|
get_logger().error(f"Error getting PR diff")
|
||||||
|
self.prediction = ""
|
||||||
|
|
||||||
async def _get_prediction(self, model: str):
|
async def _get_prediction(self, model: str):
|
||||||
variables = copy.deepcopy(self.vars)
|
variables = copy.deepcopy(self.vars)
|
||||||
@ -76,9 +84,6 @@ class PRQuestions:
|
|||||||
environment = Environment(undefined=StrictUndefined)
|
environment = Environment(undefined=StrictUndefined)
|
||||||
system_prompt = environment.from_string(get_settings().pr_questions_prompt.system).render(variables)
|
system_prompt = environment.from_string(get_settings().pr_questions_prompt.system).render(variables)
|
||||||
user_prompt = environment.from_string(get_settings().pr_questions_prompt.user).render(variables)
|
user_prompt = environment.from_string(get_settings().pr_questions_prompt.user).render(variables)
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
|
||||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
|
||||||
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
||||||
system=system_prompt, user=user_prompt)
|
system=system_prompt, user=user_prompt)
|
||||||
return response
|
return response
|
||||||
@ -86,6 +91,4 @@ class PRQuestions:
|
|||||||
def _prepare_pr_answer(self) -> str:
|
def _prepare_pr_answer(self) -> str:
|
||||||
answer_str = f"Question: {self.question_str}\n\n"
|
answer_str = f"Question: {self.question_str}\n\n"
|
||||||
answer_str += f"Answer:\n{self.prediction.strip()}\n\n"
|
answer_str += f"Answer:\n{self.prediction.strip()}\n\n"
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"answer_str:\n{answer_str}")
|
|
||||||
return answer_str
|
return answer_str
|
||||||
|
@ -3,17 +3,12 @@ import datetime
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
import yaml
|
|
||||||
from jinja2 import Environment, StrictUndefined
|
from jinja2 import Environment, StrictUndefined
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
|
||||||
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
|
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.token_handler import TokenHandler
|
||||||
from pr_agent.algo.utils import convert_to_markdown, load_yaml, try_fix_yaml, set_custom_labels, get_user_labels, \
|
from pr_agent.algo.utils import convert_to_markdown, load_yaml, ModelType
|
||||||
ModelType
|
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
from pr_agent.git_providers import get_git_provider
|
from pr_agent.git_providers import get_git_provider
|
||||||
from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language
|
from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language
|
||||||
@ -109,6 +104,9 @@ class PRReviewer:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
get_logger().info(f'Reviewing PR: {self.pr_url} ...')
|
get_logger().info(f'Reviewing PR: {self.pr_url} ...')
|
||||||
|
relevant_configs = {'pr_reviewer': dict(get_settings().pr_reviewer),
|
||||||
|
'config': dict(get_settings().config)}
|
||||||
|
get_logger().debug("Relevant configs", configs=relevant_configs)
|
||||||
|
|
||||||
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 review...", is_temporary=True)
|
||||||
@ -118,35 +116,32 @@ class PRReviewer:
|
|||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
get_logger().info('Preparing PR review...')
|
pr_review = self._prepare_pr_review()
|
||||||
pr_comment = self._prepare_pr_review()
|
get_logger().debug(f"PR output", review=pr_review)
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
get_logger().info('Pushing PR review...')
|
|
||||||
previous_review_comment = self._get_previous_review_comment()
|
previous_review_comment = self._get_previous_review_comment()
|
||||||
|
|
||||||
# publish the review
|
# publish the review
|
||||||
if get_settings().pr_reviewer.persistent_comment and not self.incremental.is_incremental:
|
if get_settings().pr_reviewer.persistent_comment and not self.incremental.is_incremental:
|
||||||
self.git_provider.publish_persistent_comment(pr_comment,
|
self.git_provider.publish_persistent_comment(pr_review,
|
||||||
initial_header="## PR Review",
|
initial_header="## PR Review",
|
||||||
update_header=True)
|
update_header=True)
|
||||||
else:
|
else:
|
||||||
self.git_provider.publish_comment(pr_comment)
|
self.git_provider.publish_comment(pr_review)
|
||||||
|
|
||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
if previous_review_comment:
|
if previous_review_comment:
|
||||||
self._remove_previous_review_comment(previous_review_comment)
|
self._remove_previous_review_comment(previous_review_comment)
|
||||||
if get_settings().pr_reviewer.inline_code_comments:
|
if get_settings().pr_reviewer.inline_code_comments:
|
||||||
get_logger().info('Pushing inline code comments...')
|
|
||||||
self._publish_inline_code_comments()
|
self._publish_inline_code_comments()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().error(f"Failed to review PR: {e}")
|
get_logger().error(f"Failed to review PR: {e}")
|
||||||
|
|
||||||
async def _prepare_prediction(self, model: str) -> None:
|
async def _prepare_prediction(self, model: str) -> None:
|
||||||
get_logger().info('Getting PR diff...')
|
|
||||||
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
||||||
if self.patches_diff:
|
if self.patches_diff:
|
||||||
get_logger().info('Getting AI prediction...')
|
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
||||||
self.prediction = await self._get_prediction(model)
|
self.prediction = await self._get_prediction(model)
|
||||||
else:
|
else:
|
||||||
get_logger().error(f"Error getting PR diff")
|
get_logger().error(f"Error getting PR diff")
|
||||||
@ -169,10 +164,6 @@ class PRReviewer:
|
|||||||
system_prompt = environment.from_string(get_settings().pr_review_prompt.system).render(variables)
|
system_prompt = environment.from_string(get_settings().pr_review_prompt.system).render(variables)
|
||||||
user_prompt = environment.from_string(get_settings().pr_review_prompt.user).render(variables)
|
user_prompt = environment.from_string(get_settings().pr_review_prompt.user).render(variables)
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
|
||||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
|
||||||
|
|
||||||
response, finish_reason = await self.ai_handler.chat_completion(
|
response, finish_reason = await self.ai_handler.chat_completion(
|
||||||
model=model,
|
model=model,
|
||||||
temperature=0.2,
|
temperature=0.2,
|
||||||
@ -180,9 +171,7 @@ class PRReviewer:
|
|||||||
user=user_prompt
|
user=user_prompt
|
||||||
)
|
)
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
get_logger().debug(f"\nAI response:\n{response}")
|
||||||
get_logger().info(f"\nAI response:\n{response}")
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _prepare_pr_review(self) -> str:
|
def _prepare_pr_review(self) -> str:
|
||||||
@ -245,10 +234,6 @@ class PRReviewer:
|
|||||||
# Add custom labels from the review prediction (effort, security)
|
# Add custom labels from the review prediction (effort, security)
|
||||||
self.set_review_labels(data)
|
self.set_review_labels(data)
|
||||||
|
|
||||||
# Log markdown response if verbosity level is high
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"Markdown response:\n{markdown_text}")
|
|
||||||
|
|
||||||
if markdown_text == None or len(markdown_text) == 0:
|
if markdown_text == None or len(markdown_text) == 0:
|
||||||
markdown_text = ""
|
markdown_text = ""
|
||||||
|
|
||||||
@ -385,7 +370,8 @@ class PRReviewer:
|
|||||||
else:
|
else:
|
||||||
current_labels_filtered = []
|
current_labels_filtered = []
|
||||||
if current_labels or review_labels:
|
if current_labels or review_labels:
|
||||||
get_logger().info(f"Setting review labels: {review_labels + current_labels_filtered}")
|
get_logger().debug(f"Current labels:\n{current_labels}")
|
||||||
|
get_logger().info(f"Setting review labels:\n{review_labels + current_labels_filtered}")
|
||||||
self.git_provider.publish_labels(review_labels + current_labels_filtered)
|
self.git_provider.publish_labels(review_labels + current_labels_filtered)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().error(f"Failed to set review labels, error: {e}")
|
get_logger().error(f"Failed to set review labels, error: {e}")
|
||||||
|
@ -3,9 +3,7 @@ from datetime import date
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from jinja2 import Environment, StrictUndefined
|
from jinja2 import Environment, StrictUndefined
|
||||||
|
|
||||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
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.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
|
||||||
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
|
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
|
||||||
@ -51,26 +49,33 @@ class PRUpdateChangelog:
|
|||||||
# assert type(self.git_provider) == GithubProvider, "Currently only Github is supported"
|
# assert type(self.git_provider) == GithubProvider, "Currently only Github is supported"
|
||||||
|
|
||||||
get_logger().info('Updating the changelog...')
|
get_logger().info('Updating the changelog...')
|
||||||
|
relevant_configs = {'pr_update_changelog': dict(get_settings().pr_update_changelog),
|
||||||
|
'config': dict(get_settings().config)}
|
||||||
|
get_logger().debug("Relevant configs", configs=relevant_configs)
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.publish_comment("Preparing changelog updates...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing changelog updates...", is_temporary=True)
|
||||||
|
|
||||||
await retry_with_fallback_models(self._prepare_prediction)
|
await retry_with_fallback_models(self._prepare_prediction)
|
||||||
get_logger().info('Preparing PR changelog updates...')
|
|
||||||
new_file_content, answer = self._prepare_changelog_update()
|
new_file_content, answer = self._prepare_changelog_update()
|
||||||
|
get_logger().debug(f"PR output", changlog=answer)
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.remove_initial_comment()
|
self.git_provider.remove_initial_comment()
|
||||||
get_logger().info('Publishing changelog updates...')
|
|
||||||
if self.commit_changelog:
|
if self.commit_changelog:
|
||||||
get_logger().info('Pushing PR changelog updates to repo...')
|
|
||||||
self._push_changelog_update(new_file_content, answer)
|
self._push_changelog_update(new_file_content, answer)
|
||||||
else:
|
else:
|
||||||
get_logger().info('Publishing PR changelog as comment...')
|
|
||||||
self.git_provider.publish_comment(f"**Changelog updates:**\n\n{answer}")
|
self.git_provider.publish_comment(f"**Changelog updates:**\n\n{answer}")
|
||||||
|
|
||||||
async def _prepare_prediction(self, model: str):
|
async def _prepare_prediction(self, model: str):
|
||||||
get_logger().info('Getting PR diff...')
|
|
||||||
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler, model)
|
||||||
get_logger().info('Getting AI prediction...')
|
if self.patches_diff:
|
||||||
self.prediction = await self._get_prediction(model)
|
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
||||||
|
self.prediction = await self._get_prediction(model)
|
||||||
|
else:
|
||||||
|
get_logger().error(f"Error getting PR diff")
|
||||||
|
self.prediction = ""
|
||||||
|
|
||||||
async def _get_prediction(self, model: str):
|
async def _get_prediction(self, model: str):
|
||||||
variables = copy.deepcopy(self.vars)
|
variables = copy.deepcopy(self.vars)
|
||||||
@ -78,9 +83,6 @@ class PRUpdateChangelog:
|
|||||||
environment = Environment(undefined=StrictUndefined)
|
environment = Environment(undefined=StrictUndefined)
|
||||||
system_prompt = environment.from_string(get_settings().pr_update_changelog_prompt.system).render(variables)
|
system_prompt = environment.from_string(get_settings().pr_update_changelog_prompt.system).render(variables)
|
||||||
user_prompt = environment.from_string(get_settings().pr_update_changelog_prompt.user).render(variables)
|
user_prompt = environment.from_string(get_settings().pr_update_changelog_prompt.user).render(variables)
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"\nSystem prompt:\n{system_prompt}")
|
|
||||||
get_logger().info(f"\nUser prompt:\n{user_prompt}")
|
|
||||||
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
||||||
system=system_prompt, user=user_prompt)
|
system=system_prompt, user=user_prompt)
|
||||||
|
|
||||||
@ -101,9 +103,6 @@ class PRUpdateChangelog:
|
|||||||
answer += "\n\n\n>to commit the new content to the CHANGELOG.md file, please type:" \
|
answer += "\n\n\n>to commit the new content to the CHANGELOG.md file, please type:" \
|
||||||
"\n>'/update_changelog --pr_update_changelog.push_changelog_changes=true'\n"
|
"\n>'/update_changelog --pr_update_changelog.push_changelog_changes=true'\n"
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
|
||||||
get_logger().info(f"answer:\n{answer}")
|
|
||||||
|
|
||||||
return new_file_content, answer
|
return new_file_content, answer
|
||||||
|
|
||||||
def _push_changelog_update(self, new_file_content, answer):
|
def _push_changelog_update(self, new_file_content, answer):
|
||||||
|
Reference in New Issue
Block a user