2026. 8. 15. 18:54ㆍPROJECT/Toy Project
[ContentOps Agent #3] AI 프로젝트에서 Evaluation을 Harness로 사용하는 방법
1. 시작하며
Playback Gate와 Live Event Stream을 진행하면서 AI Coding Assistant를 사용하는 방식도 조금씩 바뀌었다.
Playback Gate에서는 처음으로 요구사항과 설계 문서를 AI에게 Context로 제공하고, 구현과 테스트를 작은 단위로 반복하는 방식을 사용했다.
Live Event Stream에서는 여기에 ROADMAP, Experiment, Human Gate, Git History까지 추가했다.
REQUIREMENTS
↓
ROADMAP
↓
TASK
↓
PLAN
↓
IMPLEMENT
↓
TEST
↓
EXPERIMENT
↓
ANALYZE
↓
HUMAN GATE
↓
DECIDE
두 프로젝트를 진행하면서 공통적으로 유용했던 원칙은 분명했다.
AI가 구현은 빠르게 하더라도, 무엇을 만들고 어떤 기준으로 성공을 판단할지는 개발자가 통제한다.
ContentOps Agent에서도 이 원칙은 그대로 가져가려고 한다.
다만 이번 프로젝트에는 이전과 다른 문제가 하나 있다.
백엔드 기능은 대부분 테스트 결과가 비교적 명확하다.
HTTP Status가 맞는가?
DB Row가 생성됐는가?
동시 재생 제한이 지켜졌는가?
Consumer Lag이 얼마인가?
중복 Event가 발생했는가?
반면 AI 기능은 조금 다르다.
답변이 그럴듯한가?
검색 결과가 적절한가?
근거가 충분한가?
이 답변을 성공이라고 볼 수 있는가?
라는 판단이 필요하다.
몇 개의 질문을 직접 해보고:
"잘 되는 것 같은데?"
라고 판단하기 시작하면 AI 기능의 품질이 사람의 감각에 의존하게 된다.
그래서 이번 프로젝트에서는 기존 Harness에 한 가지를 더 넣어보려고 한다.
바로 Evaluation이다.
2. 이전 프로젝트에서는 무엇으로 검증했나?
Playback Gate에서는 테스트와 성능 측정이 검증 수단이었다.
예를 들어 동시 재생 제한이 1인 이용권에 동시에 여러 요청을 보낸 뒤 실제 Session 수를 확인했다.
동시 요청
↓
Playback API
↓
ACTIVE Session 확인
결과가:
허용 = 1
실제 ACTIVE = 40
이라면 문제는 명확하다.
그리고 Lock을 적용한 뒤:
실제 ACTIVE = 1
이 되면 적어도 정합성 문제가 해결됐다는 것을 확인할 수 있다.
Live Event Stream도 비슷했다.
Producer
↓
Kafka
↓
Consumer
구조에서:
Producer Events/sec
Consumer Events/sec
Consumer Lag
Duplicate Event
DLT Event
같은 값을 확인했다.
즉 두 프로젝트에서는:
코드
+
Test
+
Metric
이 구현을 검증하는 Harness 역할을 했다.
3. AI 프로젝트에서는 Test만으로 부족하다
ContentOps Agent에서도 일반적인 테스트는 필요하다.
예를 들어:
Question API가 동작하는가?
문서를 pgvector에 저장할 수 있는가?
Vector Search Query가 실행되는가?
LLM 호출이 성공하는가?
는 테스트할 수 있다.
하지만 테스트가 모두 통과했다고 해서 다음 질문에 답할 수 있는 것은 아니다.
정답 문서를 제대로 찾고 있는가?
예를 들어 다음 질문이 있다고 가정한다.
"15세 콘텐츠 공개 기준은?"
정답 문서는:
age-rating-policy.md
인데 Retriever가 다음과 같이 반환할 수도 있다.
1. youth-protection-policy.md
2. publishing-guide.md
3. age-rating-policy.md
시스템은 정상 동작했다.
Exception도 없고 Vector Search도 성공했다.
하지만 검색 품질이 좋은지는 별개의 문제다.
즉 AI 시스템에서는:
정상 동작
≠
좋은 결과
가 된다.
4. 그래서 Evaluation Dataset을 Harness에 넣는다
이번 프로젝트에서는 구현을 시작할 때부터 Evaluation Dataset을 같이 만든다.
예를 들어 다음과 같은 형태다.
QuestionExpected Document
| 15세 콘텐츠 공개 기준은? | age-rating-policy.md |
| M-03 오류는 무엇인가? | metadata-guide.md |
| CONTENT_BLOCKED는 언제 사용하는가? | content-status-policy.md |
그리고 Retriever를 실행한다.
Question
↓
Retriever
↓
Top K Document
↓
Expected와 비교
이렇게 하면:
Hit Rate@K
Recall@K
MRR
같은 값으로 Retrieval 결과를 비교할 수 있다.
중요한 것은 특정 Metric 자체가 아니다.
이번 프로젝트에서 Evaluation Dataset은:
AI에게 구현이 끝났는지 물어보는 대신, 같은 문제를 다시 풀어보게 만드는 검증 환경
역할을 한다.
5. AI 프로젝트에서 Evaluation은 Test와 비슷한 역할을 한다
일반적인 Backend Test는 입력과 기대 결과가 비교적 명확하다.
Input
BASIC 이용권
+
ACTIVE Session 1개
↓
Expected
추가 재생 거절
Retrieval Evaluation도 구조 자체는 비슷하다.
Input
"15세 콘텐츠 공개 기준은?"
↓
Expected
age-rating-policy.md가 Top K에 존재
Agent에서도 마찬가지다.
Input
"콘텐츠 100번 상태 알려줘."
↓
Expected Tool
get_content_detail
즉 평가 Dataset을 잘 정의하면 AI 기능도 어느 정도 다음 구조로 바뀐다.
Input
↓
AI System
↓
Actual Result
↓
Expected Result와 비교
완전히 결정론적인 Unit Test와 같지는 않지만, 적어도:
"몇 번 질문해보니까 잘 되더라."
보다 훨씬 나은 기준을 만들 수 있다.
6. Evaluation Dataset도 AI에게 마음대로 고치게 하면 안 된다
이번 프로젝트에서 특히 주의하려는 부분이다.
예를 들어 Vector Search가 특정 질문을 계속 틀린다고 가정한다.
Question
"M-03 오류 의미는?"
↓
Expected
metadata-guide.md
↓
Actual
operations-faq.md
여기서 Evaluation 질문이나 정답 문서를 바꿔버리면 Metric은 쉽게 좋아진다.
구현을 Evaluation에 맞춤
이 아니라:
Evaluation을 구현에 맞춤
이 되어버린다.
그래서 Evaluation Dataset도 하나의 Source of Truth처럼 관리하려고 한다.
AI가 평가 데이터가 잘못되었다고 생각할 수는 있다.
하지만 그 경우:
평가 기준 변경
↓
Human Gate
를 거친다.
실제 문제가 구현에 있는지, Evaluation 자체에 있는지 사람이 확인한 뒤 바꾼다.
7. Retrieval과 Generation 실패를 분리한다
RAG가 틀렸을 때 가장 먼저 Prompt부터 수정하면 원인을 잘못 볼 수 있다.
RAG의 흐름을 단순하게 보면:
Question
↓
Retrieval
↓
Context
↓
LLM
↓
Answer
이다.
여기서 Answer가 틀렸다고 가정한다.
가능한 원인은 하나가 아니다.
정답 문서를 찾지 못한 경우
Retrieval Failure
정답 문서는 찾았지만 순위가 너무 낮은 경우
Ranking Failure
정답 Context가 전달됐지만 LLM이 잘못 답한 경우
Generation Failure
Context에 없는 내용을 LLM이 추가한 경우
Grounding / Hallucination Failure
따라서 이번 프로젝트에서는 최종 Answer만 평가하지 않는다.
Retriever
↓
Context
↓
Generation
각 단계를 분리해서 확인한다.
8. Evaluation이 기술 도입의 Gate가 된다
이번 프로젝트에서도 처음부터 여러 RAG 기술을 넣지 않는다.
Baseline은 단순하게 시작한다.
Question
↓
Embedding
↓
Vector Search
↓
Top K
↓
LLM
이 상태에서 Evaluation을 먼저 실행한다.
예를 들어 Semantic Query는 잘 찾는데 다음 질문만 계속 실패한다고 가정한다.
OPS-101
M-03
CONTENT_BLOCKED
이때 처음으로:
정확한 문자열 검색에서는 Vector Search가 약한 것 아닐까?
라는 가설을 세울 수 있다.
그리고 Keyword Search를 후보로 올린다.
Vector Search
+
Keyword Search
그 뒤 동일한 Dataset을 다시 실행한다.
Before
Hit Rate@K
MRR
↓
Hybrid Retrieval
↓
After
Hit Rate@K
MRR
실제로 좋아졌다면 유지할 이유가 생긴다.
좋아지지 않았다면:
Hybrid Search를 구현했다.
가 성과가 되는 것이 아니라:
현재 Dataset에서는 효과가 없었다.
가 Experiment 결과가 된다.
9. RRF와 Reranker도 같은 방식으로 본다
RRF나 Reranker는 RAG 관련 글에서 자주 등장한다.
하지만 이번 프로젝트에서는:
RAG 포트폴리오니까
RRF 넣기
Reranker 넣기
방식으로 진행하지 않는다.
예를 들어 Hybrid Retrieval 결과에서 정답 문서는 Top K 안에는 들어오지만 계속 낮은 순위에 있다고 가정한다.
Expected Document
Rank 5
이 문제가 실제로 반복된다면 Ranking 개선을 검토할 이유가 생긴다.
그때:
RRF
Reranker
등을 후보로 비교한다.
그리고 다음을 함께 본다.
MRR 변화
Top-K 결과
Reranking Latency
전체 응답시간
Reranker를 넣어서 MRR이 조금 올랐지만 응답시간이 크게 증가한다면 그것 역시 Trade-off다.
즉 이번 프로젝트에서는 AI 품질과 Latency를 같이 본다.
10. Agent 단계에서는 Tool 선택도 Evaluation 대상이다
문서 RAG가 끝난 뒤에는 콘텐츠 DB Tool을 추가할 예정이다.
예를 들어:
search_policy_documents
search_contents
get_content_detail
같은 Tool이다.
이때 Agent에게:
"콘텐츠 100번 상태 알려줘."
라고 질문했다면 예상 Tool은:
get_content_detail
이다.
그런데 Agent가:
search_policy_documents
↓
search_contents
↓
get_content_detail
를 호출한 뒤 답을 맞혔다고 가정해보자.
최종 Answer만 보면 성공이다.
하지만 Agent Workflow 측면에서는 불필요한 호출이 있다.
그래서 Agent 단계에서는:
Expected Tool
Actual Tool
Tool 호출 순서
불필요한 Tool 호출
까지 Evaluation에 포함한다.
최종적으로:
Tool Selection Accuracy
도 확인해볼 수 있다.
11. No Answer도 정답이 될 수 있다
일반적인 질문 서비스라면 AI가 무엇인가 답해주는 것이 좋아 보일 수 있다.
하지만 내부 정책 검색 시스템에서는 그렇지 않을 수 있다.
예를 들어 문서 Dataset 어디에도:
해외 판권 계약 담당자
정보가 없는데 사용자가:
"해외 판권 계약 담당자가 누구야?"
라고 질문했다고 가정한다.
이때 LLM이 그럴듯한 이름이나 조직을 만들어내는 것보다:
현재 제공된 문서에서는 확인할 수 없습니다.
라고 답하는 것이 더 좋은 결과다.
따라서 Evaluation Dataset에는 일부러 답이 없는 질문도 포함한다.
이번 프로젝트에서는:
Answer를 생성했는가?
보다:
근거가 없을 때 답하지 않을 수 있는가?
도 중요한 평가 기준으로 본다.
12. Prompt도 코드처럼 실험한다
RAG와 Agent를 개발하다 보면 Prompt를 계속 수정하게 될 가능성이 높다.
예를 들어:
Context만 사용하라.
추측하지 마라.
Source를 제공하라.
모르면 모른다고 답하라.
같은 규칙을 추가할 수 있다.
하지만 Prompt를 바꾼 뒤 질문 몇 개만 실행해서:
"이게 더 나은 것 같다."
고 판단하지 않는다.
가능하면:
Prompt A
↓
Evaluation
Prompt B
↓
Evaluation
구조로 비교한다.
Prompt 역시 코드와 비슷하게 변경 전후를 검증할 대상으로 본다.
13. 이번에는 Skill이 아니라 Rule로 시작한다
Live Event Stream에서는 .cursor/skills/live-event-stream/SKILL.md를 하나 두었다.
당시에는 Skill이라고 불렀지만 프로젝트를 진행하면서 내용을 다시 보니 대부분 다음과 같은 내용이었다.
현재 Phase를 벗어나지 않는다.
문제를 먼저 재현한다.
측정 없이 개선했다고 하지 않는다.
중요한 변경은 Human Gate를 거친다.
이것은 특정 작업을 수행하는 Workflow라기보다는 프로젝트 전체에서 항상 지켜야 하는 제약에 가깝다.
그래서 이번에는 처음부터 구조를 바꾼다.
.cursor/
└── rules/
└── content-ops-agent.mdc
Rule에는:
Phase 제한
Evaluation 기준
기술 선제 도입 금지
Human Gate
Git checkpoint
문서 동기화
같은 프로젝트 전체 규칙을 넣는다.
실제 개발을 하다가 반복 Workflow가 생기면 그때 Skill을 만든다.
예를 들어 매 Phase마다:
Evaluation 실행
↓
실패 Query 분류
↓
Metric 계산
↓
Experiment Markdown 생성
작업을 반복하게 된다면 이것은 하나의 Skill 후보가 될 수 있다.
즉 이번에는:
Rule
↓
반복 Workflow 발견
↓
Skill 추출
순서로 가보려고 한다.
14. 이번 Harness 구조
프로젝트 구조는 다음처럼 가져간다.
content-ops-agent/
├── .cursor/
│ └── rules/
│ └── content-ops-agent.mdc
│
├── docs/
│ ├── REQUIREMENTS.md
│ ├── DESIGN.md
│ ├── ROADMAP.md
│ ├── TASKS.md
│ ├── adr/
│ └── experiments/
│
├── evaluation/
│ └── ...
│
└── README.md
이번에는 evaluation/이 Harness의 중요한 부분이 된다.
각 요소의 역할을 정리하면:
REQUIREMENTS
→ 무엇을 만족해야 하는가
DESIGN
→ 현재 어떻게 구현되어 있는가
ROADMAP
→ 어떤 문제를 어떤 순서로 확인할 것인가
TASKS
→ 현재 무엇을 할 것인가
Rule
→ AI가 어떤 제약 안에서 작업해야 하는가
Test
→ 코드가 정상 동작하는가
Evaluation
→ AI 기능의 품질은 어떤가
Experiment
→ 변경 전후 결과가 어떻게 달라졌는가
ADR
→ 왜 해당 구조를 선택했는가
Git
→ 문제와 해결 과정이 어떻게 변화했는가
Evaluation이 기존 Harness에 하나 더 추가된 셈이다.
15. Git도 이번에는 한 단계 더 엄격하게 사용한다
Live Event Stream 회고에서 가장 아쉬웠던 것 중 하나는 문제 재현과 해결이 같은 Commit에 묶인 경우가 있었다는 점이었다.
이번에는 Rule에 아예 다음 원칙을 넣는다.
문제 재현이 성공한 상태는 해결 코드와 별도 Commit으로 남긴다.
예를 들어 Vector Search에서 Exact Keyword Query가 실패했다면:
experiment: reproduce exact keyword retrieval misses
를 먼저 남긴다.
그 뒤 Hybrid Retrieval을 실제로 선택했다면:
feat: add hybrid retrieval
를 별도로 남긴다.
그러면 Git History에서도:
Baseline
↓
문제 재현
↓
해결
↓
재평가
흐름을 확인할 수 있다.
16. README도 Phase 1부터 관리한다
Live Event Stream에서는 README를 처음부터 관리하겠다고 했지만 실행 방법과 검증 방법은 결국 후반에 많이 보강했다.
이번에는 이 부분도 Baseline 완료 조건에 넣는다.
Phase 1이 끝났다면 README에서 최소한 다음은 확인할 수 있어야 한다.
프로젝트가 무엇인지
PostgreSQL + pgvector 실행 방법
Sample Document 적재 방법
Question API 호출 방법
Test 실행 방법
Evaluation 실행 방법
즉 README 작성도:
마지막 정리 작업
이 아니라:
Phase 1 기능
에 가깝게 본다.
17. Human Gate는 AI 품질의 의미가 달라지는 곳에 둔다
모든 Prompt 수정이나 Class 생성마다 승인하면 개발 속도가 너무 느려진다.
따라서 단순 구현은 AI에게 맡긴다.
대신 다음과 같은 선택에서는 멈춘다.
Chunking 기본 전략 변경
Embedding Model 변경
Keyword Search 추가
Hybrid Retrieval 구조 결정
RRF 적용
Reranker 적용
LLM Model 변경
Grounding Prompt 정책 변경
Tool 추가 / 제거
Tool Routing 전략 변경
LangGraph 도입
Multi-Agent 도입
Evaluation 기준 변경
이런 결정들은 시스템의 품질과 비용, Latency 또는 의미를 바꿀 수 있다.
Human Gate에서는:
현재 실패 Case
↓
Metric
↓
원인
↓
후보
↓
장단점
↓
추천안
↓
사용자 결정
순서로 진행한다.
18. ContentOps Agent의 개발 Loop
이번 프로젝트의 전체 Loop는 다음과 같다.
ROADMAP
↓
TASK
↓
PLAN
↓
IMPLEMENT
↓
TEST
↓
EVALUATE
↓
FAILURE CASE
↓
ANALYZE
↓
후보 비교
↓
HUMAN GATE
↓
DECIDE
↓
IMPLEMENT
↓
RE-EVALUATE
↓
ADR / DESIGN
↓
COMMIT
↓
NEXT TASK
Live Event Stream의:
TEST
↓
EXPERIMENT
↓
MEASURE
부분이 이번에는:
TEST
↓
EVALUATE
↓
FAILURE ANALYSIS
로 조금 달라지는 셈이다.
19. 이번 프로젝트에서 개발자가 통제할 것
AI Coding Assistant를 많이 사용하더라도 몇 가지는 계속 직접 통제한다.
무엇을 만들 것인가
→ REQUIREMENTS
어떤 문제를 확인할 것인가
→ ROADMAP
현재 구조가 무엇인가
→ DESIGN
AI가 어떤 제약 안에서 일할 것인가
→ RULE
품질을 무엇으로 판단할 것인가
→ EVALUATION DATASET
어떤 기술을 선택할 것인가
→ HUMAN GATE + ADR
특히 이번에는:
품질을 무엇으로 판단할 것인가
까지 개발자가 통제한다는 점이 이전 프로젝트와 가장 큰 차이다.
20. 정리
Playback Gate에서는 Test와 성능 Metric이 Harness의 중요한 검증 요소였다.
Live Event Stream에서는 Experiment와 Git History까지 Harness에 포함시켰다.
ContentOps Agent에서는 여기에 Evaluation Dataset과 Metric을 추가해보려고 한다.
Playback Gate
Docs
+
Test
+
Performance Metric
↓
Live Event Stream
Docs
+
Rule에 가까운 Skill
+
Test
+
Experiment
+
Git
↓
ContentOps Agent
Docs
+
Project Rule
+
Test
+
Evaluation
+
Experiment
+
Git
AI 프로젝트에서는 코드가 정상적으로 실행됐다는 사실만으로 성공을 판단하기 어렵다.
그래서 이번에는:
AI에게 코드를 작성시키는 환경뿐만 아니라, AI가 만든 시스템의 결과를 반복해서 평가할 수 있는 환경까지 Harness로 본다.
그리고 기술 선택 역시 이전 프로젝트와 동일한 원칙을 유지한다.
Baseline
↓
Evaluation
↓
실패 Case
↓
원인 분석
↓
후보 비교
↓
Human Gate
↓
개선
↓
동일 Dataset 재평가
Vector Search만으로 충분할 수도 있다.
Hybrid Search가 필요할 수도 있다.
RRF가 효과가 없을 수도 있다.
Reranker의 품질 향상이 Latency 증가보다 작을 수도 있다.
LangGraph가 필요하지 않을 수도 있다.
중요한 것은 어떤 기술을 사용했는지가 아니라:
실제 실패 Case를 기준으로 무엇을 바꿨고, 같은 Evaluation Dataset에서 결과가 어떻게 달라졌는지를 설명할 수 있는 것
이다.
다음 단계에서는 이 전략을 실제 개발에 적용하기 위해 ContentOps Agent의 ROADMAP과 초기 DESIGN을 작성해보려고 한다.
'PROJECT > Toy Project' 카테고리의 다른 글
| [LLM Router #1] 프로젝트 소개와 학습 목표 (0) | 2026.08.16 |
|---|---|
| [ContentOps Agent #4] 학습 목표, 요구사항, Harness 회고 (0) | 2026.08.16 |
| [ContentOps Agent #2] 요구사항 정의 (0) | 2026.08.15 |
| [ContentOps Agent #1] 프로젝트 소개와 학습 목표 (0) | 2026.08.15 |
| [Live Event Stream #4] 회고 (0) | 2026.08.15 |