PR-Agent의 /describe
명령어는 AI 모델에게 특정 형식의 프롬프트를 제공하여 풍부하고 구조화된 PR 설명을 생성하도록 합니다. 이 글에서는 이 프롬프트의 구조와 기능을 상세히 분석해 보겠습니다.
PR-Agent가 AI와 소통할 때는 두 가지 주요 메시지를 보냅니다:
이 프롬프트들은 pr_agent/settings/pr_description_prompts.toml
파일에 정의되어 있으며, 각각 AI의 역할과 수행할 작업을 세부적으로 안내합니다.
시스템 프롬프트는 AI에게 어떤 역할을 맡아야 하는지, 어떤 형식으로 응답해야 하는지 알려줍니다. 가장 먼저 AI의 정체성과 임무를 설정합니다:
You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
Your task is to provide a full description for the PR content - type, description, title and files walkthrough.
이 간단한 소개로 AI는 자신이 PR 리뷰어라는 것과 주요 임무가 PR 내용 설명임을 이해합니다.
다음으로 중요한 점은 AI에게 무엇에 집중해야 하는지 구체적으로 알려주는 것입니다:
- Focus on the new PR code (lines starting with '+' in the 'PR Git Diff' section).
- Keep in mind that the 'Previous title', 'Previous description' and 'Commit messages' sections may be partial, simplistic, non-informative or out of date.
- The generated title and description should prioritize the most significant changes.
이 지시사항들이 정말 중요한데요, 특히 "새로 추가된 코드에 집중하라"는 부분과 "기존 제목이나 설명은 참고만 하라"는 부분은 AI가 실제 코드 변경에 기반하여 설명을 작성하도록 유도합니다. 이전 설명이 부실하더라도 AI가 코드 자체를 분석해 정확한 설명을 만들 수 있는 이유죠!
가장 흥미로운 부분은 AI에게 원하는 출력 형식을 Python의 Pydantic 모델처럼 정의하는 부분입니다:
class PRType(str, Enum):
bug_fix = "Bug fix"
tests = "Tests"
enhancement = "Enhancement"
documentation = "Documentation"
other = "Other"
class FileDescription(BaseModel):
filename: str = Field(description="The full file path of the relevant file")
changes_summary: str = Field(description="concise summary of the changes in the relevant file")
changes_title: str = Field(description="one-line summary capturing the main theme of changes")
label: str = Field(description="a single semantic label that represents a type of code changes")
class PRDescription(BaseModel):
type: List[PRType] = Field(description="one or more types that describe the PR content")
description: str = Field(description="summarize the PR changes in up to four bullet points")
title: str = Field(description="a concise and descriptive title that captures the PR's main theme")
pr_files: List[FileDescription] = Field(description="a list of all the files that were changed")
이런 명확한 구조 정의 덕분에 AI는 정확히 어떤 정보를 어떤 형식으로 제공해야 하는지 이해하게 됩니다. 각 필드에 대한 설명까지 붙여서 AI가 각 섹션에 어떤 내용을 채워야 할지 명확히 알 수 있죠.
한 가지 중요한 점은 이 구조가 동적으로 조정될 수 있다는 것입니다:
{%- if enable_semantic_files_types %}
pr_files: List[FileDescription] = Field(...)
{%- endif %}
이런 조건문을 통해 PR-Agent 설정에 따라 출력 형식이 달라질 수 있습니다. 예를 들어, 의미론적 파일 분류 기능을 켜거나 끄는 것에 따라 출력 형식이 조정되죠.
사용자 프롬프트는 AI에게 실제 PR 데이터를 제공합니다. 여기에는 여러 요소가 포함됩니다:
{%- if related_tickets %}
Related Ticket Info:
{% for ticket in related_tickets %}
=====
Ticket Title: '{{ ticket.title }}'
...
{% endfor %}
{%- endif %}
PR과 연관된 티켓이 있다면, 이 정보를 통해 AI는 PR의 목적과 맥락을 더 잘 이해할 수 있습니다.
PR Info:
Previous title: '{{title}}'
Previous description:
=====
{{ description|trim }}
=====
Branch: '{{branch}}'
Commit messages:
=====
{{ commit_messages_str|trim }}
=====
기존 PR 제목, 설명, 브랜치 이름, 커밋 메시지 등의 정보는 AI가 분석을 시작하는 기본 정보를 제공합니다. 물론 시스템 프롬프트에서 언급했듯이, 이 정보는 참고용으로만 사용됩니다.
The PR Git Diff:
=====
{{ diff|trim }}
=====
Note that lines in the diff body are prefixed with a symbol that represents the type of change: '-' for deletions, '+' for additions, and ' ' (a space) for unchanged lines.
가장 중요한 부분은 바로 Git Diff입니다. 이것은 실제 코드 변경 내용을 담고 있으며, AI는 이를 분석하여 PR의 실제 의미를 파악합니다. AI에게 diff 라인 접두사의 의미도 설명해주어 변경 유형을 정확히 이해할 수 있게 합니다.
PR-Agent는 Jinja2 템플릿 엔진을 사용하여 프롬프트를 동적으로 생성합니다. 이는 몇 가지 멋진 기능을 가능하게 합니다:
{%- if enable_semantic_files_types %}
pr_files: List[FileDescription] = ...
{%- endif %}
설정에 따라 프롬프트 내용이 달라질 수 있습니다. 예를 들어, 의미론적 파일 분류 기능을 활성화했는지에 따라 관련 섹션을 포함하거나 제외할 수 있죠.
Previous title: '{{title}}'
실제 PR 데이터(제목, 설명, diff 등)를 프롬프트에 동적으로 삽입할 수 있습니다.
{% for ticket in related_tickets %}
Ticket Title: '{{ ticket.title }}'
...
{% endfor %}
여러 항목(티켓, 파일 등)을 반복적으로 처리할 수 있습니다.
대규모 PR을 처리할 때는 특별한 접근 방식이 필요합니다. PR-Agent는 이를 위해 두 개의 특수 프롬프트를 사용합니다:
파일 전용 프롬프트: 변경된 파일 목록과 각 파일의 변경 내용만 분석
# pr_description_only_files_prompts
설명 전용 프롬프트: PR 전체에 대한 제목, 유형, 설명만 생성
# pr_description_only_description_prompts
이렇게 큰 PR을 작은 조각으로 나누어 처리하면 AI의 토큰 제한을 효과적으로 관리하면서도 전체 PR을 포괄적으로 분석할 수 있습니다.
if not get_settings().pr_description.async_ai_calls:
# 동기 처리 (순차적)
for patches in patches_compressed_list:
# 각 청크 처리
else: # 비동기 처리 (병렬)
tasks = []
for patches in patches_compressed_list:
# 모든 청크를 동시에 처리
results = await asyncio.gather(*tasks)
비동기 처리를 활성화하면 여러 청크를 병렬로 처리하여 대규모 PR도 빠르게 분석할 수 있습니다.
실제로 프롬프트가 어떻게 적용되는지 코드 레벨에서 살펴봅시다:
async def _get_prediction(self, model: str, patches_diff: str, prompt="pr_description_prompt") -> str:
# 변수 준비
variables = copy.deepcopy(self.vars)
variables["diff"] = patches_diff
# Jinja2 환경 설정
environment = Environment(undefined=StrictUndefined)
# 프롬프트 렌더링
system_prompt = environment.from_string(get_settings().get(prompt, {}).get("system", "")).render(variables)
user_prompt = environment.from_string(get_settings().get(prompt, {}).get("user", "")).render(variables)
# AI 모델 호출
response, finish_reason = await self.ai_handler.chat_completion(
model=model,
temperature=get_settings().config.temperature,
system=system_prompt,
user=user_prompt
)
return response
이 코드는 다음과 같은 과정을 거칩니다:
PR-Agent의 프롬프트 설계는 몇 가지 주요 장점을 제공합니다:
Pydantic 모델 형식을 사용한 출력 구조 정의는 AI가 일관된 형식의 응답을 생성하도록 유도합니다. 이는 응답 파싱과 처리를 훨씬 쉽게 만듭니다.
AI에게 무엇에 집중해야 하는지(새 코드), 무엇을 참고만 해야 하는지(기존 제목/설명), 어떻게 응답해야 하는지(YAML 형식) 등 세부적인 지시사항을 제공합니다.
템플릿 엔진을 활용한 조건부 콘텐츠는 다양한 설정 옵션에 따라 프롬프트를 동적으로 조정할 수 있게 합니다.
파일 전용/설명 전용 프롬프트 분리와 비동기 처리를 통해 대규모 PR도 효과적으로 분석할 수 있습니다.
PR-Agent의 /describe
프롬프트 설계에서 배울 수 있는 몇 가지 중요한 교훈이 있습니다:
PR-Agent의 프롬프트 설계는 AI의 능력을 효과적으로 활용하여 유용하고 통찰력 있는 PR 설명을 생성하는 좋은 사례입니다. 이러한 설계 원칙은 다른 AI 애플리케이션에도 적용할 수 있으며, AI를 통해 더 가치 있는 결과물을 얻는 데 도움이 될 것입니다.