Merge branch 'main' into zmeir-fallback_deployments

This commit is contained in:
Zohar Meir
2023-08-08 11:00:13 +03:00
committed by GitHub
10 changed files with 81 additions and 23 deletions

View File

@ -37,7 +37,7 @@ class PRAgent:
def __init__(self): def __init__(self):
pass pass
async def handle_request(self, pr_url, request) -> bool: async def handle_request(self, pr_url, request, notify=None) -> bool:
# First, apply repo specific settings if exists # First, apply repo specific settings if exists
if get_settings().config.use_repo_settings_file: if get_settings().config.use_repo_settings_file:
repo_settings_file = None repo_settings_file = None
@ -67,8 +67,12 @@ class PRAgent:
if action == "reflect_and_review" and not get_settings().pr_reviewer.ask_and_reflect: if action == "reflect_and_review" and not get_settings().pr_reviewer.ask_and_reflect:
action = "review" action = "review"
if action == "answer": if action == "answer":
if notify:
notify()
await PRReviewer(pr_url, is_answer=True, args=args).run() await PRReviewer(pr_url, is_answer=True, args=args).run()
elif action in command2class: elif action in command2class:
if notify:
notify()
await command2class[action](pr_url, args=args).run() await command2class[action](pr_url, args=args).run()
else: else:
return False return False

View File

@ -1,13 +1,15 @@
import logging import logging
import litellm
import openai import openai
from litellm import acompletion
from openai.error import APIError, RateLimitError, Timeout, TryAgain from openai.error import APIError, RateLimitError, Timeout, TryAgain
from retry import retry from retry import retry
import litellm
from litellm import acompletion
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
import traceback
OPENAI_RETRIES=5 OPENAI_RETRIES = 5
class AiHandler: class AiHandler:
""" """
@ -81,15 +83,16 @@ class AiHandler:
f"{(' from deployment ' + deployment_id) if deployment_id else ''}" f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
) )
response = await acompletion( response = await acompletion(
model=model, model=model,
deployment_id=deployment_id, deployment_id=deployment_id,
messages=[ messages=[
{"role": "system", "content": system}, {"role": "system", "content": system},
{"role": "user", "content": user} {"role": "user", "content": user}
], ],
temperature=temperature, temperature=temperature,
azure=self.azure azure=self.azure,
) force_timeout=get_settings().config.ai_timeout
)
except (APIError, Timeout, TryAgain) as e: except (APIError, Timeout, TryAgain) as e:
logging.error("Error during OpenAI inference: ", e) logging.error("Error during OpenAI inference: ", e)
raise raise
@ -104,4 +107,4 @@ class AiHandler:
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"]
print(resp, finish_reason) print(resp, finish_reason)
return resp, finish_reason return resp, finish_reason

View File

@ -89,6 +89,12 @@ class BitbucketProvider:
def get_issue_comments(self): def get_issue_comments(self):
raise NotImplementedError("Bitbucket provider does not support issue comments yet") raise NotImplementedError("Bitbucket provider does not support issue comments yet")
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
return True
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
return True
@staticmethod @staticmethod
def _parse_pr_url(pr_url: str) -> Tuple[str, int]: def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
parsed_url = urlparse(pr_url) parsed_url = urlparse(pr_url)

View File

@ -3,6 +3,7 @@ from dataclasses import dataclass
# enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED) # enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED)
from enum import Enum from enum import Enum
from typing import Optional
class EDIT_TYPE(Enum): class EDIT_TYPE(Enum):
@ -88,6 +89,13 @@ class GitProvider(ABC):
def get_issue_comments(self): def get_issue_comments(self):
pass pass
@abstractmethod
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
pass
@abstractmethod
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
pass
def get_main_pr_language(languages, files) -> str: def get_main_pr_language(languages, files) -> str:
""" """

View File

@ -2,10 +2,10 @@ import logging
import hashlib import hashlib
from datetime import datetime from datetime import datetime
from typing import Optional, Tuple from typing import Optional, Tuple, Any
from urllib.parse import urlparse from urllib.parse import urlparse
from github import AppAuthentication, Auth, Github, GithubException from github import AppAuthentication, Auth, Github, GithubException, Reaction
from retry import retry from retry import retry
from starlette_context import context from starlette_context import context
@ -153,7 +153,7 @@ class GithubProvider(GitProvider):
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
position = find_line_number_of_relevant_line_in_file(self.diff_files, relevant_file.strip('`'), relevant_line_in_file) position, absolute_position = find_line_number_of_relevant_line_in_file(self.diff_files, relevant_file.strip('`'), relevant_line_in_file)
if position == -1: if position == -1:
if get_settings().config.verbosity_level >= 2: if get_settings().config.verbosity_level >= 2:
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}") logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
@ -263,6 +263,23 @@ class GithubProvider(GitProvider):
except Exception: except Exception:
return "" return ""
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
try:
reaction = self.pr.get_issue_comment(issue_comment_id).create_reaction("eyes")
return reaction.id
except Exception as e:
logging.exception(f"Failed to add eyes reaction, error: {e}")
return None
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
try:
self.pr.get_issue_comment(issue_comment_id).delete_reaction(reaction_id)
return True
except Exception as e:
logging.exception(f"Failed to remove eyes reaction, error: {e}")
return False
@staticmethod @staticmethod
def _parse_pr_url(pr_url: str) -> Tuple[str, int]: def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
parsed_url = urlparse(pr_url) parsed_url = urlparse(pr_url)
@ -393,4 +410,4 @@ class GithubProvider(GitProvider):
if get_settings().config.verbosity_level >= 2: if get_settings().config.verbosity_level >= 2:
logging.info(f"Failed adding line link, error: {e}") logging.info(f"Failed adding line link, error: {e}")
return "" return ""

View File

@ -287,6 +287,12 @@ class GitLabProvider(GitProvider):
except Exception: except Exception:
return "" return ""
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
return True
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
return True
def _parse_merge_request_url(self, merge_request_url: str) -> Tuple[str, int]: def _parse_merge_request_url(self, merge_request_url: str) -> Tuple[str, int]:
parsed_url = urlparse(merge_request_url) parsed_url = urlparse(merge_request_url)

View File

@ -4,6 +4,7 @@ import os
from pr_agent.agent.pr_agent import PRAgent from pr_agent.agent.pr_agent import PRAgent
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.tools.pr_reviewer import PRReviewer from pr_agent.tools.pr_reviewer import PRReviewer
@ -14,6 +15,8 @@ async def run_action():
OPENAI_KEY = os.environ.get('OPENAI_KEY') OPENAI_KEY = os.environ.get('OPENAI_KEY')
OPENAI_ORG = os.environ.get('OPENAI_ORG') OPENAI_ORG = os.environ.get('OPENAI_ORG')
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
# Check if required environment variables are set # Check if required environment variables are set
if not GITHUB_EVENT_NAME: if not GITHUB_EVENT_NAME:
@ -61,7 +64,9 @@ async def run_action():
pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url") pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url")
if pr_url: if pr_url:
body = comment_body.strip().lower() body = comment_body.strip().lower()
await PRAgent().handle_request(pr_url, body) comment_id = event_payload.get("comment", {}).get("id")
provider = get_git_provider()(pr_url=pr_url)
await PRAgent().handle_request(pr_url, body, notify=lambda: provider.add_eyes_reaction(comment_id))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -11,6 +11,7 @@ from starlette_context.middleware import RawContextMiddleware
from pr_agent.agent.pr_agent import PRAgent from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import get_settings, global_settings from pr_agent.config_loader import get_settings, global_settings
from pr_agent.git_providers import get_git_provider
from pr_agent.servers.utils import verify_signature from pr_agent.servers.utils import verify_signature
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
@ -80,7 +81,10 @@ async def handle_request(body: Dict[str, Any]):
return {} return {}
pull_request = body["issue"]["pull_request"] pull_request = body["issue"]["pull_request"]
api_url = pull_request.get("url") api_url = pull_request.get("url")
await agent.handle_request(api_url, comment_body) comment_id = body.get("comment", {}).get("id")
provider = get_git_provider()(pr_url=api_url)
await agent.handle_request(api_url, comment_body, notify=lambda: provider.add_eyes_reaction(comment_id))
elif action == "opened" or 'reopened' in action: elif action == "opened" or 'reopened' in action:
pull_request = body.get("pull_request") pull_request = body.get("pull_request")
@ -102,6 +106,7 @@ async def root():
def start(): def start():
# Override the deployment type to app # Override the deployment type to app
get_settings().set("GITHUB.DEPLOYMENT_TYPE", "app") get_settings().set("GITHUB.DEPLOYMENT_TYPE", "app")
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
middleware = [Middleware(RawContextMiddleware)] middleware = [Middleware(RawContextMiddleware)]
app = FastAPI(middleware=middleware) app = FastAPI(middleware=middleware)
app.include_router(router) app.include_router(router)

View File

@ -36,6 +36,7 @@ async def polling_loop():
git_provider = get_git_provider()() git_provider = get_git_provider()()
user_id = git_provider.get_user_id() user_id = git_provider.get_user_id()
agent = PRAgent() agent = PRAgent()
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
try: try:
deployment_type = get_settings().github.deployment_type deployment_type = get_settings().github.deployment_type
@ -98,8 +99,10 @@ async def polling_loop():
if user_tag not in comment_body: if user_tag not in comment_body:
continue continue
rest_of_comment = comment_body.split(user_tag)[1].strip() rest_of_comment = comment_body.split(user_tag)[1].strip()
comment_id = comment['id']
success = await agent.handle_request(pr_url, rest_of_comment) git_provider.set_pr(pr_url)
success = await agent.handle_request(pr_url, rest_of_comment,
notify=lambda: git_provider.add_eyes_reaction(comment_id)) # noqa E501
if not success: if not success:
git_provider.set_pr(pr_url) git_provider.set_pr(pr_url)
git_provider.publish_comment("### How to use PR-Agent\n" + git_provider.publish_comment("### How to use PR-Agent\n" +

View File

@ -7,6 +7,7 @@ publish_output_progress=true
verbosity_level=0 # 0,1,2 verbosity_level=0 # 0,1,2
use_extra_bad_extensions=false use_extra_bad_extensions=false
use_repo_settings_file=true use_repo_settings_file=true
ai_timeout=180
[pr_reviewer] # /review # [pr_reviewer] # /review #
require_focused_review=true require_focused_review=true