Use Cases

Claude Codeでチャットボットを開発する方法

Claude Codeを使ってAIチャットボットを効率的に開発。会話履歴管理、ストリーミング応答、RAG構成まで実践コードで解説。

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: "エラーが発生しました。再度お試しください。" }
            : 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="メッセージを入力..."
          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[]) {
  // 関連ドキュメントを検索
  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 リファレンスを参照してください。

#Claude Code #チャットボット #AI #ストリーミング #RAG