
system 페르소나·프로덕션 에러 처리·스트리밍(선택)·비용 관리를 포함한 제품 수준 챗봇 구축. axios 기반 Chatbot 클래스 완성 코드로 복사해 바로 사용.
멀티챗이 "여러 턴을 기술적으로 이어가는 법" 이었다면, 이 가이드는 사용자 앞에 내놓을 수 있는 제품 수준의 챗봇을 만듭니다.
system 으로 역할·말투·태도 고정Chatbot 클래스 (axios 기반)
아직 messages[] 멀티턴 개념이 낯설다면 멀티챗 가이드를 먼저 보세요. "도메인 지식·정책 준수·출력 형식 강제" 같은 고도화된 페르소나가 필요하면 에이전트 구축 가이드로 이어집니다.
system에 역할, 답변 언어, 말투와 길이를 직접 지정합니다. 서버는 별도 프리셋을 저장하지 않으므로 호출자가 매 요청에 같은 지침을 전달해야 합니다.
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-Turbo',
system: '당신은 친절한 한국어 요리 도우미입니다. 재료와 조리 순서를 간결하게 구분해 답하세요.',
content: '오늘 저녁 뭐 먹을지 추천해줘. 집에 계란·밥·김치만 있어.',
}, {
headers: { 'CL_AUTH_KEY': process.env.API_KEY },
});
console.log(result.data.message.content);
시스템 지침은 서버에 저장되지 않습니다. 여러 턴 대화에서도 동일한 system을 매번 보내거나 messages[0]에 system 역할로 포함하세요. 고도화된 커스터마이징은 에이전트 구축 가이드에서 다룹니다.
응답이 길 때 사용자는 "답이 다 나올 때까지의 정적 시간" 을 불편해합니다. stream: true 로 타이핑처럼 글자를 흘릴 수 있습니다.
주의: 스트리밍은 구현 복잡도를 높입니다. MVP 단계에서는 건너뛰고, 사용자 피드백이 "응답이 느려서 기다리기 싫다" 일 때 도입하세요. axios 에서 SSE 스트림은 responseType: 'stream' + Node 의 경우, 브라우저에서는 fetch 가 더 간편합니다 (axios 는 브라우저 SSE 를 공식 지원하지 않음). 아래는 Node 서버 프록시 기준 예시.
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-Turbo',
system: '당신은 친절한 한국어 상담 챗봇입니다. 핵심 답변과 다음 행동을 짧게 안내하세요.',
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 키 노출도 함께 방지)
result.api.cost 누적 로깅. 사용자별 일일 한도 초과 시 안내. result.api.pl_id 는 PaymentLog 감사 추적용speed: 'fast' 권장. 깊은 추론이 필요한 특수 질문에서만 'slow'compact.strategy: 'sliding_window' 필수. window_pairs 는 10 권장Chatbot 클래스 (axios · 복사해서 바로 사용)Step 1~3 를 통합한 클래스입니다. 히스토리 보유 · sliding_window · system 페르소나 · 에러 매핑 · 재시도 · 비용 집계 포함.
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-Turbo',
system = '당신은 친절한 한국어 상담 챗봇입니다. 핵심 답변과 다음 행동을 짧게 안내하세요.',
windowPairs = 10,
maxTokens = 512,
speed = 'fast',
} = {}) {
this.cfg = { apiKey, model, system, 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,
system: this.cfg.system,
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 });
console.log(await bot.ask('내 이름은 지훈이야.'));
console.log(await bot.ask('내 이름 기억해?'));
console.log('누적 비용:', bot.getCost(), '포인트');
이 클래스의 system을 바꿔 튜터·번역기·고객센터 등 다른 역할의 챗봇을 만들 수 있습니다.
"우리 회사 정책에 따라 답하는" 도메인 특화 챗봇이 필요하면 정책 원문·행동 규칙·출력 형식을 함께 주입하세요. 이것이 에이전트 구축 가이드의 주제입니다.