48시간 현장 기록: 프롬프트 절반이 도착하지 않았기 때문에 패치가 그럴듯했다
요약
본 글은 AI 모델을 활용하여 생성된 코드 패치를 실제 테스트에 적용하는 과정에서 겪은 현장 경험과 교훈을 공유합니다. 특히, 단순히 HTTP 200 응답이나 스트림의 끝으로만 성공을 판단해서는 안 되며, 명시적인 종료자 라인 및 실패 시 파이프라인 중단 로직(fail closed)을 구현하는 것이 중요함을 강조합니다.
핵심 포인트
- AI가 생성한 패치 적용은 단순 테스트를 넘어선 복잡한 과정이다.
- 스트리밍 응답의 경우, 명시적인 종료자 라인 검증이 필수적이다.
- 성공 여부는 HTTP 상태 코드보다 파이프라인의 정확한 종료 코드를 확인해야 한다.
- 요청에 대한 수신증(receipt)을 해싱하여 데이터 무결성을 검증하는 것이 중요하다.
48시간 현장 기록: 프롬프트 절반이 도착하지 않았기 때문에 패치가 그럴듯했다
지난 주말에 나는 한 시간 만에 끝날 수 있는 작업, 즉 생성된 패치를 실제로 수정해야 하는 실패하는 테스트에 적용시키는 작업을 이틀 동안 했다. 패치는 깨끗하게 적용되었고, 리뷰에서 잘 읽혔으며, 심지어 관련 없는 어설션(assertion)까지 강화했다. 또한 그 패치가 중요했던 파일에는 전혀 손대지 못했는데, 왜냐하면 그 파일이 내가 보냈다고 생각한 프롬프트에 아예 포함되지 않았기 때문이다. 이것은 나의 현장 기록이며, 금요일 오후에 누군가 나를 위해 써주었으면 좋았을 방식대로 작성되었다.
설정: 실패하는 테스트 하나, 매니페스트, 그리고 무료 티어
나는 단 하나의 빨간색 테스트(red test)를 가진 작은 Python 서비스를 골랐고, 모델이 diff를 제안하고 러너(runner)가 깨끗한 기계에서 이를 적용하는 루프를 연결했다. 나는 내가 시작했던 두 가지 가용성 주장(availability claims)을 그저 가용성으로만 취급했다: MonkeyCode에서의 무료 모델 접근과 무료 서버 옵션. 공개합니다: 이 글은 MonkeyCode의 제품 아웃리치(product outreach)의 일환으로 준비되었습니다.
내가 실제로 통제했던 것은, 어떤 요청이 상자 밖으로 나가기 전에 다음과 같았다:
- 프롬프트에 붙여 넣으려고 했던 모든 파일을 나열한 매니페스트(manifest).
- 패치가 작성된 트리에만 적용될 수 있도록 기록된 기본 커밋 SHA.
- 사전 점검을 실행하고, 요청을 보내고, 스트림을 검증한 다음, 비로소 패치를 작성하는 래퍼(wrapper).
- 두 가지 저렴한 어설션: 페이로드(payload)가 예산을 초과하지 않게 유지되고, 스트림이 의도적으로 끝나는 것.
내가 가진 목록 중 얼마나 많은 부분이 나 자신의 배관공사(plumbing)인지, 그리고 모델이 제공하는 부분이 얼마나 적은지 주목하라. 그 비율이 이 게시물의 전체 교훈이며, 나는 그것을 내면화하는 데 대략 30시간이 걸렸다.
3시간째: HTTP 200 응답은 모델이 끝났는지에 대해 아무것도 말해주지 않는다
첫 번째 패치는 성공적인 상태 코드를 가지고 돌아왔고, 나는 그것을 믿었다. 왜냐하면? 응답 본문이 파싱되었고, diff hunks가 일관성 있게 보였으며, 러너는 0으로 종료되었다. 내가 실제로 받은 것은 연결이 끊긴 후 문장 중간에 멈춘 스트림이었고, 내 클라이언트는 마지막 완전한 청크(chunk)를 최종 답변으로 처리했다.
스트리밍 응답(Streaming responses)은 보통 명시적인 종료자 라인(explicit terminator line)으로 끝납니다. 클라이언트가 이를 확인하지 않으면, 잘림(truncation)을 완료(completion)와 구별할 수 없게 되며, 미완성된 JSON 객체는 그저 짧아진 패치처럼 보일 뿐입니다. 제가 이제 모든 스트림에 연결하는 검증기(validator)를 소개합니다. 이것은 추측하는 대신 실패 시 닫히도록(fail closed) 설계되었습니다:
#!/usr/bin/env python3
"""validate_stream.py - SSE 스트림을 읽어 잘림 발생 시 큰 소리로 실패하게 함.
...
여기서는 파싱보다 종료 코드(exit codes)가 더 중요합니다. 코드 2는 와이어 프로토콜(wire protocol)이 깨졌음을 의미하고, 코드 3은 답변이 불완전함을 의미하며, 이 둘 다 디프(diff)가 디스크에 기록되기 전에 파이프라인을 중단시킵니다.
9시간 차: 내 프롬프트 수신증(receipt)이 잘못된 것을 해싱하고 있었다
이것은 저에게 큰 충격이었습니다. 이미 해결했다고 생각했기 때문입니다. 저는 모든 요청에 대한 수신증을 보관했고, 그 수신증에는 페이로드(payload)가 특정 해시와 특정 길이를 가졌다고 적혀 있었습니다. 그런데 이 수신증이 알지 못했던 것은 조립된 페이로드(assembled payload)가 제가 예산 책정했던 것보다 더 크다는 것이었고, 제 어셈블러는 끝부분을 조용히 잘라내어 맞추고 있었습니다. 그 끝 부분에 있던 파일은 무엇이었을까요? 실패하는 테스트였습니다.
결국 모델은 볼 수 있는 파일들에 대해 그럴듯한 패치를 생성했고, 저의 수신증은 실제로 전송되지 않은 페이로드를 기쁘게 인증했습니다. 해결책은 페이로드를 구축하고, 그것을 측정하고, 수신증을 출력한 다음, 비로소 전송할지 여부를 결정하는 것입니다:
#!/usr/bin/env python3
"""preflight_prompt.py - 설명할 수 없는 프롬프트는 보내기를 거부함.
...
tail_preview 필드는 제가 다른 모든 것을 삭제하더라도 유지하고 싶은 부분입니다. 자신의 프롬프트 마지막 80자리를 읽어보고, 당신이 신경 쓰는 것이 여전히 그 안에 있는지 스스로에게 물어보세요.
26시간 차: 나는 실행(run)이 아니라 답변(reply)을 기록하고 있었다
둘째 날 저녁이 되어서도 저는 두 개의 검증기를 가지고 있었지만, 여전히 신뢰할 수 있는 신호는 없었습니다. 왜냐하면 저는 reply.txt를 저장하고 그것을 결과라고 부르고 있었기 때문입니다. 답변(reply)은 프로세스에 대한 입력일 뿐, 그 자체가 프로세스는 아닙니다. 제가 필요했던 것은 실패를 크고 일찍 알리는 것만을 임무로 하는 래퍼(wrapper)였습니다:
#!/usr/bin/env bash
set -euo pipefail
...
set -euo pipefail을 사용하면 예산 초과 프롬프트, 불완전한 스트림 또는 적용되지 않을 패치 중 하나라도 실행을 멈추고 아무것도 일어나지 않게 합니다. 이것은 세 개의 게이트이며, 어쩌면 40줄의 shell 및 Python 코드로 구성되어 있으며, 주말 동안 제가 만났던 모든 실패 모드를 포착했습니다.
## 결정 테이블: 내 작업에 무료 경로가 맞는 경우
| 상황 | 무료 모델 접근 + 무료 서버? | 먼저 추가할 게이트 I |
| :--- | :--- | :--- |
| 테스트 하나 실패, 리포지토리 하나, 사람이 diff 검토 | 예 | 페이로드 예산 및 스트림 종료자 |
| ... |
## 논문으로 포장하지 않을 한계점
- `chars // 4` 추정치는 거친 휴리스틱입니다. 실제 토크나이저는 코드, 유니코드, 긴 식별자에 대해 다르게 작동하므로 이 숫자를 측정치가 아닌 냄새 테스트로 간주하십시오.
- 제 검증기는 서버 전송 이벤트(server-sent events)를 가정합니다. 스트리밍이 아닌 응답에는 최종 객체의 종료 이유를 검사하는 등 다른 완전성 확인 절차가 필요합니다.
- 가용성 주장은 운영자가 제공하며 변경될 수 있습니다. 무료 접근은 편리할 뿐 용량 보증은 아니며, 저는 그 위에 SLO(Service Level Objective)를 구축하지 않을 것입니다.
- 모든 게이트는 지연 시간과 유지보수 비용이 발생합니다. 한 번 실행하는 임시 스크립트의 경우 이는 과도한 엔지니어링이며, 저는 이를 건너뛸 것입니다.
이 접근 방식을 사용해서는 안 되는 사람은 누구일까요? 데이터 상주(data residency) 제약 조건이 있는 사람, 루프 내에서 검증 단계를 감당할 수 없는 사람, 그리고 스테이지가 0이 아닌 종료 코드를 반환할 때 새벽 3시에 사람이 알림을 받는 팀입니다.
## 월요일에 반복하고 싶은 것들
저는 매니페스트(manifest), 테일 미리보기가 있는 영수증, 종료자 확인, 그리고 기록된 SHA에 대한 `git apply --check`는 유지할 것입니다. 저는 작동했지만 잘못된 질문을 던지느라 낭비했던 20시간은 버릴 것입니다. 만약 저렴하게 같은 루프를 연습하고 싶다면, 이것이 제가 MonkeyCode의 무료 모델 접근 및 무료 서버 옵션에 대해 실행한 워크플로우이며, 위의 스크립트들은 실제로 작업을 수행한 부분입니다.
패치가 그럴듯했던 것은 단 하나의 이유 때문이었다. 나는 내 컴퓨터를 떠난 것이 무엇인지 한 번도 확인하지 않았기 때문이다. 생성된 diff(차이점)를 신뢰하기 전에 마지막으로 확인하는 것은 무엇인가?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기