์๋ฆฌ์ฆ 2ํธ. 1ํธ ํ๋ค์ค ์์ง๋์ด๋ง ์ ๋ฌธ์์ ์ฐ๋ฆฌ๋ ์ ์ฝ·๋๊ตฌ·ํผ๋๋ฐฑ ๋ฃจํ๋ก ์์ด์ ํธ๋ฅผ ๋๋ฌ์ธ์ผ ํ๋ค๊ณ ์ด์ผ๊ธฐํ์ต๋๋ค. ์ด๋ฒ ๊ธ์ ๊ทธ ์ค ํผ๋๋ฐฑ ๋ฃจํ์ ์ฌ์ฅ, ํ๊ฐ ํ๋ค์ค๋ฅผ ๋ค๋ฃน๋๋ค.
๋ค์ด๊ฐ๋ฉฐ: "์ด์ ๋ณด๋ค ๋์์ก๋์?"
ํ๋กฌํํธ ํ ์ค ๋ฐ๊พธ๊ณ , ๋๊ตฌ ํ๋ ์ถ๊ฐํ๊ณ , ๋ชจ๋ธ์ Sonnet์์ Opus๋ก ์ฌ๋ ธ์ต๋๋ค. ์์ด์ ํธ๊ฐ ๋ ์ข์์ก์๊น์? ๋ ๋๋น ์ก์๊น์?
"๋๋์ ์ข์์ง ๊ฒ ๊ฐ์์"๋ก ๋ตํ๋ ํ์ ๊ณง ํ๋ก๋์ ์์ ๋ฌด๋์ง๋๋ค. ํ๊ฐ ํ๋ค์ค๊ฐ ์์ผ๋ฉด ๊ฐ์ ๊ณผ ํ๊ท๋ฅผ ๊ตฌ๋ถํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
ํ๊ฐ ํ๋ค์ค๋, ์์ด์ ํธ์ ๋์์ ์ฌํ ๊ฐ๋ฅํ ๋ฐฉ์์ผ๋ก ์ธก์ ํ๊ณ ํ๊ท๋ฅผ ์ก์๋ด๋ ์๋ํ๋ ํ ์คํธ ์ธํ๋ผ์ ๋๋ค.
์ ํต ์ํํธ์จ์ด์ ๋จ์ ํ ์คํธ์ ๋น์ทํ์ง๋ง, ์ถ๋ ฅ์ด ๋น๊ฒฐ์ ์ ์ด๋ผ๋ ์ ์์ ๋ณธ์ง์ ์ผ๋ก ๋ค๋ฆ ๋๋ค.
ํ๊ฐ ํ๋ค์ค์ 4๊ฐ์ง ๊ตฌ์ฑ ์์
| ๊ตฌ์ฑ ์์ | ์ญํ | ์ ํต SW ๋น์ |
| ๋ฐ์ดํฐ์ (Dataset) | ์ ๋ ฅ + ๊ธฐ๋ ๋์ | ํ ์คํธ ์ผ์ด์ค |
| ๋ฌ๋(Runner) | ์์ด์ ํธ๋ฅผ ๊ฒฉ๋ฆฌ ํ๊ฒฝ์์ ์คํ | ํ ์คํธ ๋ฌ๋ |
| ์ฑ์ ๊ธฐ(Scorer) | ๊ฒฐ๊ณผ๋ฅผ ์ ์๋ก ๋ณํ | assert ๋ฌธ |
| ๋์๋ณด๋(Dashboard) | ์๊ณ์ด ์ถ์ + ํ๊ท ์๋ฆผ | CI ๋ฆฌํฌํธ |
1๏ธโฃ ๋ฐ์ดํฐ์ — ๊ฐ์ฅ ์ค์ํ๊ณ ๊ฐ์ฅ ์ํํ ๋ค๋ค์ง๋ ๋ถ๋ถ
์ข์ eval ๋ฐ์ดํฐ์ ์ 3๊ฐ์ง ์์น:
- ๋ค์์ฑ(Diversity) — ์ฌ์ด ์ผ์ด์ค 60%, ์ด๋ ค์ด ์ผ์ด์ค 30%, ์ฃ์ง ์ผ์ด์ค 10%
- ํ์ค์ฑ(Realism) — ํฉ์ฑ ๋ฐ์ดํฐ๋ณด๋ค ํ๋ก๋์ ๋ก๊ทธ์์ ์ถ์ถํ ์ค์ ์ผ์ด์ค๊ฐ ํจ์ฌ ๊ฐ๋ ฅ
- ๊ณ์ธตํ(Stratification) — ๊ธฐ๋ฅ๋ณ·๋์ด๋๋ณ ํ๊น ์ผ๋ก ํ๊ท ์์น๋ฅผ ์ขํ ์ ์์ด์ผ ํจ
# ์ข์ eval case ์์
{
"id": "refactor-001",
"category": "code_refactor",
"difficulty": "medium",
"input": "Extract the validation logic in user_service.py into a separate module",
"repo_snapshot": "fixtures/user_service_v1/",
"expected": {
"must_pass_tests": ["test_user_validation"],
"must_not_break": ["test_user_create", "test_user_update"],
"files_modified_max": 3
},
"tags": ["refactor", "python", "imports"]
}
ํต์ฌ ํ: ๋ฒ๊ทธ ๋ฆฌํฌํธ๊ฐ ๋ค์ด์ฌ ๋๋ง๋ค eval ์ผ์ด์ค๋ก ๋ฐ์ ํ์ธ์. ํ ๋ฒ ์ก์ ํ๊ท๋ ๋ค์ ์ผ์ด๋์ง ์์์ผ ํฉ๋๋ค.
2๏ธโฃ ๋ฌ๋ — ๊ฒฉ๋ฆฌ, ์ฌํ, ๋ณ๋ ฌํ
async def run_eval(case, agent_config):
with isolated_sandbox(case.repo_snapshot) as sandbox:
trace = await agent.run(
task=case.input,
workspace=sandbox.path,
max_steps=50,
max_cost_usd=2.0,
seed=42, # ๊ฐ๋ฅํ ๊ฒฝ์ฐ ๊ฒฐ์ ์ฑ ํ๋ณด
)
return EvalResult(case_id=case.id, trace=trace, sandbox_diff=sandbox.diff())
ํ์ ์๊ฑด: ๊ฒฉ๋ฆฌ๋ ์์ ๊ณต๊ฐ, ๋น์ฉ/์คํ ์ํ, ์ ์ฒด ํธ๋ ์ด์ค ์ ์ฅ, ๋ณ๋ ฌ ์คํ.
3๏ธโฃ ์ฑ์ ๊ธฐ — 3๊ฐ์ง ๋ ์ด์ด๋ฅผ ์กฐํฉํ๋ผ
ํ๊ฐ๋ ๋จ์ผ ์ ์๊ฐ ์๋๋๋ค. ์ ๋ ดํ๊ณ ํ์คํ ๊ฒ๋ถํฐ, ๋น์ธ๊ณ ๋ชจํธํ ๊ฒ๊น์ง ๊ณ์ธต์ ์ผ๋ก ์์ต๋๋ค.
๐ฏ ์ฑ์ ํผ๋ผ๋ฏธ๋
| ๋ ์ด์ด | ๋น์ฉ | ์ ๋ขฐ๋ | ์์ |
| L1: ๊ฒฐ์ ์ ๊ฒ์ฆ | ๋งค์ฐ ๋ฎ์ | ๋งค์ฐ ๋์ | ํ ์คํธ ํต๊ณผ? ์ปดํ์ผ๋จ? ์คํค๋ง ์ผ์น? |
| L2: ํด๋ฆฌ์คํฑ | ๋ฎ์ | ์ค๊ฐ | ํ์ผ ์์ ๊ฐ์, ํ ํฐ ์ฌ์ฉ๋, ๊ธ์ง ํจํด ๋ฏธ์ฌ์ฉ |
| L3: LLM-as-judge | ๋์ | ๋ฎ์~์ค๊ฐ | "์ฝ๋ ํ์ง์ด ์ ์ ํ๊ฐ?", "์ค๋ช ์ด ๋ช ํํ๊ฐ?" |
์ค์ํ ์์น: L1์ผ๋ก ์ก์ ์ ์์ผ๋ฉด ์ ๋ L3์ ์ฐ์ง ๋ง์ธ์. LLM ์ฑ์ ๊ธฐ๋ ๋น์ธ๊ณ , ๋ถ์์ ํ๋ฉฐ, ์์ฒด ํธํฅ์ด ์์ต๋๋ค.
def score_refactor_case(result, case):
scores = {}
# L1: ๋ฌด์กฐ๊ฑด ํต๊ณผํด์ผ ํ๋ ๊ฒ
scores["tests_pass"] = run_tests(result.sandbox, case.expected.must_pass_tests)
scores["no_regression"] = run_tests(result.sandbox, case.expected.must_not_break)
# L2: ํด๋ฆฌ์คํฑ
scores["files_within_budget"] = len(result.modified_files) <= case.expected.files_modified_max
# L3: LLM judge (์ ํ์ )
scores["code_quality"] = await llm_judge(
rubric="๋จ์ผ ์ฑ
์ ์์น ์ค์, ๋ช
ํํ ๋ค์ด๋ฐ",
diff=result.sandbox.diff()
)
return scores
LLM-as-judge์ ํจ์ ํผํ๊ธฐ
- โ ์ ์ 1~10์ ์ง์ ๋ฌป์ง ๋ง์ธ์ → LLM์ 6~8 ์ฌ์ด๋ง ๋ตํฉ๋๋ค
- โ A/B ํ์ด ๋น๊ต๋ฅผ ์ํค๊ฑฐ๋, ๋ช ํํ ๋ฃจ๋ธ๋ฆญ(์ฒดํฌ๋ฆฌ์คํธ) ์ ์ฃผ์ธ์
- โ Judge ๋ชจ๋ธ์ ํผํ๊ฐ ๋ชจ๋ธ๊ณผ ๋ค๋ฅธ ๋ชจ๋ธ์ ์ฐ์ธ์ (์๊ธฐ ํธํฅ ๋ฐฉ์ง)
- โ Judge ์์ฒด๋ ๋ฉํ ํ๊ฐ์ ์ผ๋ก ๊ฒ์ฆํด์ผ ํฉ๋๋ค
4๏ธโฃ ๋์๋ณด๋ — ํ๊ท๋ฅผ ์ฆ์ ์์์ฐจ๋ฆฌ๊ธฐ

๋์๋ณด๋๊ฐ ๋ณด์ฌ์ค์ผ ํ ๊ฒ:
- ์นดํ ๊ณ ๋ฆฌ๋ณ ํต๊ณผ์จ (์ ์ฒด๊ฐ ์๋๋ผ ๋ถํด๋ ์ ์)
- ํ๊ท ์๋ฆผ (์ด์ ๋น๋ ๋๋น N% ์ด์ ํ๋ฝ ์ ์ฌ๋)
- ์คํจ ์ผ์ด์ค์ ํธ๋ ์ด์ค ๋งํฌ (๋๋ฒ๊น ๊น์ง 1ํด๋ฆญ)
- ๋น์ฉ๊ณผ ์ง์ฐ์๊ฐ (์ ํ๋๊ฐ ๊ฐ๋ค๋ฉด ๋ ์ธ๊ณ ๋น ๋ฅธ ๊ฒ ์ด๊น)
ํ๊ฐ ํ๋ค์ค ๊ตฌ์ถ ๋ก๋๋งต
1์ฃผ์ฐจ: ํ๋ก๋์
๋ก๊ทธ์์ 20๊ฐ ์ผ์ด์ค ์ถ์ถ + L1 ์ฑ์ ๊ธฐ
2์ฃผ์ฐจ: CI์ ํตํฉ, ๋งค PR๋ง๋ค ์๋ ์คํ
3์ฃผ์ฐจ: ์นดํ
๊ณ ๋ฆฌ ํ๊น
, ๋์๋ณด๋ ๊ตฌ์ถ
4์ฃผ์ฐจ: 50๊ฐ๋ก ํ์ฅ, LLM judge ์ถ๊ฐ, ๋ฉํ ํ๊ฐ
์ดํ: ๋ฒ๊ทธ ๋ฆฌํฌํธ๋ง๋ค ์ผ์ด์ค ์ถ๊ฐ, ๋ถ๊ธฐ๋ง๋ค ๋ฐ์ดํฐ์
๋ฆฌ๋ทฐ
์๊ฒ ์์ํ์ธ์. 20๊ฐ์ ์ ํ๋ ์ดํ ๋ ์ผ์ด์ค๊ฐ 2,000๊ฐ์ ํฉ์ฑ ๋ฐ์ดํฐ๋ณด๋ค ํจ์ฌ ๊ฐ๋ ฅํฉ๋๋ค.
๋ง์น๋ฉฐ: Eval์ ์ ํ์ด๋ค
๋ง์ ํ์ด ํ๊ฐ ํ๋ค์ค๋ฅผ "์์ผ๋ฉด ์ข์ ๋๊ตฌ"๋ก ์ทจ๊ธํ์ง๋ง, ์ฑ์ํ AI ํ์์๋ eval ๋ฐ์ดํฐ์ ์์ฒด๊ฐ ๊ฐ์ฅ ์ค์ํ ์์ฐ์ ๋๋ค. ๋ชจ๋ธ์ ๊ฐ์๋ผ์ธ ์ ์์ง๋ง, 6๊ฐ์๊ฐ ํ๋ ์ดํ ํ 1,000๊ฐ์ ๊ณจ๋ ์ผ์ด์ค๋ ๊ฐ์๋ผ์ธ ์ ์์ต๋๋ค.
"์ธก์ ํ ์ ์์ผ๋ฉด ๊ฐ์ ํ ์ ์๋ค." — ํผํฐ ๋๋ฌ์ปค
๋น๊ฒฐ์ ์ ์์คํ ์์๋ ์ด ๊ฒฉ์ธ์ด ๋ ๋ฐฐ๋ก ๋ฌด๊ฒ์ต๋๋ค.
๋ค์ ๊ธ์์๋ ๋๊ตฌ ์ค๊ณ ๋ฒ ์คํธ ํ๋ํฐ์ค — ์ด๋ค tool spec์ด ์์ด์ ํธ๋ฅผ ๋๋ํด ๋ณด์ด๊ฒ ๋ง๋๋์ง — ๋ฅผ ๋ค๋ฃจ๊ฒ ์ต๋๋ค.
#AI์์ด์ ํธ #EvalHarness #LLMOps #AgentEvaluation #ํ๋ค์ค์์ง๋์ด๋ง