강의(Course)별로 자료를 분석하고 정보를 제공하는 AI 챗봇 API 서버입니다. RAG (Retrieval-Augmented Generation) 패턴을 사용하여 업로드된 PDF/텍스트 파일에서 관련 정보를 검색하고 AI 기반 답변을 생성합니다.
- 강의별 문서 관리: 각 강의(courseId)별로 독립적인 문서 저장 및 관리
- PDF 및 텍스트 파일 업로드: 한 강의에 여러 파일 업로드 가능
- 자동 문서 파싱 및 청킹: PDF/텍스트 자동 분석 및 chunk 단위 저장
- 벡터 임베딩: Cohere API를 통한 고품질 임베딩 생성
- 강의 범위 내 검색: courseId 기반 필터링으로 해당 강의 자료에서만 검색
- 범위 제한 답변: LLM이 강의 범위를 벗어나는 질문을 판단하여 거부
- OpenAI GPT 활용: 자연어 기반 답변 생성
- Docker 컨테이너 지원: 배포 간편화
- Framework: NestJS (TypeScript)
- Database: MongoDB Atlas (Vector Search)
- File Storage: Google Cloud Storage (개발 환경에서는 로컬 저장)
- Embedding: Cohere API
- LLM: OpenAI GPT API
- Containerization: Docker
Client → Upload API → GCS Storage (or Local) → PDF Parser → Text Chunker
→ Cohere Embedding → MongoDB Vector Store
Client → Chat API → Query Embedding (Cohere) → Vector Search (MongoDB)
→ Top-K Retrieval → Context Assembly → OpenAI LLM → Response
flo-ai/
├── src/
│ ├── modules/
│ │ ├── upload/ # 파일 업로드 (GCS/로컬)
│ │ ├── document/ # 문서 파싱 & chunking
│ │ ├── embedding/ # Cohere 임베딩
│ │ ├── vector-store/ # MongoDB 벡터 저장/조회
│ │ └── chat/ # RAG 검색 & OpenAI 응답
│ ├── config/ # 환경 설정
│ ├── app.module.ts
│ └── main.ts
├── uploads/ # 로컬 개발 환경 업로드 폴더
├── Dockerfile
├── docker-compose.yml
└── package.json
.env.example 파일을 .env로 복사하고 필요한 값을 입력하세요:
cp .env.example .env필수 환경 변수:
MONGODB_URI: MongoDB Atlas 연결 URICOHERE_API_KEY: Cohere API 키OPENAI_API_KEY: OpenAI API 키
개발 환경 설정:
ENABLE_REMOTE_STORAGE=false: 로컬 파일시스템 사용LOCAL_UPLOAD_PATH=./uploads: 로컬 업로드 경로
프로덕션 환경 설정 (GCS 사용):
ENABLE_REMOTE_STORAGE=true: Google Cloud Storage 사용GCS_PROJECT_ID: GCP 프로젝트 IDGCS_KEY_FILENAME: 서비스 계정 키 파일 경로GCS_BUCKET_NAME: GCS 버킷 이름
MongoDB Atlas에서 coursedocumentchunks 컬렉션에 vector_index라는 이름으로 Vector Search Index를 생성해야 합니다:
{
"mappings": {
"dynamic": true,
"fields": {
"courseId": {
"type": "token"
},
"embedding": {
"type": "knnVector",
"dimensions": 1024,
"similarity": "cosine"
}
}
}
}중요: courseId 필드를 token 타입으로 추가하여 강의별 필터링이 가능하도록 설정해야 합니다.
# 의존성 설치
npm install
# 개발 모드 실행
npm run start:dev
# 프로덕션 빌드
npm run build
npm run start:prod# Docker Compose로 실행
docker-compose up -d
# 또는 Docker 단독 실행
docker build -t flo-ai .
docker run -p 3000:3000 --env-file .env flo-ai서버가 실행되면 http://localhost:3000/api에서 API에 접근할 수 있습니다.
Swagger 문서: http://localhost:3000/api/docs
모든 API는 강의(courseId)를 기준으로 동작합니다.
POST /api/courses/:courseId/documents/upload
Content-Type: multipart/form-data
file: [PDF or TXT file]예시:
POST /api/courses/PYTHON-101/documents/upload응답:
{
"courseId": "PYTHON-101",
"documentId": "507f1f77bcf86cd799439011",
"fileName": "lecture.pdf",
"fileUrl": "file:///path/to/uploads/documents/abc123.pdf",
"status": "processing",
"message": "File uploaded successfully. Processing in background."
}GET /api/courses/:courseId/documents예시:
GET /api/courses/PYTHON-101/documentsGET /api/courses/:courseId/documents/:idDELETE /api/courses/:courseId/documents/:idGET /api/courses/:courseId/stats응답:
{
"totalDocuments": 5,
"totalChunks": 150,
"completedDocuments": 4,
"processingDocuments": 1,
"failedDocuments": 0
}POST /api/courses/:courseId/chat
Content-Type: application/json
{
"query": "파이썬에서 리스트란 무엇인가요?",
"topK": 5
}예시:
POST /api/courses/PYTHON-101/chat응답:
{
"answer": "파이썬에서 리스트는 여러 값을 순서대로 저장할 수 있는 자료구조입니다...",
"sources": [
{
"content": "리스트의 정의는...",
"fileName": "lecture.pdf",
"score": 0.95
}
]
}범위 밖 질문 시:
{
"answer": "죄송합니다. 이 질문은 현재 강의 자료의 범위를 벗어납니다.",
"sources": [...]
}GET /api/health# 단위 테스트
npm run test
# E2E 테스트
npm run test:e2e
# 테스트 커버리지
npm run test:cov# ESLint
npm run lint
# Prettier
npm run format# 첫 번째 파일 업로드
curl -X POST http://localhost:3000/api/courses/PYTHON-101/documents/upload \
-F "file=@python-basics.pdf"
# 두 번째 파일 업로드 (같은 강의에 여러 파일 가능)
curl -X POST http://localhost:3000/api/courses/PYTHON-101/documents/upload \
-F "file=@python-advanced.pdf"curl -X GET http://localhost:3000/api/courses/PYTHON-101/documentscurl -X GET http://localhost:3000/api/courses/PYTHON-101/statscurl -X POST http://localhost:3000/api/courses/PYTHON-101/chat \
-H "Content-Type: application/json" \
-d '{
"query": "파이썬에서 리스트와 튜플의 차이점은 무엇인가요?",
"topK": 5
}'응답: 파이썬 강의 자료 기반으로 답변 제공
curl -X POST http://localhost:3000/api/courses/PYTHON-101/chat \
-H "Content-Type: application/json" \
-d '{
"query": "자바스크립트의 클로저란 무엇인가요?",
"topK": 5
}'응답: "죄송합니다. 이 질문은 현재 강의 자료의 범위를 벗어납니다."
# 자바스크립트 강의에 파일 업로드
curl -X POST http://localhost:3000/api/courses/JAVASCRIPT-101/documents/upload \
-F "file=@javascript-basics.pdf"
# 자바스크립트 강의에 질문
curl -X POST http://localhost:3000/api/courses/JAVASCRIPT-101/chat \
-H "Content-Type: application/json" \
-d '{
"query": "자바스크립트의 클로저란 무엇인가요?",
"topK": 5
}'응답: 자바스크립트 강의 자료 기반으로 답변 제공
-
MongoDB Atlas Vector Index:
- 반드시 Atlas에서 Vector Search Index를 먼저 생성해야 합니다
coursedocumentchunks컬렉션에courseId필드를 token 타입으로 추가
-
courseId 설계:
- courseId는 문자열 식별자입니다 (예: "PYTHON-101", "JAVASCRIPT-101")
- 각 강의는 독립적으로 관리되며, 검색 시 강의 범위를 벗어나지 않습니다
- 한 강의에 여러 파일을 업로드할 수 있습니다
-
API Keys: 모든 API 키 (.env 파일)는 절대 git에 커밋하지 마세요.
-
개발 환경:
ENABLE_REMOTE_STORAGE=false로 설정하면 로컬 파일시스템 사용- GCS 설정 없이도 개발 가능
-
프로덕션 환경:
- GCS 버킷과 서비스 계정이 미리 생성되어 있어야 합니다
ENABLE_REMOTE_STORAGE=true로 설정
-
파일 크기: 현재 최대 파일 크기는 10MB로 제한되어 있습니다.
-
범위 제한 답변:
- LLM이 검색 결과의 유사도 점수를 분석하여 범위 밖 질문을 판단합니다
- 평균 점수가 0.3 미만이면 범위 밖으로 간주됩니다
MIT
문제가 발생하거나 질문이 있으시면 이슈를 등록해주세요.