바쁜 사람을 위한 결론 (TL;DR)
동일한 LLM 모델과 동일한 초기 과제(3개 모듈에 걸친 총 6개 지점의 복합 버그 수정)를 사용하여, 기존 방식인 **단일 루프(Single Loop)**와 단계별로 자동 검증기(Verifier)를 거치는 **유향 그래프(Directed Graph)**를 실제 환경에서 비교한 결과, 다음과 같은 극적인 개선이 확인되었습니다.
품질 (테스트 합격률): 두 방식 모두 비공개 테스트 총 20건에 100% 합격 (20 / 20 Pass) -
LLM 호출 횟수 (턴 수): 7회 ➔3회 (-57.1% 감소) -
에이전트 실행 시간: 36.69초 ➔19.07초 (-48.0% 단축 / 약 1.92배 고속) -
누적 입력 토큰 수: 23,322 ➔5,966 (-74.4% 감소 / 약 4분의 1로 압축) -
누적 추론 비용: $0.02195
➔$0.00893
( -59.3% 감소)
'어떤 순서로 파일을 보고, 언제 테스트를 돌릴지'라는 워크플로우 제어(Orchestration)를 결정론적인 유향 그래프로 옮기고, 단계별로 컨텍스트를 리셋함으로써, 출력 코드의 품질을 전혀 떨어뜨리지 않으면서 토큰 소비와 실행 시간을 대폭 압축할 수 있습니다.
1. 검증의 근거가 된 설계 이론 (@hanakoxbt 씨의 주장)
대상 게시물: "Loops and Graphs: how to stop babysitting agents and only approve the last step" (에이전트 '돌봄'을 중단하고 마지막 단계만 승인하는 방법)
기존 AI 에이전트 개발의 과제 (Single Loop의 한계)
많은 개발자들은 AI 에이전트를 만들 때 모든 도구(파일 열람/편집/테스트 실행/완료 선언)와 전체 컨텍스트를 '하나의 큰 단일 루프(Single Loop / ReAct형 루프)'에 담아,
모든 파일에 대한 자유로운 조작 권한 (inspect_all, edit_file, run_tests, SUBMIT)을 하나의 루프에서 부여하고, AI 스스로 조사-수정-테스트-제출의 순서를 판단하게 한다.
모드 B: DirectedGraphRunner (단계별 유향 그래프)
세 개의 단계 (node_1_parser ➔ node_2_pricing ➔ node_3_report ➔ node_4_terminal_approval_gate)를 유향 그래프로 정의하고, 각 노드에는 담당 파일 하나의 컨텍스트만 제공하며, 해당 단계 전용 자동 Verifier가 통과하면 자동으로 다음 노드로 이동시킨다.
검증하고 싶은 세 가지 질문
- 품질(정확성): 두 모드 모두 AI에게 숨겨진 독립 채점 스위트(총 20개 테스트 케이스)에서 100% 통과할 수 있는가?
- 효율(턴 수/실행 시간): 진행 관리를 결정론적인 유향 그래프로 옮김으로써, LLM 호출 횟수와 실행 시간을 얼마나 절감할 수 있는가?
- 경제성(컨텍스트 압축 효과): 노드별로 컨텍스트를 분리/초기화함으로써, 입력 토큰 수와 API 추론 비용을 얼마나 절감할 수 있는가?
검증용 샌드박스 VM의 내부 구성 및 부정 방지(부정행위 방지) 설계 보기
실험 전용 VM 내부에서는 AI 실행 사용자와 독립 채점 사용자가 Linux UID 및 systemd 네임스페이스 (InaccessiblePaths)에 의해 엄격하게 분리되어 있습니다. AI 프로세스는 최종 채점을 위한 테스트 코드 (evaluation/judges.py)를 전혀 읽을 수 없습니다.
3. AI에게 부여한 과제 설계 (3개 모듈 주문 처리 파이프라인 및 6가지 복합 버그)
단순히 1개 파일 수정만으로는 'Loop'와 'Graph'의 차이를 내기 어렵기 때문에, 실제 업무 백엔드 개발에서 빈번하게 발생하는 **"3개의 모듈이 직렬로 연계되는 주문/청구 처리 파이프라인"**을 설계하고, 상류의 버그가 하류의 계산이나 출력에 영향을 주는 총 6개소의 복합 버그를 심었습니다.
각 단계에 심어진 초기 버그 (총 6개소)
-
Stage 1: parser.py (parse_items)
- 버그 ① 수량:
0 수량이 누락되어, qty < 0만 걸러내고 qty == 0의 무효 명세나 bool 타입을 통과시킴.
- 버그 ② SKU 비정규화 및 입력 파괴: 전후 공백 제거(
.strip())와 대문자 변환(.upper())을 하지 않았으며, 게다가 호출 원본 딕셔너리 row를 직접 수정하여 파괴함.
-
Stage 2: pricing.py (compute_invoice)
- 버그 ③ 동일 SKU 단가 모순 통과: 같은 SKU에 다른
unit_price가 혼재해도 에러(ValueError) 없이 합산해 버림.
- 버그 ④ 할인 적용 순서 오류 및 경계값 버그: 쿠폰 할인 (
WELCOME500)을 먼저 빼고 나서 > 10000으로 볼륨 할인을 판정하기 때문에, 정확히 10,000엔 주문에서 볼륨 할인(10%)이 사라짐 (게다가 미지의 쿠폰 문자열은 무시함).
- 버그 ⑤ 소수점 처리 반올림 오류: 할인이나 소비세(10%) 계산에
round()를 사용하여, 짝수 반올림으로 인해 1엔의 오차가 발생함 (정확히는 정수 내림 // 10).
-
Stage 3: report.py (format_summary)
- 버그 ⑥ 명세 수량 하드코딩 및 할인 키 이름 불일치: 각 명세 행을 항상
x1 @단가 = 단가로 출력해버리고, 게다가 `invoice.get(
**Mode A(단일 루프)**에서는 '진행 관리'에 4턴(전체의 57%)을 사용하고 있습니다.
-
실제로 파일을 수정한 것은 Turn 3, 4, 5의 총 3회뿐이지만, 그 전후로 inspect_all
(
Turn 1), 초기 run_tests
(
Turn 2), 확인용 run_tests
(
Turn 6), SUBMIT
(
Turn 7)와 같이 총 4회의 오케스트레이션용 LLM 호출이 발생합니다. - 또한, Turn 1에서 읽어 들인 3개 파일 전체 내용과 Turn 3~5에서 모델 스스로 출력한 코드 전체가 대화 기록에 계속 남아있기 때문에, 후반 턴일수록 1회당 입력 토큰 수가 급증합니다.
-
실제로 파일을 수정한 것은 Turn 3, 4, 5의
**Mode B(유향 그래프)**에서는 '진행 관리'가 Python 코드 측에서 결정론적으로 이루어집니다. - 각 노드에 진입하는 시점에 러너(runner)가 대상 파일 1개의 내용을 프롬프트에 담아 전달하고, AI가 patch_stage를 반환하는 순간 같은 단계 내에서 자동 검증기(Verifier, verify_*_stage)를 실행합니다. - 검증기가 Pass하면 LLM에게 '다음은 어떻게 할까요?'라고 질문할 필요 없이 즉시 다음 노드로 전이하며, 게다가 과거 노드의 대화 기록을 완전히 버리고 새로운 노드 전용의 최소 컨텍스트로 호출됩니다. - Node 3의 Verifier가 Pass한 순간, Node 4(terminal_approval_gate)가 자동으로 AUTO_SUBMIT을 기록하므로, 제출만을 위한 LLM 호출도 필요 없습니다.
- 각 노드에 진입하는 시점에 러너(runner)가 대상 파일 1개의 내용을 프롬프트에 담아 전달하고, AI가
5. 실기 검증 결과 (정량 비교 데이터)
① 종합 비교 테이블
| 평가 지표 | Mode A : Single Loop | Mode B : Directed Graph | 유향 그래프화로 인한 개선 효과 |
|---|
| 독립 채점 테스트 합격 수 (전체 20 케이스) | 20 / 20 합격 (100%) | 20 / 20 합격 (100%) | 동등한 완벽한 품질 유지 |
| LLM 호출 횟수 (총 턴 수) | 7회 | 3회 | -57.1% 감축 (4턴 절감) |
파일 수정 횟수 (file_edits) | 3회 | 3회 | 실질 작업 횟수는 동일 (제어 턴만 소멸) |
| 에이전트 실행 시간 | 36.69초 | 19.07초 | -48.0% 단축 (약 1.92배 고속) |
누적 입력 토큰 수 (prompt) | 23,322 토큰 | 5,966 토큰 | -74.4% 감축 (약 4분의 1로 압축) |
누적 출력 토큰 수 (candidates) | 1,188 토큰 | 1,189 토큰 | 거의 동일 (수정 코드 3본 분량) |
| 누적 추론 비용 (추정 USD) | $0.02195 | $0.00893 | -59.3% 비용 절감 |
② 턴별 토큰 소비 추이 (왜 입력 토큰이 74.4%나 줄었는가?)
실측 로그에 기록된 총 10콜(Mode A: 통번 06, Mode B: 통번 79)의 턴별 내역을 보면, 단일 루프에서의 컨텍스트 비대화 구조가 명확하게 보입니다.
| 실행 모드 | 통번 | 실행 노드 / 액션 | 입력 토큰 | 출력 토큰 | 컨텍스트 상태 |
|---|
| Mode A | 0 | inspect_all (전체 파일 열람) | 1,652 | 8 | 사양서만 |
| Mode A | 1 | run_tests (첫 번째 테스트 → 실패) | 2,848 | 8 | +3개 파일 초기 코드 전체가 히스토리에 누적됨 |
| Mode A | 2 | edit_file (parser.py 수정) | 2,883 | 357 | +첫 번째 테스트 오류 출력이 누적됨 |
| Mode A | 3 | edit_file (pricing.py 수정) | 3,324 | 591 | +parser.py 수정 코드 전체가 누적됨 |
| Mode A | 4 | edit_file (report.py 수정) | 4,000 | 209 | +pricing.py 수정 코드 전체가 누적됨 |
| Mode A | 5 | run_tests (재테스트 → 합격) | 4,297 | 8 | +report.py 수정 코드 전체가 누적됨 |
| Mode A | 6 | SUBMIT (완료 선언) | 4,318 | 7 | 첫 번째(1,652)의 2.6배로 비대화됨 |
| Mode A 합계 | — | 총 7 턴 | 23,322 | 1,188 | 히스토리가 끝까지 눈덩이처럼 잔존함 |
| Mode B | 7 | node_1_parser : patch_stage + 자동 검증 합격 | 1,947 | 366 | parser.py만 (신선한 컨텍스트) |
| Mode B | 8 | node_2_pricing : patch_stage + 자동 검증 합격 | 2,174 | 600 | 히스토리 리셋: pricing.py만 |
| Mode B | 9 | node_3_report : patch_stage + 자동 검증 합격 ➔ AUTO_SUBMIT | 1,845 | 223 | 히스토리 리셋: report.py만 |
| Mode B 합계 | — | 총 3 턴 (+자동 승인 게이트) | 5,966 | 1,189 | 항상 2,000 토큰 전후의 경량 컨텍스트를 유지한다 |
독립 채점 스위트 (총 20 테스트 케이스) 상세 내역 보기
AI 프로세스 종료 후, 독립 채점 환경에 의해 실행된 총 20개 테스트 케이스의 결과입니다. Mode A와 Mode B 모두 전 항목 한 번에 합격(20 / 20 Pass)을 달성했습니다.
| 카테고리 | 테스트 케이스 ID | 검증 내용 | Mode A | Mode B |
|---|
| Stage 1 | parser_01_normalize_and_non_mutation | SKU 공백 제거 및 대문자 변환, 여분 키 제거, 그리고 입력 리스트 비파괴 확인 | Pass | Pass |
| Stage 1 | parser_02_empty_and_non_list_rejected | 빈 리스트 [] 또는 리스트/딕셔너리가 아닌 요소에 대한 ValueError 발생 | Pass | Pass |
| Stage 1 | parser_03_blank_or_non_str_sku_rejected | 공백만으로 이루어진 SKU " " 또는 숫자 SKU에 대한 ValueError 발생 | Pass | Pass |
| Stage 1 | parser_04_zero_or_negative_qty_rejected | qty == 0 및 음수에 대한 ValueError 발생 | Pass | Pass |
| Stage 1 | parser_05_zero_price_allowed_negative_rejected | 무료 특전(unit_price == 0) 허용 및 음수 단가 거부 | Pass | Pass |
| Stage 1 | parser_06_bool_and_float_rejected | Python에서 int의 서브클래스인 True/False 또는 부동소수점 거부 | Pass | Pass |
| Stage 2 | pricing_01_sku_aggregation_and_order | 동일 SKU의 수량/소계 합산 및 최초 등장 순서 유지 | Pass | Pass |
| Stage 2 | pricing_02_conflicting_unit_price_rejected | 동일 SKU에서 단가가 다른 명세가 혼재된 경우 ValueError 발생 | Pass | Pass |
| Stage 2 | pricing_03_below_10000_boundary_9999 | 경계값 직하(9,999엔)에서 볼륨 할인이 0이 되는지 확인 | Pass | Pass |
| Stage 2 | pricing_04_exact_10000_boundary | 경계값 정확히(10,000엔)에서 볼륨 할인 1,000엔이 적용되는지 확인 | Pass | Pass |
| Stage 2 | pricing_05_volume_before_coupon_at_10000 | 10,000엔 + WELCOME500으로 볼륨 할인이 먼저 적용되어 총 할인액이 1,500엔이 되는지 확인 | Pass | Pass |
| Stage 2 | pricing_06_floor_truncation_not_round | 10,075엔 등에서 반올림(round)이 아닌 버림(절사)(// 10)이 적용되는지 확인 | Pass | Pass |
| Stage 2 | pricing_07_coupon_capped_at_subtotal | 소액 주문(300엔)에 WELCOME500 적용 시 할인 상한액이 300엔에서 멈추는지 확인 | Pass | Pass |
| Stage 2 | pricing_08_unknown_coupon_rejected | 미정의 쿠폰 코드 문자열에 대한 ValueError 발생 | Pass | Pass |
| Stage 3 & E2E | e2e_01_single_item_report_format | 단일 명세의 포맷 및 할인액이 0엔일 때 DISCOUNT: -0 표기 확인 | Pass | Pass |
| Stage 3 & E2E | e2e_02_multi_sku_aggregation_and_discounts | 비정규화된 SKU 합산 + 볼륨 할인 + 쿠폰의 일관 결합 출력 | Pass | Pass |
| Stage 3 & E2E | e2e_03_zero_price_item_summary | 무료 아이템만으로 구성된 주문에서의 0엔 요약 출력 확인 | Pass | Pass |
| Stage 3 & E2E | e2e_04_odd_amount_floor_tax_and_coupon | | | |
전체 단계 결합 출력 (단수 절사 금액 발생 시) | Pass | Pass |
Stage 3 & E2E |
e2e_05_line_order_preserved_in_report |
다중 SKU 혼재 시 보고서 행 출력 순서 검증 | Pass | Pass |
Stage 3 & E2E |
e2e_06_error_propagation_across_stages |
스테이지 1 / 스테이지 2의 잘못된 입력 오류가 파이프라인 전체에서 올바르게 포착되는 것 | Pass | Pass |
6. 요약: 검증을 통해 얻은 엔지니어링적 통찰
@hanakoxbt 님의 게시물이 주장했던 “루프(Generator + Verifier)를 유향 그래프(Directed Graph)의 노드 안에 가두고, 인간은 마지막 승인 게이트만 보는” 설계 패턴은 실기 검증에서 명확한 우위를 보였습니다.
'무엇을 할지(추론)'와 '어떻게 진행할지(제어)'를 분리해야 한다
코드를 작성하거나 버그를 수정하는 등의 추론 작업은 LLM이 가장 잘 수행하는 영역이지만, “먼저 파일 목록을 보고, 테스트를 실행하고, 다음으로 어떤 파일을 편집하며, 마지막으로 제출 버튼을 누르는” 것과 같은 정형적인 워크플로우 제어까지 LLM의 자유 루프에 맡기면, 턴 수의 과반수(이번에는 7회 중 4회)가 진행 관리만을 위해 낭비됩니다. -
스테이지 분할 + 자동 Verifier는 '컨텍스트 압축기'로 기능한다
유향 그래프의 각 노드에 “담당 모듈 1개”와 “그 스테이지 전용의 자동 Verifier”를 할당함으로써, 이전 스테이지까지의 시행착오 로그를 다음 스테이지로 넘겨줄 필요가 없어집니다. 그 결과, 생성되는 코드의 품질(20/20 합격)과 출력 토큰량을 전혀 떨어뜨리지 않으면서, 입력 토큰량을 74.4%, 실행 시간을 48.0%, 총 비용을 59.3% 절감할 수 있음이 입증되었습니다. -
'마지막 단계만 승인하기(Only approve the last step)'의 실현
유향 그래프의 최종 게이트(terminal_approval_gate)에 도달했을 시점에는, 이미 모든 스테이지의 결정론적 Verifier가 서명(Pass)을 완료한 상태입니다. 이로 인해 개발자는 중간 단계를 감시(Babysitting)할 필요 없이, 최종 게이트를 통과한 결과물만을 안심하고 승인 및 병합할 수 있게 됩니다.
📚 AI Labo 'AI 에이전트 하네스 엔지니어링 실기 검증' 시리즈 총정리
본 기사는 SNS(X / Threads)나 엔지니어 커뮤니티에서 화제가 된 AI 에이전트 설계론을, 완전 오프라인의 격리된 샌드박스 VM 위에서 정량적 A/B 테스트를 진행하는 AI Labo 실증 시리즈의 1편입니다.
전체 21개 실험(E04 ~ E24 + 수시 추가 중)의 토큰 절감률 랭킹 & 베스트 프랙티스 총정리는 아래 마스터 기사에서 수시로 업데이트하고 있습니다. 꼭 북마크 및 팔로우하여 최신 실증 결과를 확인해 보세요!
Discussion