๐ค Next.js AI ์ฑ๋ด ๋น๋ ์คํฌ
์ด ์คํฌ์ BSD ๋ฐ์ด๋ธ์ฝ๋ฉ ์๊ฐ์๋ค์ด Next.js์ Node.js๋ฅผ ์ฌ์ฉํ์ฌ ์ง๋ฅํ AI ์ฑ๋ด์ ์น์ฌ์ดํธ์ ์ง์ ๊ตฌ์ถํ๊ณ ๋ฐฐํฌํ ์ ์๋๋ก ๋์ต๋๋ค.
๐ ์ด ์คํฌ์ด ํ๋ ์ผ
- AI ์์ง ์ค์ : OpenAI (GPT-4), Anthropic (Claude) ๋ฑ ์ต์ ์ LLM ์ ํ
- ๋ฐฑ์๋ ๊ตฌ์ถ: Next.js API Routes (Route Handlers)๋ฅผ ์ฌ์ฉํ ์ฑ๋ด ์๋ฒ ๊ตฌํ
- ํ๋ก ํธ์๋ ๊ตฌํ: React(Next.js) ๊ธฐ๋ฐ์ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ์ฑํ UI ์ ์
- ์ง์๋ฒ ์ด์ค(RAG): ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ํ์ฉํ ์ ์ฉ ๋ฐ์ดํฐ ํ์ต ๋ฐ ๋ต๋ณ ์ต์ ํ
- ์ธ๋ถ ์ฐ๋: Slack, Discord, ํ ๋ ๊ทธ๋จ ๋ฑ ๋ฉํฐ ์ฑ๋ ์ฐ๋ ๊ฐ์ด๋
๐ฏ ์ธ์ ์ด ์คํฌ์ ์ฌ์ฉํ๋์?
- "Next.js ์ฌ์ดํธ์ ์ฐ๋ฆฌ ํ์ฌ๋ง์ AI ์๋ด์์ ๋ฃ๊ณ ์ถ์ด์"
- "PDF ๋ฌธ์๋ฅผ ํ์ตํด์ ๋ต๋ณํด์ฃผ๋ ์ฑ๋ด์ ์ง์ ๊ฐ๋ฐํ๊ณ ์ถ์ด์"
- "์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํด์ ๊ฐ์ธํ๋ ์ถ์ฒ์ ํด์ฃผ๋ AI ๋ด์ด ํ์ํด์"
๐ ๏ธ ๊ธฐ์ ์คํ (Code-First)
1. Framework & Language
- Next.js 14+ (App Router): ํ๋ก ํธ์๋ ๋ฐ API ์๋ฒ ํตํฉ
- TypeScript: ์์ ์ ์ธ ์ฝ๋ ๊ฐ๋ฐ
2. AI SDKs
- AI SDK (Vercel): ํ ์คํธ ์์ฑ ๋ฐ ์คํธ๋ฆฌ๋ฐ ์ต์ ํ
- LangChain / LlamaIndex: ๋ณต์กํ ์ฒด์ธ ๋ฐ ๋ฐ์ดํฐ ์ฐ๋
3. Database & Vector Store
- Supabase (pgvector): ์ฌ์ฉ์ ๊ด๋ฆฌ ๋ฐ ๋ฒกํฐ ๊ฒ์
- Pinecone: ๋๊ท๋ชจ ๋ฐ์ดํฐ์ฉ ๋ฒกํฐ ์์ง
๐ป Next.js ์ฑ๋ด ๊ตฌํ ์์
1. Client UI (components/ChatBox.tsx)
"use client";
import { useChat } from "ai/react";
export default function ChatBox() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div className="flex flex-col w-full max-w-md mx-auto stretch">
{messages.map((m) => (
<div
key={m.id}
className={`whitespace-pre-wrap p-4 ${
m.role === "user"
? "bg-slate-100"
: "bg-blue-50 text-blue-900 border-l-4 border-blue-500"
}`}
>
<strong>{m.role === "user" ? "๐ค ๋: " : "๐ค AI: "}</strong>
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl focus:outline-none focus:ring-2 focus:ring-blue-500"
value={input}
placeholder="๋ฌด์์ด๋ ๋ฌผ์ด๋ณด์ธ์..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
2. Server API (app/api/chat/route.ts)
import { OpenAIStream, StreamingTextResponse } from "ai";
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await openai.chat.completions.create({
model: "gpt-4-turbo",
stream: true,
messages: [
{
role: "system",
content: "๋น์ ์ BSD ๋ฐ์ด๋ธ์ฝ๋ฉ ์ผํฐ์ ์น์ ํ ์๋ด์์
๋๋ค.",
},
...messages,
],
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
}
๐ ์ฑ๋ด ์ ํ๋ณ ํ์ฉ ์ฌ๋ก
1. ์ง์ ๊ธฐ๋ฐ ์๋ด ๋ด (RAG)
- ํ์ฌ์ ๋งค๋ด์ผ, ์ ์ฑ PDF๋ฅผ ํ์ตํ์ฌ ๋ต๋ณ
- API ์ฐ๋์ผ๋ก ์ค์๊ฐ ์ฌ๊ณ ๋ ๊ฐ๊ฒฉ ์ ๋ณด ๋ฐ์
2. ์์ ๋ฐ ๋ฆฌ๋ ์ ๋ค๋ ์ด์ ๋ด
- ๋ํ๋ฅผ ํตํด ์ฌ์ฉ์์ ๋์ฆ ํ์ ํ DB์ ์ ์ฅ
- ๊ด๋ จ ์ํ์ ๊ฒฐ์ ๋งํฌ(Check-out)๋ก ์ ๋
๐ฌ ์์ ๋ํ
์ฌ์ฉ์: "์ฐ๋ฆฌ ์ผํ๋ชฐ์ ์ํ ์ถ์ฒํด์ฃผ๋ AI ์ฑ๋ด์ Next.js๋ก ๋ง๋ค๊ณ ์ถ์ด"
Claude: "Next.js์ OpenAI API๋ฅผ ์ฌ์ฉํ์ฌ ๋ง์ถคํ ์ํ ์ถ์ฒ ์ฑ๋ด์ ๊ตฌ์ถํด๋๋ฆฌ๊ฒ ์ต๋๋ค. ๋ํํ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ฌ์ฉ์์ ์ทจํฅ์ ๋ถ์ํ๊ณ , ๋ด๋ถ DB์ ์ฐ๋ํ์ฌ ์ต์ ์ ์ํ์ ์ ์ํ๋ ๋ก์ง์ ํฌํจํ ์ฝ๋๋ฅผ ์์ฑํด ๋๋ฆด๊ฒ์..."
๐ฏ ํต์ฌ ์ ๋ฆฌ
์ด ์คํฌ์ ์ฌ์ฉํ๋ฉด: โ Vercel AI SDK๋ฅผ ํ์ฉํ ๊ณ ์ฑ๋ฅ ์ฑ๋ด ๊ตฌํ โ Stream ์๋ต์ผ๋ก ์ค์ ์ฌ๋๊ณผ ๋ํํ๋ ๋ฏํ UX ์ ๊ณต โ Node.js ๋ฐฑ์๋๋ฅผ ํตํด ๋ณด์ ์ด์(API Key ๋ ธ์ถ) ํด๊ฒฐ โ Custom Data ์ฐ๋์ผ๋ก ์ฐ๋ฆฌ ํ์ฌ๋ง์ ํน๋ณํ AI ๊ตฌ์ถ
BSD ํ์์ด๋ผ๋ฉด: ์ธ๋ถ ์ ๋ฃ ์ฑ๋ด ์๋ฃจ์ ์ ์์กดํ์ง ์๊ณ , ์ง์ AI ํ๋ก๋ํธ๋ฅผ ๊ฐ๋ฐํ์ฌ ๋น์ฆ๋์ค ๊ฐ์น๋ฅผ ๋์ผ ์ ์์ต๋๋ค! ๐ค
Scan to join WeChat group