
【AI 에이전트 비교 실험】 #07 AI 에이전트 비교 실험 데이터를 JSON으로 관리하는 설계 패턴
요약
AI 코딩 에이전트 비교 실험 시 발생하는 데이터 관리 문제를 해결하기 위한 JSON 설계 패턴을 소개합니다. 평가의 독립성을 보장하고 데이터 충돌을 방지하기 위해 에이전트별로 파일을 분할하는 설계 방식을 제안합니다.
핵심 포인트
- 에이전트별 파일 분할을 통해 자기 평가 시 타 에이전트 점수 노출(편향) 방지
- Git 차분(Diff) 및 컨플릭트 발생 범위를 국소화하여 관리 효율성 증대
- 파일명과 에이전트 ID를 일치시켜 데이터 식별 및 대시보드 연동 최적화
- 인간 평가와 AI 자기 평가 데이터를 구조적으로 공존시키는 설계 패턴 제시
본 기사의 집필자: Claude Code
본 시리즈는 6개의 AI 코딩 에이전트를 동일한 조건에서 비교하는 실험의 일부입니다.
AI 코딩 에이전트를 여러 개 나열하여 비교하다 보면, 가장 먼저 부딪히는 문제는 '채점 로직'이 아니라 '데이터를 두는 장소'의 문제입니다.
- 6개 에이전트 × 2개 실험 × 정량·정성·자기 평가…와 같이 항목이 늘어남
- 인간의 평가와 AI 자신의 자기 평가를 어떻게 공존시킬 것인가
- 도중에 항목을 추가하고 싶어짐
- 일부 데이터는 공개하고 싶지만, 일부를 공개하면 실험이 망가짐
채점 계산식을 아무리 깔끔하게 작성하더라도, 그 입력 데이터의 구조가 어지러우면 집계도 대시보드화도 공개도 할 수 없습니다.
이 기사는 AI 에이전트 비교 실험에서 실제로 사용한 evaluation.json의 데이터 구조·관리 설계 그 자체에 초점을 맞춥니다. 채점 로직(합격률의 정규화, 개발 시간 스코어, 자기 평가와의 차이 계산 등)은 별도 기사에서 자세히 다루고 있으므로, 본 기사에서는 계산에는 관여하지 않습니다.
채점 로직·스코어 계산의 상세 내용은 Qiita 제2회를 참조해 주세요 (목록은 GitHub 리포지토리).
제재는 6개의 AI 에이전트(Claude Code / Codex CLI / Codex IDE / Antigravity CLI / Antigravity IDE / Copilot Agent)에게 동일한 태스크 관리 Web 앱을 만들게 하여, 구현(실험 A)과 플래닝을 포함한 구현(실험 B)을 비교한 실험입니다. 본 기사에서 인용하는 JSON은 모두 이 실험에서 실제로 사용한 완성된 데이터입니다.
이 기사에서 다루는 설계상의 질문은 다음 4가지입니다.
- 왜 하나의 거대한 파일이 아니라, 에이전트별로 파일을 나누는가
- 왜
human과ai_self를 같은 계층에 나란히 두는가 - AI에게 자기 평가를 쓰게 할 때, 어떤 스키마여야 기입 실수가 줄어드는가
- 실험 중에 에이전트에게 보여주는 정보와, 인간만이 관리하는 정보를 어떻게 나누는가
평가 데이터의 보유 방식은 크게 두 가지가 있습니다.
# 패턴 1: 1개 파일에 모든 에이전트
evaluation.json # {"claude-code": {...}, "codex-cli": {...}, ...}
# 패턴 2: 에이전트별로 1개 파일 (채택)
...
본 실험에서는 패턴 2(에이전트별 파일)를 채택했습니다. 이유는 세 가지입니다.
(1) 자기 평가 시의 편향 (Bias) 방지
이것이 가장 큰 이유입니다. 본 실험에서는 각 에이전트에게 자신의 성과물에 대한 자기 평가를 쓰게 합니다. 이때 만약 하나의 파일에 모든 에이전트의 평가가 들어있다면, 자기 평가를 기입하게 할 때 그 파일을 전달하게 되어 AI는 "다른 에이전트가 몇 점을 받았는지"를 읽어버리게 됩니다.
인간도 마찬가지지만, 옆 사람의 답안이 보이는 상태에서의 자기 채점은 영향을 받습니다. AI에게 codex-cli.json만 전달하면, 다른 5개 에이전트의 점수와 인간의 소감도 보이지 않습니다. 파일 분할은 그대로 "평가의 독립성"을 담보하는 메커니즘이 됩니다.
(2) 컨플릭트(Conflict)와 차분(Diff)의 국소화
에이전트마다 기입 타이밍이 다르고, 종종 별도의 세션이나 다른 날에 작업합니다. 파일이 나누어져 있으면 claude-code.json의 업데이트가 codex-cli.json의 차분에 섞이지 않습니다. git의 차분 리뷰도 "이 에이전트의 평가가 바뀌었다"라고 한눈에 알 수 있습니다.
(3) 파일명 = 식별자 (Identifier) 가 됨
파일명 자체가 agent_id와 일치하기 때문에, 대시보드 측에서는 ${agentId}.json이라는 규칙만으로 파일을 특정할 수 있습니다 (후술).
분할에 따른 대가로 "모든 에이전트를 횡단 집계하려면 모든 파일을 읽어야 한다"는 점은 있지만, 6개 파일 정도라면 집계 측에서 루프를 돌리는 것만으로 충분하며, 편향 방지의 이점이 더 큽니다.
정성 평가에서는 각 항목을 스칼라(Scalar) 값이 아닌 객체(Object)로 만들어, 인간 평가와 AI 자기 평가를 같은 계층에 나란히 배치합니다.
"readability": {
"human": 5,
"ai_self": 5
...
흔히 하는 실수는 인간용과 AI용으로 필드명을 나누어 버리는 것입니다.
// 안티 패턴: 항목과 필드가 분산됨
"readability_human": 5,
"readability_ai": 5,
...
이렇게 되면 항목을 하나 추가할 때마다 키(key)가 두 개씩 늘어나며, "같은 항목의 human과 ai_self를 대조하는" 코드가 문자열 조작 방식이 됩니다. {human, ai_self} 형태의 객체로 만들어 두면, 항목을 늘려도 구조는 변하지 않으며 차이 집계는 "같은 키의 두 값을 빼는 것"만으로 충분해집니다 (차이 계산과 해석은 제2회 참조).
설계상의 포인트는 다음 세 가지입니다.
- 평가 축(readability 등)이 제1계층, 평가자(human / ai_self)가 제2계층. 반대로 하지 않습니다. "항목별로 인간과 AI를 비교하고 싶다"라는 분석 의도가 그대로 구조가 됩니다.
- 어느 한쪽을 "정답", 다른 한쪽을 "예측"으로 비대칭적으로 두지 않습니다. 실험에서는 "인간이 틀리고 AI가 맞았던" 케이스도 발생하기 때문에 (후술), 구조상으로는 대등하게 유지합니다.
human과ai_self는 대등한 형제 관계입니다. - 자유 기술(free description)도 같은 사상으로. 수치뿐만 아니라 판단 이유를 적는 텍스트도 평가자별로 나누어 남깁니다.
notes.human/notes.ai_self로 나눕니다.
이 설계가 효과를 발휘하는 구체적인 사례가 있습니다. 실제 데이터에서 인용하겠습니다. Codex CLI의 실험 A, 문서 평가 데이터입니다.
"documentation": {
"human": 4,
"ai_self": 1
...
자기 평가(self-evaluation)가 1이고, 인간 평가가 4로 크게 차이가 납니다. 그 이유는 notes.ai_self에 남아 있었습니다 (실제 데이터 그대로).
문서 1/5: README가 글자 깨짐으로 판단 (※ 위와 동일, 오탐지).
이는 "AI가 자신의 README를 글자 깨짐으로 오인하여 낮게 자기 평가를 했으나, 실제로는 툴 측의 인코딩 지정 누락(인간 측의 확인 실수)으로 인해 결과물은 정상이었다"라는 케이스입니다. human / ai_self를 대등한 두 필드로 나누고, 나아가 notes에 이유를 남겨 두었기에 나중에 "이것은 AI의 과소평가가 아니라 검증 환경의 문제"라고 분리해낼 수 있었습니다. 값만 가지고 있었다면, 단순히 "AI의 과소평가"로 잘못 집계되었을 것입니다.
실제로 사용한 스키마를 실제 데이터(claude-code.json의 실험 A 부분)를 그대로 인용하여 해설하겠습니다.
{
"agent_id": "claude-code",
"experiments": {
...
참고: 위의 notes.human / notes.ai_self는 지면 관계상 끝부분을 생략했습니다. 실제 파일에는 수백 자의 총평이 들어 있습니다. 수치 필드는 모두 실제 데이터 그대로 전기했습니다.
구조를 위에서부터 살펴보겠습니다.
{ "agent_id": "claude-code", "experiments": { ... } }
최상위는 agent_id (파일명과 일치)와 experiments 두 가지만 있습니다. 에이전트의 메타 정보(vendor, interface 종류 등)는 이 파일에 포함하지 않았습니다. 후술할 대시보드 측에서 일원 관리하며, 평가 파일은 "평가값 그 자체"에 집중하게 했습니다. 이는 메타 정보의 표기 불일치("OpenAI" / "openai")가 6개 파일에 흩어지는 것을 방지하기 위함입니다.
"experiments": { "A": {...}, "B": {...} }
실험 A와 실험 B를 키(key)로 나누어 한 파일에 공존시킵니다. 에이전트는 분할하지만 실험은 공존시킨다――이 비대칭성이 설계의 핵심입니다. 이유는 "같은 에이전트의 실험 A와 실험 B는 반드시 세트로 참조한다(동일 에이전트의 프로필로서)\
"agent_reported_time_min": null,
"input_tokens": null,
"output_tokens": null,
...
이 부분이 실제 운용상 중요한 설계 판단입니다. "측정할 수 없었다"와 "0이었다"를 구분하기 위해, 키(key) 자체는 항상 존재하게 하고 값은 null로 설정합니다. 예를 들어 startup_errors: 0 (기동 에러는 0회였다 = 측정함)과 input_tokens: null (토큰 수는 가져올 수 없었다)은 의미가 완전히 다릅니다. 키를 통째로 생략해 버리면 이 구분이 사라져서, 집계 시 "미측정"을 "0"으로 간주하여 평균에 섞어버리는 사고가 발생합니다.
모든 필드를 처음부터 null로 확정해 두는 스키마는 대시보드의 초기 데이터 생성 함수에도 그대로 나타나 있습니다 (실제 코드에서 발췌).
const defaultAgentData = (agentId) => ({
agent_id: agentId,
experiments: {
...
test_common_backend_total:18과 같이, 실험 전부터 확정되어 있는 값 (공통 테스트의 총 횟수)은 기본값으로 채워 넣는 점도 포인트입니다. 합격 수(pass)는 기입 대기 상태인 null, 총수(total)는 기지(known)의 값이므로 18 / 6. 스키마가 그대로 "무엇이 기지이고 무엇이 기입 대기 중인가"를 나타내는 문서가 됩니다.
실험 B(플래닝 포함)만은 실험 A의 구조에 더해 planning 블록을 가집니다. 실제 데이터(claude-code.json의 실험 B)에서 인용합니다.
"B": {
"completed": true,
"planning": {
...
여기서 설계로서 효과를 발휘하는 것은 다음 두 가지 점입니다.
- 구현 평가와 플래닝 평가에서 서로 다른 단어("design_scores" 등)를 사용하지 않는다. 같은 형태가 중첩될 뿐이므로 집계 코드를 재사용할 수 있다.
planning안에서도quantitative/qualitative라는 동일한 어휘를 재귀적으로 사용한다. - 제2장의 설계 사상을 평가 레이어가 늘어나더라도 그대로 관철한다.
planning.qualitative역시 각 항목이{human, ai_self}이다.
실험 B의 quantitative에는 실험 A에 없는 키(test_self_* = 자체 제작 테스트 관련)가 늘어나 있지만, 실험 A의 키를 삭제하는 것이 아니라 추가하고 있습니다. 실험마다 키 집합이 완전히 일치할 필요는 없으며, "공통 부분은 동일한 이름으로, 고유 부분은 더한다"라는 방침입니다.
자기 평가(ai_self와 notes.ai_self)는 AI 에이전트 스스로 작성하게 합니다. 이때 갑자기 "자기 평가를 해줘"라고 요청하면 포맷이 매번 들쭉날쭉해져서 집계할 수 없습니다. 스키마에 따라 작성하도록 하는 프롬프트 (prompt) 설계가 필요합니다.
실험에서 사용한 템플릿의 골자는 다음과 같습니다.
당신이 실험 A에서 작성한 태스크 관리 앱의 성과물에 대해 자기 평가를 수행해 주세요.
# 전제
- 평가는 타 에이전트의 성과물이나 타인의 평가를 참조하지 않고,
...
설계상의 포인트는 4가지입니다.
- 기입할 스키마를 그대로 보여준다. AI에게 스키마의 "빈칸"(
ai_self만 비어 있는 형태)을 전달하여 그 부분을 채우게 한다.human측은 보여주지 않는다. 이를 통해 인간의 점수에 휘둘리지 않게 한다. - "타인의 평가를 참조하지 않는다"를 명시한다. 제2장의 파일 분할(편향 방지)을 프롬프트 레벨에서도 이중화한다. 메커니즘과 지시 양쪽 모두에서 독립성을 지킨다.
- 항목명은 스키마의 키와 완전히 일치시킨다. 일본어 라벨만 전달하면
readability인지code_readability인지 흔들릴 수 있다. 키 이름을 직접 사용하게 하면 인간이 수동으로 구조에 전기(transcribe)하는 수고와 실수가 사라진다. - 실제 데이터의
notes.ai_self는 "점수의 근거"로 용도를 한정한다.notes.ai_self가 모두 "가독성 5/5: ~ 에러 처리 4/5: ~"와 같이 동일한 서식으로 되어 있는 것은 이 지시의 효과입니다.
그리고 이것은 시스템 측의 운용 규칙입니다만, 자기 평가는 구현과 동일한 세션이 아닌 별도의 세션에서 실시합니다 (구현 중의 대화 문맥이 자기 평가에 섞이지 않도록 하기 위함. 자세한 내용은 제2회 참조).
또한, 자기 평가에는 "정직함"에 대한 데이터도 포함됩니다. Codex IDE의 실험 A에서는 error_handling
가 human: 5 / ai_self: 3이며, notes.human에는 다음과 같은 기록이 남아 있습니다(실제 데이터).
"실제로는 PUT 부분 업데이트 시 title 필수화로 인한 중대한 버그가 존재하지만, 자기 평가에서는 전혀 언급되지 않았다."
이와 같이 "AI가 언급하지 않은 것" 또한 비교 대상이 됩니다. 그렇기 때문에 ai_self를 솔직하게(인간의 평가를 보여주지 않고) 작성하게 하고, notes.human에 인간 측의 검증 결과를 별도로 남기는 구조가 필요한 것입니다.
평가 파일군은 단일 HTML 대시보드(Vue 3, 빌드 불필요)에서 읽고 씁니다. 연동을 성립시키는 것은 "파일명 = agent_id"라는 규약입니다.
에이전트 목록(메타 정보)은 대시보드 측에서 관리합니다(실제 코드에서 발췌).
const AGENTS = [
{
id:'claude-code',
label:'Claude Code',
type:'CLI',
vendor:'Anthropic'
},
{
id:'codex-cli',
label:'Codex CLI',
type:'CLI',
vendor:'OpenAI'
},
...
];
평가 파일 측에 vendor나 type을 두지 않고, 여기서 일원 관리하는 것이 제3.1절에서 다룬 설계입니다. id가 평가 파일명(claude-code.json)과 일치하므로, 대시보드는 id를 기점으로 메타 정보와 평가값을 결합할 수 있습니다.
데이터의 입출력은 파일의 직접 읽기에 의존하지 않고 임포트 / 익스포트 (import / export) 방식으로 구성했습니다(실제 코드에서 발췌).
// 익스포트: agent_id를 그대로 파일명으로 사용
const exportJson = (agentId) => {
const data = store.value[agentId] || defaultAgentData(agentId);
...
로컬의 편집 상태는 localStorage (키 ai-exp-data-v1)에 유지하며, 확정되면 agent_id.json으로 내보내어 evaluation-jsons/에 커밋합니다. 이 방식의 장점은 다음과 같습니다.
- 대시보드는 정적 HTML 한 장으로 동작한다. 서버가 필요 없으며,
file://로 열어도 동작합니다. 배포가 용이합니다. - 진실의 원천 (source of truth)은 리포지토리 내의 JSON이다.
localStorage는 어디까지나 초안입니다. 익스포트하여 커밋한 것이 정답입니다. - 스키마의 초기 형태는 제3.3절의 기본값(
defaultAgentData()가 전담)이 결정합니다.null과 기지의total이 여기서 생성되므로, 수기 JSON과 대시보드 생성 JSON의 형태가 일치합니다.
집계가 완료된 확정 데이터(타인 테스트 수정 결과, 상호 리뷰 스코어 등)는 편집 대상이 아니기 때문에, 평가 파일과는 별개로 대시보드 내의 상수(EXP_D_DATA 등)로 임베딩(embedding)해 두었습니다. "앞으로 기입할 데이터"와 "확정되어 변경하지 않을 데이터"를 별도의 장소에 두는 것은 소소하지만 효과적인 분리입니다.
이 실험에서는 평가용 데이터를 인간이 집계하기 위한 정보와 에이전트에게 전달할 정보를 나누고 있습니다. 전부를 에이전트에게 보여주게 되면 익명 리뷰나 자기 평가의 전제가 무너지기 때문입니다.
본 실험에서의 구분은 다음과 같습니다.
| 구분 | 예 | 용도 |
|---|---|---|
| 에이전트에게 전달 | 익명화된 target-1 ~ target-6, 평가 대상 코드, 지시문 | 실험 중 입력 |
| 인간이 관리 | target 번호와 실제 에이전트 명의 대응표, 확정 평가 데이터, 스키마 정의 | 집계·검증·기사화 |
포인트는 "실험 중에는 익명 ID로 전달하고, 집계 시에는 인간이 대응표를 통해 실제 이름으로 되돌린다"는 방식입니다. 상호 리뷰(실험 E)와 타인 테스트 수정(실험 D)에서는 각 에이전트에게 다른 에이전트의 성과물을 target-1 ~ target-6이라는 익명 ID로 전달하여 리뷰하게 했습니다. 대시보드 데이터에도 그 의도가 주석으로 남아 있습니다(실제 데이터).
target 번호와 실제 에이전트 명의 대응은 target-correspondence.md(비공개)에서 관리합니다. 이 JSON 내의 키는 실제 에이전트 명으로 기록합니다.
여기서 말하는 「비공개」는 기사로서 독자에게 공개하지 않는다는 의미가 아니라, 실험 중에 리뷰어(Reviewer) 역할을 하는 에이전트에게 보여주지 않는다는 의미입니다. 기사화하는 단계에서는 분석에 필요한 범위 내에서 대응 관계를 밝히고 있습니다.
즉,
- 집계용 JSON 내부에서는
claude-code등과 같이 실명으로 키(Key)를 가짐 (집계 및 분석을 위해) - 단, 실험 중에는 「
target-3가 누구였는지」에 대한 대응표를 에이전트에게 보여주지 않음 - 리뷰어 역할을 하는 AI에게는 익명 ID만 전달
이를 통해, "벤더(Vendor)명을 알게 되면 평가가 관대해지거나 엄격해지는" 편향(Bias)(동일 계열 벤더끼리 점수가 움직이는 현상)을, 데이터를 실명으로 집계할 수 있는 편의성을 유지하면서도 방지할 수 있습니다. 실제로 이 데이터에서는 동일 계열 에이전트 간에 평가가 편향되는 케이스와 거의 소멸하는 케이스가 모두 관측되었으며, 이는 대응표를 분리해 두지 않았다면 얻을 수 없었을 분석입니다.
실험 운용 시의 체크리스트로는 다음을 권장합니다.
- 에이전트에게 전달하는 입력에는 대응표, API 키, 과금 정보를 포함하지 않는다.
- 평가 JSON 내에 리뷰 대상의 실명이나 「누구의 코드인지」 역추적할 수 있는 힌트를 적지 않는다 (
notes의 작성 방식에 주의) - 기사화할 때는 실험 시 숨겼던 정보와 독자에게 공개해도 좋은 분석 정보를 나누어 확인한다.
AI 에이전트 비교 실험의 데이터 관리는 채점 방식을 작성하기 전인 「구조를 결정하는」 단계에서 승부가 결정됩니다. 본 기사의 설계 패턴을 정리합니다.
- 에이전트별로 1개 파일. 자기 평가 시 타인의 점수를 보여주지 않기 위한 편향 방지가 그대로 파일 분할의 이유가 된다.
- 평가 축이 제1계층, 평가자가 제2계층. 항목을 늘려도 구조가 변하지 않고 차분 집계(Differential aggregation)가 단순해진다.
{human, ai_self}를 동일한 계층에 나란히 배치한다. - 「측정할 수 없음」과 「0임」을 구분하고, 키(Key)는 항상 존재시킨다. 기지(Known)의 값(테스트 총수 등)은 기본값으로 채운다.
null로 결측치를 명시한다. - 동일한 어휘를 재귀적으로 사용한다.
planning내부도quantitative/qualitative로 구성한다. 집계 코드를 재사용할 수 있다. - 기입은 스키마(Schema)의 빈 곳을 보여주며 채우게 한다. AI에게는 키 이름으로 지시하고, 인간 평가는 보여주지 않는다. 메커니즘과 프롬프트 양쪽 모두에서 독립성을 지킨다.
- 파일명 = 식별자(Identifier). 대시보드는 이 규약만으로 메타 정보와 평가값을 결합할 수 있다.
- 실명으로 기록하고, 대응표만 숨긴다. 공개의 편의성과 편향 방지를 양립한다.
스코어는 나중에 얼마든지 다시 계산할 수 있지만, 데이터 구조는 중간에 변경하면 과거 데이터가 전부 영향을 받게 됩니다. 그렇기 때문에 처음에 「누가·무엇을·어떤 입도로·어디에 둘 것인가」를 확정하는 것이 비교 실험의 재현성(Reproducibility)을 가장 크게 좌우합니다.
본 기사는 6개의 AI 코딩 에이전트 비교 실험 시리즈 중 하나입니다 (Qiita 제7회).
시리즈 전체 기사 목록은 GitHub 리포지토리를 참조해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기