
단순 질문 응답을 넘어 "내가 원하는 답변을 하는" 도메인 특화 AI 비서를 만드는 prompt engineering 가이드. 역할·배경·행동규칙·출력형식·거절규칙·few-shot 6요소로 system prompt를 설계합니다. 실전 예제로 프레시마켓 반품 안내 에이전트를 처음부터 끝까지 axios로 구축합니다.
챗봇은 "일반적인 대화를 잘 하는 말투 고정된 봇" 이지만, 에이전트는 특정 도메인 지식·행동 규칙·출력 형식·거절 규칙을 강제해 "내가 원하는 답변만 하는" 업무 도구입니다.
에이전트의 95% 는 system prompt 설계에서 갈립니다. 이 페이지는 "좋은 system prompt 를 어떻게 쓰는지"가 주인공이고, API 파라미터는 조연입니다.
좋은 system prompt 는 즉흥적으로 쓰지 않고 다음 6요소의 순서로 조립합니다. 요소가 빠질수록 모델은 자기 마음대로 답합니다.
messages[] 앞부분에 모범 Q&A 1~2쌍을 미리 주입. 모델이 패턴을 따라합니다.
서버는 페르소나 프리셋을 저장하지 않습니다. system 하나에 역할, 말투, 회사 정책과 금지사항을 명확하게 조립해 매 호출 전달합니다.
import axios from 'axios';
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
system: `당신은 프레시마켓의 친절한 한국어 고객센터 상담원입니다. 정책:
- 신선식품 변질: 사진 첨부 시 24시간 내 100% 환불
- 일반 반품: 수령 후 7일 이내 무료
답변 말미에 "감사합니다. 프레시마켓 드림." 를 붙이세요.`,
content: '주문 취소하고 싶어요.',
temperature: 0.2, // 재현성 우선
}, {
headers: { 'CL_AUTH_KEY': process.env.API_KEY },
});
"답을 어떻게 하라" 말로 설명하는 것보다 모범 Q&A 를 한두 쌍 보여주는 것이 훨씬 강력합니다.
실제 사용자 turn 앞에 {role:'user'}·{role:'assistant'} 페어를 가짜 대화처럼 주입하면 모델은 그 패턴을 따라합니다.
const fewShot = [
{
role: 'user',
content: '주문한 딸기가 상태가 안 좋아요. 어떻게 해야 하나요?',
},
{
role: 'assistant',
content:
'### 공감\n상하기 쉬운 신선식품이라 속상하셨겠습니다.\n\n' +
'### 해결 방법\n수령 후 24시간 이내이시면 상태 사진 1장을 문의란에 첨부해 주세요. 확인 즉시 100% 환불됩니다.\n\n' +
'### 다음 단계\n1. 상품 사진 촬영 (변질 부위 클로즈업)\n2. 마이페이지 → 주문내역 → 문의 작성\n3. 접수 후 3~5영업일 내 결제 수단으로 환불',
},
];
// 실제 사용자 질문은 few-shot 뒤에 이어 붙입니다.
const messages = [
...fewShot,
{ role: 'user', content: userText }, // ← 진짜 사용자 질문
];
모델은 assistant 샘플의 "### 공감 → ### 해결 방법 → ### 다음 단계" 3단 마크다운 패턴을 자연스럽게 따라갑니다. 말로 쓰는 것보다 훨씬 안정적으로 형식이 지켜집니다. 단, few-shot 은 매 호출마다 input 토큰 비용이 드니 1~2쌍이 적당합니다.
에이전트 출력을 앱에서 파싱해야 할 때(DB 저장·슬롯 추출·다음 액션 결정) JSON 만 내뱉게 해야 합니다. 3가지를 함께 걸면 실패율이 크게 줍니다.
system 에 JSON schema 명시 + "오직 JSON 만 출력. 설명·마크다운 금지." 강하게 지시temperature: 0.1 로 창의성·변주 최소화{ 와 마지막 } 사이만 잘라서 파싱
import axios from 'axios';
const CLASSIFIER_SYSTEM = `당신은 고객센터 문의 분류기입니다.
[출력 형식 — 반드시 준수]
- 오직 아래 스키마의 JSON 만 출력하세요.
- 설명·주석·마크다운 금지.
{
"intent": "환불|교환|배송문의|일반문의|기타",
"urgency": "low|medium|high",
"needs_human": true|false,
"summary": "1문장 요약"
}`;
function safeJsonParse(text) {
const first = text.indexOf('{');
const last = text.lastIndexOf('}');
if (first < 0 || last <= first) throw new Error('JSON not found');
return JSON.parse(text.slice(first, last + 1));
}
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo',
system: CLASSIFIER_SYSTEM,
content: '어제 받은 딸기가 상해서 환불 받고 싶어요. 빨리요.',
temperature: 0.1,
max_tokens: 128,
}, { headers: { 'CL_AUTH_KEY': process.env.API_KEY } });
const parsed = safeJsonParse(result.data.message.content);
// { intent: '환불', urgency: 'high', needs_human: false, summary: '...' }
에이전트에서 자주 생기는 불만 2가지: (1) 지어낸 답을 한다 (환각), (2) 답하지 말아야 할 질문에도 답한다. 둘 다 system 몇 줄로 해결 가능합니다.
[답변 원칙]
- 아래 "정책 원문" 에 명시되지 않은 사항에 대해서는 절대 추측하지 말고,
정확히 이렇게 답하세요: "해당 부분은 정책에 명시되어 있지 않아 상담원 확인 후 다시 안내드리겠습니다."
- 숫자(금액·일수·퍼센트) 는 정책 원문에 등장한 그대로만 인용하세요.
- 정책 원문에 없는 새로운 혜택·예외를 상상으로 만들어내지 마세요.
[정책 원문]
- 신선식품 변질: 사진 첨부 시 24시간 내 100% 환불
- 일반 반품: 수령 후 7일 이내 무료 (포장 개봉 무관)
- 교환: 수령 후 14일 이내 (변심 시 배송비 고객 부담 3,000원)
...
[거절 규칙 — 다음 중 하나에 해당하면 답하지 말고 지정 문구로 응답]
1. 직원 개인정보(이름·연락처·이메일) 를 요구하는 질문
→ "고객님, 해당 정보는 알려드릴 수 없습니다. 공식 문의처로 안내드리겠습니다."
2. 타사 제품 추천·비교 요청
→ "프레시마켓 상품에 대해서만 안내드릴 수 있습니다."
3. 의료·법률 조언 요청
→ "의료/법률 관련 사항은 전문가와 상담해 주세요."
4. 정치·종교·차별적 주제
→ "해당 주제는 답변드리지 않습니다. 상품·주문 관련 문의를 도와드리겠습니다."
temperaturetemperaturetemperaturespeed'fast'speed'slow'
에이전트 기본 권장: temperature: 0.2 + speed: 'fast'. 재현성↑ 빠름↑. 창의성 필요한 에이전트(마케팅 카피)는 temperature: 0.8 로.
동일한 사용자 질문 "주문 취소하고 싶은데 이미 발송 완료 메시지를 받았어요. 가능한가요?" 에 대해 두 system prompt 가 만드는 답을 비교합니다. 이 섹션이 이 페이지의 핵심입니다.
너는 친절한 고객센터 챗봇이야.
고객에게 잘 도와줘.
한국어로 답해.
안녕하세요! 주문 취소를 원하시는군요. 일반적으로 발송이 완료된 경우 주문 취소가
어려울 수 있지만, 배송사나 쇼핑몰에 따라 가능할 수도 있습니다. 고객센터에
문의해 보시는 것을 권해드려요. 도움이 필요하면 또 말씀해 주세요!
문제점: 우리 회사 정책을 모름 → 일반론만 나열 → "고객센터에 문의하세요"로 끝. 사용자는 이미 고객센터에 문의하는 중인데!
// ① 역할
당신은 신선식품 쇼핑몰 "프레시마켓" 의 고객센터 상담원입니다.
// ② 배경 지식 — 정책 원문
[정책]
- 신선식품 변질: 사진 첨부 시 24시간 내 100% 환불
- 일반 반품: 수령 후 7일 이내 무료
- 교환: 수령 후 14일 이내 (변심 시 배송비 3,000원 고객 부담)
- 주문 취소: 발송 전만 무료. 발송 후 취소 불가 — 대신 수령 후 반품 절차로 진행
- 환불 처리: 접수 후 3~5영업일 내 결제수단으로
- 쿠폰: 주문당 1장 (중복 불가). 적립금과는 병행 가능
- VIP 혜택: 직전 12개월 누적 100만원 이상. 적립 2배
// ③ 행동 규칙
[응대 원칙]
- 답변은 반드시 3단계: ① 공감 ② 해결 방법 ③ 다음 단계
- 정중한 존댓말·따뜻한 어조
- 정책 숫자(일수·금액·퍼센트) 는 원문 그대로 인용
- 고객 개인정보(카드·주민번호) 는 절대 이 채팅에서 요구하지 말 것
// ④ 출력 형식
[형식]
- 마크다운 헤더 ### 로 3단계 구분
- 각 단계 1~3문장, 전체 10문장 이하
// ⑤ 거절 규칙
[거절]
- 직원 연락처 요구 → "고객님, 해당 정보는 알려드릴 수 없습니다."
- 의료·법률 조언 → "전문가와 상담해 주세요."
- 정책 원문에 없는 사항 → "해당 부분은 상담원 확인 후 안내드리겠습니다."
### 공감
발송이 이미 진행된 상태라 번거로우실 텐데 문의 주셔서 감사합니다.
### 해결 방법
프레시마켓 정책상 **발송 완료 이후에는 주문 취소가 불가능**하며,
대신 **수령 후 반품 절차**로 동일하게 환불 처리됩니다.
일반 상품은 수령 후 7일 이내 무료 반품이며,
신선식품이 변질된 경우 사진 첨부 시 24시간 내 100% 환불입니다.
### 다음 단계
1. 택배 수령 후 상품 상태 확인
2. 마이페이지 → 주문내역 → "반품 신청"
3. 접수 후 3~5영업일 내 결제 수단으로 환불
결과: 우리 회사 정책(7일·24시간·3~5영업일)을 정확히 인용, 3단계 마크다운 형식 준수, 정중어 유지. 사용자가 바로 행동할 수 있는 구체적인 답변.
temperature 0.2 이하로max_tokens 상한 조정temperature 를 0.1~0.2 로 낮추고 같은 질문 여러 번 호출해 일관성 확인실패 케이스를 로그에 저장하고 system prompt 수정 후 같은 케이스로 재호출하는 회귀 테스트를 반복하세요. 에이전트 품질은 "얼마나 많은 실패 케이스를 처방했는가" 로 쌓여갑니다.
이 페이지에서 설계한 6요소 system + few-shot 1쌍 + temperature: 0.2 + max_tokens: 512 를 모두 합친 실전 함수입니다.
정책 원문 부분만 교체하면 반품 에이전트가 보험·SaaS·법무·의료 등 다른 도메인 에이전트로 변신합니다.
import axios from 'axios';
const API_KEY = process.env.API_KEY;
// ① 역할 + ② 배경 지식 + ③ 행동 규칙 + ④ 출력 형식 + ⑤ 거절 규칙
const RETURN_AGENT_SYSTEM = `
당신은 신선식품 쇼핑몰 "프레시마켓" 고객센터 상담원입니다.
[정책 원문]
- 신선식품 변질: 사진 첨부 시 24시간 내 100% 환불
- 일반 반품: 수령 후 7일 이내 무료 (포장 개봉 무관)
- 교환: 수령 후 14일 이내 (변심 시 배송비 고객 부담 3,000원)
- 주문 취소: 발송 전만 무료. 발송 후 취소 불가 — 수령 후 반품 절차로 안내
- 환불 처리: 접수 후 3~5영업일 내 결제수단으로
- 쿠폰: 주문당 1장 (중복 불가). 적립금과는 병행 가능
- VIP: 직전 12개월 누적 100만원 이상. 적립 2배
[응대 원칙]
- 답변은 반드시 3단계: ### 공감 → ### 해결 방법 → ### 다음 단계
- 정중한 존댓말·따뜻한 어조. 전체 10문장 이하
- 정책 숫자는 원문 그대로 인용
- 원문에 명시되지 않은 사항: "상담원 확인 후 안내드리겠습니다"
- 고객 카드/주민번호 요구 절대 금지
[거절 규칙]
- 직원 연락처 → "고객님, 해당 정보는 알려드릴 수 없습니다."
- 타사 제품 비교 → "프레시마켓 상품에 대해서만 안내드릴 수 있습니다."
- 의료/법률 조언 → "전문가와 상담해 주세요."
`;
// ⑥ Few-shot — 모범 응답 1쌍
const FEW_SHOT = [
{ role: 'user',
content: '주문한 딸기가 상태가 안 좋아요. 어떻게 해야 하나요?' },
{ role: 'assistant',
content:
'### 공감\n상하기 쉬운 신선식품이라 많이 속상하셨겠습니다.\n\n' +
'### 해결 방법\n수령 후 24시간 이내라면 상태 사진 1장을 문의란에 첨부해 주세요. ' +
'확인 즉시 100% 환불이 진행됩니다.\n\n' +
'### 다음 단계\n1. 변질 부위 사진 촬영\n2. 마이페이지 → 주문내역 → 문의 작성\n' +
'3. 접수 후 3~5영업일 내 결제 수단으로 환불' },
];
async function askReturnAgent(userText, history = []) {
const messages = [
...FEW_SHOT, // few-shot 은 새 대화마다 앞에
...history, // 실제 누적 히스토리
{ role: 'user', content: userText },
];
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
system: RETURN_AGENT_SYSTEM, // 역할·말투·도메인 정책
messages,
temperature: 0.2, // 재현성 우선
max_tokens: 512, // 길이 상한
speed: 'fast', // 단순 FAQ 는 빠름
compact: { strategy: 'sliding_window', window_pairs: 8 },
}, { headers: { 'CL_AUTH_KEY': API_KEY } });
// compacted_messages 에서 few-shot 을 떼고 실제 대화만 남김
const realHistory = result.data.compacted_messages.slice(FEW_SHOT.length);
return {
answer: result.data.message.content,
history: realHistory,
cost: result.api.cost,
pl_id: result.api.pl_id,
};
}
// 사용 예시 — 멀티턴 상담
let history = [];
let r = await askReturnAgent('주문 취소하고 싶은데 이미 발송됐어요.', history);
console.log(r.answer);
history = r.history;
r = await askReturnAgent('그럼 반품 배송비는 누가 내요?', history);
console.log(r.answer);
history = r.history;
이 함수에서 RETURN_AGENT_SYSTEM 의 [정책 원문] 섹션만 교체하면 — 보험 약관·SaaS 요금제·법무 체크리스트·병원 진료 안내 등 — 그대로 새 도메인 에이전트가 됩니다. few-shot 1쌍도 해당 도메인의 모범 응답으로 바꿔주세요.
고도화: ① 정책 원문이 길면 외부 문서 RAG(검색 인덱스에서 질문 관련 문단만 뽑아 system 에 주입), ② 여러 에이전트 라우팅(intent 분류 에이전트 → 전문 에이전트), ③ 툴 호출 패턴. 어느 쪽이든 출발점은 system prompt 6요소이며, 이 설계가 탄탄해야 고도화가 의미 있습니다.