DART 전자공시 원문을 벡터 DB에 저장하고, 사용자 질문과 의미적으로 유사한 공시를 실시간 검색해 Solar Pro2가 근거 기반으로 답변하는 RAG(Retrieval-Augmented Generation) 기반 AI 투자 비서
| 기능 | 설명 |
|---|---|
| 📡 실시간 공시 조회 | DART Open API로 최신 공시 목록을 실시간 수집 |
| 🧩 금융 문맥 청킹 | 재무제표·감사의견 구조를 고려한 의미 단위 분할 |
| 🔍 벡터 유사도 검색 | Solar Embedding 4096차원으로 의미 기반 공시 검색 |
| 💬 RAG 기반 답변 | 공시 원문을 프롬프트에 주입해 근거 있는 LLM 응답 생성 |
| ⚡ 스트리밍 UI | SSE → ReadableStream으로 첫 토큰부터 실시간 렌더링 |
| 🏢 비상장사 지원 | 종목코드 없이 기업명으로도 공시 임베딩·검색 가능 |
POST /api/embed {"stockCode": "005930"} or {"corpName": "비바리퍼블리카"}
│
├─ DART corpCode.xml (ZIP, 115,550개 기업) → 24h 캐시
├─ DART list.json → 공시 목록
├─ DART document.xml → ZIP 해제 → EUC-KR/UTF-8 감지 → HTML 파싱
├─ FinancialTextSplitter → 600자 단위 금융 문맥 청킹
├─ solar-embedding-1-large-passage → 4096차원 벡터
└─ Supabase disclosure_chunks 저장 (SSE로 진행 상황 실시간 전송)
POST /api/chat {"messages": [...]}
│
├─ 종목코드 감지 (6자리 정규식 + 기업명 사전 30개)
├─ solar-embedding-1-large-query → 질문 벡터화
├─ Supabase search_disclosures RPC → 유사 공시 청크 Top-5 반환
├─ 공시 원문을 Solar Pro2 system prompt에 주입
└─ Solar Pro2 SSE → 서버에서 파싱 → ReadableStream → 클라이언트
ChatWindow (Client Component)
└─ useChat() hook
├─ chatStatusAtom idle / loading / streaming / error
├─ streamingContentAtom 실시간 누적 텍스트
└─ chatMessagesAtom 완성된 대화 기록
Tailwind는 클래스명 자동완성이 편리하지만 런타임 오버헤드가 있고, styled-components는 CSS-in-JS 방식의 JS 번들 비용이 있습니다. vanilla-extract는 빌드 타임에 정적 CSS로 컴파일되어 런타임 비용이 없고, TypeScript 타입 시스템으로 테마 토큰 오타를 빌드 단계에서 잡을 수 있습니다. 토스증권의 TDS와 동일한 기술 스택입니다.
스트리밍 응답은 초당 수십 번 상태가 변합니다. Zustand의 selector 기반 구독보다 Jotai의 Atomic 모델이 필요한 atom만 구독해 불필요한 리렌더링을 방지합니다. 파생 atom으로 isBusy, isStreaming 같은 계산된 상태를 선언적으로 정의할 수 있습니다.
Pinecone, Weaviate 같은 전용 벡터 DB 대신 PostgreSQL 확장인 pgvector를 선택했습니다. 기존 관계형 데이터와 벡터를 단일 DB에서 관리하고, Supabase 무료 티어로 인프라 비용 없이 시작할 수 있습니다.
passage(문서 저장)와 query(검색 질의)를 별도 모델로 분리하는 비대칭 임베딩 구조가 RAG에 최적화되어 있습니다. 한국어·일본어·영어 MTEB 벤치마크에서 OpenAI text-embedding-3-large를 상회합니다.
DART의 corpCode.xml(115,550개 기업)에서 동일한 종목코드에 여러 corp_code가 매핑되는 케이스 발견. 자회사·관계사가 중복 등록된 경우로, 가장 낮은 corp_code(= 먼저 등록된 모법인)를 선택하도록 중복 skip 로직 추가.
// corp_code 오름차순 정렬 → 첫 번째 항목 = 모법인
if (result[stock_code] !== undefined) continue;Solar Embedding의 4096차원이 pgvector HNSW 인덱스의 최대 2000차원 제한을 초과. 인덱스 없이 Sequential Scan으로 운영. 현재 수천 건 규모에서는 성능 영향 없으며, 대규모 확장 시 PCA 차원 축소 또는 전용 벡터 DB 전환 예정.
Upstage의 SSE 응답(data: {"choices":[{"delta":{"content":"..."}}]})을 서버에서 파싱해 순수 텍스트만 ReadableStream으로 클라이언트 전달. 클라이언트는 TextDecoder로 누적하며 Jotai atom 업데이트 → React 리렌더링으로 실시간 타이핑 효과 구현.
오래된 DART 공시 문서는 EUC-KR 인코딩으로 저장됨. HTML <meta charset> 태그를 파싱해 인코딩을 자동 감지하고 TextDecoder에 적용.
function detectEncoding(data: Uint8Array): "euc-kr" | "utf-8" {
const header = String.fromCharCode(...data.slice(0, 600));
return /charset\s*=\s*["']?\s*euc-kr/i.test(header) ? "euc-kr" : "utf-8";
}토스(비바리퍼블리카) 등 종목코드 없는 기업도 지원. 기업명 → 기업명 기반 인덱스(비상장 포함 전체) → corp_code 변환 후 임베딩.
# 종목코드 없이 기업명으로 임베딩
POST /api/embed {"corpName": "비바리퍼블리카", "pageCount": 5}사용자 입력
│
├── ~1.5초 TTFB (RAG 벡터 검색 + Solar 첫 토큰)
│ ← 로딩 스켈레톤 표시 구간
│
├── 첫 글자 → 실시간 타이핑 시작
│
└── 2~7초 스트리밍 완료 (답변 길이에 비례)
| 지표 | 값 |
|---|---|
| TTFB | ~1.5초 |
| 총 응답 완료 | 4~10초 |
| RAG 벡터 검색 | ~1.9초 |
Solar Embedding은 비대칭 구조 특성상 OpenAI 모델(0.7~0.9)보다 낮은 절대 유사도 범위를 가지며, 검색 성능은 상대적 랭킹 정확도로 평가합니다.
| 질문 유형 | 유사도 |
|---|---|
| 비관련 쿼리 (주가 질문) | 0.36 |
| 일반 공시 쿼리 | 0.41 ~ 0.46 |
| 내용 직접 매칭 (재무제표 질의) | 0.50+ |
- 변별폭: 비관련 vs 직접 매칭 간 평균 0.14 차이 → 의미 있는 검색 변별력
- 컨텍스트 히트율: 임베딩된 종목 질의 시 100%
git clone https://github.com/seongkong/Invest_Jarvis.git
cd Invest_Jarvis
npm install
cp .env.local.example .env.local.env.local에 아래 키를 입력합니다:
DART_API_KEY= # https://opendart.fss.or.kr
UPSTAGE_API_KEY= # https://console.upstage.ai
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_ANON_KEY=Supabase SQL Editor에서 실행:
-- src/migrations/001_disclosure_chunks.sql# 상장사 (종목코드)
curl -X POST http://localhost:3000/api/embed \
-H "Content-Type: application/json" \
-d '{"stockCode": "005930", "pageCount": 10}'
# 비상장사 (기업명)
curl -X POST http://localhost:3000/api/embed \
-H "Content-Type: application/json" \
-d '{"corpName": "비바리퍼블리카", "pageCount": 5}'npm run dev # http://localhost:3000src/
├── app/
│ ├── api/
│ │ ├── chat/route.ts # RAG + Solar Pro2 스트리밍
│ │ ├── embed/route.ts # 임베딩 파이프라인 (SSE)
│ │ └── dart/disclosures/ # 공시 목록 REST API
│ └── page.tsx
├── features/
│ ├── chat/ # UI 컴포넌트, 훅, Jotai atoms
│ ├── dart/ # DART API 클라이언트 & 쿼리
│ └── embedding/ # 파이프라인, RAG 검색, Upstage 클라이언트
├── lib/supabase/ # DB 클라이언트 & 타입
└── migrations/ # pgvector 스키마 SQL
.env.local은 절대 커밋하지 마세요 (.gitignore포함됨)SUPABASE_SERVICE_ROLE_KEY는 서버 전용입니다- Upstage Embedding API는 유료입니다. 임베딩 전 크레딧을 확인하세요
- pgvector HNSW 인덱스는 4096차원 제한으로 미사용 (Sequential Scan 운영)
MIT