개발가이드

  • 개발가이드

LLM 챗봇 구축 가이드

페르소나(mode)·프로덕션 에러 처리·스트리밍(선택)·비용 관리를 포함한 제품 수준 챗봇 구축. axios 기반 Chatbot 클래스 완성 코드로 복사해 바로 사용.

이 가이드가 다루는 것

멀티챗이 "여러 턴을 기술적으로 이어가는 법" 이었다면, 이 가이드는 사용자 앞에 내놓을 수 있는 제품 수준의 챗봇을 만듭니다.

추가되는 요소
내용
페르소나
mode 또는 system 으로 말투·태도 고정
UX
스트리밍(선택), 응답 대기 인디케이터, 에러 토스트
프로덕션 방어
에러 매핑, 재시도, 비용 캡, 비용 모니터링
통합 코드
복사-붙여넣기 가능한 Chatbot 클래스 (axios 기반)

아직 messages[] 멀티턴 개념이 낯설다면 멀티챗 가이드를 먼저 보세요. "도메인 지식·정책 준수·출력 형식 강제" 같은 고도화된 페르소나가 필요하면 에이전트 구축 가이드로 이어집니다.

전제 조건

항목
설명
API 키 · 잔액
/dev_guide/start — 최소 1포인트
모델
/rest/llm/models 응답의 id 중 선택해 model 필드에 전달
엔드포인트 레퍼런스
/dev_guide/llm_chat — 전체 스펙

Step 1 — 페르소나 한 줄로 말투 잡기 (mode)

mode 파라미터 하나로 서버가 페르소나 system 프롬프트를 자동 주입합니다. 같은 질문이 다른 톤으로 돌아오는 걸 가장 쉽게 체험하는 방법입니다.

지원 모드 키 (8종)
key
특징
chatbot
일반 챗봇 — 간결·친절한 일상 톤 (기본 추천)
business
비즈니스 AI 비서 — 이메일·회의·보고 작성
coding
코딩 어시스턴트 — 스택별 실전 상담
tutor
학습 튜터 — 비유·단계 설명·이해 확인
translator
번역·윤문 — 한·영·일 자연스러운 번역
cs
고객센터 — 공감·해결·다음 단계 3단계 응대
healthcare
건강 정보 안내 — 진단·처방 불가·병원 권고
custom
커스텀 — system 파라미터로 직접 정의
axios — mode 적용

import axios from 'axios';

const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
  model: 'meta-llama/Meta-Llama-3.1-8B-Instruct',
  mode: 'chatbot',  // ← 페르소나 한 줄
  content: '오늘 저녁 뭐 먹을지 추천해줘. 집에 계란·밥·김치만 있어.',
}, {
  headers: { 'CL_AUTH_KEY': process.env.API_KEY },
});

console.log(result.data.message.content);
            

modesystem 을 함께 보내면 "mode 시스템 + [추가 사용자 지시] + system" 으로 결합됩니다 — 페르소나 위에 세부 지침을 덧씌울 수 있습니다. 고도화된 커스터마이징은 에이전트 구축 가이드에서 다룹니다.

Step 2 — UX 개선 (선택): 스트리밍

응답이 길 때 사용자는 "답이 다 나올 때까지의 정적 시간" 을 불편해합니다. stream: true 로 타이핑처럼 글자를 흘릴 수 있습니다.

주의: 스트리밍은 구현 복잡도를 높입니다. MVP 단계에서는 건너뛰고, 사용자 피드백이 "응답이 느려서 기다리기 싫다" 일 때 도입하세요. axios 에서 SSE 스트림은 responseType: 'stream' + Node 의 경우, 브라우저에서는 fetch 가 더 간편합니다 (axios 는 브라우저 SSE 를 공식 지원하지 않음). 아래는 Node 서버 프록시 기준 예시.

Node.js (axios 스트림)

import axios from 'axios';

async function askStream(history, userText, onChunk) {
  history.push({ role: 'user', content: userText });

  const res = await axios.post('https://apick.app/rest/llm/chat', {
    model: 'meta-llama/Meta-Llama-3.1-8B-Instruct',
    mode: 'chatbot',
    messages: history,
    stream: true,
    compact: { strategy: 'sliding_window', window_pairs: 10 },
  }, {
    headers: { 'CL_AUTH_KEY': process.env.API_KEY },
    responseType: 'stream',
  });

  let buf = '', event = '', full = '', done = null;
  for await (const chunk of res.data) {
    buf += chunk.toString('utf8');
    const frames = buf.split('\n\n'); buf = frames.pop();
    for (const frame of frames) {
      for (const line of frame.split('\n')) {
        if (line.startsWith('event: ')) event = line.slice(7).trim();
        else if (line.startsWith('data: ')) {
          const payload = JSON.parse(line.slice(6));
          if (event === 'data') {
            const delta = payload.delta ?? payload.content ?? '';
            full += delta; onChunk(delta);
          } else if (event === 'done') done = payload;
        }
      }
    }
  }
  return { text: full, next: done?.compacted_messages || history, cost: done?.cost };
}
            

브라우저 프론트엔드는 자체 백엔드 프록시를 두고 거기서 위 Node 코드를 호출한 뒤, 프록시 응답을 SSE 그대로 클라이언트에 흘리는 것이 가장 단순합니다. (API 키 노출도 함께 방지)

Step 3 — 프로덕션 강화

항목
대응
에러 매핑
400 (입력 오류) · 402 (잔액 부족) — 재시도 금지, UX 로 안내
502 (업스트림 일시 오류) — 지수 백오프 1회 재시도
비용 캡
미지정 시 서버가 각 모델의 max_context 까지 자동 허용. 비용·UX 관점에서 더 작은 값(예: 2048, 4096)으로 명시해 답변 길이를 제한하는 것도 권장
비용 모니터링
매 호출 result.api.cost 누적 로깅. 사용자별 일일 한도 초과 시 안내. result.api.pl_id 는 PaymentLog 감사 추적용
응답 속도
챗봇은 대기 민감 — speed: 'fast' 권장. 깊은 추론이 필요한 특수 질문에서만 'slow'
긴 대화
compact.strategy: 'sliding_window' 필수. window_pairs 는 10 권장
API 키 보안
브라우저에서 직접 호출 시 키 노출. 자체 백엔드 프록시 경유 로 키를 서버에 숨기세요

전체 코드 — Chatbot 클래스 (axios · 복사해서 바로 사용)

Step 1~3 를 통합한 클래스입니다. 히스토리 보유 · sliding_window · mode 페르소나 · 에러 매핑 · 재시도 · 비용 집계 포함.

Chatbot.js (Node.js / 브라우저 공용)

import axios from 'axios';

const API_BASE = 'https://apick.app/rest/llm/chat';

export class Chatbot {
  constructor({
    apiKey,
    model       = 'meta-llama/Meta-Llama-3.1-8B-Instruct',
    mode        = 'chatbot',   // 8종 프리셋 중 하나
    windowPairs = 10,
    maxTokens   = 512,
    speed       = 'fast',
  } = {}) {
    this.cfg = { apiKey, model, mode, windowPairs, maxTokens, speed };
    this.history = [];
    this.totalCost = 0;
  }

  async _post(body, retryLeft = 1) {
    try {
      const { data: result } = await axios.post(API_BASE, body, {
        headers: { 'CL_AUTH_KEY': this.cfg.apiKey },
        timeout: 60000,
      });
      return result;
    } catch (e) {
      const status = e.response?.status;
      if (status === 400) throw new Error('요청 오류 (입력 확인 필요)');
      if (status === 402) throw new Error('포인트 부족 — 충전 필요');
      if (retryLeft > 0 && (!status || status >= 500)) {
        await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
        return this._post(body, retryLeft - 1);
      }
      throw e;
    }
  }

  async ask(userText) {
    this.history.push({ role: 'user', content: userText });
    const result = await this._post({
      model: this.cfg.model,
      mode:  this.cfg.mode,
      messages: this.history,
      compact: { strategy: 'sliding_window', window_pairs: this.cfg.windowPairs },
      max_tokens:  this.cfg.maxTokens,
      speed: this.cfg.speed,
    });
    this.history = result.data.compacted_messages;
    this.totalCost += result.api.cost || 0;
    return result.data.message.content;
  }

  reset() { this.history = []; }
  getCost() { return this.totalCost; }
}

// 사용 예시
const bot = new Chatbot({ apiKey: process.env.API_KEY, mode: 'chatbot' });
console.log(await bot.ask('내 이름은 지훈이야.'));
console.log(await bot.ask('내 이름 기억해?'));
console.log('누적 비용:', bot.getCost(), '포인트');
            

이 클래스의 mode 를 바꿔 튜터·번역기·고객센터 등 다른 톤의 챗봇을 즉시 만들 수 있습니다. 그러나 "우리 회사 정책에 따라 답하는" 도메인 특화 챗봇이 필요하면, system 파라미터로 정책 원문·행동 규칙·출력 형식을 주입하는 기법이 필요합니다 — 이것이 에이전트 구축 가이드의 주제입니다.

현재 페이지 북마크