LLM 멀티챗 가이드
여러 턴에 걸쳐 대화를 이어가는 기본 패턴. 클라이언트가 messages[] 를 소유·전송하고 응답의 compacted_messages 로 통째 교체하는 stateless 방식, sliding_window 로 토큰 비용 관리, 재시도 전략.
이런 상황에 사용하세요
이전 대화의 맥락을 이어가야 할 때입니다. "내 이름 기억해?" 같은 질문에 모델이 "네, 지훈님" 이라고 답하려면 지난 턴의 대화를 함께 전달해야 합니다.
"페르소나·UX·업무 자동화" 는 아직 없고, 단순히 여러 턴을 이어가는 기본만 필요한 단계를 다룹니다.
코드 리뷰 대화 (설명 → 수정 요청 → 재설명)
이전 코드·피드백을 기억해야 함
번역 후 톤 조정 ("좀 더 공식적으로")
원문·초벌 번역이 맥락
계획 세우기 (여행·운동·학습)
이전 선택을 고려해 다음 제안
문서 Q&A (한 번 컨텍스트 보내고 여러 질문)
매번 긴 컨텍스트 재전송 대신 히스토리 유지
페르소나·말투·제품 정책 같은 고정된 배경지식이 필요하면 챗봇 구축/에이전트 구축 가이드로 가세요.
핵심 개념 — stateless + client-owned history
서버는 대화를 기억하지 않습니다. 각 호출은 독립적이며, 연속성은 전적으로 클라이언트가 만듭니다.
클라이언트
messages[] 배열을 보유하고, 매 턴마다 전체 히스토리를 전송
서버
요청 바디의 messages[] 만 보고 답변 생성. 어떤 세션·ID·캐시도 사용하지 않음
응답의 compacted_messages
"다음 턴 그대로 써도 되는 완성된 히스토리" — 요청 messages + 이번 assistant 응답이 append 된 상태
⚠️ 흔한 실수 금지: 응답 받은 뒤 history.push(result.data.message) 로 직접 이어붙이지 마세요. 서버 compact(트림)가 적용됐을 때 클라이언트·서버 히스토리가 어긋납니다. 항상 history = result.data.compacted_messages 로 통째 교체하세요.
Step 1 — 두 턴 이어가기 (가장 짧은 예시)
클라이언트가 보유하는 history 배열에 사용자 메시지를 push → 전체를 전송 → 응답의 compacted_messages 로 교체. 이 세 단계가 전부입니다.
import axios from 'axios';
let history = []; // messages[] — 클라이언트가 소유
async function ask(userText) {
history.push({ role: 'user', content: userText });
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: 'meta-llama/Meta-Llama-3.1-8B-Instruct',
messages: history,
}, {
headers: { 'CL_AUTH_KEY': process.env.API_KEY },
});
history = result.data.compacted_messages; // ⚠️ 교체 (push 금지)
return result.data.message.content;
}
console.log(await ask('내 이름은 지훈이야.'));
console.log(await ask('내 이름 기억해?'));
// → "네, 지훈님이라고 말씀하셨죠."
console.log(await ask('내 이름으로 삼행시 지어줘.'));
Step 2 — 긴 대화의 비용 관리: sliding_window
대화가 길어질수록 매 호출마다 보내는 messages[] 가 커지고 input 토큰이 누적 증가합니다 (10턴 → 20턴 → 50턴 ...). 비용이 기하급수적으로 늘 수 있습니다.
compact.strategy: "sliding_window" 옵션을 주면 서버가 최근 N개의 user/assistant 페어만 남기고 앞쪽을 버린 뒤 모델에 전달합니다. window_pairs 는 유지할 페어 수 (기본 10, 최소 1).
JavaScript (axios) — sliding_window 적용
async function ask(userText) {
history.push({ role: 'user', content: userText });
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: 'meta-llama/Meta-Llama-3.1-8B-Instruct',
messages: history,
compact: { strategy: 'sliding_window', window_pairs: 10 }, // 최근 10페어만 유지
}, {
headers: { 'CL_AUTH_KEY': process.env.API_KEY },
});
history = result.data.compacted_messages; // 이미 트림된 상태
console.log('누적 페어:', history.length / 2, '/ prompt 토큰:', result.data.usage.prompt_tokens);
return result.data.message.content;
}
window_pairs 얼마로? 짧은 컨텍스트(최근 2~3턴만 중요)는 5, 일반 대화는 10, 긴 상담·튜터링은 20. 커질수록 맥락↑ 비용↑. 대부분 10 이면 충분합니다.
Step 3 — 에러 대응 + 재시도
네트워크 또는 업스트림 일시 오류(502) 는 지수 백오프로 1회만 재시도. 잔액(402)·입력(400) 오류는 재시도 금지.
async function askRobust(userText, retryLeft = 1) {
history.push({ role: 'user', content: userText });
try {
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: 'meta-llama/Meta-Llama-3.1-8B-Instruct',
messages: history,
compact: { strategy: 'sliding_window', window_pairs: 10 },
}, {
headers: { 'CL_AUTH_KEY': process.env.API_KEY },
timeout: 60000,
});
history = result.data.compacted_messages;
return { text: result.data.message.content, cost: result.api.cost };
} catch (e) {
const status = e.response?.status;
// 재시도 금지
if (status === 400) throw new Error('입력이 올바르지 않습니다');
if (status === 402) throw new Error('포인트가 부족합니다 — 충전 필요');
// 502/503/네트워크: 1회 재시도 (지수 백오프)
if (retryLeft > 0 && (!status || status >= 500)) {
history.pop(); // 방금 push 한 user 제거 후 재시도
await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
return askRobust(userText, retryLeft - 1);
}
throw e;
}
}
전체 통합 예시 — axios
멀티챗의 가장 간결한 완성형 코드입니다. 이 30줄이면 "여러 턴 이어가며 대화하는" 기능은 완결됩니다.
import axios from 'axios';
const API_KEY = process.env.API_KEY;
const MODEL = 'meta-llama/Meta-Llama-3.1-8B-Instruct';
async function multiChat() {
let history = [];
const userTurns = [
'내 이름은 지훈이야.',
'내 이름 기억해?',
'그럼 나랑 역할극 하자. 넌 조선시대 학자고 난 네 제자야.',
'스승님, 오늘 날씨가 참 좋습니다.',
];
let totalCost = 0;
for (const user of userTurns) {
history.push({ role: 'user', content: user });
const { data: result } = await axios.post('https://apick.app/rest/llm/chat', {
model: MODEL,
messages: history,
compact: { strategy: 'sliding_window', window_pairs: 10 },
}, { headers: { 'CL_AUTH_KEY': API_KEY } });
history = result.data.compacted_messages;
totalCost += result.api.cost;
console.log('🙋', user);
console.log('🤖', result.data.message.content, '\n');
}
console.log('누적 비용:', totalCost, '포인트');
}
multiChat();
자주 묻는 질문
histroy 를 브라우저 새로고침해도 유지하려면?
localStorage/sessionStorage/indexedDB 에 직렬화해 저장. 서버는 도와주지 않습니다 — stateless.
대화가 100턴 넘어가면?
sliding_window 로는 부족할 수 있음. 오래된 부분을 한 번 요약 호출 후
system 메시지로 치환. (
llm_chat 참고)
여러 사용자를 동시에 서빙하려면?
각 사용자별로 독립된 history 배열을 유지. 서버는 구분하지 않으므로 사용자 단위는 앱이 책임.
같은 모델·같은 입력이면 항상 같은 답?
아니오. temperature 가 기본 > 0 이라 변주 있음. 재현성 높이려면 temperature: 0.1.