Skip to content

Commit ce8ad0e

Browse files
authored
Use feedback from live demo (#38)
* Use feedback from live demo using kent_beck persona * Update exception to be generic * Make more defensive with filename validation and sanitization * Update default persona, temperature * Update magic strings
1 parent af83268 commit ce8ad0e

1 file changed

Lines changed: 85 additions & 43 deletions

File tree

action_code_review.py

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,100 @@
22
import os
33
import sys
44
import openai
5+
import string
56
import re
7+
from typing import List
68

7-
def extract_filenames_from_diff(diff_text):
8-
"""
9-
This function extracts filenames from git diff text using regular expressions.
10-
11-
:param diff_text: str, git diff text
12-
:return: list of str, list of filenames
13-
"""
9+
OPENAI_API_KEY = "OPENAI_API_KEY"
10+
VALID_CHARS = "-_.() %s%s" % (string.ascii_letters, string.digits)
11+
GIT_DIFF_REGEX_PATTERN = r'\+\+\+ b/(.*)'
12+
DEFAULT_MODEL = "gpt-3.5-turbo-16k"
13+
DEFAULT_STYLE = "concise"
14+
DEFAULT_PERSONA = "kent_beck"
15+
OPENAI_TEMPERATURE = 0.1
16+
OPENAI_MAX_TOKENS = 2048
17+
OPENAI_ERROR_NO_RESPONSE = "No response from OpenAI. Error:\n"
18+
OPENAI_ERROR_FAILED = "OpenAI failed to generate a review. Error:\n"
19+
20+
# Make sure the necessary environment variables are set
21+
if OPENAI_API_KEY not in os.environ:
22+
print(f"The {OPENAI_API_KEY} environment variable is not set.")
23+
sys.exit(1)
24+
25+
def validate_filename(filename: str) -> bool:
26+
"""
27+
Validates a filename by checking for directory traversal and unusual characters.
28+
29+
Args:
30+
filename: str, filename to be validated
31+
32+
Returns:
33+
bool: True if the filename is valid, False otherwise
34+
"""
35+
# Check for directory traversal
36+
if ".." in filename or "/" in filename:
37+
return False
1438

15-
# Pattern for lines that start with '+++ b/', which are lines that contain filenames
16-
# of the "new version" files in the diff. The parentheses (.) create a group that
17-
# captures the filename that follows '+++ b/'.
18-
pattern = r'\+\+\+ b/(.*)'
39+
# Check for unusual characters
40+
for char in filename:
41+
if char not in VALID_CHARS:
42+
return False
1943

20-
# re.findall function finds all matches of the pattern in the diff_text
21-
# and returns them as a list.
22-
filenames = re.findall(pattern, diff_text)
44+
return True
2345

24-
return filenames
46+
def extract_filenames_from_diff_text(diff_text: str) -> List[str]:
47+
"""
48+
Extracts filenames from git diff text using regular expressions.
49+
50+
Args:
51+
diff_text: str, git diff text
52+
53+
Returns:
54+
List of filenames
55+
"""
56+
filenames = re.findall(GIT_DIFF_REGEX_PATTERN, diff_text)
57+
sanitized_filenames = [fn for fn in filenames if validate_filename(fn)]
58+
return sanitized_filenames
2559

26-
def format_file_contents(filenames):
60+
def format_file_contents_as_markdown(filenames: List[str]) -> str:
2761
"""
28-
This function iteratively goes through each filename and concatenates
62+
Iteratively goes through each filename and concatenates
2963
the filename and its content in a specific markdown format.
3064
31-
:param filenames: list of str, list of filenames
32-
:return: str, formatted string
65+
Args:
66+
filenames: List of filenames
67+
68+
Returns:
69+
Formatted string
3370
"""
34-
files_str = ""
71+
formatted_files = ""
3572
for filename in filenames:
36-
with open(filename, 'r') as file:
37-
content = file.read()
38-
files_str += f"\n{filename}\n```\n{content}\n```\n"
39-
return files_str
73+
try:
74+
with open(filename, 'r') as file:
75+
file_content = file.read()
76+
formatted_files += f"\n{filename}\n```\n{file_content}\n```\n"
77+
except Exception as e:
78+
print(f"Could not read file {filename}: {e}")
79+
return formatted_files
4080

4181
REQUEST = "Reply on how to improve the code (below). Think step-by-step. Give code examples of specific changes\n"
4282

4383
STYLES = {
44-
"zen": "Format feedback in the style of a zen koan",
45-
"concise": "Format feedback concisely with numbered list"
84+
"zen": "Format feedback in the style of a zen koan",
85+
"concise": "Format feedback concisely with numbered list"
4686
}
4787

4888
PERSONAS = {
49-
"developer": "You are an experienced software developer in a variety of programming languages and methodologies. You create efficient, scalable, and fault-tolerant solutions",
50-
"kent_beck": "You are Kent Beck. You are known for software design patterns, test-driven development (TDD), and agile methodologies",
51-
"marc_benioff": "You are Marc Benioff, internet entrepreneur and experienced software developer",
52-
"yoda": "You are Yoda, legendary Jedi Master. Speak like Yoda",
89+
"developer": "You are an experienced software developer in a variety of programming languages and methodologies. You create efficient, scalable, and fault-tolerant solutions",
90+
"kent_beck": "You are Kent Beck. You are known for software design patterns, test-driven development (TDD), and agile methodologies",
91+
"marc_benioff": "You are Marc Benioff, internet entrepreneur and experienced software developer",
92+
"yoda": "You are Yoda, legendary Jedi Master. Speak like Yoda",
5393
}
5494

55-
openai.api_key = os.environ["OPENAI_API_KEY"]
56-
model = os.environ.get("MODEL", "gpt-3.5-turbo-16k")
57-
persona = PERSONAS.get(os.environ.get("PERSONA"), PERSONAS["developer"])
58-
style = STYLES.get(os.environ.get("STYLE"), STYLES["concise"])
95+
openai.api_key = os.environ[OPENAI_API_KEY]
96+
model = os.environ.get("MODEL", DEFAULT_MODEL)
97+
persona = PERSONAS.get(os.environ.get("PERSONA"), PERSONAS[DEFAULT_PERSONA])
98+
style = STYLES.get(os.environ.get("STYLE"), STYLES[DEFAULT_STYLE])
5999
include_files = os.environ.get("INCLUDE_FILES", "false") == "true"
60100

61101
# Read in the diff
@@ -64,27 +104,29 @@ def format_file_contents(filenames):
64104
prompt = f"{persona}.{style}.{REQUEST}\n{diff}"
65105

66106
kwargs = {'model': model}
67-
kwargs['temperature'] = 0.5
68-
kwargs['max_tokens'] = 2048
107+
kwargs['temperature'] = OPENAI_TEMPERATURE
108+
kwargs['max_tokens'] = OPENAI_MAX_TOKENS
69109
kwargs['messages']=[{"role": "system", "content": prompt}]
70110

71111
# Optionally include files from the diff
72112
if include_files:
73-
filenames = extract_filenames_from_diff(diff)
74-
formatted_str = format_file_contents(filenames)
75-
new_message = {"role": "user", "content": formatted_str}
113+
filenames = extract_filenames_from_diff_text(diff)
114+
formatted_files = format_file_contents_as_markdown(filenames)
115+
new_message = {"role": "user", "content": formatted_files}
76116
kwargs['messages'].append(new_message)
77-
117+
78118
try:
79-
response = openai.ChatCompletion.create(**kwargs)
119+
response = openai.ChatCompletion.create(**kwargs)
80120
if response.choices:
81121
if 'text' in response.choices[0]:
82122
review_text = response.choices[0].text.strip()
83123
else:
84124
review_text = response.choices[0].message.content.strip()
85125
else:
86-
review_text = f"No response from OpenAI\n{response.text}"
126+
review_text = OPENAI_ERROR_NO_RESPONSE + response.text
127+
sys.exit(1)
87128
except Exception as e:
88-
review_text = f"OpenAI failed to generate a review: {e}"
129+
review_text = OPENAI_ERROR_FAILED + str(e)
130+
sys.exit(1)
89131

90132
print(f"{review_text}")

0 commit comments

Comments
 (0)