How to Develop a Chatbot: Claude Code 활용 가이드
develop a chatbot: Claude Code 활용. 실용적인 코드 예시와 단계별 가이드를 포함합니다.
Claude Code로 챗봇 개발하기
AI 챗봇 개발에는 UI 구현, API 연동, 대화 관리, 스트리밍 처리 등 많은 요소가 필요합니다. Claude Code를 사용하면 이것들을 통합한 챗봇을 짧은 시간에 구축할 수 있습니다.
기본 채팅 UI 구현
> React로 챗봇 컴포넌트를 만들어 줘.
> 스트리밍 응답 대응, 대화 기록 표시, 입력 폼을 포함해서.
import { useState, useRef, useEffect } from "react";
interface Message {
id: string;
role: "user" | "assistant";
content: string;
}
export function ChatBot() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const sendMessage = async () => {
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: crypto.randomUUID(),
role: "user",
content: input,
};
setMessages((prev) => [...prev, userMessage]);
setInput("");
setIsLoading(true);
const assistantId = crypto.randomUUID();
setMessages((prev) => [
...prev,
{ id: assistantId, role: "assistant", content: "" },
]);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [...messages, userMessage].map(({ role, content }) => ({
role,
content,
})),
}),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
setMessages((prev) =>
prev.map((m) =>
m.id === assistantId
? { ...m, content: m.content + chunk }
: m
)
);
}
} catch (error) {
setMessages((prev) =>
prev.map((m) =>
m.id === assistantId
? { ...m, content: "An error occurred. Please try again." }
: m
)
);
}
setIsLoading(false);
};
return (
<div className="flex flex-col h-[600px] border rounded-lg">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg) => (
<div
key={msg.id}
className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
>
<div
className={`max-w-[70%] p-3 rounded-lg ${
msg.role === "user"
? "bg-blue-600 text-white"
: "bg-gray-100 text-gray-900"
}`}
>
{msg.content}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="border-t p-4 flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && sendMessage()}
placeholder="Type a message..."
className="flex-1 p-2 border rounded-lg"
disabled={isLoading}
/>
<button
onClick={sendMessage}
disabled={isLoading}
className="px-4 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
>
전송
</button>
</div>
</div>
);
}
스트리밍 대응 API 라우트
백엔드에서 Anthropic API를 호출하고, 스트리밍으로 반환하는 API 라우트입니다.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = await client.messages.stream({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system: "당신은 친절하고 정중한 어시스턴트입니다. 한국어로 답변해 주세요.",
messages,
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const event of stream) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
controller.enqueue(encoder.encode(event.delta.text));
}
}
controller.close();
},
});
return new Response(readable, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
대화 기록 영속화
대화를 데이터베이스에 저장하여, 나중에 이어서 다시 시작할 수 있게 합니다.
import { db } from "@/lib/database";
export async function saveConversation(
userId: string,
messages: Message[]
) {
return db.conversation.upsert({
where: { id: `${userId}-current` },
update: {
messages: JSON.stringify(messages),
updatedAt: new Date(),
},
create: {
id: `${userId}-current`,
userId,
messages: JSON.stringify(messages),
},
});
}
export async function loadConversation(userId: string): Promise<Message[]> {
const conv = await db.conversation.findUnique({
where: { id: `${userId}-current` },
});
return conv ? JSON.parse(conv.messages as string) : [];
}
RAG(검색 증강 생성) 통합
사내 문서를 바탕으로 답변하는 챗봇을 만들 때는 RAG 구성이 유효합니다.
import { searchDocuments } from "@/lib/vector-search";
async function generateRAGResponse(query: string, conversationHistory: Message[]) {
// Search related documents
const relevantDocs = await searchDocuments(query, { limit: 5 });
const context = relevantDocs
.map((doc) => `---\n${doc.title}\n${doc.content}\n---`)
.join("\n");
const systemPrompt = `아래 문서를 참고하여 질문에 답변해 주세요.
문서에 정보가 없는 경우에는 "그 정보는 찾을 수 없었습니다"라고 답해 주세요.
${context}`;
return client.messages.stream({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system: systemPrompt,
messages: conversationHistory,
});
}
MCP 서버와의 연동으로 기능을 확장하는 방법은 MCP 서버 가이드를, 효과적인 프롬프트 설계는 프롬프트를 개선하는 5가지 팁을 확인하세요.
정리
Claude Code를 활용하면 채팅 UI, 스트리밍 API, 대화 관리, RAG 구성까지 포함한 챗봇을 효율적으로 개발할 수 있습니다. 단계적으로 기능을 추가해 나가는 접근이 효과적입니다.
자세한 내용은 Claude Code 공식 문서와 Anthropic API 레퍼런스를 참고하세요.
2026년 프로덕션 업그레이드
챗봇은 사용자의 문장을 받고, 필요한 대화 맥락을 유지한 뒤, 다음에 도움이 되는 답변을 돌려주는 애플리케이션입니다. 쉽게 말하면 웹사이트 안의 대화형 접수 창구입니다. 고객 지원, 영업 상담, 내부 문서 검색, 교육 안내, 장애 접수처럼 반복되는 흐름을 줄이는 데 쓸 수 있습니다.
Claude Code로 만들 때 중요한 것은 처음부터 만능 비서를 목표로 하지 않는 것입니다. 자주 묻는 질문 10개만 안정적으로 처리하는 봇이, 모든 질문에 답하려는 봇보다 빨리 검증되고 운영 비용도 낮습니다. 첫 버전에서는 무엇을 답할지, 무엇은 모른다고 말할지, 언제 사람에게 넘길지를 먼저 정해야 합니다.
아키텍처 표
| 계층 | 역할 | 운영 시 확인할 점 |
|---|---|---|
| React UI | 입력, 대화 목록, 로딩, 재시도 표시 | 기본 상태 관리는 React useState 문서를 참고합니다 |
| API Route | API key를 서버에 숨기고 요청을 검증 | 길이 제한, 인증, timeout을 넣습니다 |
| Streaming | 답변을 조금씩 보내 대기감을 줄임 | 원리는 MDN Streams API에서 확인합니다 |
| 대화 저장소 | 이어서 대화하기, 감사, 개선에 사용 | 개인정보는 최소 저장하고 삭제 절차를 둡니다 |
| RAG | 제품 문서나 정책에서 근거 검색 | 근거가 약하면 추측하지 않습니다 |
| Webhook | CRM, 티켓, Slack으로 이벤트 전송 | Claude Code Webhook Implementation와 연결합니다 |
| Analytics | 해결률, 이탈, CTA 클릭 측정 | Claude Code Analytics Implementation로 개선합니다 |
API 구조는 Claude Code API Development의 형태처럼 단순하게 두는 편이 좋습니다. 프런트엔드는 role과 content만 보내고, 시스템 지시는 서버에서 관리하며, 응답은 stream으로 내려보냅니다. 이렇게 해두면 나중에 모바일 UI나 다른 채널을 붙여도 수정 범위가 작습니다.
바로 실행할 수 있는 streaming demo
아래 코드를 chatbot-stream-demo.mjs로 저장한 뒤 node chatbot-stream-demo.mjs를 실행하세요. 외부 API를 호출하지 않고, 스트리밍 출력과 대화 기록 흐름만 확인합니다.
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const faq = new Map([
["password", "Open the account page, choose Reset password, and follow the email link."],
["pricing", "The pricing page explains plans. For a custom quote, collect team size and required features."],
["refund", "Refund requests should be routed to support with the order id and purchase email."],
]);
const history = [];
function chooseAnswer(question) {
const normalized = question.toLowerCase();
for (const [keyword, answer] of faq) {
if (normalized.includes(keyword)) return answer;
}
return "I could not find a safe answer in the FAQ. I will hand this to a human operator.";
}
async function* streamText(text) {
for (const token of text.split(/(\s+)/)) {
await new Promise((resolve) => setTimeout(resolve, 15));
yield encoder.encode(token);
}
}
async function ask(question) {
history.push({ role: "user", content: question });
const answer = chooseAnswer(question);
process.stdout.write(`\nUser: ${question}\nAssistant: `);
let fullAnswer = "";
for await (const chunk of streamText(answer)) {
const token = decoder.decode(chunk);
fullAnswer += token;
process.stdout.write(token);
}
history.push({ role: "assistant", content: fullAnswer });
}
await ask("How do I reset my password?");
await ask("Can I see pricing before talking to sales?");
console.log(`\n\nSaved ${history.length} messages.`);
실서비스에서는 chooseAnswer를 Claude API 호출로 바꾸면 됩니다. 순서는 유지하세요. 사용자 메시지를 저장하고, 답변을 stream으로 보여주고, 마지막에 완성된 답변을 저장합니다. 이 순서가 깨지면 화면에는 답이 보이는데 DB에는 빈 답변이 남는 failure mode가 생깁니다.
실제 use case
첫 번째는 SaaS 영업 문의 선별입니다. 가격, 보안, 권한, 연동 질문을 챗봇이 먼저 처리하고, 구매 의도가 높은 사용자만 상담 폼으로 보냅니다. 반복 질문을 줄이고 상담의 품질을 높일 수 있습니다.
두 번째는 사내 헬프데스크입니다. 휴가, 비용 정산, VPN, 장비 교체, 온보딩 질문은 반복성이 높습니다. RAG로 정책 문서를 찾아 근거를 보여주고, 승인이나 확인이 필요하면 webhook으로 티켓을 만들면 됩니다.
세 번째는 교육 사이트나 기술 블로그입니다. 독자가 API, Webhook, Analytics 중 무엇을 먼저 배울지 묻는다면 관련 글을 추천하고, 실습이 필요할 때 training으로 안내합니다. 이 workflow는 단순 배너보다 자연스럽게 수익화로 이어집니다.
네 번째는 장애 접수입니다. 에러 메시지, 발생 시각, 브라우저, 계정, 재현 절차를 챗봇이 차례대로 모읍니다. 목표는 즉시 해결이 아니라 지원팀이 바로 조사할 수 있는 정보를 확보하는 것입니다.
실패 사례와 주의점
대화 기록을 제한 없이 모델에 보내지 마세요. 비용이 늘고 응답이 느려지며, 오래된 지시가 현재 질문을 방해할 수 있습니다. 오래된 대화는 요약하고 현재 문제에 필요한 사실만 남기는 편이 안전합니다.
RAG 결과가 약할 때 확신 있게 답하지 마세요. 근거 문서가 없으면 찾지 못했다고 말하고 사람에게 넘기는 경로를 제공해야 합니다. 이 원칙이 잘못된 안내와 지원 리스크를 줄입니다.
Streaming을 단순한 시각 효과로 보지 마세요. 네트워크 중단, 중복 전송, 빈 말풍선, 끝나지 않는 로딩은 모두 신뢰를 떨어뜨립니다. 버튼 비활성화, 요청 취소, timeout, 재시도 문구를 첫 버전에 넣어야 합니다.
측정 없이 공개하지 마세요. 해결되지 않은 질문, 상담 전환, CTA 클릭, 구매로 이어진 대화를 기록해야 합니다. 메시지 수가 많다는 사실만으로는 챗봇이 도움이 되는지 알 수 없습니다.
수익화 CTA
상업용 챗봇은 마지막 행동이 분명해야 합니다. 구현 리뷰, 상담, 템플릿 구매, 교육 신청 중 하나를 우선순위로 정하세요. ClaudeCodeLab에서는 관심이 높은 독자를 training으로 안내해 실제 구현 상담으로 이어지게 합니다.
추천 흐름은 이 글에서 전체 구조를 이해하고, Claude Code API Development로 API를 만들고, webhooks로 외부 시스템을 연결한 뒤, analytics로 전환을 측정하는 것입니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
제작사가 Claude Code에 고객 사이트를 맡기기 전 권한 체크리스트
고객 사이트를 안전하게 AI로 수정하기 위한 에이전시용 권한과 검증 절차입니다.
SaaS 고객지원 버그 신고를 Claude Code로 재현 절차로 바꾸는 방법
모호한 문의를 재현 단계, 증거, 개발자 전달 메모로 정리하는 지원팀 워크플로입니다.
Obsidian 묵은 메모를 Claude Code 지시서로 바꾸는 10분 루틴
Obsidian에 쌓인 메모가 매번 쓸모없어지는 분께. 사실·결정·미확인으로 분류해 Claude Code가 그대로 움직일 지시서로 바꾸는 아침 10분의 형(型)을 소개합니다.