Compare commits

..

6 Commits

9 changed files with 77 additions and 48 deletions

View File

@ -8,7 +8,7 @@ jobs:
steps: steps:
- name: PR Agent action step - name: PR Agent action step
id: pragent id: pragent
uses: Codium-ai/pr-agent@feature/github_action uses: Codium-ai/pr-agent@main
env: env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }} OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
OPENAI_ORG: ${{ secrets.OPENAI_ORG }} OPENAI_ORG: ${{ secrets.OPENAI_ORG }}

View File

View File

@ -0,0 +1 @@
FROM codiumai/pr-agent:github_action

View File

@ -2,4 +2,4 @@ name: 'PR Agent'
description: 'Summarize, review and suggest improvements for pull requests' description: 'Summarize, review and suggest improvements for pull requests'
runs: runs:
using: 'docker' using: 'docker'
image: 'Dockerfile.github_action' image: 'Dockerfile.github_action_dockerhub'

View File

@ -10,48 +10,29 @@ from pr_agent.tools.pr_reviewer import PRReviewer
def run(): def run():
parser = argparse.ArgumentParser(description='AI based pull request analyzer', usage="""\ parser = argparse.ArgumentParser(description='AI based pull request analyzer')
Usage: cli.py --pr-url <URL on supported git hosting service> <command> [<args>].
Supported commands:
review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement.
ask / ask_question [question] - Ask a question about the PR.
describe / describe_pr - Modify the PR title and description based on the PR's contents.
improve / improve_code - Suggest improvements to the code in the PR as pull request comments ready to commit.
""")
parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', required=True) parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', required=True)
parser.add_argument('command', type=str, help='The', choices=['review', 'review_pr', parser.add_argument('--question', type=str, help='Optional question to ask', required=False)
'ask', 'ask_question', parser.add_argument('--pr_description', action='store_true', required=False)
'describe', 'describe_pr', parser.add_argument('--pr_code_suggestions', action='store_true', required=False)
'improve', 'improve_code'], default='review')
parser.add_argument('rest', nargs=argparse.REMAINDER, default=[])
args = parser.parse_args() args = parser.parse_args()
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO")) logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
command = args.command.lower() if args.question:
if command in ['ask', 'ask_question']: print(f"Question: {args.question} about PR {args.pr_url}")
question = ' '.join(args.rest).strip() reviewer = PRQuestions(args.pr_url, args.question)
if len(question) == 0:
print("Please specify a question")
parser.print_help()
return
print(f"Question: {question} about PR {args.pr_url}")
reviewer = PRQuestions(args.pr_url, question)
asyncio.run(reviewer.answer()) asyncio.run(reviewer.answer())
elif command in ['describe', 'describe_pr']: elif args.pr_description:
print(f"PR description: {args.pr_url}") print(f"PR description: {args.pr_url}")
reviewer = PRDescription(args.pr_url) reviewer = PRDescription(args.pr_url)
asyncio.run(reviewer.describe()) asyncio.run(reviewer.describe())
elif command in ['improve', 'improve_code']: elif args.pr_code_suggestions:
print(f"PR code suggestions: {args.pr_url}") print(f"PR code suggestions: {args.pr_url}")
reviewer = PRCodeSuggestions(args.pr_url) reviewer = PRCodeSuggestions(args.pr_url)
asyncio.run(reviewer.suggest()) asyncio.run(reviewer.suggest())
elif command in ['review', 'review_pr']: else:
print(f"Reviewing PR: {args.pr_url}") print(f"Reviewing PR: {args.pr_url}")
reviewer = PRReviewer(args.pr_url, cli_mode=True) reviewer = PRReviewer(args.pr_url, cli_mode=True)
asyncio.run(reviewer.review()) asyncio.run(reviewer.review())
else:
print(f"Unknown command: {command}")
parser.print_help()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,8 +1,11 @@
import asyncio import asyncio
import json import json
import os import os
import re
from pr_agent.config_loader import settings from pr_agent.config_loader import settings
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
from pr_agent.tools.pr_description import PRDescription
from pr_agent.tools.pr_questions import PRQuestions from pr_agent.tools.pr_questions import PRQuestions
from pr_agent.tools.pr_reviewer import PRReviewer from pr_agent.tools.pr_reviewer import PRReviewer
@ -16,10 +19,11 @@ async def run_action():
if not GITHUB_EVENT_PATH: if not GITHUB_EVENT_PATH:
print("GITHUB_EVENT_PATH not set") print("GITHUB_EVENT_PATH not set")
return return
try:
event_payload = json.load(open(GITHUB_EVENT_PATH, 'r')) event_payload = json.load(open(GITHUB_EVENT_PATH, 'r'))
RUNNER_DEBUG = os.environ.get('RUNNER_DEBUG', None) except json.decoder.JSONDecodeError as e:
if not RUNNER_DEBUG: print(f"Failed to parse JSON: {e}")
print("RUNNER_DEBUG not set") return
OPENAI_KEY = os.environ.get('OPENAI_KEY', None) OPENAI_KEY = os.environ.get('OPENAI_KEY', None)
if not OPENAI_KEY: if not OPENAI_KEY:
print("OPENAI_KEY not set") print("OPENAI_KEY not set")
@ -48,10 +52,21 @@ async def run_action():
if comment_body: if comment_body:
pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url", None) pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url", None)
if pr_url: if pr_url:
if comment_body.strip().lower() == "review": body = comment_body.strip().lower()
if any(cmd in body for cmd in ["/review", "/review_pr"]):
await PRReviewer(pr_url).review() await PRReviewer(pr_url).review()
elif comment_body.lstrip().lower().startswith("answer"): elif any(cmd in body for cmd in ["/describe", "/describe_pr"]):
await PRQuestions(pr_url, comment_body).answer() await PRDescription(pr_url).describe()
elif any(cmd in body for cmd in ["/improve", "/improve_code"]):
await PRCodeSuggestions(pr_url).suggest()
elif any(cmd in body for cmd in ["/ask", "/ask_question"]):
pattern = r'(/ask|/ask_question)\s*(.*)'
matches = re.findall(pattern, comment_body, re.IGNORECASE)
if matches:
question = matches[0][1]
await PRQuestions(pr_url, question).answer()
else:
print(f"Unknown command: {body}")
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import logging import logging
import re
import sys import sys
from datetime import datetime, timezone from datetime import datetime, timezone
@ -8,6 +9,11 @@ import aiohttp
from pr_agent.agent.pr_agent import PRAgent from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import settings from pr_agent.config_loader import settings
from pr_agent.git_providers import get_git_provider from pr_agent.git_providers import get_git_provider
from pr_agent.servers.help import bot_help_text
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
from pr_agent.tools.pr_description import PRDescription
from pr_agent.tools.pr_questions import PRQuestions
from pr_agent.tools.pr_reviewer import PRReviewer
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
NOTIFICATION_URL = "https://api.github.com/notifications" NOTIFICATION_URL = "https://api.github.com/notifications"
@ -83,8 +89,24 @@ 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()
agent = PRAgent()
await agent.handle_request(pr_url, rest_of_comment) if any(cmd in rest_of_comment for cmd in ["/review", "/review_pr"]):
await PRReviewer(pr_url).review()
elif any(cmd in rest_of_comment for cmd in ["/describe", "/describe_pr"]):
await PRDescription(pr_url).describe()
elif any(cmd in rest_of_comment for cmd in ["/improve", "/improve_code"]):
await PRCodeSuggestions(pr_url).suggest()
elif any(cmd in rest_of_comment for cmd in ["/ask", "/ask_question"]):
pattern = r'(/ask|/ask_question)\s*(.*)'
matches = re.findall(pattern, rest_of_comment, re.IGNORECASE)
if matches:
question = matches[0][1]
await PRQuestions(pr_url, question).answer()
else:
git_provider.set_pr(pr_url)
git_provider.publish_comment("### How to user PR-Agent\n" +
bot_help_text(user_id))
elif response.status != 304: elif response.status != 304:
print(f"Failed to fetch notifications. Status code: {response.status}") print(f"Failed to fetch notifications. Status code: {response.status}")

14
pr_agent/servers/help.py Normal file
View File

@ -0,0 +1,14 @@
commands_text = "> /review - Ask for a new review after your update the PR\n" \
"> /describe - Modify the PR title and description based " \
"on the PR's contents.\n" \
"> /improve - Suggest improvements to the code in the PR as pull " \
"request comments ready to commit.\n" \
"> /ask <QUESTION> - Ask a question about the PR.\n"
def bot_help_text(user: str):
return f"> Tag me in a comment '@{user}' and add one of the following commands:\n" + commands_text
actions_help_text = "> Add a comment to to invoke PR-Agent, use one of the following commands:\n" + \
commands_text

View File

@ -11,6 +11,7 @@ from pr_agent.algo.utils import convert_to_markdown, try_fix_json
from pr_agent.config_loader import settings from pr_agent.config_loader import settings
from pr_agent.git_providers import get_git_provider from pr_agent.git_providers import get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language from pr_agent.git_providers.git_provider import get_main_pr_language
from pr_agent.servers.help import bot_help_text, actions_help_text
class PRReviewer: class PRReviewer:
@ -97,14 +98,9 @@ class PRReviewer:
if not self.cli_mode: if not self.cli_mode:
markdown_text += "\n### How to use\n" markdown_text += "\n### How to use\n"
if user and '[bot]' not in user: if user and '[bot]' not in user:
markdown_text += f"> Tag me in a comment '@{user}' to ask for a new review after you update the PR.\n" markdown_text += bot_help_text(user)
markdown_text += "> You can also tag me and ask any question, " \
f"for example '@{user} is the PR ready for merge?'"
else: else:
markdown_text += "> Add a comment that says 'review' to ask for a new review " \ markdown_text += actions_help_text
"after you update the PR.\n"
markdown_text += "> You can also add a comment that says 'answer QUESTION', " \
"for example 'answer is the PR ready for merge?'"
if settings.config.verbosity_level >= 2: if settings.config.verbosity_level >= 2:
logging.info(f"Markdown response:\n{markdown_text}") logging.info(f"Markdown response:\n{markdown_text}")