이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Prompt Engineering은 정확히 무엇을 개선하는가?
- 왜 프롬프트만 잘 쓰는 것으로는 운영 서비스가 안정되지 않는가?
- 백엔드 개발자는 프롬프트를 어떤 실행 계약으로 봐야 하는가?
- 좋은 프롬프트와 위험한 프롬프트는 코드 리뷰에서 어떻게 구분하는가?
개요
Prompt Engineering은 모델에게 줄 지시문, 역할, 입력 데이터, 예시, 제약 조건, 출력 형식을 설계하는 일이다. Anthropic 공식 문서는 프롬프트를 명확하고 구체적으로 작성하고, 예시와 단계적 사고 구조를 활용해 모델의 행동을 유도하는 방법을 설명한다. OpenAI 공식 문서도 structured output, function calling, prompt caching 같은 기능과 함께 모델 입력을 설계하는 방식을 제공한다.
하지만 실무에서 Prompt Engineering은 시작점일 뿐이다. 백엔드 서비스에서는 프롬프트가 HTTP 요청, 사용자 권한, DB 데이터, RAG 검색 결과, tool 호출, 응답 schema, timeout, 비용 제한과 연결된다. 프롬프트 문장 하나를 바꾸는 일이 실제로는 API 계약을 바꾸는 일에 가까워질 수 있다.
왜 백엔드 개발자에게 중요한가
백엔드 개발자는 프롬프트를 “모델에게 하는 말” 정도로만 보면 안 된다. 프롬프트는 외부 모델 provider로 전달되는 실행 명세다. 이 실행 명세가 불안정하면 API 응답, DB 저장 형식, 사용자 경험, 운영 비용이 흔들린다.
예를 들어 “JSON으로 답해”라고만 쓰면 데모에서는 충분해 보인다. 그러나 운영에서는 다음 문제가 생긴다.
- 모델이 설명 문장을 JSON 앞뒤에 붙인다.
- 정책 위반 질문에서 refusal을 반환해 DTO 파싱이 실패한다.
- 긴 context 때문에 출력이 중간에 잘린다.
- 프롬프트 변경 후 특정 고객의 업무 용어를 잘못 분류한다.
- 로그에 프롬프트 전체를 남기면서 내부 문서가 노출된다.
따라서 백엔드 개발자는 프롬프트를 문자열이 아니라 request contract, validation rule, versioned artifact로 다뤄야 한다.
핵심 원리
좋은 프롬프트는 보통 다음 요소를 가진다.
- 역할: 모델이 어떤 관점으로 답해야 하는지 정의한다.
- 목표: 이번 요청에서 수행할 일을 구체화한다.
- 입력 데이터: 사용자가 준 질문과 시스템이 붙인 context를 구분한다.
- 제약 조건: 답변 범위, 금지 사항, 모르는 경우의 행동을 정한다.
- 출력 형식: JSON schema, markdown, bullet 등 호출자가 기대하는 형식을 정한다.
- 예시: 모델이 따라야 할 패턴을 보여준다.
나쁜 프롬프트는 대개 너무 넓다.
아래 질문에 친절하게 답해줘.
{user_question}이 프롬프트는 호출자가 원하는 품질 기준을 거의 전달하지 않는다. “친절하게”는 좋은 제품 요구사항이 아니다. 답변 범위, 근거 사용, 모르는 경우, 출력 형식, 금지 행동이 없다.
더 나은 프롬프트는 계약을 드러낸다.
당신은 사내 백엔드 기술 문서를 기반으로 답하는 도우미입니다.
규칙:
- 제공된 context에 있는 내용만 근거로 답합니다.
- context에 근거가 없으면 추측하지 말고 "문서에서 확인되지 않습니다"라고 답합니다.
- 답변은 한국어로 작성합니다.
- 마지막에 사용한 source_id 목록을 포함합니다.
context:
{retrieved_chunks}
사용자 질문:
{user_question}
출력 형식:
{
"answer": "string",
"source_ids": ["string"],
"confidence": "high | medium | low"
}이 정도로 써도 완전하지 않다. 모델이 항상 JSON만 반환한다는 보장은 없고, retrieved chunk 자체가 잘못되었을 수도 있다. 그래서 다음 단계가 context engineering과 harness engineering이다.
백엔드 설계 판단
프롬프트 설계에서 백엔드 개발자가 먼저 정할 것은 “모델이 실패했을 때 API가 어떻게 실패할 것인가”다.
- 모델 답변을 그대로 사용자에게 보여줄 수 있는가?
- 모델 응답이 깨지면 500으로 처리할 것인가, 사용자에게 재시도 안내를 줄 것인가?
- 모델이 모른다고 답하면 빈 결과인가, 비즈니스 실패인가?
- 프롬프트 버전이 바뀌면 기존 캐시와 저장된 결과는 어떻게 해석할 것인가?
- 같은 사용자 요청에 같은 답을 기대해야 하는가, 매번 달라져도 되는가?
분류, 심사, 요약, 추천처럼 후속 시스템이 모델 출력을 소비하는 기능은 특히 조심해야 한다. 사람이 읽는 답변보다 기계가 소비하는 출력이 더 엄격하다. 이 경우 자연어 프롬프트보다 structured output, schema validation, retry 정책이 함께 필요하다.
코드로 이해하기
프롬프트 템플릿은 함수처럼 다루는 편이 좋다. 기존 Python 메모의 keyword argument 관례처럼, 어떤 값이 어떤 의미로 들어가는지 명시하면 읽기 쉽다.
from dataclasses import dataclass
@dataclass(frozen=True)
class PromptVersion:
name: str
template: str
def render(self, *, retrieved_chunks: str, user_question: str) -> str:
if not user_question.strip():
raise ValueError("user_question must not be blank")
return self.template.format(
retrieved_chunks=retrieved_chunks,
user_question=user_question,
)
QA_PROMPT_V3 = PromptVersion(
name="internal-doc-qa-v3",
template="""당신은 사내 백엔드 기술 문서를 기반으로 답하는 도우미입니다.
규칙:
- 제공된 context에 있는 내용만 근거로 답합니다.
- 근거가 없으면 "문서에서 확인되지 않습니다"라고 답합니다.
- 답변은 한국어로 작성합니다.
context:
{retrieved_chunks}
사용자 질문:
{user_question}
""",
)
prompt = QA_PROMPT_V3.render(
retrieved_chunks="- source_id=spring-aop-03: Spring AOP는 proxy 기반이다.",
user_question="self-invocation에서는 왜 AOP가 안 먹나요?",
)핵심은 템플릿을 흩어진 문자열로 두지 않고 이름, 버전, 변수 계약을 가진 객체로 다루는 것이다.
장애 상황과 대응
프롬프트 때문에 생기는 장애는 의외로 코드 장애처럼 보이지 않는다.
- 배포 후 “답변이 이상하다”는 CS 문의가 늘어난다.
- JSON 파싱 실패율이 올라간다.
- 특정 고객군에서 거절 응답이 늘어난다.
- token 사용량이 증가해 비용이 오른다.
- RAG 답변이 오래된 정책을 더 자주 인용한다.
대응은 추측으로 하면 안 된다. 최소한 프롬프트 버전, 모델, token count, selected source id, validation result, retry count를 로그나 trace에 남겨야 한다. 단, prompt 전문과 context 전문은 민감정보가 섞일 수 있으므로 무조건 남기지 않는다.
인프라 협업 포인트
인프라팀은 프롬프트 문장의 아름다움보다 운영 특성을 걱정한다.
- 프롬프트 길이가 길어지면 평균 latency와 비용이 얼마나 늘어나는가?
- prompt caching을 쓸 수 있는 구조인가?
- 프롬프트 버전별 트래픽과 에러율을 볼 수 있는가?
- 모델 provider 장애 시 fallback 경로가 있는가?
- 로그에 민감정보가 남지 않는가?
백엔드 개발자는 프롬프트 변경 PR에서 예상 token 변화, 캐시 영향, 로그 영향, eval 결과를 함께 보여주는 습관을 들이는 편이 좋다.
개인 프로젝트 최소 기준
- 프롬프트를 코드에서 분리해 버전 이름을 붙인다.
- 모델 응답을 그대로 믿지 말고 최소한 JSON 파싱 실패를 처리한다.
- 프롬프트 변경 전후 대표 질문 10개 정도는 직접 돌려본다.
- API key와 prompt 로그를 공개 저장소에 올리지 않는다.
기업 운영 수준 기준
- 프롬프트 변경은 코드 리뷰와 eval regression gate를 통과해야 한다.
- production prompt는 누가, 언제, 왜 바꿨는지 감사 가능해야 한다.
- 프롬프트 버전별 품질, 비용, latency, validation 실패율을 추적한다.
- 보안팀과 prompt injection, 민감정보, 로그 보존 정책을 합의한다.
- rollback 가능한 version routing을 둔다.
실전 팁
- 프롬프트는 “친절하게”, “정확하게”보다 관찰 가능한 행동으로 쓴다. 예를 들어 “근거가 없으면 추측하지 않는다”가 낫다.
- 출력 형식을 자연어로만 요구하지 말고 가능하면 provider의 structured output 기능과 schema validation을 같이 쓴다.
- few-shot 예시는 너무 많이 넣지 않는다. 비용과 latency가 증가하고, 예시가 특정 패턴에 과적합될 수 있다.
- 프롬프트 변경은 작게 한다. 한 번에 역할, 예시, 출력 형식, 제약 조건을 모두 바꾸면 회귀 원인을 찾기 어렵다.
위험 신호!
- production prompt를 관리자 화면에서 바로 수정할 수 있는데 리뷰와 eval이 없다.
- 프롬프트 안에 DB 권한 판단을 자연어로만 적어 둔다.
- 모델 응답이 깨지면 서버가 500을 던진다.
- prompt 전문과 context 전문을 평문 로그로 남긴다.
- 프롬프트 변경 후 token 사용량과 validation 실패율을 보지 않는다.
확인 질문
확인 질문
- Prompt Engineering은 운영 AI 시스템에서 어떤 위치인가?
- 모델 입력을 설계하는 시작점이지만, 운영 안정성은 context 조립과 harness까지 포함해야 한다.
- 백엔드 개발자가 프롬프트를 코드처럼 봐야 하는 이유는 무엇인가?
- 프롬프트 변경이 응답 형식, 품질, 비용, 보안 위험을 바꾸는 기능 변경이기 때문이다.
- 좋은 프롬프트가 반드시 포함해야 하는 요소는 무엇인가?
- 역할, 목표, 입력 데이터, 제약 조건, 출력 형식, 필요한 예시다.