콘텐츠로 이동

Lab 06: 인스트럭션 튜닝

중급 마감: 2026-04-14
  • Ralph 루프 실행 로그에서 반복 오류 패턴을 추출하고 분류
  • 오류 패턴을 반영해 PROMPT.md를 체계적으로 개선
  • 두 버전의 PROMPT.md를 A/B 테스트로 성능 비교

프롬프트 엔지니어링에서 “튜닝”이란 모델 가중치를 건드리는 것이 아니라, 지시문(PROMPT.md)을 반복적으로 개선해 에이전트의 행동을 원하는 방향으로 조정하는 과정이다. 핵심은 로그 기반의 데이터 드리븐 접근이다.

harness.log 분석
오류 패턴 추출
PROMPT.md 수정
A/B 테스트
반복

1. log_analyzer.py — 오류 패턴 분석기

섹션 제목: “1. log_analyzer.py — 오류 패턴 분석기”
log_analyzer.py
import re
from collections import Counter
from pathlib import Path
from dataclasses import dataclass
@dataclass
class ErrorPattern:
pattern: str
count: int
examples: list[str]
category: str # "syntax" | "logic" | "timeout" | "api" | "other"
class LogAnalyzer:
"""harness.log에서 반복 오류 패턴을 추출한다."""
ERROR_REGEXES = {
"syntax": r"SyntaxError|IndentationError|NameError",
"logic": r"AssertionError|assert .+ == .+|FAILED tests/",
"timeout": r"TimeoutError|timed out|Killed",
"api": r"anthropic\.APIError|RateLimitError|overloaded",
}
def __init__(self, log_path: str):
self.lines = Path(log_path).read_text().splitlines()
def extract_errors(self) -> list[ErrorPattern]:
raw_errors: list[str] = []
for i, line in enumerate(self.lines):
if any(kw in line for kw in ["ERROR", "FAILED", "Error", "Exception"]):
# 전후 2줄 포함해 컨텍스트 수집
ctx_start = max(0, i - 1)
ctx_end = min(len(self.lines), i + 3)
raw_errors.append("\n".join(self.lines[ctx_start:ctx_end]))
# 카테고리별 분류
categorized: dict[str, list[str]] = {k: [] for k in self.ERROR_REGEXES}
categorized["other"] = []
for err in raw_errors:
matched = False
for cat, pattern in self.ERROR_REGEXES.items():
if re.search(pattern, err):
categorized[cat].append(err)
matched = True
break
if not matched:
categorized["other"].append(err)
results = []
for cat, errors in categorized.items():
if not errors:
continue
counter = Counter(errors)
results.append(ErrorPattern(
pattern=cat,
count=len(errors),
examples=list(counter.most_common(3)), # 상위 3개 예시
category=cat
))
return sorted(results, key=lambda x: x.count, reverse=True)
def generate_report(self) -> str:
patterns = self.extract_errors()
lines = ["# 오류 패턴 분석 보고서\n"]
for p in patterns:
lines.append(f"## [{p.category.upper()}] — {p.count}회")
lines.append(f"\n대표 예시:\n```\n{p.examples[0][0][:300]}\n```\n")
return "\n".join(lines)
# Role
You are an autonomous coding agent fixing bugs in Python code.
# Task
Make all pytest tests pass without modifying test files.
# Done
Write DONE.md when all tests pass.

v1의 문제점을 분석한 뒤 다음 요소를 추가한다.

# Role
You are an autonomous coding agent. Your sole objective is to make all
pytest tests pass in `tests/`. Do NOT modify test files.
# Before You Start
1. Read `fix_plan.md` if it exists — contains prior analysis
2. Run `pytest tests/ -q --tb=short` to see current failures
3. Read only the files relevant to failing tests
# Coding Rules
- Change the minimal amount of code needed to fix each failure
- After each fix, run pytest immediately to verify
- Do NOT refactor working code
- Do NOT add new dependencies
# When Stuck (same error 2+ times)
1. Write your analysis to `fix_plan.md`:
- Exact error message
- Root cause hypothesis
- Two alternative solutions
2. Try the first alternative
# Completion
When `pytest tests/ -q` exits 0, write `DONE.md` with:
- Number of files changed
- Brief description of each fix
- Total iterations used
ab_test.py
import subprocess
import time
import json
from pathlib import Path
from dataclasses import dataclass, asdict
@dataclass
class ABResult:
variant: str # "v1" or "v2"
iterations: int
success: bool
duration_sec: float
final_test_output: str
def run_variant(prompt_path: str, variant: str, max_iter: int = 8) -> ABResult:
"""단일 변형(variant)을 실행하고 결과를 반환한다."""
# 초기 상태 초기화
for f in ["DONE.md", "fix_plan.md", "harness.log"]:
Path(f).unlink(missing_ok=True)
# 버그 있는 초기 코드 복원
subprocess.run(["git", "checkout", "src/"], capture_output=True)
env = {"MAX_ITER": str(max_iter), "PROMPT_FILE": prompt_path}
start = time.time()
result = subprocess.run(
["bash", "harness.sh"],
capture_output=True,
text=True,
env={**__import__("os").environ, **env}
)
duration = time.time() - start
# 이터레이션 횟수 파싱
log = Path("harness.log").read_text() if Path("harness.log").exists() else ""
iterations = log.count("=== 이터레이션")
# 최종 테스트 결과
test_result = subprocess.run(
["python", "-m", "pytest", "tests/", "-q", "--tb=no"],
capture_output=True, text=True
)
return ABResult(
variant=variant,
iterations=iterations,
success=test_result.returncode == 0,
duration_sec=round(duration, 1),
final_test_output=test_result.stdout
)
def compare(v1_result: ABResult, v2_result: ABResult):
print("\n===== A/B 테스트 결과 =====")
for r in [v1_result, v2_result]:
status = "성공" if r.success else "실패"
print(f"[{r.variant}] {status} | {r.iterations}회 이터레이션 | {r.duration_sec}초")
if v1_result.success and v2_result.success:
diff = v1_result.iterations - v2_result.iterations
print(f"\nv2가 {abs(diff)}{'적게' if diff > 0 else '많이'} 이터레이션 사용")
if __name__ == "__main__":
r1 = run_variant("prompt_v1.md", "v1")
r2 = run_variant("prompt_v2.md", "v2")
compare(r1, r2)
Path("ab_results.json").write_text(
json.dumps([asdict(r1), asdict(r2)], indent=2, ensure_ascii=False)
)
  1. Lab 04의 harness.loglog_analyzer.py로 분석
  2. 분석 결과를 바탕으로 prompt_v2.md 작성
  3. python ab_test.py 실행
  4. ab_results.json 검토 — 어느 버전이 더 빨리 테스트를 통과했는가?
  5. v2가 개선되지 않았다면 이유를 분석하고 prompt_v3.md 초안 작성

assignments/lab-06/[학번]/에 PR:

  • log_analyzer.py — 오류 패턴 분류 및 보고서 생성
  • error_report.mdlog_analyzer.py 출력 결과
  • prompt_v1.md, prompt_v2.md — A/B 테스트 두 변형
  • ab_test.py — 자동화된 비교 스크립트
  • ab_results.json — 실제 실행 결과
  • README.md — v1 대비 v2 개선 사항 분석 및 추가 개선 제안