Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing #8

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 118 additions & 205 deletions pr_reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,25 @@ def get_context(patch):
# print("prompt: ",prompt,"patch: ",patch)
return prompt + patch

def get_context_for_individual_line(patch):
prompt = ''
# Open the prompt file and read its contents into a variable
with open('linePrompt.txt', 'r') as file:
prompt = file.read()

print("prompt: ",prompt,"patch: ",patch)
return prompt + patch


# Function to post a review comment on a pull request
# def post_review_comment(body, pr_number, repo_name):
# url = f'https://api.github.com/repos/{REPO_OWNER}/{repo_name}/pulls/{pr_number}/reviews'
# data = {
# "body": body,
# "event": "COMMENT"
# }
# response = requests.post(url, headers=get_headers(), data=json.dumps(data))
# return response.json()
def post_review_comment(body, pr_number, repo_name):
url = f'https://api.github.com/repos/{REPO_OWNER}/{repo_name}/pulls/{pr_number}/reviews'
data = {
"body": body,
"event": "COMMENT"
}
response = requests.post(url, headers=get_headers(), data=json.dumps(data))
return response.json()


# Function to get the files changed in a specific pull request
Expand Down Expand Up @@ -97,9 +106,9 @@ def authenticate_request(event, context):

def lambda_handler(event, context):

authenticated, response = authenticate_request(event, context)
if not authenticated:
return response
# authenticated, response = authenticate_request(event, context)
# if not authenticated:
# return response

pr_number = ''
repo_name = ''
Expand All @@ -112,7 +121,6 @@ def lambda_handler(event, context):
'body': json.dumps('Invalid payload format')
}
#print(payload)

if payload.get('action') == 'opened' or payload.get('action') == 'synchronize':
# Extract the pull request number and repository name
pr_number = payload['pull_request']['number']
Expand All @@ -130,13 +138,9 @@ def lambda_handler(event, context):
}

files = get_pull_request_files(pr_number, repo_name)

commit_id=payload['pull_request']['head']['sha']
openai_review_concatenated = openai_review_comments(files, pr_number, repo_name)
post_line_level_comment(pr_number, repo_name, commit_id, openai_review_concatenated)

# print(i,openai_review_concatenated[i].path)
# print(i,openai_review_concatenated[i].position)
# post_review_comment(body=openai_review_concatenated, pr_number=pr_number, repo_name=repo_name)
print("openai comment",openai_review_concatenated)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented-out code should be removed to maintain code cleanliness and readability.

# print("individual comment")
# review_comments_on_lines(files)
# print(f'comment posted on #{pr_number} in repository {repo_name}')
Expand All @@ -145,225 +149,134 @@ def lambda_handler(event, context):
'body': json.dumps(f'comment posted on #{pr_number} in repository {repo_name}')
}

def post_line_level_comment(pr_number, repo_name, commit_id, openai_review_concatenated):
for i, review_comment in enumerate(openai_review_concatenated):
body = review_comment.get('body')
path = review_comment.get('path')
start_line = review_comment.get('start_line')
line = review_comment.get('line')
start_side = review_comment.get('start_side')
side = review_comment.get('side')
diff_hunk = review_comment.get('diff_hunk')
position = review_comment.get('position')

# Validate that start_line precedes line
# if start_line is not None and line is not None and start_line >= line:
# print(f"Invalid comment data: start_line ({start_line}) must be less than line ({line}).")
# continue

if body and path and commit_id and start_line and line and start_side and side:
# print("body: ", body)
# print("path: ", path)
# print("commit_id: ", commit_id)
# # print("start_line: ", start_line)
# # print("line: ", line)
# # print("start_side: ", start_side)
# print("side: ", side)
# print("pr_number", pr_number)
# print("repo_name", repo_name)
# print("diff_hunk", diff_hunk)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncomment the authenticate_request function call to enable request authentication.

# print("position", position)
# print("_______________")
if side == "RIGHT":
post_review_comment_on_line(pr_number, repo_name, path, body, commit_id,side, diff_hunk, position)


def openai_review_comments(files, pr_number, repo_name):
openai_review_concatenated = []
openai_review_concatenated = ''

# Handle batch processing if files exceed 10
if len(files) >= 10:
num_batches = len(files) // 10
batch_size = (len(files) + num_batches - 1) // num_batches
batches = [files[i:i + batch_size] for i in range(0, len(files), batch_size)]
for batch in batches:
concatenated_patch = ''.join(file.get('patch', '') for file in batch)
batch_review = generate_openai(get_context(concatenated_patch))
openai_review_concatenated.extend(batch_review)
openai_review_concatenated += generate_openai(
get_context(concatenated_patch),
get_context_for_individual_line(concatenated_patch)
)
else:
patch = get_patch_from_pr(pr_number, repo_name)
context = get_context(patch)
openai_review_concatenated = generate_openai(context)
linePrompt = get_context_for_individual_line(patch)
openai_review_concatenated = generate_openai(context, linePrompt)

# Convert the list of review comments to a single object
return openai_review_concatenated



def generate_openai(context):
openai_response = [] # Initialize an empty string to accumulate response content

tools = [
def generate_openai(context,linePrompt):
# with open('system.txt', 'r') as file:
# system = file.read()
# linePrompt = ''
# with open('linePrompt.txt', 'r') as file:
# linePrompt = file.read()
openai_response = ''
functions = [
{
"type": "function",
"function": {
"name": "post_review_comment_on_line",
"description": "Post a review comment on a specific range of lines in a file within a commit.",
"parameters": {
"type": "object",
"properties": {
"body": {
"type": "string",
"description": "The body text of the review comment to be posted."
},
"path": {
"type": "string",
"description": "The path to the file in which the review comment will be posted."
},
"commit_id": {
"type": "string",
"description": "The ID of the commit that contains the modified file."
},
"start_line": {
"type": "integer",
"description": "The starting line number of the range in the file where the comment should be applied."
},
"line": {
"type": "integer",
"description": "The ending line number of the range in the file where the comment should be applied."
},
"start_side": {
"type": "string",
"description": "The side of the diff where the comment starts. LEFT for deleted lines and RIGHT for added lines.",
"enum": ["LEFT", "RIGHT"]
},
"side": {
"type": "string",
"description": "The side of the diff where the comment ends. LEFT for deleted lines and RIGHT for added lines.",
"enum": ["LEFT", "RIGHT"]
},
"diff_hunk": {
"type": "string",
"description": "The diff hunk of the comment."
},
"position": {
"type": "integer",
"description": "The line number in the file where the comment should be applied."
},
"in_reply_to": {
"type": "string",
"description": "The ID of the review comment to which the new comment should be a reply."
},
"subject_type": {
"type": "string",
"description": "The type of the subject of the review comment. Can be either 'REVIEW' or 'ISSUE'.",
}
"name": "post_review_comment_on_line",
"description": "Post the comment on the specific line in the pull request",
"parameters": {
"type": "object",
"properties": {
"body": {
"type": "string",
"description": "The body of the review comment"
},
"required": ["path", "body", "commit_id", "start_line", "line", "start_side", "side", "diff_hunk", "position"]
}
}
}
"position": {
"type": "integer",
"description": "The line number in the file"
},
"path": {
"type": "string",
"description": "The path of the file"
}
},
"required": ["path","position","body"]
}
}
]


# Create a streaming completion request
stream = openai_client.chat.completions.create(
model="gpt-4o",
# messages=[{"role": "system", "content": system},{"role": "user", "content": context}],
messages=[
{"role": "user", "content": context}
{"role": "user", "content": context},
{"role":"user","content":linePrompt}
],
tools=tools,
# stream=True # Enable streaming
tools=functions,
stream=True,
)
# print("context",context)
# Extract tool calls from the response
choices = stream.choices
# print(choices)
for choice in choices:
if hasattr(choice.message, 'tool_calls'):
for tool_call in choice.message.tool_calls:
if tool_call.function:
function_arguments = tool_call.function.arguments
if function_arguments:
try:
# Load the function arguments as JSON
arguments_dict = json.loads(function_arguments)
# Extract the 'body' field
body = arguments_dict.get('body')
path = arguments_dict.get('path')
commit_id = arguments_dict.get('commit_id')
start_line = arguments_dict.get('start_line')
line = arguments_dict.get('line')
start_side = arguments_dict.get('start_side')
side = arguments_dict.get('side')
diff_hunk = arguments_dict.get('diff_hunk')

position = arguments_dict.get('position')
in_reply_to = arguments_dict.get('in_reply_to')
subject_type = arguments_dict.get('subject_type')
# print("Extracted body:", body)
# print('path,',path)
# print('position',position)
openai_response.append({
"body":body,
"path":path,
"commit_id":commit_id,
"start_line":start_line,
"line":line,
"start_side":start_side,
"side":side,
"diff_hunk":diff_hunk,

"position":position,
"in_reply_to":in_reply_to,
"subject_type":subject_type
})
except json.JSONDecodeError as e:
print("Error decoding JSON:", e)
else:
print("Function arguments are empty.")
# print('openai_response',openai_response)
print("stream",stream)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
openai_response = openai_response + chunk.choices[0].delta.content
# print("openai_response",openai_response)
#print(openai_response)
return openai_response





def post_review_comment_on_line(pr_number, repo_name, path, body, commit_id, side, diff_hunk, position):

# Validate that required fields are not None
if path is None or body is None or commit_id is None:
raise ValueError("Missing required fields.")

if side != "RIGHT":
print(f"Skipping comment on side {side} for path {path}.")
return None

# def review_comments_on_lines(files):
# # print("files",files)
# for file in files:
# file_name = file.get('filename') or file['filename']
# patch = file.get('patch') or file['patch']

# if not patch:
# continue

# # Split the patch into lines
# patch_lines = patch.split('\n')
# line_number_in_file = 0
# # print(patch)
# for patch_line in patch_lines:
# # Git diff format uses lines that start with '+' to indicate additions
# if patch_line.startswith('+') and not patch_line.startswith('+++'):
# line_number_in_file += 1

# # Generate a review comment using OpenAI
# context = get_context(patch_line)
# review_comment = generate_openai(context)

# # Post the review comment on the specific line in the pull request
# # print(file_name)
# # print(review_comment)
# # print(line_number_in_file)
# # post_review_comment_on_line(
# # body=review_comment,
# # pr_number=pr_number,
# # repo_name=repo_name,
# # path=file_name,
# # position=line_number_in_file
# # )


def post_review_comment_on_line(body, pr_number, repo_name, path, position):
url = f'https://api.github.com/repos/{REPO_OWNER}/{repo_name}/pulls/{pr_number}/comments'
data = {
"body": body,
"path": path,
"commit_id": commit_id,
# "start_line": start_line,
# "line": line,
# "start_side": start_side,
"side": side,
"diff_hunk": diff_hunk,
"position": position
"position": position,
}

# Remove keys with None values to avoid sending unnecessary fields
data = {k: v for k, v in data.items() if v is not None}

response = requests.post(url, headers=get_headers(), data=json.dumps(data))
print("posting completed")
print(response.json())
return response.json()


# if __name__ == '__main__':
# with open('sample_event.json', 'r') as fileVariable:
# event_text = json.load(fileVariable)

# # event = ast.literal_eval(event_text)
# # event = json.loads(event_text)
# context = ''
# lambda_handler(event_text, context)



if __name__ == '__main__':
with open('sample.json', 'r') as fileVariable:
event_text = json.load(fileVariable)

# event = ast.literal_eval(event_text)
# event = json.loads(event_text)
context = ''
lambda_handler(event_text, context)