Sweep: fix the following bug
kevinlu1248 opened this issue Β· comments
The exception message is:too many values to unpack (expected 3)
The stack trace is:Traceback (most recent call last):
File "/app/sweepai/handlers/on_comment.py", line 370, in on_comment
modify_files_dict, changes_made, _ = handle_file_change_requests(
ValueError: too many values to unpack (expected 3)
π Here's the PR! #3714
72c67a8e14
)Tip
I can email you next time I complete a pull request if you set up your email here!
Actions (click)
- β» Restart Sweep
Step 1: π Searching
I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.
Some code snippets I think are relevant in decreasing order of relevance (click to expand). If some file is missing from here, you can mention the path in the ticket description.
sweep/sweepai/handlers/on_comment.py
Lines 1 to 475 in c9c4b91
""" | |
on_comment is responsible for handling PR comments and PR review comments, called from sweepai/api.py. | |
It is also called in sweepai/handlers/on_ticket.py when Sweep is reviewing its own PRs. | |
""" | |
import copy | |
import re | |
import time | |
import traceback | |
from typing import Any | |
from loguru import logger | |
from tabulate import tabulate | |
from sweepai.config.client import get_documentation_dict | |
from sweepai.config.server import ( | |
DEFAULT_GPT4_MODEL, | |
ENV, | |
GITHUB_BOT_USERNAME, | |
MONGODB_URI, | |
) | |
from sweepai.core.entities import MockPR, NoFilesException | |
from sweepai.core.sweep_bot import SweepBot, get_files_to_change, validate_file_change_requests | |
from sweepai.handlers.create_pr import handle_file_change_requests | |
from sweepai.handlers.on_review import get_pr_diffs | |
from sweepai.utils.chat_logger import ChatLogger | |
from sweepai.utils.diff import generate_diff | |
from sweepai.utils.event_logger import posthog | |
from sweepai.utils.github_utils import ClonedRepo, commit_multi_file_changes, get_github_client, sanitize_string_for_github, validate_and_sanitize_multi_file_changes | |
from sweepai.utils.progress import TicketProgress | |
from sweepai.utils.prompt_constructor import HumanMessageCommentPrompt | |
from sweepai.utils.str_utils import BOT_SUFFIX, FASTER_MODEL_MESSAGE | |
from sweepai.utils.ticket_utils import fire_and_forget_wrapper, prep_snippets | |
num_of_snippets_to_query = 30 | |
total_number_of_snippet_tokens = 15_000 | |
num_full_files = 2 | |
num_extended_snippets = 2 | |
ERROR_FORMAT = "β {title}\n\nPlease join our [Discourse](https://community.sweep.dev/) to report this issue." | |
def on_comment( | |
repo_full_name: str, | |
repo_description: str, | |
comment: str, | |
pr_path: str | None, | |
pr_line_position: int | None, | |
username: str, | |
installation_id: int, | |
pr_number: int = None, | |
comment_id: int | None = None, | |
chat_logger: Any = None, | |
pr: MockPR = None, # For on_comment calls before PR is created | |
repo: Any = None, | |
comment_type: str = "comment", | |
type: str = "comment", | |
tracking_id: str = None, | |
): | |
with logger.contextualize( | |
tracking_id=tracking_id, | |
): | |
logger.info( | |
f"Calling on_comment() with the following arguments: {comment}," | |
f" {repo_full_name}, {repo_description}, {pr_path}" | |
) | |
organization, repo_name = repo_full_name.split("/") | |
start_time = time.time() | |
_token, g = get_github_client(installation_id) | |
repo = g.get_repo(repo_full_name) | |
if pr is None: | |
pr = repo.get_pull(pr_number) | |
pr_title = pr.title | |
pr_body = ( | |
pr.body.split("<details>\n<summary><b>π Latest improvements to Sweep:")[0] | |
if pr.body | |
and "<details>\n<summary><b>π Latest improvements to Sweep:" in pr.body | |
else pr.body | |
) | |
pr_file_path = None | |
diffs = get_pr_diffs(repo, pr) | |
pr_chunk = None | |
formatted_pr_chunk = None | |
assignee = pr.assignee.login if pr.assignee else None | |
issue_number_match = re.search(r"Fixes #(?P<issue_number>\d+).", pr_body or "") | |
original_issue = None | |
if issue_number_match or assignee: | |
issue_number = issue_number_match.group("issue_number") | |
if not assignee: | |
# issue_number = issue_number_match.group("issue_number") | |
original_issue = repo.get_issue(int(issue_number)) | |
author = original_issue.user.login | |
else: | |
author = assignee | |
logger.info(f"Author of original issue is {author}") | |
chat_logger = ( | |
chat_logger | |
if chat_logger is not None | |
else ChatLogger( | |
{ | |
"repo_name": repo_name, | |
"title": "(Comment) " + pr_title, | |
"issue_url": pr.html_url, | |
"pr_file_path": pr_file_path, # may be None | |
"pr_chunk": pr_chunk, # may be None | |
"repo_full_name": repo_full_name, | |
"repo_description": repo_description, | |
"comment": comment, | |
"pr_path": pr_path, | |
"pr_line_position": pr_line_position, | |
"username": author, | |
"installation_id": installation_id, | |
"pr_number": pr_number, | |
"type": "comment", | |
}, | |
active=True, | |
) | |
if MONGODB_URI | |
else None | |
) | |
else: | |
chat_logger = None | |
if chat_logger: | |
is_paying_user = chat_logger.is_paying_user() | |
use_faster_model = chat_logger.use_faster_model() | |
else: | |
# Todo: chat_logger is None for MockPRs, which will cause all comments to use GPT-4 | |
is_paying_user = True | |
use_faster_model = False | |
if use_faster_model: | |
raise Exception(FASTER_MODEL_MESSAGE) | |
assignee = pr.assignee.login if pr.assignee else None | |
metadata = { | |
"repo_full_name": repo_full_name, | |
"repo_name": repo_name, | |
"organization": organization, | |
"repo_description": repo_description, | |
"installation_id": installation_id, | |
"username": username if not username.startswith("sweep") else assignee, | |
"function": "on_comment", | |
"model": "gpt-4", | |
"tier": "pro" if is_paying_user else "free", | |
"mode": ENV, | |
"pr_path": pr_path, | |
"pr_line_position": pr_line_position, | |
"pr_number": pr_number or pr.id, | |
"pr_html_url": pr.html_url, | |
"comment_id": comment_id, | |
"comment": comment, | |
"issue_number": issue_number if issue_number_match else "", | |
"tracking_id": tracking_id, | |
} | |
logger.bind(**metadata) | |
elapsed_time = time.time() - start_time | |
posthog.capture( | |
username, | |
"started", | |
properties={ | |
**metadata, | |
"duration": elapsed_time, | |
"tracking_id": tracking_id, | |
}, | |
) | |
logger.info(f"Getting repo {repo_full_name}") | |
file_comment = bool(pr_path) and bool(pr_line_position) | |
item_to_react_to = None | |
reaction = None | |
bot_comment = None | |
def edit_comment(new_comment: str) -> None: | |
new_comment = sanitize_string_for_github(new_comment) | |
if bot_comment is not None: | |
bot_comment.edit(new_comment + BOT_SUFFIX) | |
try: | |
# Check if the PR is closed | |
if pr.state == "closed": | |
return {"success": True, "message": "PR is closed. No event fired."} | |
if comment_id: | |
try: | |
item_to_react_to = pr.get_issue_comment(comment_id) | |
reaction = item_to_react_to.create_reaction("eyes") | |
except Exception: | |
try: | |
item_to_react_to = pr.get_review_comment(comment_id) | |
reaction = item_to_react_to.create_reaction("eyes") | |
except Exception: | |
pass | |
if reaction is not None: | |
# Delete rocket reaction | |
reactions = item_to_react_to.get_reactions() | |
for r in reactions: | |
if ( | |
r.content == "rocket" | |
and r.user.login == GITHUB_BOT_USERNAME | |
): | |
item_to_react_to.delete_reaction(r.id) | |
branch_name = ( | |
pr.head.ref if pr_number else pr.pr_head # pylint: disable=no-member | |
) | |
cloned_repo = ClonedRepo( | |
repo_full_name, | |
installation_id, | |
branch=branch_name, | |
repo=repo, | |
token=_token, | |
) | |
# Generate diffs for this PR | |
pr_diff_string = None | |
pr_files_modified = None | |
if pr_number: | |
patches = [] | |
pr_files_modified = {} | |
files = pr.get_files() | |
for file in files: | |
if file.status == "modified": | |
# Get the entire file contents, not just the patch | |
pr_files_modified[file.filename] = repo.get_contents( | |
file.filename, ref=branch_name | |
).decoded_content.decode("utf-8") | |
patches.append( | |
f'<file file_path="{file.filename}">\n{file.patch}\n</file>' | |
) | |
pr_diff_string = ( | |
"<files_changed>\n" + "\n".join(patches) + "\n</files_changed>" | |
) | |
# This means it's a comment on a file | |
if file_comment: | |
pr_file = repo.get_contents( | |
pr_path, ref=branch_name | |
).decoded_content.decode("utf-8") | |
pr_lines = pr_file.splitlines() | |
start = max(0, pr_line_position - 11) | |
end = min(len(pr_lines), pr_line_position + 10) | |
original_line = pr_lines[pr_line_position - 1] | |
pr_chunk = "\n".join(pr_lines[start:end]) | |
pr_file_path = pr_path.strip() | |
formatted_pr_chunk = ( | |
"\n".join(pr_lines[start : pr_line_position - 1]) | |
+ f"\n{pr_lines[pr_line_position - 1]} <- COMMENT: {comment.strip()}\n" | |
+ "\n".join(pr_lines[pr_line_position:end]) | |
) | |
if comment_id: | |
try: | |
bot_comment = pr.create_review_comment_reply( | |
comment_id, "Working on it..." + BOT_SUFFIX | |
) | |
except Exception as e: | |
print(e) | |
else: | |
formatted_pr_chunk = None # pr_file | |
bot_comment = pr.create_issue_comment("Working on it..." + BOT_SUFFIX) | |
try: | |
search_query = (comment).strip("\n") | |
formatted_query = (f"{comment}").strip("\n") | |
repo_context_manager = prep_snippets( | |
cloned_repo, search_query, TicketProgress(tracking_id="none") | |
) | |
snippets = repo_context_manager.current_top_snippets | |
tree = str(repo_context_manager.dir_obj) | |
cloned_repo = repo_context_manager.cloned_repo | |
except Exception as e: | |
logger.error(traceback.format_exc()) | |
raise e | |
get_documentation_dict(repo) | |
docs_results = "" | |
logger.info("Getting response from ChatGPT...") | |
human_message = HumanMessageCommentPrompt( | |
comment=comment, | |
repo_name=repo_name, | |
repo_description=repo_description if repo_description else "", | |
diffs=diffs, | |
title=pr_title, | |
tree=tree, | |
summary=pr_body, | |
snippets=snippets, | |
pr_file_path=pr_file_path, # may be None | |
pr_chunk=formatted_pr_chunk, # may be None | |
original_line=original_line if pr_chunk else None, | |
relevant_docs=docs_results, | |
) | |
logger.info(f"Human prompt{human_message.construct_prompt()}") | |
if use_faster_model: | |
raise Exception("GPT-3.5 is not supported for comments") | |
sweep_bot = SweepBot.from_system_message_content( | |
# human_message=human_message, model="claude-v1.3-100k", repo=repo | |
human_message=human_message, | |
repo=repo, | |
chat_logger=chat_logger, | |
model=DEFAULT_GPT4_MODEL, | |
cloned_repo=cloned_repo, | |
) | |
except Exception as e: | |
stack_trace = traceback.format_exc() | |
logger.error(stack_trace) | |
elapsed_time = time.time() - start_time | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": str(e), | |
"reason": f"An error occured! The stack trace is below:\n\n{stack_trace}", | |
"duration": elapsed_time, | |
"tracking_id": tracking_id, | |
**metadata, | |
}, | |
) | |
edit_comment(ERROR_FORMAT.format(title=f"An error occured!\n\nThe exception message is:{str(e)}\n\nThe stack trace is:{stack_trace}")) | |
raise e | |
try: | |
logger.info("Fetching files to modify/create...") | |
if file_comment: | |
formatted_query = f"The user left this comment: <comment>\n{comment}\n</comment>\nThis was where they left their comment:\n<review_code_chunk>\n{formatted_pr_chunk}\n</review_code_chunk>.\n\nResolve their comment." | |
file_change_requests, plan = get_files_to_change( | |
relevant_snippets=repo_context_manager.current_top_snippets, | |
read_only_snippets=repo_context_manager.read_only_snippets, | |
problem_statement=formatted_query, | |
repo_name=repo_name, | |
pr_diffs=pr_diff_string, | |
cloned_repo=cloned_repo, | |
) | |
validate_file_change_requests(file_change_requests, repo_context_manager.cloned_repo) | |
file_change_requests = sweep_bot.validate_file_change_requests( | |
file_change_requests, branch=branch_name | |
) | |
sweep_response = "I couldn't find any relevant files to change." | |
if file_change_requests: | |
table_message = tabulate( | |
[ | |
[ | |
f"`{file_change_request.filename}`", | |
file_change_request.instructions_display.replace( | |
"\n", "<br/>" | |
).replace("```", "\\```"), | |
] | |
for file_change_request in file_change_requests | |
], | |
headers=["File Path", "Proposed Changes"], | |
tablefmt="pipe", | |
) | |
sweep_response = ( | |
f"I am making the following changes:\n\n{table_message}" | |
) | |
quoted_comment = "> " + comment.replace("\n", "\n> ") | |
response_for_user = ( | |
f"{quoted_comment}\n\nHi @{username},\n\n{sweep_response}" | |
) | |
if pr_number: | |
edit_comment(response_for_user) | |
sweep_bot.comment_pr_diff_str = pr_diff_string | |
sweep_bot.comment_pr_files_modified = pr_files_modified | |
modify_files_dict, changes_made, _ = handle_file_change_requests( | |
file_change_requests=file_change_requests, | |
request=file_comment, | |
branch_name=branch_name, | |
sweep_bot=sweep_bot, | |
username=username, | |
installation_id=installation_id, | |
chat_logger=chat_logger, | |
) | |
logger.info("\n".join(generate_diff(file_data["original_contents"], file_data["contents"]) for file_data in modify_files_dict.values())) | |
commit_message = f"feat: Updated {len(modify_files_dict or [])} files"[:50] | |
try: | |
new_file_contents_to_commit = {file_path: file_data["contents"] for file_path, file_data in modify_files_dict.items()} | |
previous_file_contents_to_commit = copy.deepcopy(new_file_contents_to_commit) | |
new_file_contents_to_commit, files_removed = validate_and_sanitize_multi_file_changes(sweep_bot.repo, new_file_contents_to_commit, file_change_requests) | |
if files_removed and username: | |
posthog.capture( | |
username, | |
"polluted_commits_error", | |
properties={ | |
"old_keys": ",".join(previous_file_contents_to_commit.keys()), | |
"new_keys": ",".join(new_file_contents_to_commit.keys()) | |
}, | |
) | |
commit_multi_file_changes(sweep_bot.repo, new_file_contents_to_commit, commit_message, branch_name) | |
except Exception as e: | |
logger.info(f"Error in updating file{e}") | |
raise e | |
try: | |
if comment_id: | |
if changes_made: | |
response_for_user = "Done." | |
else: | |
response_for_user = 'I wasn\'t able to make changes. This could be due to an unclear request or a bug in my code.\n As a reminder, comments on a file only modify that file. Comments on a PR (at the bottom of the "conversation" tab) can modify the entire PR. Please try again or contact us on [Discourse](https://community.sweep.dev/)' | |
except Exception as e: | |
logger.error(f"Failed to reply to comment: {e}") | |
if not isinstance(pr, MockPR): | |
if pr.user.login == GITHUB_BOT_USERNAME and pr.title.startswith( | |
"[DRAFT] " | |
): | |
# Update the PR title to remove the "[DRAFT]" prefix | |
pr.edit(title=pr.title.replace("[DRAFT] ", "", 1)) | |
logger.info("Done!") | |
except NoFilesException: | |
elapsed_time = time.time() - start_time | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": "No files to change", | |
"reason": "No files to change", | |
"duration": elapsed_time, | |
**metadata, | |
}, | |
) | |
edit_comment(ERROR_FORMAT.format(title="Could not find files to change")) | |
return {"success": True, "message": "No files to change."} | |
except Exception as e: | |
stack_trace = traceback.format_exc() | |
logger.error(stack_trace) | |
elapsed_time = time.time() - start_time | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": str(e), | |
"reason": "Failed to make changes", | |
"duration": elapsed_time, | |
**metadata, | |
}, | |
) | |
edit_comment(ERROR_FORMAT.format(title=f"Failed to make changes:\n\nThe exception message is:{str(e)}\n\nThe stack trace is:{stack_trace}")) | |
raise e | |
# Delete eyes | |
if reaction is not None: | |
item_to_react_to.delete_reaction(reaction.id) | |
try: | |
item_to_react_to = pr.get_issue_comment(comment_id) | |
reaction = item_to_react_to.create_reaction("rocket") | |
except Exception: | |
try: | |
item_to_react_to = pr.get_review_comment(comment_id) | |
reaction = item_to_react_to.create_reaction("rocket") | |
except Exception: | |
pass | |
if response_for_user is not None: | |
try: | |
edit_comment(f"## π Wrote Changes\n\n{response_for_user}") | |
except Exception: | |
pass | |
elapsed_time = time.time() - start_time | |
# make async | |
fire_and_forget_wrapper(posthog.capture)( | |
username, | |
"success", | |
properties={ | |
**metadata, | |
"tracking_id": tracking_id, | |
"duration": elapsed_time, | |
}, | |
) |
sweep/sweepai/handlers/create_pr.py
Lines 1 to 408 in c9c4b91
""" | |
create_pr is a function that creates a pull request from a list of file change requests. | |
It is also responsible for handling Sweep config PR creation. test | |
""" | |
import copy | |
import datetime | |
import openai | |
from github.Repository import Repository | |
from loguru import logger | |
from sweepai.agents.modify import modify | |
from sweepai.config.client import DEFAULT_RULES_STRING, SweepConfig | |
from sweepai.config.server import ( | |
ENV, | |
GITHUB_BOT_USERNAME, | |
GITHUB_CONFIG_BRANCH, | |
GITHUB_DEFAULT_CONFIG, | |
GITHUB_LABEL_NAME, | |
) | |
from sweepai.core.entities import ( | |
FileChangeRequest, | |
MaxTokensExceeded, | |
) | |
from sweepai.core.sweep_bot import SweepBot | |
from sweepai.utils.chat_logger import ChatLogger | |
from sweepai.utils.event_logger import posthog | |
from sweepai.utils.github_utils import ClonedRepo, get_github_client | |
num_of_snippets_to_query = 10 | |
max_num_of_snippets = 5 | |
INSTRUCTIONS_FOR_REVIEW = """\ | |
### π‘ To get Sweep to edit this pull request, you can: | |
* Comment below, and Sweep can edit the entire PR | |
* Comment on a file, Sweep will only modify the commented file | |
* Edit the original issue to get Sweep to recreate the PR from scratch""" | |
# this should be the only modification function | |
def handle_file_change_requests( | |
file_change_requests: list[FileChangeRequest], | |
branch_name: str, | |
request: str, | |
sweep_bot: SweepBot, | |
username: str, | |
installation_id: int, | |
chat_logger: ChatLogger = None, | |
previous_modify_files_dict: dict = {}, | |
): | |
sweep_bot.chat_logger = chat_logger | |
organization, repo_name = sweep_bot.repo.full_name.split("/") | |
metadata = { | |
"repo_full_name": sweep_bot.repo.full_name, | |
"organization": organization, | |
"repo_name": repo_name, | |
"repo_description": sweep_bot.repo.description, | |
"username": username, | |
"installation_id": installation_id, | |
"function": "create_pr", | |
"mode": ENV, | |
} | |
posthog.capture(username, "started", properties=metadata) | |
try: | |
completed_count, fcr_count = 0, len(file_change_requests) | |
relevant_filepaths = [] | |
for file_change_request in file_change_requests: | |
if file_change_request.relevant_files: | |
# keep all relevant_filepaths | |
for file_path in file_change_request.relevant_files: | |
relevant_filepaths.append(file_path) | |
# actual modification logic | |
modify_files_dict = modify( | |
fcrs=file_change_requests, | |
request=request, | |
cloned_repo=sweep_bot.cloned_repo, | |
relevant_filepaths=relevant_filepaths, | |
previous_modify_files_dict=previous_modify_files_dict, | |
) | |
# If no files were updated, log a warning and return | |
if not modify_files_dict: | |
logger.warning( | |
"No changes made to any file!" | |
) | |
return ( | |
modify_files_dict, | |
False, | |
None, | |
file_change_requests, | |
) | |
# update previous_modify_files_dict | |
if not previous_modify_files_dict: | |
previous_modify_files_dict = {} | |
if modify_files_dict: | |
for file_name, file_content in modify_files_dict.items(): | |
previous_modify_files_dict[file_name] = copy.deepcopy(file_content) | |
# update status of corresponding fcr to be succeeded | |
for file_change_request in file_change_requests: | |
if file_change_request.filename == file_name: | |
file_change_request.status = "succeeded" | |
completed_count = len(modify_files_dict or []) | |
logger.info(f"Completed {completed_count}/{fcr_count} files") | |
if completed_count == 0 and fcr_count != 0: | |
logger.info("No changes made") | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": "No changes made", | |
"reason": "No changes made", | |
**metadata, | |
}, | |
) | |
return modify_files_dict, True, file_change_requests | |
except MaxTokensExceeded as e: | |
logger.error(e) | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": str(e), | |
"reason": "Max tokens exceeded", | |
**metadata, | |
}, | |
) | |
raise e | |
except openai.BadRequestError as e: | |
logger.error(e) | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": str(e), | |
"reason": "Invalid request error / context length", | |
**metadata, | |
}, | |
) | |
raise e | |
except Exception as e: | |
logger.error(e) | |
posthog.capture( | |
username, | |
"failed", | |
properties={ | |
"error": str(e), | |
"reason": "Unexpected error", | |
**metadata, | |
}, | |
) | |
raise e | |
def safe_delete_sweep_branch( | |
pr, # Github PullRequest | |
repo: Repository, | |
) -> bool: | |
""" | |
Safely delete Sweep branch | |
1. Only edited by Sweep | |
2. Prefixed by sweep/ | |
""" | |
pr_commits = pr.get_commits() | |
pr_commit_authors = set([commit.author.login for commit in pr_commits]) | |
# Check if only Sweep has edited the PR, and sweep/ prefix | |
if ( | |
len(pr_commit_authors) == 1 | |
and GITHUB_BOT_USERNAME in pr_commit_authors | |
and pr.head.ref.startswith("sweep") | |
): | |
branch = repo.get_git_ref(f"heads/{pr.head.ref}") | |
# pr.edit(state='closed') | |
branch.delete() | |
return True | |
else: | |
# Failed to delete branch as it was edited by someone else | |
return False | |
def create_config_pr( | |
sweep_bot: SweepBot | None, repo: Repository = None, cloned_repo: ClonedRepo = None | |
): | |
if repo is not None: | |
# Check if file exists in repo | |
try: | |
repo.get_contents("sweep.yaml") | |
return | |
except SystemExit: | |
raise SystemExit | |
except Exception: | |
pass | |
title = "Configure Sweep" | |
branch_name = GITHUB_CONFIG_BRANCH | |
if sweep_bot is not None: | |
branch_name = sweep_bot.create_branch(branch_name, retry=False) | |
try: | |
# commit_history = [] | |
# if cloned_repo is not None: | |
# commit_history = cloned_repo.get_commit_history( | |
# limit=1000, time_limited=False | |
# ) | |
# commit_string = "\n".join(commit_history) | |
# sweep_yaml_bot = SweepYamlBot() | |
# generated_rules = sweep_yaml_bot.get_sweep_yaml_rules( | |
# commit_history=commit_string | |
# ) | |
sweep_bot.repo.create_file( | |
"sweep.yaml", | |
"Create sweep.yaml", | |
GITHUB_DEFAULT_CONFIG.format( | |
branch=sweep_bot.repo.default_branch, | |
additional_rules=DEFAULT_RULES_STRING, | |
), | |
branch=branch_name, | |
) | |
sweep_bot.repo.create_file( | |
".github/ISSUE_TEMPLATE/sweep-template.yml", | |
"Create sweep template", | |
SWEEP_TEMPLATE, | |
branch=branch_name, | |
) | |
except SystemExit: | |
raise SystemExit | |
except Exception as e: | |
logger.error(e) | |
else: | |
# Create branch based on default branch | |
repo.create_git_ref( | |
ref=f"refs/heads/{branch_name}", | |
sha=repo.get_branch(repo.default_branch).commit.sha, | |
) | |
try: | |
# commit_history = [] | |
# if cloned_repo is not None: | |
# commit_history = cloned_repo.get_commit_history( | |
# limit=1000, time_limited=False | |
# ) | |
# commit_string = "\n".join(commit_history) | |
# sweep_yaml_bot = SweepYamlBot() | |
# generated_rules = sweep_yaml_bot.get_sweep_yaml_rules( | |
# commit_history=commit_string | |
# ) | |
repo.create_file( | |
"sweep.yaml", | |
"Create sweep.yaml", | |
GITHUB_DEFAULT_CONFIG.format( | |
branch=repo.default_branch, additional_rules=DEFAULT_RULES_STRING | |
), | |
branch=branch_name, | |
) | |
repo.create_file( | |
".github/ISSUE_TEMPLATE/sweep-template.yml", | |
"Create sweep template", | |
SWEEP_TEMPLATE, | |
branch=branch_name, | |
) | |
except SystemExit: | |
raise SystemExit | |
except Exception as e: | |
logger.error(e) | |
repo = sweep_bot.repo if sweep_bot is not None else repo | |
# Check if the pull request from this branch to main already exists. | |
# If it does, then we don't need to create a new one. | |
if repo is not None: | |
pull_requests = repo.get_pulls( | |
state="open", | |
sort="created", | |
base=SweepConfig.get_branch(repo) | |
if sweep_bot is not None | |
else repo.default_branch, | |
head=branch_name, | |
) | |
for pr in pull_requests: | |
if pr.title == title: | |
return pr | |
logger.print("Default branch", repo.default_branch) | |
logger.print("New branch", branch_name) | |
pr = repo.create_pull( | |
title=title, | |
body="""π Thank you for installing Sweep! We're thrilled to announce the latest update for Sweep, your AI junior developer on GitHub. This PR creates a `sweep.yaml` config file, allowing you to personalize Sweep's performance according to your project requirements. | |
## What's new? | |
- **Sweep is now configurable**. | |
- To configure Sweep, simply edit the `sweep.yaml` file in the root of your repository. | |
- If you need help, check out the [Sweep Default Config](https://github.com/sweepai/sweep/blob/main/sweep.yaml) or [Join Our Discourse](https://community.sweep.dev/) for help. | |
If you would like me to stop creating this PR, go to issues and say "Sweep: create an empty `sweep.yaml` file". | |
Thank you for using Sweep! π§Ή""".replace( | |
" ", "" | |
), | |
head=branch_name, | |
base=SweepConfig.get_branch(repo) | |
if sweep_bot is not None | |
else repo.default_branch, | |
) | |
pr.add_to_labels(GITHUB_LABEL_NAME) | |
return pr | |
def add_config_to_top_repos(installation_id, username, repositories, max_repos=3): | |
user_token, g = get_github_client(installation_id) | |
repo_activity = {} | |
for repo_entity in repositories: | |
repo = g.get_repo(repo_entity.full_name) | |
# instead of using total count, use the date of the latest commit | |
commits = repo.get_commits( | |
author=username, | |
since=datetime.datetime.now() - datetime.timedelta(days=30), | |
) | |
# get latest commit date | |
commit_date = datetime.datetime.now() - datetime.timedelta(days=30) | |
for commit in commits: | |
if commit.commit.author.date > commit_date: | |
commit_date = commit.commit.author.date | |
# since_date = datetime.datetime.now() - datetime.timedelta(days=30) | |
# commits = repo.get_commits(since=since_date, author="lukejagg") | |
repo_activity[repo] = commit_date | |
# print(repo, commits.totalCount) | |
logger.print(repo, commit_date) | |
sorted_repos = sorted(repo_activity, key=repo_activity.get, reverse=True) | |
sorted_repos = sorted_repos[:max_repos] | |
# For each repo, create a branch based on main branch, then create PR to main branch | |
for repo in sorted_repos: | |
try: | |
logger.print("Creating config for", repo.full_name) | |
create_config_pr( | |
None, | |
repo=repo, | |
cloned_repo=ClonedRepo( | |
repo_full_name=repo.full_name, | |
installation_id=installation_id, | |
token=user_token, | |
), | |
) | |
except SystemExit: | |
raise SystemExit | |
except Exception as e: | |
logger.print(e) | |
logger.print("Finished creating configs for top repos") | |
def create_gha_pr(g, repo): | |
# Create a new branch | |
branch_name = "sweep/gha-enable" | |
repo.create_git_ref( | |
ref=f"refs/heads/{branch_name}", | |
sha=repo.get_branch(repo.default_branch).commit.sha, | |
) | |
# Update the sweep.yaml file in this branch to add "gha_enabled: True" | |
sweep_yaml_content = ( | |
repo.get_contents("sweep.yaml", ref=branch_name).decoded_content.decode() | |
+ "\ngha_enabled: True" | |
) | |
repo.update_file( | |
"sweep.yaml", | |
"Enable GitHub Actions", | |
sweep_yaml_content, | |
repo.get_contents("sweep.yaml", ref=branch_name).sha, | |
branch=branch_name, | |
) | |
# Create a PR from this branch to the main branch | |
pr = repo.create_pull( | |
title="Enable GitHub Actions", | |
body="This PR enables GitHub Actions for this repository.", | |
head=branch_name, | |
base=repo.default_branch, | |
) | |
return pr | |
SWEEP_TEMPLATE = """\ | |
name: Sweep Issue | |
title: 'Sweep: ' | |
description: For small bugs, features, refactors, and tests to be handled by Sweep, an AI-powered junior developer. | |
labels: sweep | |
body: | |
- type: textarea | |
id: description | |
attributes: | |
label: Details | |
description: Tell Sweep where and what to edit and provide enough context for a new developer to the codebase | |
placeholder: | | |
Unit Tests: Write unit tests for <FILE>. Test each function in the file. Make sure to test edge cases. | |
Bugs: The bug might be in <FILE>. Here are the logs: ... | |
Features: the new endpoint should use the ... class from <FILE> because it contains ... logic. | |
Refactors: We are migrating this function to ... version because ... | |
- type: input | |
id: branch | |
attributes: | |
label: Branch | |
description: The branch to work off of (optional) | |
placeholder: | |
Step 2: β¨οΈ Coding
Modify sweepai/handlers/on_comment.py with contents: Update line 370 to unpack the 4 return values from `handle_file_change_requests`.<original_code>
try:
search_query = (comment).strip("\n")
formatted_query = (f"{comment}").strip("\n")
repo_context_manager = prep_snippets(
cloned_repo, search_query, TicketProgress(tracking_id="none")
)
snippets = repo_context_manager.current_top_snippets
tree = str(repo_context_manager.dir_obj)
cloned_repo = repo_context_manager.cloned_repo
except Exception as e:
logger.error(traceback.format_exc())
raise e
get_documentation_dict(repo)
docs_results = ""
logger.info("Getting response from ChatGPT...")
human_message = HumanMessageCommentPrompt(
comment=comment,
repo_name=repo_name,
repo_description=repo_description if repo_description else "",
diffs=diffs,
title=pr_title,
tree=tree,
summary=pr_body,
snippets=snippets,
pr_file_path=pr_file_path, # may be None
pr_chunk=formatted_pr_chunk, # may be None
original_line=original_line if pr_chunk else None,
relevant_docs=docs_results,
)
logger.info(f"Human prompt{human_message.construct_prompt()}")if use_faster_model: raise Exception("GPT-3.5 is not supported for comments") sweep_bot = SweepBot.from_system_message_content( # human_message=human_message, model="claude-v1.3-100k", repo=repo human_message=human_message, repo=repo, chat_logger=chat_logger, model=DEFAULT_GPT4_MODEL, cloned_repo=cloned_repo, ) except Exception as e: stack_trace = traceback.format_exc() logger.error(stack_trace) elapsed_time = time.time() - start_time posthog.capture( username, "failed", properties={ "error": str(e), "reason": f"An error occured! The stack trace is below:\n\n{stack_trace}", "duration": elapsed_time, "tracking_id": tracking_id, **metadata, }, ) edit_comment(ERROR_FORMAT.format(title=f"An error occured!\n\nThe exception message is:{str(e)}\n\nThe stack trace is:{stack_trace}")) raise e try: logger.info("Fetching files to modify/create...") if file_comment: formatted_query = f"The user left this comment: <comment>\n{comment}\n</comment>\nThis was where they left their comment:\n<review_code_chunk>\n{formatted_pr_chunk}\n</review_code_chunk>.\n\nResolve their comment." file_change_requests, plan = get_files_to_change( relevant_snippets=repo_context_manager.current_top_snippets, read_only_snippets=repo_context_manager.read_only_snippets, problem_statement=formatted_query, repo_name=repo_name, pr_diffs=pr_diff_string, cloned_repo=cloned_repo, ) validate_file_change_requests(file_change_requests, repo_context_manager.cloned_repo) file_change_requests = sweep_bot.validate_file_change_requests( file_change_requests, branch=branch_name ) sweep_response = "I couldn't find any relevant files to change." if file_change_requests: table_message = tabulate( [ [ f"`{file_change_request.filename}`", file_change_request.instructions_display.replace( "\n", "<br/>" ).replace("```", "\\```"), ] for file_change_request in file_change_requests ], headers=["File Path", "Proposed Changes"], tablefmt="pipe", ) sweep_response = ( f"I am making the following changes:\n\n{table_message}" ) quoted_comment = "> " + comment.replace("\n", "\n> ") response_for_user = ( f"{quoted_comment}\n\nHi @{username},\n\n{sweep_response}" ) if pr_number: edit_comment(response_for_user) sweep_bot.comment_pr_diff_str = pr_diff_string sweep_bot.comment_pr_files_modified = pr_files_modified modify_files_dict, changes_made, _ = handle_file_change_requests( file_change_requests=file_change_requests, request=file_comment, branch_name=branch_name, sweep_bot=sweep_bot, username=username, installation_id=installation_id, chat_logger=chat_logger, )
</original_code>
<new_code>
try:
search_query = (comment).strip("\n")
formatted_query = (f"{comment}").strip("\n")
repo_context_manager = prep_snippets(
cloned_repo, search_query, TicketProgress(tracking_id="none")
)
snippets = repo_context_manager.current_top_snippets
tree = str(repo_context_manager.dir_obj)
cloned_repo = repo_context_manager.cloned_repo
except Exception as e:
logger.error(traceback.format_exc())
raise e
get_documentation_dict(repo)
docs_results = ""
logger.info("Getting response from ChatGPT...")
human_message = HumanMessageCommentPrompt(
comment=comment,
repo_name=repo_name,
repo_description=repo_description if repo_description else "",
diffs=diffs,
title=pr_title,
tree=tree,
summary=pr_body,
snippets=snippets,
pr_file_path=pr_file_path, # may be None
pr_chunk=formatted_pr_chunk, # may be None
original_line=original_line if pr_chunk else None,
relevant_docs=docs_results,
)
logger.info(f"Human prompt{human_message.construct_prompt()}")if use_faster_model: raise Exception("GPT-3.5 is not supported for comments") sweep_bot = SweepBot.from_system_message_content( # human_message=human_message, model="claude-v1.3-100k", repo=repo human_message=human_message, repo=repo, chat_logger=chat_logger, model=DEFAULT_GPT4_MODEL, cloned_repo=cloned_repo, ) except Exception as e: stack_trace = traceback.format_exc() logger.error(stack_trace) elapsed_time = time.time() - start_time posthog.capture( username, "failed", properties={ "error": str(e), "reason": f"An error occured! The stack trace is below:\n\n{stack_trace}", "duration": elapsed_time, "tracking_id": tracking_id, **metadata, }, ) edit_comment(ERROR_FORMAT.format(title=f"An error occured!\n\nThe exception message is:{str(e)}\n\nThe stack trace is:{stack_trace}")) raise e try: logger.info("Fetching files to modify/create...") if file_comment: formatted_query = f"The user left this comment: <comment>\n{comment}\n</comment>\nThis was where they left their comment:\n<review_code_chunk>\n{formatted_pr_chunk}\n</review_code_chunk>.\n\nResolve their comment." file_change_requests, plan = get_files_to_change( relevant_snippets=repo_context_manager.current_top_snippets, read_only_snippets=repo_context_manager.read_only_snippets, problem_statement=formatted_query, repo_name=repo_name, pr_diffs=pr_diff_string, cloned_repo=cloned_repo, ) validate_file_change_requests(file_change_requests, repo_context_manager.cloned_repo) file_change_requests = sweep_bot.validate_file_change_requests( file_change_requests, branch=branch_name ) sweep_response = "I couldn't find any relevant files to change." if file_change_requests: table_message = tabulate( [ [ f"`{file_change_request.filename}`", file_change_request.instructions_display.replace( "\n", "<br/>" ).replace("```", "\\```"), ] for file_change_request in file_change_requests ], headers=["File Path", "Proposed Changes"], tablefmt="pipe", ) sweep_response = ( f"I am making the following changes:\n\n{table_message}" ) quoted_comment = "> " + comment.replace("\n", "\n> ") response_for_user = ( f"{quoted_comment}\n\nHi @{username},\n\n{sweep_response}" ) if pr_number: edit_comment(response_for_user) sweep_bot.comment_pr_diff_str = pr_diff_string sweep_bot.comment_pr_files_modified = pr_files_modified modify_files_dict, changes_made, _, file_change_requests = handle_file_change_requests( file_change_requests=file_change_requests, request=file_comment, branch_name=branch_name, sweep_bot=sweep_bot, username=username, installation_id=installation_id, chat_logger=chat_logger, )
</new_code>
Step 3: π Code Review
I have finished reviewing the code for completeness. I did not find errors for sweep/fix_the_following_bug
.
π Latest improvements to Sweep:
- New dashboard launched for real-time tracking of Sweep issues, covering all stages from search to coding.
- Integration of OpenAI's latest Assistant API for more efficient and reliable code planning and editing, improving speed by 3x.
- Use the GitHub issues extension for creating Sweep issues directly from your editor.
π‘ To recreate the pull request edit the issue title or description.
Something wrong? Let us know.
This is an automated message generated by Sweep AI.