@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
249 lines (215 loc) • 7.3 kB
text/typescript
import { NextRequest, NextResponse } from "next/server";
import { RAGEngine } from "../../../../rag";
interface ChatRequestBody {
message: string;
sessionId?: string;
context?: Record<string, any>;
}
// 간단한 세션 저장소 (실제 프로덕션에서는 Redis나 데이터베이스 사용)
const sessions = new Map<
string,
{
id: string;
messages: Array<{
id: string;
content: string;
sender: "user" | "ai";
timestamp: Date;
}>;
createdAt: Date;
lastActivity: Date;
}
>();
// RAG 엔진 인스턴스 (실제로는 싱글톤 패턴 사용)
let ragEngine: RAGEngine | null = null;
async function initializeRAGEngine() {
if (!ragEngine) {
ragEngine = new RAGEngine({
embeddingModel: "text-embedding-ada-002",
chunkSize: 1000,
chunkOverlap: 200,
vectorStorePath: "./store",
supabaseConfig: {
url: process.env.SUPABASE_URL || "",
anonKey: process.env.SUPABASE_ANON_KEY || "",
bucket: "documents",
},
llmConfig: {
modelName: process.env.OPENAI_MODEL || "gpt-3.5-turbo",
temperature: 0.7,
maxTokens: 1000,
apiKey: process.env.OPENAI_API_KEY || "",
},
});
try {
await ragEngine.initialize();
} catch (error) {
console.error("RAG 엔진 초기화 실패:", error);
ragEngine = null;
throw error;
}
}
return ragEngine;
}
function generateMessageId(): string {
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
function getOrCreateSession(sessionId: string) {
if (!sessions.has(sessionId)) {
sessions.set(sessionId, {
id: sessionId,
messages: [],
createdAt: new Date(),
lastActivity: new Date(),
});
}
const session = sessions.get(sessionId)!;
session.lastActivity = new Date();
return session;
}
export async function POST(request: NextRequest) {
try {
const body: ChatRequestBody = await request.json();
if (!body.message?.trim()) {
return NextResponse.json(
{ error: "메시지를 입력해주세요" },
{ status: 400 }
);
}
const sessionId =
body.sessionId ||
`session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const session = getOrCreateSession(sessionId);
// 사용자 메시지 저장
const userMessage = {
id: generateMessageId(),
content: body.message,
sender: "user" as const,
timestamp: new Date(),
};
session.messages.push(userMessage);
let aiResponse: string;
let sources: Array<{
title: string;
content: string;
url?: string;
score: number;
}> = [];
try {
// RAG 엔진 초기화 및 응답 생성
const engine = await initializeRAGEngine();
if (engine) {
// RAG 검색 수행
const searchResults = await engine.search(body.message, 3);
// 컨텍스트 구성
const context = searchResults
.map((result) => result.chunk.content)
.join("\n\n");
// LLM에 컨텍스트와 함께 질문 전달
const prompt = `다음 컨텍스트를 바탕으로 사용자의 질문에 답변해주세요:
컨텍스트:
${context}
사용자 질문: ${body.message}
답변은 한국어로 친근하고 도움이 되는 톤으로 작성해주세요. 컨텍스트에 관련 정보가 없다면 일반적인 도움을 제공해주세요.`;
// OpenAI API를 직접 호출하여 응답 생성
const response = await fetch(
"https://api.openai.com/v1/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: prompt }],
max_tokens: 500,
temperature: 0.7,
}),
}
);
if (response.ok) {
const data = await response.json();
aiResponse =
data.choices[0]?.message?.content ||
getDefaultResponse(body.message);
} else {
aiResponse = getDefaultResponse(body.message);
}
// 검색 결과를 sources로 변환
sources = searchResults.map((result, index) => ({
title: `관련 문서 ${index + 1}`,
content: result.chunk.content.substring(0, 200) + "...",
score: result.score,
}));
} else {
// RAG 엔진이 없을 때 기본 응답
aiResponse = getDefaultResponse(body.message);
}
} catch (ragError) {
console.error("RAG 처리 오류:", ragError);
aiResponse = getDefaultResponse(body.message);
}
// AI 응답 저장
const aiMessage = {
id: generateMessageId(),
content: aiResponse,
sender: "ai" as const,
timestamp: new Date(),
};
session.messages.push(aiMessage);
// 응답 반환
return NextResponse.json({
id: aiMessage.id,
content: aiResponse,
timestamp: aiMessage.timestamp,
sessionId,
sources: sources.length > 0 ? sources : undefined,
metadata: {
messageCount: session.messages.length,
sessionCreated: session.createdAt,
},
});
} catch (error) {
console.error("챗봇 API 오류:", error);
return NextResponse.json(
{
error:
"죄송합니다. 일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.",
code: "INTERNAL_ERROR",
},
{ status: 500 }
);
}
}
function getDefaultResponse(message: string): string {
const lowerMessage = message.toLowerCase();
if (lowerMessage.includes("안녕") || lowerMessage.includes("hello")) {
return "안녕하세요! 무엇을 도와드릴까요?";
}
if (lowerMessage.includes("가격") || lowerMessage.includes("요금")) {
return "가격 관련 문의를 주셔서 감사합니다. 자세한 요금제 정보는 영업팀에 문의해주시면 상세히 안내드리겠습니다.";
}
if (lowerMessage.includes("기능") || lowerMessage.includes("서비스")) {
return "저희 서비스는 다양한 기능을 제공합니다. 구체적으로 어떤 부분이 궁금하신지 말씀해주시면 더 자세히 설명드릴게요.";
}
if (
lowerMessage.includes("문제") ||
lowerMessage.includes("오류") ||
lowerMessage.includes("error")
) {
return "문제가 발생하신 것 같네요. 구체적인 상황을 말씀해주시면 해결 방법을 안내드리겠습니다. 급한 경우 고객지원팀으로 연락해주세요.";
}
return "말씀해주신 내용에 대해 더 구체적인 정보가 필요합니다. 어떤 부분이 궁금하신지 자세히 알려주시면 더 정확한 답변을 드릴 수 있습니다.";
}
// OPTIONS 메서드 처리 (CORS)
export async function OPTIONS() {
return new NextResponse(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}