주성진·study·2026.08.27·8 min read·조회수409
LLM 관리자 화면을 만들다가 공부한 토큰, 컨텍스트, 임베딩, RAG
기획서에 있는 "토큰 사용량", "RAG 문서", "temperature" 같은 항목을 이해하지 못한 채로 화면을 만든 적이 있습니다. 그게 아쉬워서 생성형 AI 기초 과정을 들으며 정리한 개념을, 화면 설계 관점에서 풀어 봤습니다.
프론트엔드 개발자의 AI 기초· 2 / 3펼치기접기
- 1. Anthropic AI Fluency 수강 후기, 4D 프레임워크를 개발 업무에 대입해 보기
- 2. LLM 관리자 화면을 만들다가 공부한 토큰, 컨텍스트, 임베딩, RAG
- 3. 매일 쓰는 Claude Code가 궁금해서 들은 허깅페이스 AI 에이전트 과정
LLMOps 서비스와 AI AGENT ONE의 관리자 화면을 만들면서 모델 관리, RAG 문서 관리, 프롬프트 관리, 토큰 사용량 대시보드를 구현했습니다. 지금 와서 고백하자면, 처음엔 기획서 항목을 거의 그대로 화면에 옮기기만 했습니다. "임베딩 상태"가 정확히 뭘 뜻하는지 모른 채 상태 배지를 달았던 거죠.
개념을 이해하고 나서야 어떤 값을 어디에, 어떻게 보여 줘야 하는지가 보이기 시작했습니다. 이 글에서는 데이터브릭스 생성형 AI 기초와 오라클 OCI AI Foundations 과정을 들으며 정리한 내용 중, 프론트엔드 개발자가 알아 두면 좋을 만큼만 추려서 화면 설계와 연결해 보겠습니다.
1. 토큰: 사용량은 입력과 출력을 나눠서 봐야 합니다
LLM은 글자가 아니라 토큰 단위로 읽고 씁니다. 토큰은 단어보다 작은 조각입니다. 영어는 대략 단어 하나가 1~2토큰 정도이고, 한국어는 같은 의미를 담는 데 토큰이 더 드는 편입니다.
토큰이 중요한 이유는 비용, 속도, 한도가 전부 토큰으로 정해지기 때문입니다. 비용은 입력 토큰과 출력 토큰으로 계산되고, 응답 속도는 출력 토큰 수에 비례하고, 한 번에 다룰 수 있는 양(컨텍스트 윈도우)도 토큰으로 정해집니다.
그래서 사용량 대시보드는 요청 수가 아니라 입력과 출력 토큰을 나눠서 보여 줘야 의미가 있습니다. 저도 처음엔 둘을 합친 숫자 하나만 보여 줬다가, 비용 단가가 다르다는 걸 알고 나서 나눴습니다.
type UsageRow = {
userId: string;
service: "chat" | "translate" | "ocr" | "minutes";
inputTokens: number;
outputTokens: number;
requestedAt: string;
};
// 서비스별 합계. 입력/출력 단가가 달라서 합치지 않고 따로 모은다.
function summarize(rows: UsageRow[]) {
const by = new Map<string, { input: number; output: number; count: number }>();
for (const r of rows) {
const s = by.get(r.service) ?? { input: 0, output: 0, count: 0 };
s.input += r.inputTokens;
s.output += r.outputTokens;
s.count += 1;
by.set(r.service, s);
}
return by;
}2. 컨텍스트 윈도우: 답변이 들어갈 자리도 필요합니다
모델이 한 번에 볼 수 있는 토큰의 한도입니다. 여기에는 시스템 프롬프트, 대화 기록, 첨부 문서, 질문뿐 아니라 답변까지 다 들어가야 합니다.
┌──────────────────────── 컨텍스트 윈도우 ────────────────────────┐
│ 시스템 프롬프트 │ 검색된 문서(RAG) │ 이전 대화 │ 질문 │ ← 답변 공간 │
└────────────────────────────────────────────────────────────────┘화면을 만들 때 이게 영향을 주는 곳이 두 군데 있습니다. 하나는 긴 대화입니다. 대화가 길어지면 오래된 메시지를 요약하거나 잘라야 하는데, 사용자 입장에서는 "아까 말한 걸 왜 잊어버리지?"가 됩니다. 대화가 길어졌다는 신호를 UI에 살짝 주는 게 좋습니다. 다른 하나는 파일 업로드입니다. 용량 제한만이 아니라 토큰 기준 제한도 필요합니다.
3. 생성 파라미터: 운영자는 대부분 temperature만 만집니다
관리자 화면의 "모델 설정"에 자주 나오는 값들이 있습니다. temperature는 출력의 무작위성으로, 낮으면 일관되고 높으면 다양해집니다. max tokens는 답변의 최대 길이이고, top-p는 확률 상위 몇 %의 후보 중에서 고를지를 정합니다.
처음엔 이 셋을 똑같은 크기로 나란히 배치했습니다. 그런데 운영자분들이 실제로 만지는 건 거의 temperature 하나더라고요. 그래서 temperature는 "정확하게, 창의적으로"라는 라벨이 붙은 슬라이더로 크게 두고, 나머지는 "고급 설정"으로 접었습니다.
4. 임베딩: 의미가 비슷하면 가깝습니다
임베딩은 문장을 의미를 담은 숫자 벡터로 바꾼 겁니다. 의미가 비슷한 문장은 벡터 공간에서도 가깝게 놓입니다. 가까운 정도는 주로 코사인 유사도로 잽니다.
function cosine(a: number[], b: number[]) {
let dot = 0, na = 0, nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return dot / (Math.sqrt(na) * Math.sqrt(nb));
}5. RAG: 흐름을 알면 필요한 화면이 보입니다
LLM은 학습하지 않은 사내 문서를 모릅니다. RAG(검색 증강 생성)는 질문이 들어오면 관련 문서를 먼저 찾아서 프롬프트에 붙여 주는 방식입니다.
[문서 등록] PDF/HWP → 텍스트 추출 → 조각내기(chunk) → 임베딩 → 벡터 DB 저장
[질문 처리] 질문 → 임베딩 → 비슷한 조각 k개 검색 → 프롬프트에 붙임 → LLM 답변(+출처)이 흐름을 알고 나니 RAG 관리 화면에 뭐가 필요한지가 자연스럽게 나왔습니다.
먼저 문서 처리 상태입니다. 업로드에서 텍스트 추출, 임베딩, 사용 가능까지 단계가 길어서, 지금 어디쯤인지 보여 주지 않으면 운영자가 문서가 안 올라간 줄 압니다. 실패하면 이유도 보여 줘야 하고요.
다음은 조각 미리보기입니다. 문서가 어떻게 잘렸는지 보여 줘야 운영자가 "답변이 왜 엉뚱하지?"를 추적할 수 있습니다.
그리고 사용자 화면에는 출처를 붙입니다. 어떤 문서 조각을 근거로 답했는지 링크를 달아 두면, 사용자가 직접 답을 확인할 수 있습니다.
type RagDocument = {
id: string;
name: string;
status: "uploaded" | "extracting" | "embedding" | "ready" | "failed";
chunkCount?: number;
error?: string;
};
const STATUS_LABEL: Record<RagDocument["status"], string> = {
uploaded: "대기",
extracting: "텍스트 추출 중",
embedding: "임베딩 중",
ready: "사용 가능",
failed: "실패",
};6. 프롬프트 관리에는 버전이 필요합니다
운영자가 시스템 프롬프트를 고치는 화면에서 제일 중요했던 건 버전 관리였습니다. 프롬프트 한 줄만 바꿔도 모든 답변이 달라집니다. 그래서 이전 버전으로 되돌릴 수 있어야 하고, 어떤 버전이 언제부터 적용됐는지 기록이 남아야 합니다. 처음 기획에는 없던 기능인데, 개념을 이해하고 나서 먼저 제안했습니다.
마치며
개념을 알기 전과 후에 만든 화면은 확실히 다릅니다. 예전엔 "기획서에 있으니까" 만들었다면, 지금은 "이 값은 왜 필요한지"부터 이해하고 만듭니다. 그래야 기획서에 없는 걸 먼저 제안할 수도 있고요.
AI 제품 화면을 만들고 계신 프론트엔드 개발자라면, 토큰과 RAG 흐름 정도는 한번 정리해 두시길 권합니다. 화면이 달라집니다. 다음 글에서는 허깅페이스의 AI 에이전트 과정에서 배운 내용을 정리해 보겠습니다.
Comments (0)