LLM 프롬프트 관리: 버전, 테스트, 배포까지
프롬프트를 코드처럼 버전 관리하고, A/B 테스트로 개선을 측정하며, CI/CD에 통합하는 프롬프트 엔지니어링 운영 체계를 Langfuse 실전 예제와 함께 다룹니다.
지난 글에서 LLMOps의 전체 그림을 살펴봤다. 이번 글에서는 그 중에서도 가장 독특한 영역인 프롬프트 관리를 집중적으로 다룬다. 프롬프트는 코드이고, 코드는 관리되어야 한다.
많은 팀이 프롬프트를 하드코딩된 문자열로 관리한다. 코드 파일 안에 system_prompt = "..." 형태로 박혀 있다. 이렇게 하면 프롬프트를 수정할 때마다 코드 배포가 필요하고, 어떤 버전이 어떤 성능을 냈는지 추적할 수 없으며, A/B 테스트는 꿈도 꾸기 어렵다. 프롬프트 레지스트리는 이 문제를 해결한다.
프롬프트 관리의 세 원칙
1. 외부화: 프롬프트를 코드 파일에서 분리해 별도 저장소에 보관한다. 애플리케이션은 런타임에 레지스트리에서 최신 프롬프트를 조회한다.
2. 버전 관리: 모든 프롬프트 변경에 버전 번호를 부여하고, 변경 이유와 성능 변화를 기록한다. Git 커밋 히스토리처럼 언제든 이전 버전으로 돌아갈 수 있어야 한다.
3. 측정: 프롬프트 변경의 영향을 데이터로 증명한다. “이 버전이 더 좋은 것 같아”가 아니라 “이 버전이 LLM 평가 점수 기준 0.04점 높다”로 말할 수 있어야 한다.
프롬프트 관리 워크플로우
Langfuse 설정과 기본 사용법
pip install langfuse
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_HOST="https://cloud.langfuse.com"
from langfuse import Langfuse
import anthropic
lf = Langfuse()
claude = anthropic.Anthropic()
# 프롬프트 등록 (최초 1회 또는 업데이트 시)
lf.create_prompt(
name="document-summarizer",
type="chat",
prompt=[
{
"role": "system",
"content": "당신은 전문 요약 작가입니다. 핵심만 담은 간결한 요약을 작성하세요."
},
{
"role": "user",
"content": "다음 문서를 {{length}}줄 이내로 요약해주세요:\n\n{{document}}"
}
],
config={
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"temperature": 0.3,
},
labels=["production"],
)
# 런타임에서 프롬프트 조회 및 사용
def summarize(document: str, length: int = 3) -> str:
prompt = lf.get_prompt("document-summarizer", label="production")
messages = prompt.compile(document=document, length=length)
with lf.trace(name="summarize") as trace:
response = claude.messages.create(
model=prompt.config["model"],
max_tokens=prompt.config["max_tokens"],
messages=messages,
)
text = response.content[0].text
trace.generation(
name="claude-response",
model=prompt.config["model"],
input=messages,
output=text,
usage={
"input": response.usage.input_tokens,
"output": response.usage.output_tokens,
},
)
return text
A/B 테스트 설계
import hashlib
def get_prompt_variant(user_id: str, experiment: str) -> str:
"""사용자 ID를 기반으로 일관된 variant 할당 (같은 사용자는 항상 같은 variant)"""
hash_val = int(hashlib.md5(f"{user_id}:{experiment}".encode()).hexdigest(), 16)
bucket = hash_val % 100 # 0~99
return "treatment" if bucket < 30 else "control" # 30% treatment
def summarize_with_experiment(document: str, user_id: str) -> str:
variant = get_prompt_variant(user_id, "cot-summarizer-v2")
label = "staging" if variant == "treatment" else "production"
prompt = lf.get_prompt("document-summarizer", label=label)
messages = prompt.compile(document=document, length=3)
with lf.trace(name="summarize", tags=[f"variant:{variant}"]) as trace:
response = claude.messages.create(
model=prompt.config["model"],
max_tokens=prompt.config["max_tokens"],
messages=messages,
)
text = response.content[0].text
# Langfuse에 variant 정보와 함께 로깅 → 대시보드에서 비교 가능
trace.update(metadata={"variant": variant, "prompt_version": prompt.version})
return text
CI에서 프롬프트 자동 평가
프롬프트를 PR로 변경할 때 CI가 자동으로 평가 점수를 계산하고, 임계값 미달 시 merge를 막는다.
# tests/test_prompts.py (pytest)
import pytest
from src.summarizer import summarize
from src.evaluator import llm_judge
TEST_CASES = [
{
"document": "..." , # 긴 문서
"expected_topics": ["핵심 주제", "결론"],
"max_sentences": 5,
},
]
@pytest.mark.parametrize("case", TEST_CASES)
def test_summarizer_quality(case):
summary = summarize(case["document"])
# LLM-as-Judge로 품질 평가
score = llm_judge(
prompt=f"요약 품질을 1~5점으로 평가하세요. 문서: {case['document'][:500]}... 요약: {summary}",
criteria=["간결성", "정확성", "완성도"],
)
assert score >= 3.5, f"요약 품질 임계값 미달: {score:.2f}"
assert len(summary.split("\n")) <= case["max_sentences"]
# .github/workflows/prompt-eval.yml
name: Prompt Evaluation
on: [pull_request]
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: pytest tests/test_prompts.py -v --tb=short
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
LANGFUSE_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }}
프롬프트 파일 구조 (Git 기반 관리)
Langfuse 같은 SaaS가 없을 때는 Git에 YAML로 프롬프트를 관리하는 방법도 실용적이다.
# prompts/document-summarizer/v2.yaml
name: document-summarizer
version: 2
description: "Chain-of-Thought 추가 버전"
created_at: "2026-05-01"
author: "alice@company.com"
system: |
당신은 전문 요약 작가입니다.
먼저 문서의 핵심 주제를 파악하고,
그 다음 {{length}}줄 이내로 요약을 작성하세요.
user: |
문서:
{{document}}
config:
model: claude-sonnet-4-6
temperature: 0.3
max_tokens: 1024
eval:
baseline_version: 1
score_delta: +0.04
test_dataset: datasets/summarizer-eval-v2.json
프롬프트 변경 리뷰 체크리스트
코드 리뷰처럼 프롬프트 변경에도 리뷰어가 확인할 항목이 있다.
- 의도 명확성: 프롬프트가 원하는 동작을 정확히 명세하는가
- 경계 케이스: 빈 입력, 매우 긴 입력, 특수 문자 입력에서 동작이 안정적인가
- 보안: 프롬프트 인젝션 취약점이 없는가
- 비용 영향: 프롬프트 길이 변화로 토큰 비용이 얼마나 증가하는가
- 평가 결과: 자동화 평가 점수가 기존 버전 대비 개선됐는가
지난 글: LLMOps 개요: LLM 운영의 새로운 과제
다음 글: LLM 평가 파이프라인: 자동화된 품질 보장
읽어주셔서 감사합니다. 😊