ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프롬프트만으로는 못 고친다 — 도구 가이드를 5단으로 줘도 schema 에러는 그대로였다 (Ralph Loop 시리즈 4편)
    IT 2026. 5. 17. 21:00
    프롬프트만으로는 못 고친다 — 도구 가이드를 5단으로 줘도 schema 에러는 그대로였다 (Ralph Loop 시리즈 4편)

    자율 코딩 루프를 짜면서 가장 답답했던 시기는 모델이 도구 호출 시 필수 필드를 빠뜨리는 schema 에러 가 매 iter 평균 3.7회 발생하던 때였다. filePath 누락 43회, description 누락 26회, content 누락 23회. 가장 기본적인 식별자조차 빠뜨리는 모델 을 두고 처음엔 prompt 가이드로 해결할 수 있다고 믿었다. 그래서 prompt 안에 도구 사용 필수 사항을 5단으로 명시했다 — Bash 도구의 두 필드, Edit 도구의 세 필드, Write 도구의 두 필드, commit 메시지 형식, 실패 시 즉시 재시도 명령. 결과는 — 효과가 사실상 0이었다.

    이 글은 ralph-loop.sh 의 prompt 템플릿이 어떻게 수행 순서 + 도구 사용 가이드 + 제약 조건 의 세 영역으로 짜여있는지 분해한다. 그리고 도구 가이드라는 한 영역이 왜 효과가 없었는지 — 프롬프트 엔지니어링의 한계 를 직접 부딪힌 경험을 본다. 시리즈 4편이고 1~3편은 컨텍스트 3축, 부팅·셧다운, vLLM 헬스 보장을 다뤘다.

    출발점 — 한 prompt 에 무엇을 담아야 하는가

    매 iter 마다 새 opencode 세션이라 — 모델은 PLAN 첨부 + CHECKLIST 첨부 + git 인라인 + 그리고 prompt 자체 에서 모든 컨텍스트를 새로 받는다. prompt 가 한 줄짜리 "다음 항목 1개 구현해" 라면 모델이 알아서 한다고 가정하는 거다. 하지만 그 가정은 부서졌다.

    모델이 매번 다른 행동을 했다 — 어떤 iter 는 PLAN 을 다 읽고 시작했고, 어떤 iter 는 그냥 작업부터 시도했다. 어떤 iter 는 commit 메시지를 영문으로 한 줄로 깔끔하게 적었고, 어떤 iter 는 다줄 한국어 메시지에 큰따옴표 escape 사고로 commit 자체가 깨졌다. 어떤 iter 는 도구 호출을 정확히 했고, 어떤 iter 는 필드 누락 에러를 5번 연속 던지고 포기했다.

    변동성을 줄이려면 prompt 가 수행 순서를 명시 해야 했다. 단순한 "1개 구현해" 가 아니라 — git log 부터 보고, 디렉토리 구조 확인하고, 한 항목만 구현하고, 테스트 작성하고, vitest 돌리고, CHECKLIST 토글하고, commit 한다 — 의 7단계 흐름이다.

    prompt 의 3영역 구조

    최종 정착한 prompt 템플릿은 200줄 정도. 그 구조를 추상화하면 세 영역이다.

    Ralph Loop prompt 의 3영역 구조

    그림 설명 — prompt 가 위에서 아래로 ① 컨텍스트 ② 수행 순서 ③ 도구 가이드+제약 의 세 영역으로 흐른다. 영역 ①은 불변/가변/실측 3축의 형태를 모델에게 명시하는 부분 (1편 주제). 영역 ② 는 변동성 줄이기 를 위한 7단계 절차. 영역 ③ 은 도구 호출 시 필드 누락 방지 + 제약 조건. 세 영역이 같은 prompt 안에 있지만 효과는 천양지차였다 — ② 는 효과 있음, ③ 은 효과 거의 0. 색깔이 그 효과 차이를 표현한다 (녹색 계열은 효과적, 빨강 계열은 거의 무용).

    영역 ② 수행 순서 — 효과가 있었던 부분

    수행 순서를 명시한 것은 효과가 있었다. 7단계로 명시한 prompt 가 적용된 후 모델 행동의 변동성이 눈에 띄게 줄었다.

    ## 수행 순서
    1. 먼저 `git log --oneline -5` 와 `git diff HEAD~1` 로 직전 iter 의 변경을 파악한다.
    2. `ls -la src/ tests/ 2>/dev/null` 등으로 PLAN.md 의 목표 디렉토리 구조 대비
       현재 상태를 확인한다.
    3. 위 작업 항목을 **하나만** 구현한다 (다른 미체크 항목은 건드리지 않는다).
    4. 코드 변경 시 관련 단위 테스트를 같은 iter 에 작성/갱신한다 (TDD).
    5. 테스트가 있으면 `npx vitest run` 으로 통과 확인 (실패 시 고친다).
    6. 작업 완료 후 CHECKLIST.md 의 line $line_num 을 `- [ ]` → `- [x]` 로 변경한다.
    7. 작업 중 PLAN.md 에 없던 새 요구사항을 발견하면 CHECKLIST.md 끝에
       `- [ ] <새 항목>` 으로 자유롭게 추가한다.
    8. `git add -A && git commit -m 'feat: <한 줄 요약>'` 로 커밋한다.

    왜 이게 효과가 있었나? 추측건대 — 수행 순서는 모델의 의사결정 영역이다. "git log 부터 봐라" 는 명령은 모델이 응답 생성 시점에 자기 행동 계획을 짜는 단계에 영향을 준다. 모델이 응답을 "네, 시작합니다. 먼저 git log 를 봅니다." 로 시작하는 패턴이 38 iter 중 80% 이상 관찰됐다. 즉 prompt 의 영역 ②는 모델의 chain-of-thought 에 직접 박혀 작동한다.

    영역 ③ 도구 가이드 — 효과가 0이었던 부분

    같은 prompt 의 다음 영역인 도구 가이드는 그렇지 않았다.

    ## 도구 사용 필수 사항 (매우 중요)
    opencode 의 도구 호출 시 모든 필수 필드를 빠짐없이 채워라. SchemaError 가
    발생하면 누락 필드를 추가해 **즉시 재시도**한다 — 절대 포기하지 마라.
    
    - **Bash 도구**: `{ "command": "...", "description": "한 줄 설명" }` 두 필드 모두 필수.
    - **Edit 도구**: `{ "filePath": "...", "oldString": "...", "newString": "..." }` 세 필드 모두 필수.
    - **Write 도구**: `{ "filePath": "...", "content": "..." }` 두 필드 모두 필수.
    - **git commit 메시지**: `git commit -m '메시지'` 처럼 **작은따옴표 단일 인수**를 사용하라.
      큰따옴표 안에 줄바꿈/특수문자가 들어가면 셸 escape 사고가 잦다.
    
    도구 호출이 한 번 실패해도 멈추지 마라. 누락 필드를 채워 다시 호출하라.

    이 가이드를 prompt 에 박은 후 30 iter 의 schema 에러를 다시 카운트했다. 결과:

    에러 종류 가이드 적용 전 (10 iter 평균) 가이드 적용 후 (10 iter 평균)
    filePath 누락 1.4회/iter 1.4회/iter
    description 누락 0.9회/iter 0.8회/iter
    content 누락 0.8회/iter 0.8회/iter
    oldString 누락 / 매치 실패 0.4회/iter 0.4회/iter

    차이가 통계적 노이즈 수준 이다. 가이드를 5단으로 명시했는데도 누락 빈도가 거의 같았다. 모델이 응답 텍스트로는 "Edit 도구는 filePath/oldString/newString 세 필드 모두 필수" 를 인지한 듯한 메시지를 출력하기도 했지만 — 정작 다음 호출에서 또 필드를 빠뜨렸다.

    왜 효과가 없었나? 이 부분이 결국 시리즈 8편(별도 글) 의 주제다. 짧게 결론만 — "필드 누락" 자체가 모델의 직접적 실수가 아니었다. 진짜 원인은 vLLM 의 qwen3xml tool parser 가 streaming chunk 를 파싱하다가 모델 출력의 XML 형식 위반에 부딪혀 ValueError 를 던지는 거였고, 그 결과 부분 파싱된 tool_call JSON 이 클라이언트에 도달했다. opencode 가 보기엔 "필드 누락" 이지만 모델이 보기엔 "내가 필드 다 적었는데 vLLM 이 끊어 보낸 것" 이었다. 그래서 prompt 로 모델 행동을 바꿔도 — 모델 출력은 그대로인데 그 다음 layer 인 vLLM 파서에서 깨지니 — 효과가 없었다.

    핵심 발견 — prompt 가 미치는 layer 와 미치지 못하는 layer

    이 경험에서 정리된 일반화는 — prompt 는 모델의 의사결정 layer 만 통제할 수 있고, 그 너머 layer 는 못 닿는다.

    prompt 가 닿을 수 있는 layer 와 닿지 못하는 layer

    그림 설명 — prompt 의 영향력이 어느 layer 까지 닿는지 시각화한 것이다. Layer 1 (모델의 의사결정·계획·chain-of-thought) 까지는 prompt 의 명시적 지시가 효과를 발휘한다. 하지만 Layer 2 (모델이 생성하는 토큰 시퀀스의 형식 — XML 태그 위치, streaming chunk 분할 시점), Layer 3 (vLLM 의 tool parser 가 그 형식을 어떻게 파싱하는지), Layer 4 (클라이언트가 받은 결과를 어떻게 해석하는지) 는 prompt 가 닿지 못하는 영역이다. 우리는 그 단순한 사실을 30 iter 의 schema 에러를 겪고서야 깨달았다.

    실패한 prompt 가 가르쳐준 것 — "도구 가이드를 줘봐야"

    도구 가이드 영역을 prompt 에 박은 게 헛수고였나? 완전히 그렇진 않다. 두 가지 가치가 있었다.

    첫째, 가이드가 효과 없다는 사실을 데이터로 확정 했다. 만약 가이드를 안 주고 schema 에러가 평균 3.7회였다면 — 그 원인이 prompt 가 부족해서 인지 모델 능력이 부족해서 인지 알 길이 없다. 가이드를 주고도 같은 빈도면 — 분명히 prompt 너머의 layer 에 원인이 있다는 증거가 된다. 잘못된 가설을 빨리 기각한 셈이다.

    둘째, 막힌 후의 재시도 명령 은 일부 효과 있었다. "도구 호출 한 번 실패해도 멈추지 마라. 누락 필드 채워 다시 호출하라" 라는 부분. 이게 없을 때는 모델이 1회 SchemaError 후 응답을 끝내는 패턴이 흔했는데, 명시한 후엔 같은 호출을 2~3번 재시도하는 패턴이 자주 나타났다. 재시도 자체가 schema 에러를 줄이진 못했지만 — 작업의 깊이 는 늘어났다 (한 호출 실패로 끝나지 않고 다른 접근으로 시도). 이 부분은 Layer 1 (의사결정) 에 영향을 준 셈이다.

    진짜 답 — vLLM 의 한 줄 변경

    schema 에러를 진짜로 고친 건 prompt 가 아니라 vLLM 의 --tool-call-parser 옵션 변경이었다. qwen3_xmlqwen3_coder 한 줄이었다.

    # Before — schema 에러 평균 3.7회/iter
    VLLM_EXTRA_ARGS=... --tool-call-parser qwen3_xml ...
    
    # After — schema 에러 0회/iter
    VLLM_EXTRA_ARGS=... --tool-call-parser qwen3_coder ...

    이 변경 후의 다음 30 iter 에서 schema 에러가 완전히 사라졌다. prompt 의 도구 가이드는 아무것도 안 바꿨다. 즉 같은 모델, 같은 prompt, 같은 클라이언트 — vLLM 측 한 줄만 바꿨는데 결과가 정반대가 됐다.

    이건 프롬프트 엔지니어링이 무용하다 는 게 아니라 — 프롬프트로 풀 문제와 인프라로 풀 문제를 구분하는 안목 이 결정적이라는 거다. 우리가 30 iter 동안 prompt 만 만지작거린 시간은 사실상 낭비였다. vLLM 의 tool parser 옵션을 일찍 의심했다면 시간을 훨씬 아꼈을 것이다.

    그러면 prompt 의 도구 가이드를 빼야 하나

    고민했다. qwen3_coder 로 바꾼 후 schema 에러가 0이 됐으니 prompt 의 도구 가이드 영역은 더 이상 의미 없는 거 아닐까?

    그래도 빼지 않았다. 두 가지 이유로:

    1. 안전망 — 향후 다른 모델 (Qwen 3.7, 4.0 등) 또는 다른 vLLM 버전에서 또 다른 형식의 schema 에러가 날 수 있다. 그때 prompt 의 가이드가 최소한의 안전장치 가 된다. 효과가 작아도 0은 아니다.
    2. "실패 시 즉시 재시도" 의 가치 — Layer 1 에 영향을 주는 부분이라 schema 에러 외 다른 종류의 도구 호출 실패에서도 도움이 된다. 예를 들어 oldString 매치 실패 같은 케이스 (Edit 의 oldString 이 파일에 없는 경우) — 이건 prompt 의 재시도 명령으로 모델이 다시 파일을 읽고 정확한 oldString 을 추출하는 패턴이 자주 작동한다.

    즉 prompt 의 도구 가이드는 주된 효과는 없지만 부수적 가치는 있는 영역이라 그대로 두는 게 합리적이다. 시리즈 8편에서 다룰 내용이지만 — 이 명목상의 가이드 + 실제 효과는 인프라 변경 패턴은 자율 시스템 디버깅에서 자주 만나는 구조다.

    결론 — prompt 엔지니어링의 경계 인식

    이 글의 한 문장 결론 — prompt 가 어디까지 닿는지 알아야 어디서 멈추고 인프라를 만져야 하는지 안다. prompt 는 모델의 의사결정·계획에 영향을 미치지만, 모델이 생성하는 토큰의 형식적 구조나 그 다음 layer 의 파싱·해석에는 닿지 못한다.

    그래서 자율 시스템 운영에서는 layer 별 진단 이 결정적이다. 보고된 증상이 "schema 에러" 라고 prompt 부터 만지작거리는 게 아니라 — 어느 layer 에서 진짜 문제가 발생하는지 한 단계씩 추적해야 한다. 우리 경우는 클라이언트(opencode) 가 보고한 "필드 누락" 이 사실은 vLLM 파서의 streaming chunk 처리 실패였고, 그건 prompt 와 무관하게 인프라 한 줄 변경으로 해결됐다.

    그럼에도 prompt 의 영역 ② (수행 순서) 는 효과가 있었다 — 모델 의사결정 layer 에 직접 박혀 작동했다. prompt 가 무용하다 가 아니라 — 어느 layer 에서 효과가 있고 어느 layer 에서 한계가 있는지 를 명확히 아는 게 중요하다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.