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つのTips를 확인하세요.
정리
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 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.