GitHub Issue의 우선순위와 영역을 Jev로 판별하여 GitHub Actions로 라벨링하기
요약
GitHub Issue의 우선순위와 영역을 Jev 모델로 판별하여 GitHub Actions를 통해 자동으로 라벨링하는 시스템 구축 사례입니다. 이 글은 LLM과 달리 '판단'만 수행하는 System One 모델인 Jev를 활용하여, 이슈 본문에서 필요한 변경 영역이나 우선순위를 정확하게 분류하고 자동화하는 방법을 다룹니다.
핵심 포인트
- Jev는 생성 대신 판단만 하는 System One 모델로, 빠르고 저렴한 응답이 특징입니다.
- 판별의 정확도를 높이기 위해 질문 문구와 조건(criteria)을 구체적으로 설계하는 것이 중요합니다.
- 단순 '언급 여부'가 아닌 '주요 변경 영역인지'를 묻는 방식으로 프롬프팅을 개선했습니다.
개인 개발용 대시보드 앱에서 ToDo 관리를 GitHub Issue를 통해 하고 있습니다. 각 Issue에는 '변경 유형', '처리 우선순위', '변경이 필요한 대상(백엔드, 프론트엔드 등) 영역'의 세 가지 관점으로 라벨을 붙이고 있습니다. 변경 유형은 Issue 템플릿이 자동으로 붙여주지만, 우선순위와 영역은 이슈를 생성할 때마다 수동으로 선택해야 했고, 라벨링을 잊어버린 이슈들이 쌓여 있었습니다.
이에 따라, Issue가 생성되면 GitHub Actions에서 Jev에게 본문을 전달하여 우선순위와 영역을 판별하게 하고, 이를 바탕으로 라벨을 붙이는 시스템을 구축했습니다.
TypeSafe이 제공하는 System One이라는 종류의 모델입니다. 매우 간단히 말해 '판단만 수행하는 모델'입니다. ChatGPT나 Claude처럼 문장을 생성하지는 않으며, 답변의 형태는 다음 세 가지로 한정됩니다.
| 질문 유형 | 반환되는 값 | 적합한 용도 |
|---|---|---|
| Choice | 각 선택지의 확률과 가장 확률이 높은 선택지 | 정해진 선택지 중 하나를 고르는 분류. 문의를 어느 팀으로 배정할지, 기사는 어떤 카테고리인지 등 |
| ... |
Jev가 등장한 이후로도 여러 회사들에서 판단을 수행하는 모델들이 연이어 나오고 있습니다. OpenAI에서는 'Decisions API', Microsoft에서는 'Microsoft-Decision-1', Strands Labs에서는 'Strands Decider 2B' 등 많은 모델이 있습니다. 각 모델의 사양은 다르지만, LLM과 비교했을 때 저렴하고 빠른 응답을 받는다는 점이 특징입니다.
이번 검증 환경입니다.
| 항목 | 값 |
|---|---|
| 모델 | jev-1.13 |
| ... |
운영 규칙으로 정한 것은 다음 두 가지입니다.
- 이미 붙어 있는 라벨은 수정하지 않는다.
- 확신이 들지 않는 판별은 붙이지 않는다.
- 우선순위는 confidence가 0.5 미만이거나, 영역은 yes의 확률이 0.5 미만이면 라벨을 붙이지 않는다.
공식 문서에 따르면 Jev는 영어를 주요 언어로 학습했기 때문에 정확도가 가장 높은 것도 영어입니다. 일본어를 포함한 CJK(중국어・일본어・한국어) 문장도 받을 수 있지만, 영어만큼의 정확도는 나오지 않는다고 되어 있습니다.
그래서 질문문과 선택지 설명은 모두 영어로 작성했습니다. 라벨 설명문 역시 리포지토리에 있는 한국어 정의를 번역하여 사용하고 있습니다. Issue의 제목과 본문은 그대로 일본어로 전송했습니다.
지금까지 제가 직접 라벨을 붙여왔던 기존 이슈들을 정답 데이터로 삼아, 질문문을 변경해가며 정확도를 측정해 보았습니다.
처음에는 영역별로 '이 영역과 관련되는가'라고 물었습니다. 이 방식의 경우, 예를 들어 프론트엔드 화면에 나타나는 에러에 대한 이야기라면, 원인이 백엔드여도 프론트엔드에 yes가 붙는 등, 언급만 된 영역까지 대상이 되어버리는 문제가 있었습니다.
질문을 '이 영역이 이 Issue를 해결하기 위해 변경이 필요한 주요 영역 중 하나인가?'로 바꾸고, no의 조건으로 '언급만 되었거나, 간접적으로 영향을 주거나, 문제가 발생한 장소일 뿐이라면 해당하지 않는다'라고 추가 설명을 덧붙이자 정확도가 향상되었습니다. Jev는 지시를 글자 그대로 읽기 때문에, 어디까지를 포함할지 문장으로 작성해두면 그대로 판별해 줍니다.
Noul(
instructions=f"Is {description} one of the main areas that must be changed to resolve this issue?",
criteria={
...
4가지 패턴으로 검증한 결과, 우선순위는 Score보다 Choice가 일치율이 더 높았습니다.
| 방식 | confidence 제외 기준 | 부착률 | 정답과 일치 | ±1단계 이내 |
|---|---|---|---|---|
| Choice | 없음 | 100% | 67% | 95% |
| ... |
Score는 기대값이 단계 사이에 떨어지기 때문에, 값의 처리 방식에 대해 결정할 필요가 있습니다. 이 방식에서는 반올림(예: 2.5를 3으로 간주)하여 판단했습니다.
Choice는 선택지별 확률이 그대로 나오기 때문에, 가장 확률이 높은 것만 취하면 되었습니다. 오류는 medium을 high로 판정하는 상향 조정이 주를 이루었고, ±1단계 이내에서는 95%가 포함되었습니다. 이번에는 라벨 부착률을 고려하여 0.5 이상의 confidence일 때 라벨을 붙이는 Choice 방식을 채택했습니다.
질문 문구는 "How urgently should this issue be addressed?"라고 묻고, 선택지 설명에는 라벨의 정의문을 그대로 영문화한 것을 사용했습니다.
Choice(
instructions="How urgently should this issue be addressed?",
criteria={
...
이하와 같은 GitHub Actions용 yml 파일을 작성했습니다.
name: Issue Triage
on:
issues:
...
스크립트에 전달하는 것은 Issue 번호뿐이며, 본문은 스크립트 측에서 gh issue view를 사용하여 가져옵니다.
requirements.txt
의 내용은 typesafe-sdk~=0.7.1 한 줄입니다. API 키는 리포지토리의 Secrets에 TYPESAFE_API_KEY로 등록해 둡니다.
uses:
의 버전을 태그가 아닌 커밋 SHA로 고정하는 것은, 태그를 가리키는 지점을 변경할 수 있는 공급망 공격(supply chain attack)에 대한 대비책입니다. 동작에는 관계없으므로, actions/checkout@v7처럼 태그로 작성해도 작동합니다.
처리는 위에서부터 순서대로, Issue 획득, 질문 구성, Jev에 문의, 임계값 판별, 라벨 부착 순입니다.
import json
import os
import subprocess
...
WAF의 403을 무력화하는 부분에 대한 보충 설명입니다. TypeSafe API 앞에는 Cloudflare의 WAF가 있어, 요청 본문에 공격적인 패턴이 있다고 판단되면 403으로 차단합니다. curl https://...와 같은 명령어는 서버에 외부 URL에 접근하도록 시키는 명령어 삽입(command injection)의 전형적인 형태이므로, 보안 대책으로 간주하고 막고 있는 것으로 생각합니다.
SDK 사용법은 간단하여, TypeSafeClient를 만들고 system_one(state, questions)을 호출하기만 하면 됩니다. state에는 판별받고 싶은 내용(여기서는 Issue의 제목과 본문)을 전달합니다. 답은 response.choices["priority"]나 response.nouls["area_backend"]처럼 확인합니다.
직접 테스트할 때는 --dry-run을 붙이면, 판별 결과만 표시하고 라벨은 부착하지 않습니다.
TYPESAFE_API_KEY=... GH_REPO=owner/repo python3 scripts/issue_triage.py 647 --dry-run
"Issue의 본문을 읽고 라벨을 붙이는" 작업은, 문장을 생성할 필요가 전혀 없기 때문에 확률만 반환하는 모델이 다루기 쉽다고 느꼈습니다. 요금은 입력 100만 토큰당 0.042달러이고, 출력은 무료(2026년 10월 기준)라 비용이 저렴한 점도 매력적입니다. 이번처럼 자동으로 라벨을 붙이는 경우에는 활용하기 어렵지만, 응답이 빠르다는 점도 좋습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기