개발가이드

  • 개발가이드

AI 에이전트(비서) 구축 가이드

단순 질문 응답을 넘어 "내가 원하는 답변을 하는" 도메인 특화 AI 비서를 만드는 prompt engineering 가이드. 역할·배경·행동규칙·출력형식·거절규칙·few-shot 6요소로 system prompt 를 설계하고, mode 와 결합해 페르소나를 강화합니다. 실전 예제로 프레시마켓 반품 안내 에이전트를 처음부터 끝까지 axios 로 구축합니다.

챗봇 vs 에이전트 — 무엇이 다른가

챗봇은 "일반적인 대화를 잘 하는 말투 고정된 봇" 이지만, 에이전트는 특정 도메인 지식·행동 규칙·출력 형식·거절 규칙을 강제해 "내가 원하는 답변만 하는" 업무 도구입니다.

관점
일반 챗봇
AI 에이전트
목적
잡담·일반 지식 Q&A
특정 업무·도메인 문제 해결
사전 지식
모델 파라메트릭 지식만
우리 회사 정책·상품·FAQ 를 prompt 에 주입
답변 톤
모델 기본값 (또는 mode 프리셋)
지정된 톤 (고객센터·의료·법률·교사 등)
출력 형식
제한 없음
JSON·마크다운·문장수 상한 강제
거절
모델 기본 거절만
정책 외·민감 주제 명시 거절
재현성
답변이 매번 다름
핵심 질문은 매번 같은 답 (temperature 0.1~0.3)

에이전트의 95% 는 system prompt 설계에서 갈립니다. 이 페이지는 "좋은 system prompt 를 어떻게 쓰는지"가 주인공이고, API 파라미터는 조연입니다.

에이전트 = 6겹의 system prompt

좋은 system prompt 는 즉흥적으로 쓰지 않고 다음 6요소의 순서로 조립합니다. 요소가 빠질수록 모델은 자기 마음대로 답합니다.

#
요소
어떻게 쓰는가
역할(Role)
"당신은 ___ 회사의 ___ 입니다." — 한 문장으로 페르소나 고정.
배경 지식(Context)
우리 회사 정책·상품 정보·FAQ·가격표 등을 prompt 본문에 원문 그대로 박기.
행동 규칙(Behavior)
"반드시 ~하라 / 절대 ~하지 마라" 불릿 5~10개.
출력 형식(Format)
JSON schema / 마크다운 템플릿 / "최대 3문장" 등 — 매번 같은 모양을 강제.
거절 규칙(Refusal)
"다음에 해당하면 답하지 말고 정해진 문구로 응답: (a) 개인정보 (b) 타사 비교 (c) 의료/법률..."
Few-shot 예시
messages[] 앞부분에 모범 Q&A 1~2쌍을 미리 주입. 모델이 패턴을 따라합니다.

mode + system 결합 — 서버 레이어링

mode 로 기본 톤(고객센터·코딩 등 8종)을 잡고, system 으로 우리 회사 도메인 지식을 덧씌우는 레이어링이 가장 효율적입니다. 서버는 "모드 system + [추가 사용자 지시] + 내 system" 으로 결합해 messages 최전방에 배치합니다.

axios — mode + 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',
  mode: 'cs',        // 고객센터 톤
  system: `우리는 프레시마켓입니다. 정책:
- 신선식품 변질: 사진 첨부 시 24시간 내 100% 환불
- 일반 반품: 수령 후 7일 이내 무료
답변 말미에 "감사합니다. 프레시마켓 드림." 를 붙이세요.`,
  content: '주문 취소하고 싶어요.',
  temperature: 0.2,  // 재현성 우선
}, {
  headers: { 'CL_AUTH_KEY': process.env.API_KEY },
});
            

Few-shot 으로 출력 모양 학습시키기

"답을 어떻게 하라" 말로 설명하는 것보다 모범 Q&A 를 한두 쌍 보여주는 것이 훨씬 강력합니다. 실제 사용자 turn 앞에 {role:'user'}·{role:'assistant'} 페어를 가짜 대화처럼 주입하면 모델은 그 패턴을 따라합니다.

few-shot 주입 구조

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쌍이 적당합니다.

출력 형식 강제 — JSON only

에이전트 출력을 앱에서 파싱해야 할 때(DB 저장·슬롯 추출·다음 액션 결정) JSON 만 내뱉게 해야 합니다. 3가지를 함께 걸면 실패율이 크게 줍니다.

  1. system 에 JSON schema 명시 + "오직 JSON 만 출력. 설명·마크다운 금지." 강하게 지시
  2. temperature: 0.1 로 창의성·변주 최소화
  3. 파서 쪽 방어 — 첫 { 와 마지막 } 사이만 잘라서 파싱
axios — JSON 분류기

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',
  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. 정치·종교·차별적 주제
   → "해당 주제는 답변드리지 않습니다. 상품·주문 관련 문의를 도와드리겠습니다."
            

결정성 다이얼 — speed / temperature

파라미터
언제 쓰는가
temperature
0.1 ~ 0.3
FAQ·정책 안내·JSON 분류 — 매번 같은 질문에 같은 답이 나와야 할 때 (에이전트 기본값)
temperature
0.4 ~ 0.6
일반 상담·요약 — 자연스러우면서도 크게 변주되지 않는 답
temperature
0.7 ~ 1.0
마케팅 카피·브레인스토밍·창의 글쓰기
speed
'fast'
단순 FAQ·분류·짧은 응답
speed
'slow'
정책 해석·케이스 분석·복잡 논리 — tag:reasoning 모델에서 차이가 가장 큼

에이전트 기본 권장: temperature: 0.2 + speed: 'fast'. 재현성↑ 빠름↑. 창의성 필요한 에이전트(마케팅 카피)는 temperature: 0.8 로.

나쁜 system prompt vs 좋은 system prompt — Side-by-side

동일한 사용자 질문 "주문 취소하고 싶은데 이미 발송 완료 메시지를 받았어요. 가능한가요?" 에 대해 두 system prompt 가 만드는 답을 비교합니다. 이 섹션이 이 페이지의 핵심입니다.

❌ 나쁜 예 — 3줄 system

너는 친절한 고객센터 챗봇이야.
고객에게 잘 도와줘.
한국어로 답해.
            
→ 실제 응답 (예시)

안녕하세요! 주문 취소를 원하시는군요. 일반적으로 발송이 완료된 경우 주문 취소가
어려울 수 있지만, 배송사나 쇼핑몰에 따라 가능할 수도 있습니다. 고객센터에
문의해 보시는 것을 권해드려요. 도움이 필요하면 또 말씀해 주세요!
            

문제점: 우리 회사 정책을 모름 → 일반론만 나열 → "고객센터에 문의하세요"로 끝. 사용자는 이미 고객센터에 문의하는 중인데!

✅ 좋은 예 — 6요소를 갖춘 system

// ① 역할
당신은 신선식품 쇼핑몰 "프레시마켓" 의 고객센터 상담원입니다.

// ② 배경 지식 — 정책 원문
[정책]
- 신선식품 변질: 사진 첨부 시 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단계 마크다운 형식 준수, 정중어 유지. 사용자가 바로 행동할 수 있는 구체적인 답변.

반복 개선 워크플로우 — 답변이 마음에 안 들 때

증상
원인
해결 — 어느 요소를 보강?
답이 너무 일반적·두루뭉술
도메인 지식 없음
② 배경 지식 에 정책 원문·상품 정보·FAQ 더 구체적으로 추가
매번 답변 형식이 제각각
형식 지시 부족·예시 없음
④ 출력 형식 명시 + ⑥ Few-shot 1~2쌍 주입
없는 사실을 지어낸다 (환각)
앵커링 부재·창의성 과다
"원문에 없으면 '확인 후 안내' 로 답하라" 규칙 추가 + temperature 0.2 이하로
거절해야 할 질문에도 답한다
거절 규칙 부재
⑤ 거절 규칙 케이스별 문구까지 명시 + 거절 few-shot 1쌍 추가
답이 너무 길거나 짧다
길이 제약 없음
④ 출력 형식 에 "최대 N문장" 명시 + max_tokens 상한 조정
매번 답이 달라 재현성↓
temperature 높음
temperature 를 0.1~0.2 로 낮추고 같은 질문 여러 번 호출해 일관성 확인

실패 케이스를 로그에 저장하고 system prompt 수정 후 같은 케이스로 재호출하는 회귀 테스트를 반복하세요. 에이전트 품질은 "얼마나 많은 실패 케이스를 처방했는가" 로 쌓여갑니다.

전체 완성 코드 — 프레시마켓 반품 에이전트 (axios)

이 페이지에서 설계한 6요소 system + mode: 'cs' + 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',
    mode: 'cs',                         // 고객센터 톤
    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요소이며, 이 설계가 탄탄해야 고도화가 의미 있습니다.

현재 페이지 북마크