모델보다 하네스(Harness)가 더 중요하다: Ziya Code 구축하기
요약
AI 코딩 도구 Ziya Code의 개발 사례를 통해 모델 자체보다 모델 주변의 엔지니어링 레이어인 '하네스(harness)'의 중요성을 강조합니다. 단계적 격상(escalation) 전략과 정교한 데이터 인제스션(ingestion)을 통해 에이전트의 신뢰성을 높이는 방법을 다룹니다.
핵심 포인트
- 모델은 초안을 만들지만, 하네스는 정확한 최종 답변을 보장함
- 단순 원샷 방식 대신 난이도에 따른 단계적 격상(escalation) 전략 사용
- 검색 품질은 단순 Retrieval보다 데이터 인제스션 품질에 좌우됨
- 에이전트의 신뢰성은 모델 성능보다 엔지니어링 루프에서 발생함
저는 17살이며, Ziya Code라는 AI 코딩 도구를 만들고 있습니다. 이 포스트는 출시 홍보를 위한 것이 아닙니다. 거의 아무도 쓰지 않는 부분, 즉 첫 번째 답변이 틀렸을 때 무엇을 할지 결정하는 모델 '주변(around)'의 코드에 관한 이야기입니다. 그 레이어(layer)야말로 신뢰성이 실제로 발생하는 지점이며, 그 대부분은 직접 구축할 수 있는 배관(plumbing) 작업입니다.
제가 계속해서 되새기는 논지는 다음과 같습니다:
더 나은 모델은 더 나은 초안을 제공합니다. 더 나은 하네스(harness)는 정확한 최종 답변을 제공합니다. 이 둘은 서로 다른 문제이며, 두 번째 문제는 대부분 엔지니어링(engineering)의 영역입니다.
Ziya Code는 Ziya 자체 모델 위에서 실행되지만, 이 포스트는 의도적으로 모델에 대해 다루지 않습니다. 모델 주변에 제가 작성한 루프(loop)에 관한 것입니다. 아래의 모든 것은 하네스(harness)입니다. 즉, 에이전트 런타임(agent runtime) 위에 구축한 플러그인 레이어(plugin layer)와 그 앞에 위치한 게이트웨이(gateway) 및 인증(auth)입니다.
핵심 아이디어: 원샷(one-shot)이 아닌 단계적 격상(escalate)
대부분의 코딩 에이전트(coding agents)는 모든 요청에 대해 동일한 작업을 수행합니다. 프롬프트(prompt)를 보내고, 결과가 무엇이든 그대로 받아들입니다. 이는 쉬운 작업에서는 낭비이고, 어려운 작업에서는 절망적입니다.
대신, Ziya Code는 노력을 사다리처럼 취급합니다. 저렴한 경로를 먼저 시도하고, 저렴한 경로가 실패할 때만 위로 올라갑니다.
// 단순화된 격상 컨트롤러(escalation controller).
async function solve(task) {
// 0단계(Rung 0): 그냥 시도해 봅니다. 대부분의 작업은 여기서 끝납니다.
...
왜
- 공식 문서 약 **34,000개의 청크 (chunks)**를 임베딩(embedding)하여 저장했습니다.
- 검색(Retrieval)은 단순히 원시 프롬프트(raw prompt)에만 의존하는 것이 아니라, 작업 세트(working set) 내의 식별자(identifiers)를 기준으로 수행되므로 현재 작성 중인 정확한 함수에 대한 문서를 가져옵니다.
- 모델이 API 표면(API surface)을 확정하기 전에 컨텍스트(context)가 주입됩니다.
화려하지 않은 진실은 다음과 같습니다: _인제스터(ingester)_를 제대로 만드는 것이 검색(retrieval)을 구현하는 것보다 더 오래 걸렸습니다. 파이프라인에서 세 가지 별개의 버그를 배포했습니다 (잘못된 중복 제거(dedup), 코드 샘플을 반으로 쪼개버린 청크 경계의 오프 바이 원(off-by-one) 오류, 그리고 약 5%의 청크를 조용히 건너뛰는 임베딩(embedding) 단계 오류). 검색 품질은 인제스션(ingestion) 품질의 하류(downstream)에 있으며, 인제스션은 모든 지루한 버그들이 발생하는 곳입니다.
그리고 솔직한 부분은 이렇습니다: 그라운딩(grounding)은 환각(hallucination)된 API 문제를 해결하는 데 도움이 되었지만, 그 자체만으로는 정확도 향상의 가장 큰 요인은 아니었습니다. 이는 저를 가장 놀라게 했던 지점으로 이어집니다.
측정 결과, Best-of-N에 대해 제가 틀렸음을 알게 되었습니다
단순한 느낌(vibes)을 믿고 싶지 않았기에, BigCodeBench(실제 라이브러리 사용 비중이 높은 현실적인 Python 작업 벤치마크)에서 A/B 테스트를 수행했습니다. 모델은 동일하게 유지하고, 하네스(harness) 구성만 다르게 설정했습니다.
| 구성 (Configuration) | 통과율 (Pass rate) |
|---|---|
| 베이스라인 (one-shot) | ~31% |
| ... |
제가 예상하지 못했던 두 가지 결과가 있었습니다:
1. 그라운딩(Grounding)만으로는 수치가 거의 변하지 않았습니다. 이는 특정 _카테고리_의 오류(잘못된 API)를 수정했지만, 해당 카테고리는 이 벤치마크에서 병목(bottleneck)이 아니었습니다. 가장 시끄러운 버그를 고치는 것이 가장 흔한 버그를 고치는 것과 같지는 않습니다.
2. Best-of-N이 패배했습니다. 다섯 개의 답변을 샘플링하여 그중 최선책을 선택하는 것은 공짜로 정확도를 얻는 것처럼 들립니다. 하지만 그렇지 않았으며, 그 이유는 이 포스트의 핵심 교훈과 같습니다: 다섯 개 중 최선을 유지하려면, 다섯 개 중 무엇이 최선인지 판단할 수 있는 무언가가 필요합니다. 만약 선택기(selector)가 약하다면, 당신은 다섯 개의 틀린 답변 중 가장 자신만만한 틀린 답변을 고르게 될 뿐입니다. 그것은 더 나아진 것이 아니라, 단지 비용만 더 많이 들게 된 것입니다.
이제 제가 설계를 할 때 기준으로 삼는 교훈은 다음과 같습니다:
검증기(verifier)가 병목입니다. 샘플링(sampling)이 아니라 검증(verification)에 컴퓨팅 자원을 투입하세요.
바로 이 이유 때문에 자기 수정(Self-repair)이 승리합니다. 그것은 어떤 답변이 좋은지 추측하는 것이 아니라, 코드를 직접 실행하고 트레이스백(traceback)을 읽습니다. 테스트 실행 자체가 바로 검증기(verifier)인 것입니다. 저렴하고 실제적인 정확성 신호(correctness signal)는 매번 다섯 번의 비싼 추측을 이깁니다. 제가 문제를 이런 방식으로 정의하자, 전체 사다리(ladder) 구조가 더 명확해졌습니다. 즉, 모든 비싼 단계는 단순히 주사위를 더 많이 던지는 방법이 아니라, 더 나은 신호를 얻고 그에 따라 행동하는 방법인 것입니다.
게이트웨이: OpenAI와 호환되지만, 한계에 대해서는 솔직하게
이 도구는 제가 작성한 게이트웨이를 통해 모델과 통신합니다. 이는 바로 교체 가능한 /v1/chat/completions 엔드포인트이므로, OpenAI 형식을 따르는 것이라면 무엇이든 이 엔드포인트를 가리킬 수 있습니다. 하지만 일반적인 모델 엔드포인트가 하지 못하는 두 가지 일을 수행합니다. 바로 모든 턴(turn)을 기록하고, 정밀하게 미터링(metering)하는 것입니다.
제가 이상할 정도로 자부심을 느끼는 미터링의 세부 사항은 다음과 같습니다: 입력(input) 및 출력(output) 토큰이 일일 및 주간 단위로 각각 별도로 제한된다는 점입니다.
왜 굳이 이를 나누었을까요? 그것들이 서로 다른 자원이기 때문입니다. 제 계정에서 진행한 한 번의 에이전트 세션은 브라우저 자동화 환경을 설정하고, 파일을 읽고, 로그를 덤프하고, 재시도하는 과정에서 약 500만 토큰까지 불어났습니다. 그중 거의 대부분은 사용자가 직접 입력하지 않은 컨텍스트인 입력(input) 토큰이었습니다. 모델이 실제로 생성하는 부분인 출력(output)은 희소하고 비싼 자원입니다. 만약 이 둘을 하나의 통합된 숫자로 제한한다면, 컨텍스트 집약적인 단 한 번의 세션이 사용자가 작성하지도 않은 토큰으로 인해 누군가의 전체 예산을 날려버릴 수 있습니다.
// 모델을 호출하기 전, 모든 /v1 턴은 이곳을 통과합니다.
function enforceQuota(user, estIn) {
const u = usage(user) // DB에서 읽어온 이동 카운터(rolling counters)
...
이로부터 파생된 작은 UX 결정 사항은 다음과 같습니다: 앱은 사용량을 원시 토큰 숫자가 아닌 **비율 바(proportion bar)**로 보여줍니다. 사람들은 토큰 단위로 생각하지 않으며, 바 형태(
데스크톱 도구는 사용자의 계정에 대해 _사용자 자신_처럼 동작해야 하지만, 사용자가 압축을 풀 수 있는 바이너리(binary) 내부에 어떤 종류의 비밀 정보라도 포함하는 것은 시작조차 할 수 없는 문제입니다. 따라서 로그인은 스마트 TV가 사용하는 것과 동일한 흐름인 OAuth 2.0 디바이스 권한 부여 방식(device authorization grant)을 사용합니다.
1. 도구가 서버에 디바이스 코드(device code)를 요청합니다.
2. 도구가 짧은 사용자 코드를 보여주고 승인 페이지를 엽니다.
3. 사용자가 브라우저(이미 로그인되어 있는 상태)에서 승인합니다.
...
바이너리에는 자격 증명(credential)이 절대 포함되지 않습니다. 토큰은 사람이 화면에 표시된 정확한 코드를 승인한 후에만 발행됩니다. 제가 발견하고 수정한 버그 하나는 다음과 같습니다. 승인 페이지를 열었을 때 로그인되어 있지 않은 경우, 로그인으로 리다이렉트(redirect)되는 과정에서 대기 중인 코드가 유실되어 로그인을 해도 아무런 페이지로도 이동하지 못하는 문제가 있었습니다. 해결 방법은 인증 리다이렉트 과정에서 코드를 계속 유지하여, 막다른 길이 아닌 승인 단계로 다시 돌아오게 만드는 것이었습니다. 그런 작은 흐름 버그들은 실제 사용자가 마주하기 전까지는 보이지 않습니다.
6개월 전의 나에게 해주고 싶은 말
- 하네스(harness)가 곧 제품이다. 모델은 하나의 구성 요소일 뿐입니다. 모델을 둘러싼 루프(loop) 안에 신뢰성, 비용 제어, 그리고 실제 코드의 대부분이 존재합니다.
- 믿기 전에 측정하라. Best-of-N 방식은 분명 좋아 보였지만 제 A/B 테스트에서 패배했습니다. 직관만 따랐다면 잘못된 것을 출시했을 것입니다.
- 검증(Verification)이 게임의 전부다. 옳고 그름을 저렴한 비용으로 구분할 수 있다면, 나머지 모든 것(수정, 선택, 에스컬레이션)은 쉬워집니다. 구분할 수 없다면, 아무리 많은 샘플링을 해도 구원받을 수 없습니다.
- 지루한 파이프라인은 지루한 방식으로 고장 난다. 제가 겪은 최악의 그라운딩(grounding) 버그들은 모두 똑똑한 로직이 아니라 데이터 수집(ingestion) 단계에서 발생했습니다.
대부분 17살이라는 것은, 많이 틀려도 괜찮고 계속 나아갈 시간이 있다는 것을 의미합니다. 위의 내용 중 절반은 제가 확신했던 아이디어로 시작했다가, 수치를 확인한 후 철회해야 했던 것들입니다. 추측하고, 측정하고, 수정하는 그 루프는 제가 도구에 구축한 루프와 동일하며, 이것은 우연이 아니라고 생각합니다.
직접 살펴보고 싶다면, Ziya Code는 여기에 있습니다: https://ziya.codes
댓글을 통해 이 내용의 어떤 부분에 대해서든, 특히 단계적 상승 사다리 (escalation ladder)와 검증기 병목 현상 (verifier-bottleneck) 문제에 대해 더 자세히 논의할 수 있다면 기쁘겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기