LLM

[ PR Agent ] CLI 방식 동작구조

코줍 2025. 5. 8. 22:58

PR Agent 를 로컬에서 실행해보았다. 

CLI 방식으로 실행해보았고, 동작구조를 살펴보기 위해 pr_agent/cli.py 파일을 들여다 보았다. 

 

동작구조 도식화


0. cli.py 파일 트리거

cli.py 파일이 실행되면, run 메소드가 실행된다.

if __name__ == '__main__':
    run()

 

1. CLI argument parser

run 메소드가 실행되면, 가장 먼저 set_parser 즉, CLI로 입력된 커맨드라인 인자값을 파싱하는 메소드(=set_parser)가 실행된다.

def run(inargs=None, args=None):
    parser = set_parser()

 

set_parser() 메소드

PR-Agent를 실행할 때 어떤 인자를 받을지 정의하는 역할을 한다.

def set_parser():
    parser = argparse.ArgumentParser(description='AI based pull request analyzer', usage="...")
    parser.add_argument('--version', action='version', version=f'pr-agent {get_version()}')
    parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', default=None)
    parser.add_argument('--issue_url', type=str, help='The URL of the Issue to review', default=None)
    parser.add_argument('command', type=str, help='The', choices=commands, default='review')
    parser.add_argument('rest', nargs=argparse.REMAINDER, default=[])
    return parser
  • PR-Agent 버전 확인 : parser.add_argument('--version', action='version', version=f'pr-agent {get_version()}') 
  • 분석할 PR URL : parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', default=None)
  • 분석할 Issue URL : parser.add_argument('--issue_url', type=str, help='The URL of the Issue to review', default=None)
  • 실행할 커맨드를 선택 ( review, describe, improve 등 ) : parser.add_argument('command', type=str, help='The', choices=commands, default='review')
  • 나머지 추가 인자를 모두 문자열 리스트 형태로 수집 ( ask "What does this PR do?" ): parser.add_argument('rest', nargs=argparse.REMAINDER, default=[])

 

2. 입력인자 유효성 체크 및 분기처리

 

인자값 없을 때, 보완처리

if not args:
        args = parser.parse_args(inargs)
  • 인자가 비어 있다면, inargs를 파싱해서 args에 넣는다.
  • 즉, 사용자가 명령어를 입력하지 않고 실행했을 경우, inargs로 받은 실제 커맨드라인 인자를 사용하도록 보완하는 처리다.

--pr_url이나 --issue_url 둘 다 입력되지 않았을 경우

 

 
if not args.pr_url and not args.issue_url:
    parser.print_help()
    return

 

  • CLI 사용법과 명령어 목록이 포함된 도움말 메시지를 출력하고,
  • 프로그램 실행을 중단(return)한다.

 

3. 비동기 루틴 async def inner()

3.1 pr / issue 분기처리

if args.issue_url:
    result = await asyncio.create_task(
        PRAgent().handle_request(args.issue_url, [command] + args.rest)
    )
else:
    result = await asyncio.create_task(
        PRAgent().handle_request(args.pr_url, [command] + args.rest)
    )

 

  • PRAgent().handle_request(...)가 핵심 처리 함수로, 내부적으로 LLM을 호출하거나 코드 리뷰를 실행하는 기능이 여기에 포함된다.
  • command는 review, improve, ask 등 사용자가 CLI에 입력한 명령어이며, args.rest는 추가 인자를 리스트로 전달한다.
  • issue_url이 있으면 해당 이슈를, 없으면 PR을 대상으로 한다.
  • asyncio.create_task(...)로 작업을 비동기 태스크로 실행하며, await로 그 결과를 기다린다.

📌 pr_agent.py/handle_request 메소드를 잠시 보자면, 

# pr_agent.py

class PRAgent:
    def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
        self.ai_handler = ai_handler  # will be initialized in run_action

    async def handle_request(self, pr_url, request, notify=None) -> bool:
        # First, apply repo specific settings if exists
        apply_repo_settings(pr_url)

        # Then, apply user specific settings if exists
        if isinstance(request, str):
            request = request.replace("'", "\\'")
            lexer = shlex.shlex(request, posix=True)
            lexer.whitespace_split = True
            action, *args = list(lexer)
        else:
            action, *args = request

        # validate args
        is_valid, arg = CliArgs.validate_user_args(args)
        if not is_valid:
            get_logger().error(
                f"CLI argument for param '{arg}' is forbidden. Use instead a configuration file."
            )
            return False

        # Update settings from args
        args = update_settings_from_args(args)

        # Append the response language in the extra instructions
        response_language = get_settings().config.get('response_language', 'en-us')
        if response_language.lower() != 'en-us':
            get_logger().info(f'User has set the response language to: {response_language}')
            for key in get_settings():
                setting = get_settings().get(key)
                if str(type(setting)) == "<class 'dynaconf.utils.boxing.DynaBox'>":
                    if hasattr(setting, 'extra_instructions'):
                        current_extra_instructions = setting.extra_instructions
                        if current_extra_instructions:
                            setting.extra_instructions = current_extra_instructions+ f"\n======\n\nIn addition, Your response MUST be written in the language corresponding to local code: {response_language}. This is crucial."
                        else:
                            setting.extra_instructions = f"Your response MUST be written in the language corresponding to locale code: '{response_language}'. This is crucial."

        action = action.lstrip("/").lower()
        if action not in command2class:
            get_logger().warning(f"Unknown command: {action}")
            return False
        with get_logger().contextualize(command=action, pr_url=pr_url):
            get_logger().info("PR-Agent request handler started", analytics=True)
            if action == "answer":
                if notify:
                    notify()
                await PRReviewer(pr_url, is_answer=True, args=args, ai_handler=self.ai_handler).run()
            elif action == "auto_review":
                await PRReviewer(pr_url, is_auto=True, args=args, ai_handler=self.ai_handler).run()
            elif action in command2class:
                if notify:
                    notify()

                await command2class[action](pr_url, ai_handler=self.ai_handler, args=args).run()
            else:
                return False
            return True

 

  • PR URL 기반으로 레포지토리 및 사용자 설정을 적용한다.
  • 입력받은 request 문자열을 파싱해 실행할 액션과 인자를 분리한다.
  • 금지된 인자가 포함되어 있는지 확인하고, 있으면 에러 로그 출력 후 종료한다. ( return False ) 
  • 설정 파일에서 응답 언어(response_language)를 가져와, 영어가 아닐 경우 AI에게 언어 지시를 추가로 부여함.
  • 인식 가능한 명령어이면 command2class를 통해 해당 클래스의 run() 메서드를 실행하고, 성공 여부를 True 또는 False로 반환함.

 


3.2 비동기 콜백처리 

if get_settings().litellm.get("enable_callbacks", False):
    get_logger().debug("Waiting for event queue to complete")
    await asyncio.wait([
        task for task in asyncio.all_tasks()
        if task is not asyncio.current_task()
    ])
  • enable_callbacks 설정이 켜져 있다면, PR-Agent는 LLM 호출에 따른 Task Queue를 기다리게 된다. 예를 들어 웹훅 전송, 결과 로깅, 후처리 작업 등이 있을 경우 그 태스크들이 완료될 때까지 기다리는 처리다.
  • 현재 실행 중인 작업은 제외하고, 나머지 모든 태스크를 기다린다. ( if task is not asyncio.current_task() ~ )

3.3 결과반환

return result
  • 위에서 수행한 PRAgent().handle_request()의 결과를 반환한다.
  • 이 결과는 CLI에서 사용자에게 보여질 수도 있고, 후속 로직에 사용될 수도 있다.

'LLM' 카테고리의 다른 글

[PR Agent] 1. 환경설정 및 테스트  (0) 2025.05.02