TP-68136 | code critic

This commit is contained in:
Aman Chaturvedi
2024-08-23 10:30:04 +05:30
parent f6cf9985ac
commit 80f6c2ce4b
2 changed files with 1 additions and 157 deletions

View File

@@ -88,7 +88,7 @@ jobs:
- name: Run code review script
shell: bash
run: python scripts/pr-review.py $GITHUB_WORKSPACE
run: python code-critic/generic_code_review/generic_code_review_client.py $GITHUB_WORKSPACE
env:
GPT_MODEL_NAME: ${{ secrets.GPT_MODEL_NAME }}
AZURE_API_KEY: ${{ secrets.AZURE_API_KEY }}

View File

@@ -1,156 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
import os
import re
import base64
import sys
import subprocess
import concurrent.futures
from typing import List, Tuple
from litellm import completion
from tenacity import retry, wait_exponential, stop_after_attempt
OUTPUT_FILE = 'code_review_output.txt'
DEFAULT_MAX_WORKERS = 3
# Function to check if the current directory is a git repository root
def is_git_repository_root(directory: str) -> bool:
return os.path.isdir(os.path.join(directory, '.git'))
def install_packages_from_file(filename: str):
try:
with open(filename, 'r') as f:
for line in f:
package_name = line.strip()
if package_name:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package_name, '--quiet'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except Exception as e:
print("")
def create_system_prompt() -> str:
encoded_prompt = os.getenv('ENCODED_CODE_REVIEW_SYSTEM_PROMPT', 'Um9sZTpHUFQgQ29kZSBSZXZpZXcgQWdlbnQ7IApPYmplY3RpdmU6UmV2aWV3IEpTLCBKU1gsIFRTLCBUU1ggY29kZTsKVGhpbmdzIHRvIHJldmlldzogRm9jdXMgb24gbWFqb3IgaXNzdWVzCjEuIE5vIG1hZ2ljIG51bWJlcnMgYW5kIG1hZ2ljIHN0cmluZ3MKMi4gaW1wcm92ZSBuYW1pbmcgb2YgdmFyaWFibGVzIGFuZCBmdW5jdGlvbnMKMy4gaW1wcm92ZSB0eXBlc2NyaXB0IHVzZQo0LiBubyBoYXJkY29kaW5nIG9mIHotaW5kZXggYW5kIGNvbG91cnMKNS4gYXZvaWQgZGF0ZS5ub3coKSB3ZSBoYXZlIHNlcnZlciB0aW1lIGF2YWlsYWJsZSB3aGljaCBpcyBtb3JlIGNvbnNpc3RlbnQKNi4gdXNlIG9wdGlvbmFsIGNoYWluaW5nIHdoZXJlIGV2ZXIgcG9zc2libGUuCjcuIHByZWZlciBmdW5jdGlvbmFsIHByb2dyYW1taW5nCjguIHJldXNlIGV4aXN0aW5nIGhlbHBlciBjb2RlCjkuIERvbuKAmXQgdXNlIGNhcnJldCBpbiBwYWNrYWdlLmpzb24KMTAuIEFwcHJvcHJpYXRlIHVzZSBvZiB1c2VNZW1vIGFuZCB1c2VDYWxsYmFjawoxMS4gRG9uJ3QgY3JlYXRlIGNvbXBvbmVudHMgaW5zaWRlIHJlbmRlciBmdW5jdGlvbgoxMi4gRG9uJ3QgY3JlYXRlIGZ1bmN0aW9ucyBpbnNpZGUgcmVuZGVyIGZ1bmN0aW9uCjEzLiBEb24ndCB1c2UgaW5saW5lIHN0eWxlcwoxNC4gRG9uJ3QgdXNlIGlubGluZSBldmVudCBoYW5kbGVycwoxNS4gcHJlZmVyIHRvIGV4dHJhY3QgZnVuY3Rpb24gb3V0c2lkZSBqc3ggaWYgbW9yZSB0aGFuIHR3byBjb25kaXRpb25zIGFyZSByZXF1aXJlZAoxNi4gRG9uJ3QgdXNlIGFueSB0eXBlCjE3LiBPbmx5IHVzZSBjb25zdCBhbmQgaWYgcmVxdWlyZWQgbGV0CjE4LiBFcnJvciBIYW5kbGluZwoxOS4gT3B0aW1pemF0aW9uOiBSZWNvbW1lbmQgYXZvaWRpbmcgcHJlbWF0dXJlIG9wdGltaXphdGlvbiBidXQgYWxzbyBzdWdnZXN0IGtlZXBpbmcgYW4gZXllIG91dCBmb3Igb2J2aW91cyBwZXJmb3JtYW5jZSBpc3N1ZXMsIHN1Y2ggYXMgdW5uZWNlc3NhcnkgY29tcHV0YXRpb25zIGluc2lkZSBsb29wcyBvciBleGNlc3NpdmUgRE9NIG1hbmlwdWxhdGlvbnMuCjIwLiBNZW1vcnkgTGVha3M6IEFkdmlzZSBjaGVja2luZyBmb3IgYW5kIGVsaW1pbmF0aW5nIHBvdGVudGlhbCBtZW1vcnkgbGVha3MsIGZvciBleGFtcGxlLCBieSBlbnN1cmluZyBldmVudCBsaXN0ZW5lcnMgYXJlIHByb3Blcmx5IHJlbW92ZWQgd2hlbiBubyBsb25nZXIgbmVlZGVkLgoKRm9ybWF0IG9mIG91dHB1dCB3aWxsIGJlOiAKRm9ybWF0dGVkIEdpdEh1YiBQUiBjb21tZW50IGluIGJlbG93IGdpdmVuIGZvcm1hdC4gRG9uJ3Qgd3JhcCB0aGUgb3V0cHV0IGluIHRyaXBsZSB0aWNrcyAoYGBgKS4KCiMjIyBwYXRoL3RvL2ZpbGUKKipJc3N1ZXMgRm91bmQqKgpgYGAKY29kZSBzbmlwcGV0CmBgYAoqKklzc3VlOioqIFNob3J0IElzc3VlIERlc2NyaXB0aW9uIChlZy4gbWFnaWMgc3RyaW5nIGxpdGVyYWwgdXNlZCkuCioqU3VnZ2VzdGVkIEZpeDoqKiBJc3N1ZSBGaXguIChlZy4gRGVmaW5lIGEgY29uc3RhbnQgZm9yIHRoZSBtYWdpYyBzdHJpbmcgbGl0ZXJhbCBhbmQgdXNlIGl0IGluc3RlYWQpLgoK')
decoded_bytes = base64.b64decode(encoded_prompt)
system_prompt = decoded_bytes.decode('utf-8')
return system_prompt
def create_gpt_prompt(file_path: str, code: str) -> str:
encoded_prompt = os.getenv('ENCODED_CODE_REVIEW_USER_PROMPT', 'UmV2aWV3IHRoZSBmb2xsb3dpbmcgSlMvSlNYL1RTL1RTWCBjb2RlIGZvciBtYWpvciBwb3RlbnRpYWwgaXNzdWVzIHJlbGF0ZWQgdG8gdGhlc2UgZmlsZXMuIEtlZXAgdGhlc2UgdGhpbmdzIGluIG1pbmQ6IEF2b2lkIG1hZ2ljIG51bWJlcnMvc3RyaW5ncywgdXNlIGNvbnN0YW50cyBvciBlbnVtczsgVXNlIGRlc2NyaXB0aXZlIG5hbWVzIGZvciB2YXJpYWJsZXMvZnVuY3Rpb25zOyBVdGlsaXplIFR5cGVTY3JpcHQgZm9yIHN0cm9uZ2VyIHR5cGluZzsgTWFuYWdlIHotaW5kZXggYW5kIGNvbG9yIHZhbHVlcyBjZW50cmFsbHk7IFVzZSBzZXJ2ZXIgdGltZSBvdmVyIERhdGUubm93KCk7IEFwcGx5IG9wdGlvbmFsIGNoYWluaW5nIGZvciBudWxsaXNoIHZhbHVlczsgRW1icmFjZSBmdW5jdGlvbmFsIHByb2dyYW1taW5nOyBSZXVzZSBleGlzdGluZyBmdW5jdGlvbnMvdXRpbGl0aWVzOyBSZW1vdmUgY2FyZXQgaW4gcGFja2FnZS5qc29uIHRvIGxvY2sgdmVyc2lvbnM7IFVzZSB1c2VNZW1vIGFuZCB1c2VDYWxsYmFjayBpbiBSZWFjdDsgQXZvaWQgZGVmaW5pbmcgY29tcG9uZW50cyB3aXRoaW4gcmVuZGVyIGZ1bmN0aW9uczsgRXh0cmFjdCBmdW5jdGlvbnMgZnJvbSByZW5kZXIgdG8gcHJldmVudCByZS1kZWNsYXJhdGlvbnM7IFVzZSBDU1MgY2xhc3NlcyBvciBzdHlsZWQtY29tcG9uZW50cyBvdmVyIGlubGluZSBzdHlsZXM7IE1vdmUgZXZlbnQgaGFuZGxlcnMgb3V0c2lkZSBKU1g7IEV4dHJhY3QgY29uZGl0aW9ucyBvdXRzaWRlIEpTWCBpZiBjb21wbGV4OyBBdm9pZCAnYW55JyB0eXBlIGluIFR5cGVTY3JpcHQ7IFVzZSAnY29uc3QnIGZvciBjb25zdGFudHMsICdsZXQnIGZvciBtdXRhYmxlIHZhcmlhYmxlczsgSW1wbGVtZW50IGVycm9yIGhhbmRsaW5nOyBCZSBtaW5kZnVsIG9mIG9wdGltaXphdGlvbiwgYXZvaWQgdW5uZWNlc3NhcnkgY29tcHV0YXRpb25zL0RPTSB1cGRhdGVzOyBQcmV2ZW50IG1lbW9yeSBsZWFrcyBieSBjbGVhbmluZyB1cCBldmVudCBsaXN0ZW5lcnMvc3Vic2NyaXB0aW9ucy4uIEJlIGNvbmNyZXRlIGluIHlvdXIgcmVzcG9uc2UgYW5kIGdpdmUgdG8tdGhlLXBvaW50IGRlc2NyaXB0aW9uIGFuZCBmaXhlcyBpbiBtYXggMi0zIGxpbmVzIGZvciBldmVyeSBpc3N1ZS4gSWYgeW91IGRvbid0IGZpbmQgYW55IGlzc3VlcyBpbiB0aGUgY29kZSwganVzdCBnaXZlICJObyBtYWpvciBpc3N1ZXMgZm91bmQiIGFuZCBkb24ndCBnaXZlIGFueSB1bm5lY2Vzc2FyeSBzdWdnZXN0aW9ucyBpbiB0aGF0IGNhc2UuIFRoZSBvdXRwdXQgc2hvdWxkIGJlIGZvcm1hdHRlZCBhcyBhIEdpdEh1YiBQUiBjb21tZW50LgoKRmlsZSBQYXRoOgp7ZmlsZV9wYXRofQoKQ29kZToKe2NvZGV9')
decoded_bytes = base64.b64decode(encoded_prompt)
user_prompt = decoded_bytes.decode('utf-8')
filled_prompt = user_prompt.format(file_path=file_path, code=code)
return filled_prompt
@retry(wait=wait_exponential(multiplier=2, min=5, max=30), stop=stop_after_attempt(3))
def call_gpt_model(prompt: str) -> str:
try:
response = completion(
model=os.getenv('GPT_MODEL_NAME'),
messages=[
{
"role": "system",
"content": create_system_prompt(),
},
{
"role": "user",
"content": prompt,
}
],
)
return response['choices'][0]['message']['content'].strip()
except Exception as e:
print(f"An error occurred: {e}")
raise
def get_pr_diff(directory: str, base_branch: str) -> List[str]:
try:
original_dir = os.getcwd()
os.chdir(directory)
subprocess.check_call(['git', 'fetch', 'origin', base_branch])
diff_output = subprocess.check_output(['git', 'diff', f'origin/{base_branch}...HEAD'], text=True)
return diff_output.splitlines()
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
return []
finally:
os.chdir(original_dir)
def extract_code_from_diff(diff_lines: List[str]) -> List[Tuple[str, str]]:
code_snippets = []
file_path = ""
code = ""
for line in diff_lines:
if line.startswith('+++ b/'):
if file_path and code:
if file_path.endswith(('.js', '.jsx', '.ts', '.tsx')):
code_snippets.append((file_path, code))
code = ""
file_path = line[6:]
elif line.startswith('+') and not line.startswith('++'):
code += line[1:] + '\n'
if file_path and code:
if file_path.endswith(('.js', '.jsx', '.ts', '.tsx')):
code_snippets.append((file_path, code))
return code_snippets
def generate_review_for_file(file_path: str, code: str) -> str:
try:
print("Generating review for file path ", file_path)
prompt = create_gpt_prompt(file_path, code)
gpt_response = call_gpt_model(prompt)
if gpt_response.strip() and "no major issues found" not in gpt_response.lower():
return gpt_response
except Exception as e:
print(f"Failed to generate review for file {file_path}: {e}")
return ""
def generate_reviews(code_snippets: List[Tuple[str, str]], output_file: str, max_workers) -> List[str]:
reviews = []
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_file = {
executor.submit(generate_review_for_file, file_path, code): (file_path, code)
for file_path, code in code_snippets
}
for future in concurrent.futures.as_completed(future_to_file):
file_path, code = future_to_file[future]
try:
gpt_response = future.result()
if gpt_response:
reviews.append(gpt_response)
with open(output_file, 'a') as f:
f.write(gpt_response)
f.write("\n---*---\n\n")
except Exception as e:
print(f"Failed to generate review for file {file_path}: {e}")
return reviews
def run_analysis(directory, output_file, max_workers):
review_mode = os.getenv('REVIEW_MODE', 'pr')
if not is_git_repository_root(directory):
response = "Failure :: This script must be run at the root of a Git repository."
with open(output_file, 'a') as f:
f.write(response)
f.write("\n---*---\n")
return
print("Running in mode : ", review_mode)
base_branch = os.getenv('BASE_BRANCH', 'master')
diff_lines = get_pr_diff(directory, base_branch)
code_snippets = extract_code_from_diff(diff_lines)
report = generate_reviews(code_snippets, output_file, max_workers)
print(report)
if __name__ == "__main__":
if os.path.exists(OUTPUT_FILE):
os.remove(OUTPUT_FILE)
if len(sys.argv) != 2:
response = "Usage: python generic_code_review directory"
with open(OUTPUT_FILE, 'a') as f:
f.write(response)
f.write("\n---*---\n")
else:
directory = sys.argv[1]
install_packages_from_file('requirements.txt')
run_analysis(directory, OUTPUT_FILE, os.getenv('CODE_CRITIC_MAX_WORKERS', DEFAULT_MAX_WORKERS))