프롬프트를 코드처럼 버전 관리하기: 변경 이력, 평가, 롤백

프롬프트 파일 분리, promptfoo 평가를 PR 검사로, 배포 버전 기록과 되돌리기

핵심 요약

  1. 프롬프트를 코드 밖 파일로 분리하고 버전별 파일과 git 이력으로 변경을 추적한다.
  2. 운영 버전을 환경 변수로 선택하게 하면 코드 배포 없이 롤백할 수 있다.
  3. promptfoo로 프롬프트·모델·테스트 조합을 평가하고 PR 검사로 연결한다.
  4. 응답 로그에 prompt_version을 남겨 문제 응답의 원인 버전을 추적한다.

LLM 기능의 동작은 코드보다 프롬프트 한 줄에 더 크게 좌우된다. 그런데 프롬프트가 코드 안 문자열로 흩어져 있으면 누가 언제 무엇을 바꿨는지, 어떤 변경이 품질을 떨어뜨렸는지 추적하기 어렵다. 운영 중 답변 품질이 갑자기 나빠졌을 때 직전 프롬프트로 되돌리는 것도 쉽지 않다. 이 글은 프롬프트를 파일로 분리하고, promptfoo 같은 평가 도구로 변경마다 검사하며, 배포 버전을 기록해 되돌릴 수 있게 만드는 방법을 정리한다. 프롬프트 작성 원칙은 개발자를 위한 프롬프트 엔지니어링 글에서 다뤘다.

프롬프트 관리에 필요한 네 가지

요소내용얻는 것
분리프롬프트를 코드 밖 파일(prompts/*.md 등)로변경 diff를 리뷰에서 바로 확인
버전파일마다 버전 식별자, 변경 이력은 git으로어떤 버전이 운영 중인지 명확
평가테스트 입력과 기대 결과로 자동 검사품질 저하를 배포 전에 발견
기록·롤백응답 로그에 프롬프트 버전 저장, 이전 버전으로 전환문제 발생 시 원인 추적과 빠른 복구

1단계: 프롬프트를 파일로 분리하고 버전 붙이기

prompts/
  ticket-summary/
    v3.md          # 현재 운영 버전
    v2.md          # 직전 버전(롤백 대상)
  ticket-classify/
    v1.md
src/llm/prompts.ts # 버전 선택과 로딩
// src/llm/prompts.ts
import { readFileSync } from 'node:fs'

const ACTIVE = {
  'ticket-summary': process.env.PROMPT_TICKET_SUMMARY ?? 'v3',
  'ticket-classify': process.env.PROMPT_TICKET_CLASSIFY ?? 'v1',
} as const

export function loadPrompt(name: keyof typeof ACTIVE) {
  const version = ACTIVE[name]
  const text = readFileSync('prompts/' + name + '/' + version + '.md', 'utf8')
  return { name, version, text }
}

운영 버전을 환경 변수로 선택하게 하면 코드 배포 없이 설정 변경만으로 이전 버전으로 되돌릴 수 있다. 새 버전 파일을 추가할 때 이전 파일은 지우지 않는다.

2단계: promptfoo로 평가 케이스 만들기

npx promptfoo@latest init
# promptfooconfig.yaml
prompts:
  - file://prompts/ticket-summary/v2.md
  - file://prompts/ticket-summary/v3.md
providers:
  - anthropic:messages:claude-sonnet-5-5
tests:
  - vars:
      ticket: "결제가 두 번 됐어요. 주문번호 ORD-1182, 환불해 주세요."
    assert:
      - type: icontains
        value: "ORD-1182"
      - type: javascript
        value: output.length < 400
  - vars:
      ticket: "로그인 화면이 계속 새로고침돼요. 크롬 최신 버전입니다."
    assert:
      - type: llm-rubric
        value: "로그인 문제와 브라우저 정보를 모두 언급하고 해결책을 지어내지 않는다"

문서에 따르면 설정 파일은 프롬프트, 공급자(모델), 테스트로 구성되고, 테스트마다 변수와 단언(assert)을 둔다. 단언 유형에는 문자열 포함(contains, icontains), 자바스크립트 사용자 정의 검사, 모델이 채점하는 llm-rubric, 비용·지연 시간 상한 등이 있다. 공급자 이름 형식은 설치한 버전의 공급자 문서에서 확인한다.

npx promptfoo@latest eval     # 프롬프트 × 모델 × 테스트 조합 평가
npx promptfoo@latest view     # 결과를 웹 화면에서 비교

3단계: 평가를 PR 검사로 연결하기

  1. 프롬프트 파일이 바뀐 PR에서만 평가 작업이 실행되도록 경로 필터를 건다.
  2. 평가 결과의 통과율이 기준(예: 이전 버전 이상)보다 낮으면 PR 검사를 실패시킨다.
  3. 결과 요약을 PR 코멘트로 남겨 리뷰어가 버전 간 차이를 볼 수 있게 한다.
  4. API 키는 CI 비밀값으로 넣고, 평가 비용이 커지지 않도록 테스트 케이스 수를 관리한다.

평가 케이스는 실제 운영에서 문제가 됐던 입력을 계속 추가해 회귀 테스트 세트로 키운다. Anthropic의 프롬프트 모범 사례 문서도 프롬프트를 개선하기 전에 성공 기준과 이를 경험적으로 시험할 방법부터 마련하라고 안내한다.

4단계: 응답마다 프롬프트 버전 기록하기

const prompt = loadPrompt('ticket-summary')
const result = await callModel(prompt.text, input)
logger.info({
  feature: 'ticket-summary',
  prompt_version: prompt.version,   // 어떤 버전이 이 응답을 만들었는지
  model: result.model,
  latency_ms: result.latencyMs,
  input_tokens: result.usage.input,
  output_tokens: result.usage.output,
})

롤백 절차

  1. 사용자 신고나 지표 이상이 발생하면 로그에서 문제 응답의 prompt_version을 확인한다.
  2. 환경 변수를 직전 버전(예: PROMPT_TICKET_SUMMARY=v2)으로 바꿔 즉시 되돌린다.
  3. 문제 입력을 평가 케이스에 추가하고, 새 버전을 고친 뒤 평가를 통과해야 다시 배포한다.

운영 지표 설계는 LLMOps 모니터링 글을, 배포 전 검증 항목은 LLM 배포 전 검증 체크리스트 글을 참고한다.

초보자가 자주 실수하는 포인트

  • 프롬프트를 코드 곳곳의 문자열로 흩어 두어 변경 이력을 찾지 못하는 경우
  • 새 버전을 만들며 이전 파일을 덮어써 롤백 대상이 사라지는 경우
  • 정상 입력 몇 개로만 평가해 회귀를 놓치는 경우
  • 응답 로그에 프롬프트 버전을 남기지 않는 경우

체크리스트

  • 프롬프트가 버전별 파일로 분리돼 있는가
  • 운영 버전을 설정으로 전환할 수 있는가
  • 프롬프트 변경 PR에서 평가가 자동 실행되는가
  • 운영 문제 입력이 평가 케이스로 추가되는가
  • 응답 로그에 prompt_version과 모델이 기록되는가

자주 묻는 질문

프롬프트 관리 전용 서비스를 써야 하나요?

필수는 아니다. 파일과 git, 평가 도구, 로그 기록만으로도 버전 관리와 롤백이 가능하다. 비개발자가 프롬프트를 편집해야 한다면 전용 도구를 검토한다.

llm-rubric 단언은 신뢰할 수 있나요?

모델이 채점하므로 결과가 흔들릴 수 있다. 문자열 포함이나 길이 같은 결정적 단언을 함께 두고, 루브릭은 보조 기준으로 쓴다.

모델을 바꿀 때도 같은 평가를 쓰나요?

같은 테스트 세트를 공급자만 바꿔 실행하면 모델 교체 영향을 비교할 수 있다.

참고 자료 · 검증 기준

위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.

이 글은 위 참고 자료를 바탕으로 정리했으며, 내용은 운영 과정에서 순차적으로 보완될 수 있습니다.