Refactor logging statements for better readability and debugging

This commit is contained in:
mrT23
2024-02-24 16:47:23 +02:00
parent df3a463668
commit 877796b539
15 changed files with 156 additions and 158 deletions

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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")

View File

@ -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 #

View File

@ -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)

View File

@ -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>"

View File

@ -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"

View File

@ -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

View File

@ -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}")

View File

@ -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):