이전 장에서는 LLM API를 호출해 요약, 대화, 스트리밍 같은 기본 기능을 실행해 보았다. 이제 질문이 하나 남는다. 같은 모델을 쓰는데 왜 어떤 요청은 놀라울 만큼 정확하고, 어떤 요청은 엉뚱한 방향으로 흘러갈까?
많은 사람이 프롬프트 엔지니어링을 "마법의 문장 모음"처럼 생각한다. "너는 최고의 전문가야"라고 시작하면 답이 좋아진다거나, "차근차근 생각해"를 붙이면 무조건 정확해진다는 식이다. 이런 표현이 도움이 되는 경우도 있지만, 프롬프트 엔지니어링의 핵심은 특정 주문을 외우는 일이 아니다.
프롬프트 엔지니어링은 모델에게 맡길 작업을 입력, 맥락, 제약, 예시, 출력 기준으로 설계하는 일이다. 다시 말해 프롬프트는 모델과 맺는 입력 계약이다.
사람에게 일을 맡길 때도 마찬가지다. "이거 정리해 주세요"라고 말하면 상대는 자기 방식대로 정리한다. 하지만 "이 회의록에서 결정 사항과 담당자와 마감일만 뽑아 표로 정리해 주세요. 불확실한 담당자는 '미정'이라고 표시해 주세요"라고 말하면 결과가 훨씬 안정적이다. LLM도 비슷하다. 모델은 사용자의 머릿속 의도를 읽지 않는다. 주어진 텍스트를 바탕으로 다음 토큰을 예측할 뿐이다. 그래서 입력이 모호하면 가능한 답변의 범위가 넓어지고, 입력이 분명하면 모델이 따라갈 길이 좁아진다.1
이 장의 목표는 멋진 프롬프트 문구를 외우는 것이 아니다. 원하는 작업을 안정적으로 수행하게 만드는 프롬프트의 기본 구조를 익히는 것이다.

좋은 프롬프트는 부탁이 아니라 계약이다
LLM은 대체로 친절하게 답하려고 한다. 문제는 "친절함"만으로는 서비스 품질을 만들 수 없다는 점이다. 고객지원 챗봇이 환불 규정을 답해야 하는데 그럴듯한 상식을 섞어 말하면 곤란하다. 문서 요약기가 핵심 수치를 빠뜨리면 쓸모가 줄어든다. 코드 생성기가 라이브러리 버전을 마음대로 바꾸면 바로 실행되지 않는다.
그래서 실무의 프롬프트는 단순한 질문이 아니라 계약서처럼 작성하는 편이 좋다. 계약서에는 누가 무엇을 해야 하는지, 어떤 자료를 근거로 삼아야 하는지, 결과물을 어떤 형식으로 내야 하는지, 해서는 안 되는 일이 무엇인지가 들어간다.
예를 들어 다음 요청을 보자.
이 글 요약해 줘.겉보기에는 자연스럽지만, 모델 입장에서는 빈칸이 많다. 얼마나 짧게 요약해야 할까? 독자는 누구일까? 핵심 논지만 뽑아야 할까, 수치와 근거도 포함해야 할까? 문체는 보고서체일까, 블로그체일까? 모델은 이 빈칸을 훈련 데이터에서 자주 본 패턴으로 채운다. 운이 좋으면 마음에 드는 답이 나오고, 운이 나쁘면 너무 길거나 너무 얕거나 엉뚱한 요약이 나온다.
프롬프트를 계약처럼 바꾸면 다음과 같다.
[목표]
아래 글을 제품팀 주간 회의에서 빠르게 공유할 수 있도록 요약한다.
[대상 독자]
AI 기능을 기획하는 제품 매니저와 개발자.
[입력]
---
{여기에 원문}
---
[출력 형식]
1. 핵심 요약: 3문장
2. 중요한 수치 또는 사실: 최대 5개
3. 제품팀이 검토할 질문: 3개
[제약]
- 원문에 없는 사실을 추가하지 않는다.
- 불확실한 내용은 '원문에서 확인할 수 없음'이라고 쓴다.
- 마케팅 문구처럼 과장하지 않는다.이 프롬프트는 길기 때문에 좋은 것이 아니다. 모호함을 줄였기 때문에 좋다. 모델이 해야 할 일과 하지 말아야 할 일을 분리했고, 결과물이 어떤 모습이어야 하는지 미리 정했다. 이렇게 하면 같은 요청을 여러 번 보내도 결과의 흔들림이 줄어든다.
프롬프트 엔지니어링은 그래서 "모델을 설득하는 기술"이라기보다 "모델이 덜 추측하게 만드는 기술"에 가깝다.
프롬프트의 여섯 가지 구성 요소
프롬프트를 매번 새로 고민하면 피곤하다. 대신 구성 요소로 나누어 생각하면 훨씬 쉽다. Microsoft의 프롬프트 구성 설명에서도 지시, 처리할 본문, 예시, 출력 시작 단서 같은 요소를 구분해 생각할 것을 권한다.2 실무에서는 다음 여섯 가지를 기본 골격으로 삼으면 좋다.
첫째는 지시문이다. 모델에게 어떤 작업을 하라는 명령이다. "요약해라", "분류해라", "번역해라", "비교해라", "코드를 작성해라"처럼 동작이 분명해야 한다. "도와줘"나 "알려줘"는 대화에서는 자연스럽지만, 자동화된 기능에서는 모호하다.
둘째는 목표다. 같은 요약이라도 목표가 다르면 결과가 달라진다. 투자 검토를 위한 요약인지, 고객 안내를 위한 요약인지, 개발자가 구현 범위를 파악하기 위한 요약인지에 따라 강조점이 바뀐다.
셋째는 배경 맥락이다. 독자, 상황, 서비스의 도메인, 용어 정의, 현재 사용자가 처한 상태 같은 정보다. 모델은 대화창 밖의 사정을 알지 못한다. "우리 서비스에 맞게"라고 쓰고 서비스 설명을 주지 않으면 모델은 일반적인 서비스를 상상한다.
넷째는 참고 자료 또는 입력 데이터다. 요약할 문서, 분류할 문의, 분석할 로그, 변환할 코드가 여기에 해당한다. 입력 데이터는 지시문과 명확히 구분하는 것이 좋다. 구분자가 없으면 모델이 어디까지가 자료이고 어디부터가 명령인지 헷갈릴 수 있다.
다섯째는 출력 형식이다. 문단, 표, 목록, JSON, 이메일 초안, 코드 블록처럼 결과물의 모양을 지정한다. 출력 형식은 생각보다 강력한 제어 장치다. "간단히 답해 줘"보다 "2문장으로 답해 줘"가 낫고, "정리해 줘"보다 "원인/영향/대응으로 나누어 표로 작성해 줘"가 낫다.
여섯째는 제약 조건과 금지 조건이다. 원문에 없는 내용을 만들지 않기, 특정 톤을 피하기, 개인정보를 요구하지 않기, 모르면 되묻기, 제공된 문서 안에서만 답하기 같은 규칙이다. 다만 금지 조건만 잔뜩 쓰는 것은 좋지 않다. "하지 말라"와 함께 "대신 무엇을 하라"를 적어야 한다.
이 여섯 가지를 템플릿으로 만들면 다음과 같다.
[역할]
당신은 {역할}이다.
[목표]
{이 작업의 목적}을 달성해야 한다.
[맥락]
- 대상 독자: {독자}
- 상황: {상황}
- 중요한 배경: {배경}
[입력]
---
{처리할 텍스트 또는 데이터}
---
[작업]
{구체적으로 수행할 일}
[출력 형식]
{원하는 구조, 길이, 문체, 필드}
[제약]
{근거 범위, 금지 사항, 불확실성 처리 규칙}항상 이 형식을 전부 채울 필요는 없다. "다음 문장을 영어로 번역해 줘" 같은 간단한 작업에는 지시와 입력만으로 충분하다. 하지만 서비스 기능으로 붙일 프롬프트라면, 특히 결과를 후처리하거나 사용자에게 그대로 보여 줄 프롬프트라면 구성 요소를 분리해 두는 편이 안전하다.
시스템 메시지와 사용자 메시지
API로 LLM을 사용할 때는 프롬프트가 하나의 긴 문자열만으로 구성되지 않는 경우가 많다. 채팅 모델에서는 보통 시스템 메시지와 사용자 메시지를 나누어 보낸다. 시스템 메시지는 대화 전체에서 유지되는 기본 규칙이고, 사용자 메시지는 매번 바뀌는 개별 요청이다. OpenAI의 문서도 효과적인 지시를 작성해 모델이 요구사항에 맞는 출력을 일관되게 생성하도록 하는 과정을 프롬프트 엔지니어링으로 설명한다.3
예를 들어 고객지원 챗봇을 만든다고 해 보자. 시스템 메시지에는 챗봇의 역할과 항상 지켜야 할 정책을 넣는다.
당신은 쇼핑몰 고객지원 상담 도우미다.
항상 정중하고 간결하게 답한다.
환불, 배송, 주문 취소에 관한 질문만 답한다.
정책 문서에 없는 내용은 추측하지 말고 확인이 필요하다고 말한다.
개인정보 전체를 요구하지 말고 주문번호만 요청한다.사용자 메시지에는 실제 고객의 질문과 검색된 정책 문서 일부를 넣는다.
[정책 문서]
환불은 상품 수령 후 7일 이내 신청할 수 있다.
단, 개봉한 소모품은 환불이 제한될 수 있다.
[고객 질문]
어제 받은 이어폰 포장을 뜯었는데 환불 가능한가요?이렇게 나누면 재사용성이 좋아진다. 시스템 메시지는 제품의 성격을 정의하고, 사용자 메시지는 매 요청의 데이터만 담는다. 나중에 말투를 바꾸거나 정책을 강화할 때도 어느 부분을 수정해야 하는지 찾기 쉽다.
다만 시스템 메시지가 있다고 해서 모든 문제가 해결되는 것은 아니다. 시스템 메시지에 지나치게 많은 규칙을 넣으면 중요한 규칙이 묻힐 수 있다. 핵심 규칙은 짧고 분명하게 두고, 길고 자주 바뀌는 지식은 다음 장들에서 다룰 RAG나 도구 호출로 분리하는 편이 좋다.
생성 설정도 프롬프트의 일부다
프롬프트 문구가 같아도 모델의 생성 설정이 다르면 답변이 달라진다. Hugging Face의 LLM 프롬프팅 가이드도 프롬프트 최적화와 함께 텍스트 생성 전략이 출력 품질에 영향을 준다고 설명한다.4 이 말은 실무에서 매우 중요하다. "프롬프트를 바꿨더니 답이 이상해졌다"고 생각했는데, 실제 원인은 temperature 값이 바뀐 것일 수 있다.
가장 자주 만나는 설정은 temperature다. 이 값은 모델이 다음 토큰을 고를 때 얼마나 다양한 후보를 허용할지에 영향을 준다. 낮은 temperature는 더 예측 가능한 답을 만들고, 높은 temperature는 더 다양한 표현을 만든다. 고객지원 FAQ, 문서 기반 질의응답, 정보 추출처럼 정확성과 일관성이 중요한 작업에서는 낮게 시작하는 편이 좋다. 반대로 광고 문구, 제목 후보, 아이디어 발산처럼 다양한 표현이 필요한 작업에서는 조금 높여 볼 수 있다.
top_p는 후보 토큰의 범위를 확률 질량 기준으로 제한하는 방식이다. 예를 들어 top_p가 낮으면 확률이 높은 후보군 안에서만 선택하고, 높으면 더 넓은 후보를 고려한다. temperature와 top_p는 둘 다 출력의 다양성에 영향을 주므로, 처음부터 둘을 동시에 크게 바꾸면 결과를 해석하기 어렵다. 한쪽을 기준으로 조정하고, 테스트 결과를 보며 바꾸는 편이 낫다.
max tokens 또는 max_new_tokens는 모델이 생성할 수 있는 최대 길이를 제한한다. 이 값은 비용과 응답 속도에 직접 연결된다. 모델은 친절하게 설명을 덧붙이려는 경향이 있기 때문에, 길이 제한이 없으면 필요 이상으로 긴 답이 나올 수 있다. 요약기는 "최대 500자", 분류기는 "라벨 1개만", 챗봇은 "3문장 이내"처럼 프롬프트의 출력 형식과 생성 길이 설정을 함께 맞추는 것이 좋다.
stop sequence는 특정 문자열이 나오면 생성을 멈추게 하는 장치다. 예를 들어 번호 목록을 정확히 10개만 받고 싶다면 "11."이 나오기 전에 멈추도록 설정할 수 있다. 다만 stop sequence만 믿어서는 안 된다. 모델이 "열한 번째"처럼 다른 방식으로 이어 쓸 수도 있다. 그래서 "정확히 10개만 출력"이라는 지시와 함께 사용하는 것이 안전하다.
목적별 출발점은 다음처럼 잡을 수 있다.
| 작업 유형 | 추천 방향 | 이유 |
|---|---|---|
| 문서 기반 Q&A | 낮은 temperature, 짧은 max tokens | 근거 안에서 간결하게 답해야 한다. |
| 정보 추출/분류 | 낮은 temperature, 엄격한 출력 형식 | 후처리와 평가가 쉬워야 한다. |
| 요약 | 낮거나 중간 temperature, 길이 제한 | 핵심을 유지하되 문장을 자연스럽게 만든다. |
| 마케팅 문구 생성 | 중간 이상의 temperature | 다양한 후보가 필요하다. |
| 브레인스토밍 | 중간 이상의 temperature, 넉넉한 길이 | 예상 밖의 아이디어를 탐색한다. |
좋은 프롬프트는 문장만이 아니라 설정값까지 포함한다. 운영 환경에서는 프롬프트 버전과 함께 모델 이름, temperature, top_p, max tokens 같은 설정도 같이 기록해야 한다. 그래야 나중에 품질이 흔들렸을 때 원인을 찾을 수 있다.
Zero-shot, Few-shot, Chain-of-Thought
프롬프트 기법 이름은 많지만, 처음에는 세 가지만 제대로 이해해도 충분하다. zero-shot, few-shot, chain-of-thought다.
Zero-shot prompting은 예시 없이 바로 작업을 지시하는 방식이다. 모델이 이미 훈련 과정에서 많이 접한 익숙한 작업이라면 zero-shot으로도 잘 작동한다.
다음 문장을 positive, neutral, negative 중 하나로 분류해라.
출력은 라벨 하나만 작성해라.
문장: 배송은 늦었지만 상담원 응대는 친절했다.이런 감정 분류, 간단한 번역, 짧은 요약은 zero-shot으로 시작해도 된다. 장점은 프롬프트가 짧고 비용이 적다는 점이다. 단점은 원하는 판단 기준이 모델의 기본 기준과 다를 때 결과가 흔들릴 수 있다는 점이다.
Few-shot prompting은 원하는 입력과 출력의 예시를 몇 개 보여 주는 방식이다. 예시는 단순한 참고 자료가 아니라 "이런 패턴으로 답하라"는 강한 신호다. OpenAI와 Microsoft의 프롬프트 문서에서도 예시를 통해 모델의 원하는 행동을 유도하는 방법을 주요 기법으로 다룬다.5
다음 고객 문의를 billing, delivery, cancel, other 중 하나로 분류해라.
출력은 라벨 하나만 작성해라.
예시 1
문의: 카드 결제가 두 번 된 것 같아요.
라벨: billing
예시 2
문의: 택배가 아직 도착하지 않았어요.
라벨: delivery
예시 3
문의: 주문을 취소하고 싶어요.
라벨: cancel
실제 문의: 쿠폰을 적용했는데 금액이 이상해요.
라벨:few-shot은 원하는 형식과 판단 기준을 고정해야 할 때 특히 유용하다. 다만 예시가 편향되면 모델도 그 방향으로 기울어진다. 긍정 예시만 보여 주면 중립이나 부정 사례를 잘 못 다룰 수 있고, 너무 깨끗한 예시만 보여 주면 실제 사용자의 어수선한 문장에 약해질 수 있다. 예시는 실제 입력과 닮아 있어야 한다.
Chain-of-Thought 계열 prompting은 모델에게 중간 추론을 하도록 유도하는 방식이다. 수학 문제, 복잡한 조건 비교, 원인 분석처럼 여러 단계를 거쳐야 하는 작업에서 도움이 될 수 있다. 예를 들어 다음처럼 요청할 수 있다.
아래 문제를 단계적으로 검토한 뒤, 마지막 줄에 최종 답만 써라.
문제: 월 구독료가 12,000원인 서비스가 20% 할인 중이다.
사용자가 6개월을 결제하면 총액은 얼마인가?이런 방식은 모델이 바로 결론으로 점프하지 않게 만든다. 하지만 항상 좋은 것은 아니다. 단순한 질문에 단계적 추론을 요구하면 답변만 길어지고 비용과 지연 시간이 늘어난다. 또 모델이 중간 추론을 장황하게 쓰다가 오히려 잘못된 가정을 만들 수도 있다. 사용자에게 모든 추론 과정을 그대로 노출하는 것이 적절하지 않은 경우도 있다.
그래서 실무에서는 "생각 과정을 길게 보여 줘"보다 다음처럼 목적에 맞게 조정하는 편이 좋다.
결론을 먼저 제시하고, 그 아래에 근거를 3개 이내로 요약해라.
불확실한 가정이 있으면 별도 항목으로 표시해라.또는 내부 검토용 프롬프트라면 다음처럼 쓸 수 있다.
답을 내기 전에 조건 누락 여부를 점검해라.
최종 출력에는 결론, 근거, 확인이 필요한 정보만 포함해라.핵심은 기법 이름을 외우는 것이 아니라 작업의 성격에 맞추는 것이다. 익숙하고 단순한 작업은 zero-shot으로 시작한다. 출력 패턴을 고정해야 하면 few-shot을 붙인다. 여러 조건을 따져야 하면 제한된 형태의 추론 지시를 넣는다.
모호한 요청을 리팩터링하는 법
프롬프트를 잘 쓰는 가장 좋은 연습은 나쁜 프롬프트를 고치는 것이다. 개발자가 코드를 리팩터링하듯, 프롬프트도 목적과 구조가 드러나도록 리팩터링할 수 있다.
먼저 흔한 요청을 보자.
이 문서 보고 중요한 내용 정리해 줘.이 요청은 일상 대화에서는 괜찮지만, 서비스 기능으로는 불안정하다. "중요한 내용"의 기준이 없고, 출력 형식도 없고, 문서에 없는 내용을 어떻게 처리할지도 정해지지 않았다. 이를 리팩터링할 때는 네 가지 질문을 던지면 된다.
무엇을 해야 하는가?
무엇을 근거로 판단하는가?
어떤 형식으로 답해야 하는가?
하지 말아야 할 것은 무엇인가?
이 질문을 반영하면 다음처럼 바뀐다.
[작업]
아래 문서에서 제품 출시 일정과 관련된 내용을 추출해라.
[근거]
제공된 문서 내용만 사용한다.
[입력]
---
{문서}
---
[출력 형식]
마크다운 표로 작성한다.
열: 항목, 내용, 문서 근거 문장, 확실성
[제약]
- 문서에 없는 일정은 추측하지 않는다.
- 날짜가 명확하지 않으면 확실성에 '불명확'이라고 쓴다.
- 제품 출시와 관련 없는 내용은 제외한다.이제 모델은 "중요함"을 마음대로 판단하지 않는다. 제품 출시 일정이라는 기준으로 문서를 보고, 근거 문장까지 함께 내야 한다. 사람도 검수하기 쉬워진다.
다른 예도 보자. 이번에는 글쓰기 요청이다.
블로그 글 써 줘. 주제는 RAG야.이 요청은 거의 백지 위임이다. 독자 수준, 글의 목적, 길이, 다룰 범위가 없다. 리팩터링하면 다음과 같다.
[목표]
LLM API를 써 본 초급 개발자가 RAG의 필요성을 이해하도록 블로그 글 초안을 작성한다.
[독자]
Python으로 API 호출은 해 봤지만 벡터 데이터베이스는 처음 접하는 개발자.
[포함할 내용]
- LLM이 모르는 최신 정보에 답하기 어려운 이유
- RAG가 검색과 생성을 결합하는 방식
- 문서 임베딩, 벡터 검색, 답변 생성의 관계
- RAG가 파인튜닝과 다른 점
[출력]
제목 후보 3개를 먼저 제시한 뒤, 1,200자 안팎의 본문을 작성한다.
[제약]
- 수학 공식은 사용하지 않는다.
- '마법처럼' 같은 과장 표현은 피한다.
- 실제 구현 코드는 넣지 않는다.이 프롬프트는 모델이 글을 쓰기 전에 독자와 목표를 이해하게 만든다. 특히 "포함할 내용"과 "제약"이 함께 있으므로 글이 너무 넓게 퍼지지 않는다.
분류 작업에서는 라벨 정의가 중요하다.
문의 분류해 줘.이를 다음처럼 바꿀 수 있다.
[작업]
고객 문의를 아래 라벨 중 하나로 분류해라.
[라벨 정의]
billing: 결제 실패, 중복 결제, 영수증, 환불 금액 관련 문의
delivery: 배송 지연, 배송 추적, 주소 변경 관련 문의
account: 로그인, 비밀번호, 회원정보 관련 문의
other: 위 세 범주에 속하지 않는 문의
[출력]
라벨만 소문자로 출력한다.
[문의]
정기 결제 영수증은 어디에서 받을 수 있나요?여기서는 "라벨만 출력"이 중요하다. 사람이 읽는 답변이라면 설명이 도움이 되지만, 후속 코드가 라벨을 파싱해야 한다면 설명은 오히려 방해가 된다. 프롬프트는 항상 최종 사용 방식까지 고려해야 한다.
환각을 줄이는 프롬프트 규칙
앞 장에서 환각을 다뤘다. LLM은 모르는 내용도 그럴듯한 문장으로 이어 쓸 수 있다. 프롬프트만으로 환각을 완전히 없앨 수는 없지만, 줄일 수는 있다. 특히 서비스에서는 "정답처럼 보이는 오답"을 막는 장치를 프롬프트에 넣어야 한다.
가장 기본적인 규칙은 근거 범위를 제한하는 것이다.
아래 [문서]에 있는 내용만 근거로 답변해라.
문서에서 확인할 수 없는 내용은 '문서에서 확인할 수 없습니다'라고 답해라.이 한 문장만으로도 모델의 행동이 달라진다. 모델은 자기 내부 지식을 마음대로 끌어오기보다 제공된 문서를 우선하게 된다. 물론 모델이 항상 완벽하게 지키는 것은 아니므로, 중요한 서비스에서는 검색 결과와 출처를 함께 보여 주거나 후처리 검증을 붙이는 것이 좋다.
두 번째 규칙은 불확실성을 표현하게 하는 것이다.
확실한 내용과 추정 내용을 구분해라.
확실하지 않은 정보는 단정하지 말고 '추정'이라고 표시해라.많은 LLM 답변은 문체가 너무 자신만만하다. 그래서 사용자는 모델이 정말 알고 있다고 착각하기 쉽다. 불확실성 표시를 요구하면 답변을 검토하기 쉬워진다.
세 번째 규칙은 필요한 정보를 되묻게 하는 것이다.
답변에 꼭 필요한 정보가 없으면 임의로 가정하지 말고, 가장 적은 수의 추가 질문을 해라.예를 들어 "내 주문 취소해 줘"라는 요청이 들어왔는데 주문번호가 없다면, 모델이 취소 가능 여부를 지어내서는 안 된다. "주문번호를 알려 주세요"라고 되물어야 한다. 이 규칙은 고객지원, 예약, 결제, 의료, 법률처럼 정보 누락이 위험한 영역에서 특히 중요하다.
네 번째 규칙은 금지보다 대안을 함께 주는 것이다.
개인정보를 요구하지 마라.이 문장도 필요하지만, 더 좋은 지시는 다음과 같다.
주민등록번호, 카드번호 전체, 비밀번호는 요구하지 않는다.
주문 확인이 필요하면 주문번호 또는 가입 이메일의 일부만 요청한다.모델에게 "하지 말라"만 말하면 대화가 막힐 수 있다. "대신 무엇을 하라"를 함께 주면 안전성과 유용성을 동시에 높일 수 있다.
문서 기반 Q&A 프롬프트는 다음처럼 구성할 수 있다.
[역할]
당신은 사내 정책 문서 Q&A 도우미다.
[규칙]
- 반드시 제공된 문서 내용만 근거로 답한다.
- 문서에 근거가 없으면 '문서에서 확인할 수 없습니다'라고 답한다.
- 답변 끝에 근거로 사용한 문서 제목을 표시한다.
- 서로 충돌하는 내용이 있으면 충돌 사실을 말하고 단정하지 않는다.
[문서]
---
{검색된 문서 조각}
---
[질문]
{사용자 질문}
[출력]
답변: ...
근거: ...이 형식은 이후 RAG 챕터에서 다시 등장한다. RAG는 결국 "모델에게 외부 문서를 찾아 주고, 그 문서 안에서 답하게 만드는 구조"다. 따라서 좋은 RAG의 출발점도 좋은 프롬프트다.
좋은 프롬프트는 한 번에 완성되지 않는다
초보자는 "완벽한 프롬프트"를 찾으려고 한다. 하지만 실무에서는 한 번 잘 나온 답보다 반복 가능성이 중요하다. 같은 프롬프트가 다양한 입력에서도 안정적으로 작동해야 한다. OpenAI의 도움말도 초기 프롬프트를 작성한 뒤 결과를 보고 문구, 맥락, 요청 범위를 조정하는 반복 개선을 권한다.6
프롬프트를 운영 자산으로 다루려면 최소한 네 가지를 관리해야 한다.
첫째, 프롬프트 버전이다. support_answer_v1, support_answer_v2처럼 이름을 붙이고 변경 이유를 남긴다. "말투 수정", "근거 없음 처리 추가", "출력 형식 JSON으로 변경"처럼 기록해 두면 문제가 생겼을 때 되돌리기 쉽다.
둘째, 대표 테스트 케이스다. 프롬프트를 수정할 때마다 실제 서비스에서 자주 들어오는 입력으로 테스트해야 한다. 정상 사례만 넣으면 안 된다. 애매한 질문, 정보가 부족한 질문, 정책 문서에 답이 없는 질문, 악의적인 요청도 포함해야 한다.
셋째, 기대 출력 기준이다. LLM 답변은 매번 완전히 같지 않을 수 있으므로, 정답 문장을 하나로 고정하기보다 평가 기준을 정하는 편이 좋다. 예를 들어 고객지원 답변이라면 "정책 근거 포함", "환불 가능 여부 단정 금지", "추가 확인 질문 포함" 같은 기준을 둘 수 있다.
넷째, 실패 사례 로그다. 실제 사용 중 모델이 틀린 답을 했거나 형식을 어겼거나 불필요하게 장황했다면, 그 입력과 출력과 설정값을 저장해야 한다. 실패 사례는 다음 프롬프트 개선의 재료다.
실무 로그에는 적어도 다음 정보가 있으면 좋다.
| 항목 | 예시 | 이유 |
|---|---|---|
| requestId | req_20260512_001 | 개별 요청 추적 |
| promptVersion | support_v3 | 프롬프트 변경 영향 확인 |
| model | 사용한 모델명 | 모델별 품질 차이 확인 |
| temperature | 0.2 | 생성 다양성 추적 |
| inputTokens/outputTokens | 1,240 / 180 | 비용과 지연 분석 |
| latencyMs | 1,850 | 성능 모니터링 |
| userFeedback | good/bad | 실제 만족도 확인 |
| failureType | hallucination/format/error | 개선 방향 분류 |
이런 관리는 귀찮아 보이지만, 제품이 커질수록 차이를 만든다. 프롬프트를 파일 하나에 대충 넣어 두면 나중에 어떤 변경이 품질을 올렸는지 알 수 없다. 반대로 버전과 테스트 케이스가 있으면 프롬프트도 코드처럼 개선할 수 있다.
프롬프트 리팩터링 실습: 요약기 만들기
마지막으로 이전 장에서 만든 LLM API 호출을 떠올리며, 간단한 요약기 프롬프트를 단계적으로 개선해 보자.
처음에는 이렇게 시작할 수 있다.
다음 글을 요약해 줘.
{article}이 프롬프트는 데모용으로는 충분하다. 하지만 실제 서비스라면 부족하다. 사용자는 어떤 요약을 기대할까? 뉴스 요약일까, 회의 공유용일까, 검색 결과 미리보기일까? 먼저 목표를 넣어 보자.
다음 글을 바쁜 직장인이 30초 안에 이해할 수 있도록 요약해 줘.
{article}조금 나아졌다. 이제 출력 형식을 고정해 보자.
다음 글을 바쁜 직장인이 30초 안에 이해할 수 있도록 요약해 줘.
출력 형식:
- 한 줄 요약: 1문장
- 핵심 포인트: 3개
- 알아야 할 숫자: 있으면 최대 3개, 없으면 '없음'
글:
---
{article}
---이제 사용자가 보기 쉬워졌다. 하지만 환각 방지 규칙이 없다. 숫자가 없는데 모델이 그럴듯한 수치를 만들 수도 있다. 제약을 추가하자.
[작업]
아래 글을 바쁜 직장인이 30초 안에 이해할 수 있도록 요약해라.
[입력]
---
{article}
---
[출력 형식]
- 한 줄 요약: 1문장
- 핵심 포인트: 3개
- 알아야 할 숫자: 원문에 있는 숫자만 최대 3개. 없으면 '없음'
- 불확실한 내용: 원문만으로 판단하기 어려운 내용이 있으면 작성. 없으면 '없음'
[제약]
- 원문에 없는 사실, 수치, 인물, 기관명을 추가하지 않는다.
- 과장된 표현을 쓰지 않는다.
- 각 항목은 짧고 명확하게 쓴다.이제 꽤 실무적인 프롬프트가 되었다. 여기에 생성 설정을 붙이면 더 안정적이다. 요약은 정확성이 중요하므로 temperature를 낮게 시작하고, 응답 길이를 제한한다.
{
"model": "사용할 모델명",
"temperature": 0.2,
"max_tokens": 500
}프롬프트와 설정을 함께 저장하면 하나의 요약기 버전이 된다. 이후 테스트 케이스를 돌려 보며 너무 길면 max tokens를 줄이거나 "각 항목은 20자 안팎" 같은 지시를 추가할 수 있다. 숫자를 자주 놓치면 "숫자가 포함된 문장을 먼저 확인한 뒤 요약하라"는 규칙을 넣을 수 있다.
이 과정이 프롬프트 엔지니어링이다. 멋진 한 문장을 찾는 것이 아니라, 원하는 결과가 반복해서 나오도록 입력 계약을 다듬는 일이다.
프롬프트 작성 체크리스트
실제로 프롬프트를 작성할 때는 다음 체크리스트를 떠올리면 된다.
| 질문 | 확인할 내용 |
|---|---|
| 무엇을 해야 하는가? | 요약, 분류, 추출, 변환, 생성 등 작업 동사가 분명한가? |
| 누구를 위한 결과인가? | 독자, 사용자, 상황이 명시되어 있는가? |
| 무엇을 근거로 삼는가? | 제공 문서, 입력 데이터, 내부 지식 사용 범위가 정해졌는가? |
| 어떤 형식이어야 하는가? | 문단, 표, JSON, 라벨, 글자 수, 문체가 지정되었는가? |
| 무엇을 하지 말아야 하는가? | 추측, 과장, 개인정보 요구, 형식 이탈을 막는 규칙이 있는가? |
| 모르면 어떻게 해야 하는가? | 모른다고 답하기, 되묻기, 불확실성 표시 규칙이 있는가? |
| 설정값은 맞는가? | temperature, top_p, max tokens가 작업 성격과 맞는가? |
| 반복 테스트했는가? | 대표 입력과 실패 사례로 품질을 확인했는가? |
이 체크리스트를 거치면 프롬프트는 훨씬 덜 흔들린다. 물론 LLM은 여전히 확률적 시스템이다. 같은 입력에도 미세하게 다른 답을 낼 수 있고, 복잡한 작업에서는 실수할 수 있다. 하지만 프롬프트를 입력 계약으로 설계하면 모델의 자유도를 필요한 곳에서는 열어 두고, 위험한 곳에서는 좁힐 수 있다.
다음 장에서는 이 흐름을 한 단계 더 밀고 나간다. 지금까지는 "형식을 말로 요구하는 법"을 배웠다. 하지만 서비스에서는 말로만 "JSON으로 답해"라고 요청하는 것만으로 부족할 때가 많다. 후속 코드가 안정적으로 처리할 수 있도록 응답 구조를 더 엄격하게 고정해야 한다. 그것이 바로 구조화 출력이다.
참고
2Prompt engineering techniques - Microsoft Foundry
3Prompt engineering - OpenAI API
5Prompt engineering techniques - Microsoft Foundry
6Prompt engineering best practices for ChatGPT - OpenAI Help Center
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.