AI 챗봇 만들기
AI 챗봇 만들기 — 서버리스 함수와 LLM을 연결해 AI 챗봇을 만드는 가이드
서버리스 함수와 LLM을 연결해 AI 챗봇을 만드는 가이드
이 튜토리얼에서 배울 내용
RAG AI 챗봇 만들기
ConnectBase 의 AI 스트리밍 + Knowledge Base (RAG) 를 결합해 PDF / DOCX / text 문서를 컨텍스트로 답변하는 챗봇을 만듭니다. 외부 LLM API 키를 코드에서 직접 다룰 필요 없이 콘솔에서 AI 프로바이더(Gemini / OpenAI / Claude)를 등록하고 cb.ai.chatStream({ knowledgeBaseId }) 한 줄로 호출합니다.
완성된 기능
- AI 스트리밍 응답 (token-by-token 표시)
- Knowledge Base 검색 결과를 컨텍스트로 자동 주입 (RAG)
- PDF / DOCX / text 파일을 그대로 업로드 → 청킹 + 인덱싱
- 한국어 nori 형태소 분석기 자동 적용
아키텍처
프론트엔드 → cb.ai.chatStream({ knowledgeBaseId })
↓
AI Server (BM25 검색 → 컨텍스트 조립 → LLM 스트리밍)
↑
Knowledge Base (문서 → 청크)사전 준비
- AI 프로바이더 등록: 콘솔 > AI 탭에서 Gemini / OpenAI / Claude 중 하나의 API 키 등록 (BYOK)
- Knowledge Base 생성: 콘솔 > 지식베이스 > 새 KB 생성 → KB ID 복사 (
chunk_size: 500,chunk_overlap: 50권장)
1. 프로젝트 설정
npm create vite@latest rag-chatbot -- --template react-ts
cd rag-chatbot
npm install connectbase-client.env:
VITE_CONNECT_BASE_PUBLIC_KEY=cb_pk_여기에_퍼블릭키_입력
VITE_KB_ID=콘솔에서_복사한_KB_UUID2. SDK 설정
src/lib/connectbase.ts:
import { ConnectBase } from 'connectbase-client'
export const cb = new ConnectBase({
publicKey: import.meta.env.VITE_CONNECT_BASE_PUBLIC_KEY,
})
export const KB_ID = import.meta.env.VITE_KB_ID as string3. 문서 업로드 컴포넌트
PDF / DOCX / text 파일을 그대로 드롭하면 addDocumentFromFile 헬퍼가 base64 인코딩 + MIME 추출 + 50MB 검증을 자동 처리합니다.
src/components/KbUploader.tsx:
import { useState } from 'react'
import { cb, KB_ID } from '../lib/connectbase'
export function KbUploader() {
const [uploading, setUploading] = useState(false)
const [status, setStatus] = useState<string>('')
const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
setUploading(true)
setStatus(`업로드 중: ${file.name}`)
try {
const doc = await cb.knowledge.addDocumentFromFile(KB_ID, file, {
metadata: { source: 'user-upload' },
})
// 백그라운드 청킹 폴링
setStatus(`인덱싱 중... (${doc.status})`)
while (true) {
const { documents } = await cb.knowledge.listDocuments(KB_ID)
const target = documents.find((d) => d.id === doc.id)
if (target?.status === 'ready') { setStatus(`완료: ${file.name}`); break }
if (target?.status === 'failed') {
setStatus(`실패: ${target.error_message ?? '알 수 없음'}`)
break
}
await new Promise((r) => setTimeout(r, 2000))
}
} catch (err) {
setStatus(`에러: ${err instanceof Error ? err.message : String(err)}`)
} finally {
setUploading(false)
}
}
return (
<div>
<input type="file" accept=".pdf,.docx,.txt,.md,.csv,.json" onChange={handleFile} disabled={uploading} />
<span style={{ marginLeft: 8 }}>{status}</span>
</div>
)
}지원 파일: PDF (텍스트), DOCX, text/*, JSON. 미지원: 스캔 이미지 PDF / OCR / HWP / XLSX →
unsupported mime type for text extraction에러. 상한: 50MB.
4. 스트리밍 챗봇 훅
cb.ai.chatStream 에 knowledgeBaseId 만 전달하면 서버가 자동으로 검색 → 컨텍스트 주입 → LLM 호출까지 처리합니다. 토큰이 도착할 때마다 onToken 콜백이 호출되어 실시간 타이핑 효과를 구현할 수 있습니다.
src/hooks/useRagChatbot.ts:
import { useState, useCallback } from 'react'
import { cb, KB_ID } from '../lib/connectbase'
interface ChatMessage { role: 'user' | 'assistant'; content: string }
export function useRagChatbot() {
const [messages, setMessages] = useState<ChatMessage[]>([])
const [streaming, setStreaming] = useState(false)
const [sources, setSources] = useState<Array<{ document_name: string; content: string }>>([])
const sendMessage = useCallback(async (content: string) => {
const nextMessages: ChatMessage[] = [...messages, { role: 'user', content }, { role: 'assistant', content: '' }]
setMessages(nextMessages)
setSources([])
setStreaming(true)
try {
await cb.ai.chatStream(
{
messages: nextMessages.slice(0, -1), // assistant placeholder 제외
knowledgeBaseId: KB_ID, // ← RAG 활성화
topK: 5,
agentic: true, // AI 가 검색 쿼리 자동 생성 (선택)
},
{
onToken: (token, metadata) => {
if (metadata?.type === 'sources') {
setSources(metadata.sources)
return
}
// 일반 토큰: 마지막 assistant 메시지에 누적
setMessages((prev) => {
const copy = [...prev]
copy[copy.length - 1] = { ...copy[copy.length - 1], content: copy[copy.length - 1].content + token }
return copy
})
},
onDone: () => setStreaming(false),
onError: (err) => {
setStreaming(false)
setMessages((prev) => [...prev.slice(0, -1), { role: 'assistant', content: `에러: ${err}` }])
},
}
)
} catch (err) {
setStreaming(false)
}
}, [messages])
return { messages, sendMessage, streaming, sources }
}5. 챗봇 UI
src/components/RagChatbot.tsx:
import { useState } from 'react'
import { useRagChatbot } from '../hooks/useRagChatbot'
import { KbUploader } from './KbUploader'
export function RagChatbot() {
const [input, setInput] = useState('')
const { messages, sendMessage, streaming, sources } = useRagChatbot()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim() || streaming) return
sendMessage(input.trim())
setInput('')
}
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4 gap-4">
<KbUploader />
<div className="flex-1 overflow-y-auto space-y-3">
{messages.map((msg, i) => (
<div key={i} className={`p-3 rounded-lg ${msg.role === 'user' ? 'bg-blue-100 ml-12' : 'bg-gray-100 mr-12 whitespace-pre-wrap'}`}>
{msg.content || (streaming && i === messages.length - 1 ? '검색 중...' : '')}
</div>
))}
</div>
{sources.length > 0 && (
<div className="text-xs text-secondary border-t pt-2">
<strong>참조 문서:</strong>{' '}
{sources.map((s) => s.document_name).filter((v, i, a) => a.indexOf(v) === i).join(', ')}
</div>
)}
<form onSubmit={handleSubmit} className="flex gap-2 border-t pt-3">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="문서에 대해 질문하세요..."
disabled={streaming}
className="flex-1 px-3 py-2 border rounded"
/>
<button type="submit" disabled={streaming} className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50">전송</button>
</form>
</div>
)
}6. 사용자별 격리 (선택)
다중 사용자 RAG 시나리오 (예: 각자 자기 PDF 만 검색) 라면 cb.auth.signInMember(...) 로 AppMember JWT 를 활성화하세요. 서버가 자동으로:
addDocumentFromFile시metadata.user_id자동 태깅chatStream검색에서 본인 문서로만 제한listDocuments/deleteDocument도 본인 자료만 노출
추가 코드 변경 불필요 — 토큰이 활성화되면 서버가 알아서 격리합니다.
다음 단계
- 대화 기록 영속화 —
cb.database의conversations테이블에messagesJSON 저장 - 다중 KB 라우팅 — 카테고리별 KB 를 만들어 질문 키워드에 따라
knowledgeBaseId선택 - Agentic 옵션 —
agentic: false로 단일 쿼리 검색 (응답 빠름, 비용↓),agentic: true로 AI 가 다중 쿼리 자동 생성 (정확도↑) - 출처 표시 UI —
sources배열의chunk_index/score활용해 신뢰도 시각화 - 이미지 PDF — OCR 결과를 텍스트로 변환 후
source_type='text'로 직접 추가 (서버측 OCR 미지원)
이 튜토리얼이 도움이 됐나요?