콘텐츠로 이동

9주차: QA 에이전트 구현

Phase 39주차고급강의일: 2026-04-28

개념 관점

Worker가 자기 코드를 검증할 때 발생하는 conflict-of-interest를 설명하고, QA 에이전트가 가져야 할 독립성 4원칙을 정의한다.

설계 관점

deterministic test → policy gate → LLM-as-Judge → human review로 이어지는 feedback loop와 escalation 경로를 설계한다.

구현 관점

QA 에이전트와 LLM-as-Judge를 코드로 통합해 strict JSON verdict와 retry 로직을 자동화한다.

운영 관점

judge 신뢰도(상관관계)·human override 빈도·false-pass 비율을 정기적으로 보고하고 prompt/rubric을 갱신한다.

왜 QA 에이전트가 별도여야 하는가

섹션 제목: “왜 QA 에이전트가 별도여야 하는가”

7주차에서 설계한 멀티에이전트 SDLC 파이프라인의 Phase 5: Verify를 기억하는가. 당시 핵심 설계 원칙은 “검증 에이전트는 생성 에이전트와 독립적이어야 한다”였다. 이 원칙은 직관이 아니라 실증 데이터에 근거한다.

DeepMind와 MIT의 에이전트 시스템 스케일링 연구에 따르면, 검증 에이전트가 생성 에이전트와 컨텍스트를 공유할 경우 동일한 편향을 상속한다. 코더가 특정 가정 하에 코드를 작성했다면, 같은 컨텍스트를 가진 QA 에이전트는 그 가정을 전제로 삼아 검증을 생략할 수 있다. 이는 공유 컨텍스트가 검증 독립성을 약화시키는 사례다.

PwC의 2025 AI Agent Report는 더 구체적인 수치를 제시한다. 단일 에이전트(코더만) 구조에서 정확도는 10% 수준이었으나, 독립적 judge agent를 추가하자 70%로 향상됐다. 7배 개선이다. QA 에이전트 독립성은 선택이 아니라 시스템 신뢰성의 전제 조건이다.

sdlc-toolkit에서는 이를 2단계로 구현한다:

  • /reflect — 자기 리뷰(self-review): 코더 에이전트 자신이 결과물을 먼저 검토
  • /review — 독립 리뷰(independent review): 별도 QA 에이전트가 코드+테스트만 보고 판단

자기 리뷰가 있음에도 독립 리뷰를 별도로 두는 이유는 단순하다. /reflect는 명백한 오류와 미완성 항목을 빠르게 잡아 /review의 부담을 줄이는 역할이고, /review는 편향 없는 최종 게이트 역할을 한다. 두 단계가 서로 다른 목적을 가진다.


QA 에이전트는 코더 에이전트와 절대 공유 컨텍스트를 사용하지 않는다. 동일한 컨텍스트를 가진 두 에이전트는 동일한 편향을 공유하므로, 독립적인 검증이 불가능하다.

독립성을 실제로 보장하는 메커니즘은 세 가지다:

1. 컨텍스트 격리 (Context Isolation)

QA 에이전트는 코더의 reasoning trace, 중간 결정 과정, system prompt를 볼 수 없다. 입력은 오직 코드 파일과 테스트 파일뿐이다. 코더가 “왜 이렇게 구현했는가”를 아는 순간 QA는 그 합리화를 수용하기 시작한다. 모르는 것이 더 정확한 판단을 낳는다.

2. 도구 제한 (Tool Restriction)

sdlc-toolkit의 /review 단계에서 QA 에이전트에게는 Edit 권한이 없다. ReadBash(테스트 실행)만 허용된다. QA가 발견한 버그를 직접 고칠 수 있으면 “어차피 내가 고칠 테니까” 식의 느슨한 리뷰가 발생한다. 도구를 제한하면 QA는 발견에 집중하고, 수정은 코더에게 넘긴다. 역할 분리가 품질을 높인다.

3. 별도 모델 티어 (Model Tier Separation)

Claude Code의 model routing 기능을 활용하면 QA 에이전트에 더 강력한 모델을 배정할 수 있다. 코더가 claude-sonnet을 사용할 때 QA는 claude-opus를 사용하는 구성이 가능하다. 생성보다 검증에 더 많은 추론 능력을 투자하는 것이 비용 대비 효율적이다.


qa_agent.py
import os
import subprocess
import anthropic
from pathlib import Path
class QAAgent:
def __init__(self):
self.client = anthropic.Anthropic()
def run_tests(self, test_dir: str) -> dict:
"""pytest 실행 및 결과 파싱"""
result = subprocess.run(
["python", "-m", "pytest", test_dir, "-v", "--tb=short", "--json-report"],
capture_output=True, text=True
)
return {
"passed": result.returncode == 0,
"output": result.stdout,
"errors": result.stderr
}
def code_review(self, diff: str) -> str:
"""Claude를 통한 코드 리뷰"""
response = self.client.messages.create(
model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5"),
max_tokens=2048,
system="""당신은 시니어 소프트웨어 엔지니어입니다.
코드 diff를 검토하고 다음을 확인하세요:
1. 논리적 오류
2. 엣지 케이스 미처리
3. 보안 취약점
4. 성능 문제
5. 테스트 누락
출력: JSON {"approved": bool, "issues": [...], "suggestions": [...]}""",
messages=[{"role": "user", "content": f"코드 리뷰 요청:\n{diff}"}]
)
return response.content[0].text
def review_pr(self, pr_diff: str, test_dir: str) -> dict:
"""PR 전체 검증"""
test_result = self.run_tests(test_dir)
review_result = self.code_review(pr_diff)
return {
"tests_passed": test_result["passed"],
"test_output": test_result["output"],
"code_review": review_result,
"approved": test_result["passed"] and "approved: true" in review_result.lower()
}

7주차에서 설계한 3-parallel reviewer 패턴을 실제로 구현한다. 정확성(Correctness), 품질(Quality), 아키텍처(Architecture) 세 관점이 동시에 검토되고, 심각도 기반 PASS/FAIL 게이트가 최종 판단을 내린다.

parallel_reviewer.py
import concurrent.futures
import os
import anthropic
from dataclasses import dataclass
from typing import Literal
@dataclass
class ReviewResult:
dimension: str
passed: bool
severity: Literal["critical", "major", "minor", "info"]
issues: list[str]
score: int # 0-10
class ParallelReviewer:
def __init__(self):
self.client = anthropic.Anthropic()
def _call_claude(self, system: str, user: str) -> str:
response = self.client.messages.create(
model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5"),
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": user}]
)
return response.content[0].text
def review_correctness(self, code: str, tests: str) -> ReviewResult:
"""정확성 리뷰: 논리 오류, 엣지 케이스, 테스트 충분성"""
result = self._call_claude(
system="""코드의 정확성만 검토하라. 스타일은 무시한다.
확인 항목: 논리 오류, 엣지 케이스 미처리, 테스트 커버리지 공백.
출력: JSON {"score": 0-10, "severity": "critical|major|minor|info", "issues": [...]}""",
user=f"코드:\n{code}\n\n테스트:\n{tests}"
)
import json
data = json.loads(result)
return ReviewResult(
dimension="correctness",
passed=data["score"] >= 4 and data["severity"] != "critical",
severity=data["severity"],
issues=data["issues"],
score=data["score"]
)
def review_quality(self, code: str) -> ReviewResult:
"""품질 리뷰: 코딩 컨벤션, 가독성, 유지보수성"""
result = self._call_claude(
system="""코드 품질만 검토하라. 기능 정확성은 무시한다.
확인 항목: 네이밍, 함수 길이, 중복, 주석 충분성.
출력: JSON {"score": 0-10, "severity": "critical|major|minor|info", "issues": [...]}""",
user=f"코드:\n{code}"
)
import json
data = json.loads(result)
return ReviewResult(
dimension="quality",
passed=data["score"] >= 4 and data["severity"] != "critical",
severity=data["severity"],
issues=data["issues"],
score=data["score"]
)
def review_architecture(self, code: str, context: str) -> ReviewResult:
"""아키텍처 리뷰: 설계 결정, 의존성, 확장성"""
result = self._call_claude(
system="""아키텍처 관점에서만 검토하라.
확인 항목: 단일 책임 원칙, 의존성 방향, 인터페이스 설계, 확장 가능성.
출력: JSON {"score": 0-10, "severity": "critical|major|minor|info", "issues": [...]}""",
user=f"컨텍스트:\n{context}\n\n코드:\n{code}"
)
import json
data = json.loads(result)
return ReviewResult(
dimension="architecture",
passed=data["score"] >= 4 and data["severity"] != "critical",
severity=data["severity"],
issues=data["issues"],
score=data["score"]
)
def parallel_review(self, code: str, tests: str, context: str) -> dict:
"""3-병렬 리뷰 실행 및 결과 통합"""
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
f_correctness = executor.submit(self.review_correctness, code, tests)
f_quality = executor.submit(self.review_quality, code)
f_architecture = executor.submit(self.review_architecture, code, context)
results = [
f_correctness.result(),
f_quality.result(),
f_architecture.result()
]
# 심각도 기반 PASS/FAIL 게이트
has_critical = any(r.severity == "critical" for r in results)
all_pass = all(r.passed for r in results)
avg_score = sum(r.score for r in results) / len(results)
return {
"overall_passed": all_pass and not has_critical,
"average_score": avg_score,
"results": results,
"blocking_issues": [
issue
for r in results if r.severity == "critical"
for issue in r.issues
]
}

독립 리뷰 전에 코더 에이전트 자신이 먼저 검토한다. 명백한 오류를 선제적으로 잡고, 모호한 요구사항을 질문으로 표면화하여 QA 에이전트의 부담을 줄인다.

self_reflect_agent.py
import anthropic
class SelfReflectAgent:
"""코더 에이전트의 자기 리뷰 — /reflect 패턴 구현"""
def __init__(self):
self.client = anthropic.Anthropic()
def reflect(self, code: str, original_requirement: str) -> dict:
"""구현 결과를 요구사항과 대조하여 자기 검토"""
response = self.client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="""당신은 방금 코드를 작성한 개발자다. 냉정하게 자기 검토하라.
확인 항목:
1. 요구사항의 모든 항목이 구현됐는가?
2. 명백한 버그나 오타가 있는가?
3. 테스트가 실제 요구사항을 검증하는가?
4. 모호하거나 가정에 의존한 부분이 있는가?
출력: JSON {
"obvious_issues": [...],
"ambiguous_requirements": [...],
"questions_for_qa": [...],
"self_confidence": 0-10
}""",
messages=[{
"role": "user",
"content": f"요구사항:\n{original_requirement}\n\n내가 작성한 코드:\n{code}"
}]
)
import json
return json.loads(response.content[0].text)
def should_proceed_to_review(self, reflect_result: dict) -> bool:
"""자기 리뷰 결과로 독립 리뷰 진행 여부 결정"""
# 명백한 이슈가 있으면 먼저 수정 후 재시도
if reflect_result["obvious_issues"]:
return False
# 자신감이 너무 낮으면 재작업
if reflect_result["self_confidence"] < 4:
return False
return True

QA FEEDBACK LOOP
QA Agent 실패 감지
실패 정보 패키징
  • 실패한 테스트 케이스
  • 스택 트레이스
  • 코드 diff
Coder Agent에 재할당task_queue.json에 새 태스크 추가 (priority: HIGH)
Coder Agent 재실행

피드백 루프는 자동 복구를 가능하게 하지만, 7주차에서 경고한 “무한 정제 루프” 위험도 함께 안고 있다. 이를 해결하는 수렴 보장(convergence guarantee) 설계가 필요하다.

반복 상한 (Iteration Cap): 3회

루프는 최대 3회로 제한한다. 3회 내에 해결되지 않는 문제는 코더의 접근 방식 자체가 잘못됐을 가능성이 높다. 자동 수정을 계속하는 것이 오히려 더 복잡한 버그를 낳을 수 있다.

에스컬레이션 경로 (Escalation Path)

1회 실패 → 자동 수정 시도 (Coder 재실행)
2회 실패 → 상세 피드백 + 요구사항 재검토 요청
3회 실패 → Human intervention 플래그 설정 (pipeline-state.json에 기록)

sdlc-toolkit의 /proceed Phase 5는 이 패턴을 reflect → review → escalate 3단계로 통합한다. pipeline-state.jsonqa_iteration_count 필드가 반복 횟수를 추적하여 에스컬레이션 트리거를 결정한다.


sdlc-toolkit의 llm-review-prompt.md에서 정의하는 4차원 스코어링 체계다. 주관적 “좋다/나쁘다” 판단을 0-10 정량 점수로 변환하여 파이프라인 자동화를 가능하게 한다. Week 12의 텔레메트리 시스템에서 이 점수를 집계해 시스템 전반의 품질 트렌드를 추적한다.

PASS 기준: 4개 차원 모두 ≥ 4점 AND Critical 이슈 0개

{
"review_id": "string",
"timestamp": "ISO-8601",
"target": {
"file": "string",
"commit": "string"
},
"scores": {
"correctness": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
},
"conventions": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
},
"test_coverage": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
},
"security": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
}
},
"critical_issues": [],
"verdict": "PASS | FAIL",
"feedback_for_coder": "string"
}

이 스코어링 체계는 QA 에이전트 자체가 LLM-as-Judge 패턴을 구현한 것이다. Week 12에서는 이 점수 데이터를 집계하여 에이전트 성능 텔레메트리를 구축하는 방법을 다룬다.


Week 8(PlannerAgent) → Coder → Week 9(QAAgent)로 이어지는 end-to-end 체인이다. 7주차에서 설계한 pipeline-state.json이 각 Phase 완료를 추적하는 중앙 상태 저장소 역할을 한다.

아티팩트 체인 (Artifact Chain)

ARTIFACT CHAIN
Planner 입력requirement.md
Planner 출력 · Week 8architecture.md
task_queue.json 기반 Coder 할당TASK-001.md · TASK-002.md
Coder 출력PR (코드 + 테스트)
QA 출력 · Week 9review-results.json
실패 패턴 학습 기록LESSON-001.md
Central State Storepipeline-state.json

각 아티팩트의 생성 시점, 담당 에이전트, 현재 Phase를 기록하고 중단 지점부터 재시작할 수 있게 한다.

pipeline-state.json은 각 아티팩트가 생성된 시점, 담당 에이전트, 현재 Phase를 기록한다. 파이프라인이 중단되더라도 어느 단계까지 완료됐는지 파악하고 재시작할 수 있다.


토론은 정답을 찾는 것이 아니라 트레이드오프를 명확히 하는 것이 목적이다.

Q1. QA 에이전트에게 Edit 권한을 주면 어떤 문제가 발생하는가?

“발견한 버그를 직접 고치면 더 효율적이지 않은가?” — 이 주장의 논리적 결함을 찾아라. Edit 권한이 있는 QA는 역할이 어떻게 바뀌는가? 단기 효율과 장기 신뢰성 사이의 트레이드오프를 논하라.

Q2. 3-병렬 리뷰어(정확성, 품질, 아키텍처) 중 하나만 선택해야 한다면?

팀의 현재 상황(스타트업 MVP vs 금융 시스템 vs 오픈소스 라이브러리)에 따라 답이 달라질 수 있다. 각 상황에서 어느 차원이 가장 중요하고, 그 근거는 무엇인가? 선택하지 않은 두 차원은 어떻게 보완할 수 있는가?

Q3. 피드백 루프 반복 상한을 3회로 설정한 이유는?

7주차의 “무한 정제 루프” 위험과 연결하여 설명하라. 상한을 1회로 줄이면 어떤 문제가 생기는가? 10회로 늘리면? 3이라는 숫자에 수학적 근거가 있는가, 아니면 경험적 휴리스틱인가?

Q4. Week 12의 LLM-as-Judge와 이번 주의 QA 에이전트는 어떤 관계인가?

“QA 에이전트 자체가 이미 LLM-as-Judge 아닌가?”라는 질문은 정당하다. 둘의 공통점과 차이점을 찾아라. Week 12에서 추가되는 것은 무엇인가? 텔레메트리와 집계가 단순한 1회성 판단과 어떻게 다른가?


  1. QA 에이전트 구현 — 위 코드 기반으로 QAAgent 클래스 완성

  2. 자동 코드 리뷰 파이프라인 — git diff 추출 → Claude 리뷰 → 결과 구조화

  3. 피드백 루프 연동 — QA 실패 시 자동으로 Coder에게 재할당

  4. 전체 파이프라인 통합 — Planner → Coder → QA end-to-end 실행

제출 마감: 2026-05-05 23:59

요구사항:

  1. 동작하는 QAAgent 구현
  2. 자동 코드 리뷰 기능 (Claude API 활용)
  3. 피드백 루프 구현 (QA 실패 → Coder 재실행)
  4. Planner → Coder → QA 3단계 파이프라인 end-to-end 시연 영상 또는 로그

  1. QA 독립성 = 컨텍스트 격리 + 도구 제한 + 별도 모델. 편향 상속 차단이 핵심이며, PwC 연구에서 정확도 10%→70% 향상이 실증됐다.

  2. 2단계 리뷰: /reflect(자기 리뷰) → /review(독립 리뷰). 자기 리뷰가 명백한 오류를 선제적으로 제거하여 독립 리뷰의 부담을 줄인다.

  3. 3-병렬 리뷰어: 정확성, 품질, 아키텍처 각각 특화. 심각도 기반 PASS/FAIL 게이트가 최종 판단을 내리며, 병렬 실행으로 지연 없이 세 관점을 동시에 확보한다.

  4. 피드백 루프 + 반복 상한: 자동 복구 + 무한 루프 방지의 균형. 3회 상한과 에스컬레이션 경로(자동 수정 → 상세 피드백 → human intervention)가 수렴을 보장한다.

  5. LLM-as-Judge 스코어링: 4차원(정확성, 컨벤션, 테스트 커버리지, 보안) × 0-10 점수. 전 차원 ≥ 4 AND Critical 0개가 PASS 조건이며, Week 12 텔레메트리의 데이터 소스가 된다.


  1. DeepMind + MIT “Towards a Science of Scaling Agent Systems” — 검증 에이전트가 생성 에이전트와 컨텍스트를 공유할 때 편향이 상속된다는 실증적 근거. 에이전트 독립성 설계의 이론적 토대.

  2. PwC AI Agent Report (2025) — 단일 에이전트 대비 독립적 judge agent 추가 시 정확도가 10%에서 70%로 향상된다는 산업 데이터. 7배 개선의 구체적 메커니즘 분석 포함.

  3. sdlc-toolkit /review + /reflect 공식 문서 — 프로덕션 수준의 2단계 리뷰 패턴 레퍼런스 구현. 도구 제한 설정, 모델 라우팅, pipeline-state.json 스키마 포함.

  4. “Judging the Judges: A Systematic Investigation of Position Bias in Pairwise Comparative Assessments by ChatGPT” (arXiv, 2024) — LLM 기반 자동 평가의 편향과 한계 분석. QA 에이전트 설계 시 스코어링 신뢰성을 높이는 실용적 지침 제공.