Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5a5407a
feat: integrate korean book metadata and UI citations
SanghunYun95 Mar 2, 2026
8a01e1d
fix: apply coderabbit review suggestions
SanghunYun95 Mar 2, 2026
133442a
fix(backend): apply coderabbit review feedback for db and mapping scr…
SanghunYun95 Mar 2, 2026
43d1722
fix(backend): address additional coderabbit PR inline comments
SanghunYun95 Mar 2, 2026
0dd84a4
refactor(backend): use shared env parser and HTTPS for API
SanghunYun95 Mar 3, 2026
3057ad7
fix(backend): allow key rotation for all errors in book mapping
SanghunYun95 Mar 3, 2026
fc24774
feat: implement dynamic chat title and dynamic philosopher highlighting
SanghunYun95 Mar 3, 2026
cdbc817
fix: apply CodeRabbit PR review feedback
SanghunYun95 Mar 3, 2026
6c7566d
fix(pr): address CodeRabbit review feedback on backend tools and DB s…
SanghunYun95 Mar 3, 2026
78fc51a
chore: resolve merge conflicts
SanghunYun95 Mar 3, 2026
9de894d
fix(pr): address additional CodeRabbit comments
SanghunYun95 Mar 3, 2026
3d773d7
style: update welcome messages and input placeholder to be more gener…
SanghunYun95 Mar 3, 2026
4335bee
fix(pr): address additional CodeRabbit feedback for title truncation …
SanghunYun95 Mar 3, 2026
7298aac
UI: Remove redundant buttons (useful, copy, regenerate) from MessageList
SanghunYun95 Mar 3, 2026
30dd215
Merge branch 'main' into feat/book-metadata
SanghunYun95 Mar 3, 2026
ce91d6a
Refactor: apply CodeRabbit review suggestions
SanghunYun95 Mar 3, 2026
0bd1fcd
docs: rewrite README for interviewers
SanghunYun95 Mar 3, 2026
1196e30
docs, refactor: refine README and MessageList observer logic per PR c…
SanghunYun95 Mar 3, 2026
1b31b83
refactor: resolve observer unmount leak, Biome formatting, exhaustive…
SanghunYun95 Mar 3, 2026
e1ec3fc
fix: clear visibleMessages on unmount & use targeted eslint disable
SanghunYun95 Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 179 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,207 @@
# PhiloRAG
# Philo-RAG (철학자와의 대화)

PhiloRAG is a conversational RAG interface focusing on philosophical discussions. The project consists of a Next.js frontend and a FastAPI (Python) backend using LangChain and Google's Gemini models.
**Philo-RAG**는 위대한 철학자들의 저술과 사상을 바탕으로, 사용자의 질문에 답변을 제공하는 대화형 RAG(Retrieval-Augmented Generation) 웹 애플리케이션입니다.

## Getting Started
---

### Prerequisites
- Node.js (for frontend)
- Python 3.10+ (for backend)
## 🏗 아키텍처 및 기술 스택 (Architecture & Tech Stack)

본 애플리케이션은 **Next.js (App Router)** 기반의 프론트엔드와 **FastAPI** 기반의 백엔드로 완전히 분리되어 작동하는 현대적인 마이크로서비스 지향 구조를 가집니다.

### 프론트엔드 (Frontend)
- **Framework**: Next.js 16.1.6 (React 19.2.4)
- **Styling**: Tailwind CSS, Lucide React (Icons)
- **Language**: TypeScript
- **State Management**: React Hooks (`useState`, `useRef`, `useCallback`)
- **Key Features**:
- `fetch` API의 `ReadableStream`을 활용한 커스텀 SSE 스트리밍 UI 구현
- `IntersectionObserver`를 활용한 가시성 기반 메타데이터 동적 로딩 (스크롤 위치에 따른 컨텍스트 소스 변경)
- 다크모드 기반의 미려하고 반응형(Responsive)을 지원하는 컴포넌트 설계

### 백엔드 (Backend)
- **Framework**: FastAPI (Python 3.10+)
- **LLM/AI Model**: Google Gemini API (`gemini-2.5-flash`)
- **RAG & Vector Store**: LangChain, Supabase pgvector
- **Embeddings**: HuggingFace (`jhgan/ko-sroberta-multitask`)
- **Key Features**:
- `SSE-Starlette`를 적용한 비동기 스트리밍 응답 (응답 지연 감소)
- LangChain 기반의 문서 청킹(Chunking) 및 벡터DB 구축
- 철학자 이름 및 서적 메타데이터를 클라이언트에 실시간 이벤트로 전송하여 사용자 경험 강화
- Rate-limit 대응 및 에러 핸들링 (에러 발생 시 스트리밍 중단 및 클라이언트에 명확한 에러 메시지 전달)

### 1. Backend Setup (FastAPI)
---

Navigate to the `backend` directory from the root of the project:
```bash
cd backend
```
## 🔄 데이터 흐름 (Data Flow)

Create a virtual environment:
```bash
python -m venv .venv
Philo-RAG의 주요 질문-답변 파이프라인은 다음과 같이 작동합니다.

# On Windows:
.venv\Scripts\activate
# On Mac/Linux:
source .venv/bin/activate
```
1. **Question Input**: 사용자가 프론트엔드 UI를 통해 질문을 입력합니다.
2. **Streaming Request**: 프론트엔드는 이전 대화 기록(Context)과 함께 백엔드 FastAPI 엔드포인트에 `POST` 요청을 보냅니다.
3. **Vector Search (Retrieval)**: FastAPI는 사용자의 질문을 임베딩 모델을 통해 벡터화하고, Supabase(pgvector)에서 가장 유사도가 높은 철학 서적 청크(문서 조각)를 검색합니다.
4. **Metadata Extraction**: 검색된 문서를 바탕으로 어떤 철학자와 어떤 책에서 인용되었는지 메타데이터를 추출합니다.
5. **LLM Generation**: 검색된 문서 내용(Context)과 대화 기록, 사용자 질문을 조합하여 Gemini 모델에게 프롬프트로 전달합니다.
6. **SSE Streaming**:
- 모델 응답 전 1단계: 먼저 추출된 메타데이터를 JSON 형태로 직렬화하여 클라이언트에 스트리밍 `event: metadata`로 전송합니다.
- 모델 응답 전 2단계: 이어서 LLM이 생성해내는 답변을 청크 단위로 클라이언트에 스트리밍 `event: content`로 전송합니다.
7. **Client Rendering**: 프론트엔드는 이벤트를 수신하여 메타데이터 기반으로 참고문헌(Citation) 카드를 생성하고, 답변 텍스트를 타이핑 애니메이션처럼 실시간으로 렌더링합니다.

---

## 🌟 주요 특징 (Key Features)

- **출처 표기 기능 (Citation Cards)**: AI가 답변을 생성할 때 참고한 철학자 이름과 책 제목의 메타데이터를 보여주어 환각(Hallucination)을 방지하고 신뢰도를 높입니다.
- **스크롤 반응형 컨텍스트 메뉴**: 대화 내용이 길어질 경우, 사용자가 스크롤을 올려 과거의 AI 응답을 보고 있으면 좌측 사이드바가 해당 응답 당시의 철학자 메타데이터로 즉시 업데이트됩니다 (`IntersectionObserver` 활용).
- **철학자 필터링**: 좌측 사이드바에서 특정 철학자를 클릭하면, 해당 철학자의 사상만을 기반으로 참조문헌을 필터링하여 볼 수 있습니다. 자동 리셋 로직을 통해 유효하지 않은 필터는 즉각 폐기됩니다.
- **스트리밍 기반의 빠른 체감 속도**: RAG의 고질적인 문제인 '시간 지연'을 최소화하기 위해 답변을 청크별로 즉시 화면에 띄웁니다.

---

## 💡 사용 예시 (Usage Examples)

1. **윤리적 딜레마 관련 질문**: "칸트의 입장에서 선의의 거짓말도 나쁜 것인가요?"
- 결과: AI는 칸트의 정언 명령과 관련된 텍스트를 검색하여 실시간으로 도덕적 의무에 대한 답변을 작성합니다.
2. **존재론적 질문**: "하이데거는 죽음을 어떻게 바라보았나요?"
- 결과: 우측 화면에 하이데거의 '시간과 존재' 등의 출처가 표기되며, 현존재(Dasein) 개념을 바탕으로 답변이 스트리밍 됩니다.
3. **정치 철학**: "마키아벨리는 군주가 어떻게 행동해야 한다고 했나요?"
- 결과: 군주론의 관련 구절을 RAG 파이프라인으로 찾아 직설적이고 현실적인 답변을 제시합니다.

Install the required dependencies:
---

## 💻 실행 방법 (Local Setup)

> ⚠️ **안내사항 (Cold Start)**
> 본 프로젝트의 백엔드 서버는 무료 클라우드 인스턴스에 배포되어 운영 중입니다. 일정 시간 요청이 없으면 서버가 휴면 상태로 전환되므로, **최초 접속 시 (Cold start) 백엔드 응답까지 약 1분 정도의 대기 시간이 발생**할 수 있습니다.

### 사전 요구 사항
- Node.js
- Python 3.10+
- Supabase 프로젝트 & Gemini API 키

### 백엔드 설정 (Backend)
```bash
cd backend
python -m venv .venv
# 가상환경 활성화 (.venv\Scripts\activate - Windows 환경)
pip install -r requirements.txt
```

Set up environment variables:
Create a `.env` file in the `backend` directory based on the `.env.example` structure.
You will need your `GEMINI_API_KEY`, as well as `SUPABASE_URL` and `SUPABASE_SERVICE_KEY` which are required by the backend configuration (`app/core/config.py`).
```bash
# example .env contents
GEMINI_API_KEY="your-api-key-here"
SUPABASE_URL="your-supabase-url"
SUPABASE_SERVICE_KEY="your-supabase-service-key"
# .env.example을 참고하여 .env 파일 생성
echo "GEMINI_API_KEY=your_key" > .env
echo "SUPABASE_URL=your_url" >> .env
echo "SUPABASE_SERVICE_KEY=your_service_key" >> .env

# FastAPI 서버 실행
uvicorn app.main:app --reload
```

Start the backend server on `http://localhost:8000`:
### 프론트엔드 설정 (Frontend)
```bash
uvicorn app.main:app --reload
cd frontend
npm install

# .env.local 파일 생성
echo "NEXT_PUBLIC_API_BASE_URL=http://localhost:8000" > .env.local

# Next.js 클라이언트 실행
npm run dev
```
You can access the generated API docs at `http://localhost:8000/docs`.
이후 `http://localhost:3000`에 접속하여 서비스를 이용하실 수 있습니다.

---
---

# Philo-RAG (Philosophical Discourse)

**Philo-RAG** is an interactive RAG (Retrieval-Augmented Generation) web application that provides profound answers based on the writings and thoughts of great philosophers.

---

## 🏗 Architecture & Tech Stack

The application employs a modern microservices-oriented architecture, completely decoupling the **Next.js (App Router)** frontend from the **FastAPI** backend.

### Frontend
- **Framework**: Next.js 16.1.6 (React 19.2.4)
- **Styling**: Tailwind CSS, Lucide React (Icons)
- **Language**: TypeScript
- **State Management**: Enhanced React Hooks (`useState`, `useRef`, `useCallback`)
- **Key Features**:
- Custom SSE streaming UI utilizing the `ReadableStream` of the native `fetch` API.
- Visibility-based dynamic metadata loading using `IntersectionObserver` (changing context sources based on user scroll position).
- A sleek, responsive, dark-mode native component design system.

### Backend
- **Framework**: FastAPI (Python 3.10+)
- **LLM/AI Model**: Google Gemini API (`gemini-2.5-flash`)
- **RAG & Vector Store**: LangChain, Supabase pgvector
- **Embeddings**: HuggingFace (`jhgan/ko-sroberta-multitask`)
- **Key Features**:
- Asynchronous streaming responses using `SSE-Starlette` to minimize perceived latency.
- Vector database construction and document chunking using LangChain.
- Real-time transmission of metadata (philosopher names, book titles) as specific server events to enhance UX.
- Handling of external API rate limits and robust fail-safes (terminating the stream cleanly if errors occur).

---

## 🔄 Data Flow Pipeline

The core Q&A pipeline operates as follows:

1. **Question Input**: The user inputs a query through the UI.
2. **Streaming Request**: The frontend sends a `POST` request to the FastAPI endpoint, attaching the conversation history as context.
3. **Vector Search (Retrieval)**: FastAPI categorizes the query, converts it into an embedding vector, and searches Supabase (pgvector) for the most semantically similar philosophical text chunks.
4. **Metadata Extraction**: Metadata (Author, Title) from the retrieved documents is pooled and analyzed.
5. **LLM Generation**: The retrieved context, conversation history, and user query are combined into a prompt and sent to the Gemini model.
6. **SSE Streaming Starts**:
- Output 1: The extracted metadata is serialized as JSON and streamed to the client via an `event: metadata` signal.
- Output 2: The LLM's generated response is streamed chunk-by-chunk via `event: content`.
7. **Client Rendering**: The frontend listens to these distinct events. It renders the metadata as a citation/source card instantly and updates the AI response text with a typing effect as chunks arrive.

---

## 🌟 Key Features

- **Dynamic Citation Cards**: Displaying the philosopher's name and book origin helps prevent AI hallucinations and builds high reliability.
- **Scroll-Responsive Context Sidebar**: If the conversation grows long and the user scrolls up to an older AI response, the left sidebar automatically reads the metadata for that exact message and updates the sources visually (`IntersectionObserver`).
- **Philosopher Filtering**: Clicking on a philosopher in the sidebar will filter the list of references to show only that philosopher's background context. Auto-reset logic ensures invalid filters are dropped seamlessly.
- **High-Speed Streaming UX**: Addressing the common latency issue of RAG pipelines by actively streaming tokens the moment the LLM begins generation.

---

### 2. Frontend Setup (Next.js)
## 💻 How to Run (Local Setup)

Navigate to the `frontend` directory from the root of the project:
> ⚠️ **Notice (Cold Start)**
> The backend server is currently hosted on a free cloud instance. If there are no requests for a while, the server enters a sleep state. Therefore, **upon your first access (cold start), it may take approximately 1 minute for the backend to respond**.

### Prerequisites
- Node.js
- Python 3.10+
- A Supabase Instance & Gemini API Key

### Backend Setup
```bash
cd frontend
cd backend
python -m venv .venv
# Activate venv (.venv\Scripts\activate on Windows)
pip install -r requirements.txt

# Create .env file based on .env.example
echo "GEMINI_API_KEY=your_key" > .env
echo "SUPABASE_URL=your_url" >> .env
echo "SUPABASE_SERVICE_KEY=your_service_key" >> .env

# Run FastAPI Server
uvicorn app.main:app --reload
```

Install dependencies:
### Frontend Setup
```bash
cd frontend
npm install
```

Set up environment variables:
Create a `.env.local` file (or `.env` depending on your setup) and specify the backend API base url if needed:
```bash
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
```
# Create .env.local
echo "NEXT_PUBLIC_API_BASE_URL=http://localhost:8000" > .env.local

Start the development server:
```bash
# Run Next.js Server
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Architecture Highlights
- Frontend: Next.js 14+ (App Router), TailwindCSS, TypeScript, custom SSE streaming integration.
- Backend: FastAPI, LangChain, HuggingFace embedding, and Supabase integration.
Open `http://localhost:3000` to start using the system.
2 changes: 1 addition & 1 deletion backend/scripts/generate_book_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async def translate_book_info(file_name: str) -> dict:

# If all keys exhausted or other error, fallback
print(f"LLM Failed for {file_name}, falling back to Kyobo Search...")
name_without_ext = Path(file_name).stem
name_without_ext = file_name
parts = name_without_ext.rsplit(" by ", 1)
fallback_title = parts[0].strip()
fallback_author = parts[1].strip() if len(parts) == 2 else ""
Expand Down
6 changes: 5 additions & 1 deletion frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState, useCallback } from "react";
import { useState, useCallback, useRef } from "react";
import { Sidebar } from "../components/sidebar/Sidebar";
import { ChatMain } from "../components/chat/ChatMain";
import { Message, DocumentMetadata } from "../types/chat";
Expand All @@ -11,6 +11,7 @@ export default function Home() {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [chatTitle, setChatTitle] = useState<string>("새로운 대화");
const [activeMetadata, setActiveMetadata] = useState<DocumentMetadata[]>([]);
const titleRequestSeqRef = useRef(0);

const processLine = useCallback((line: string, eventObj: { current: string }, aiMsgId: string): boolean => {
if (line.startsWith("event: ")) {
Expand Down Expand Up @@ -73,13 +74,15 @@ export default function Home() {
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";

if (isFirstMessage) {
const requestSeq = ++titleRequestSeqRef.current;
fetch(`${baseUrl}/api/v1/chat/title`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: query })
})
.then(res => res.json())
.then(data => {
if (titleRequestSeqRef.current !== requestSeq) return;
if (data.title) setChatTitle(data.title);
})
.catch(err => console.error("Failed to fetch title:", err));
Expand Down Expand Up @@ -176,6 +179,7 @@ export default function Home() {
onSendMessage={handleSendMessage}
isSubmitting={isSubmitting}
onClearChat={() => {
titleRequestSeqRef.current += 1;
setMessages([]);
setChatTitle("새로운 대화");
setActiveMetadata([]);
Expand Down
Loading