SEC NVIDIA 공시
RAG 분석 도구
이 실습이 끝나면 미국 SEC EDGAR의 실제 NVIDIA 공시(10-K·10-Q)를 수집·청킹·벡터화해서, 한국어로 질문하면 근거 원문과 함께 답하는 나만의 공시 분석 앱이 생긴다. 6H의 임베딩·유사도 검색과 7H의 청킹 전략·지식 베이스 구축을 하나의 파이프라인으로 관통하는 실습이며, 샘플이 아니라 실제 공시 데이터를 쓴다.
수백 페이지 공시를 검색해서 읽는 구조
공시 문서는 사람이 통독하기엔 너무 길다. AI가 관련 조각만 찾아 근거와 함께 답하게 만드는 것이 이 실습의 목표다.
크롤러에서 완성 앱까지, 5단계
코드를 직접 작성하지 않는다(바이브코딩). 각 단계마다 개념을 이해하고, AI에게 요청하고, 결과를 확인하는 순서로 진행한다.
| 단계 | 이름 | 하는 일 |
|---|---|---|
| Step 1 🔵 | AI 크롤러 | SEC EDGAR에서 NVIDIA 공시 수집 |
| Step 2 🟡 | 데이터 구성 | 공시 문서 정제 & 청크 분할 |
| Step 3 🟢 | RAG 엔진 | 벡터 DB 구축 & 검색 연결 |
| Step 4 🟣 | Gradio UI | 질문-답변 분석 도구 |
| Step 5 🏆 | 통합 | 탭별 분석 카테고리 완성 |
앞 단계의 산출물이 다음 단계의 입력이 된다. 수집한 원문(Step 1)을 검색 가능한 조각으로 나누고(Step 2), 조각을 의미 기반으로 찾을 수 있게 만들고(Step 3), 찾은 조각으로 답하는 화면을 붙이고(Step 4), 마지막에 분석 카테고리와 프롬프트를 고도화한다(Step 5).
작업 폴더와 라이브러리, HF 토큰
시작 전에 세 가지를 준비한다. 작업 폴더 sec_nvda, 필요한 라이브러리 설치, Hugging Face 토큰이다.
- 작업 폴더 —
sec_nvda폴더를 만들고 그 안에서 진행한다. - 라이브러리 설치 — 아래 한 줄을 실행한다.
- HF 토큰 — Hugging Face 토큰을 준비한다. Step 4에서 LLM 호출(HF Inference API)에 쓴다.
pip install gradio sentence-transformers faiss-cpu requests beautifulsoup4 huggingface_hub -q
- sec_nvda 폴더 생성
- 라이브러리 설치 완료(에러 없음)
- HF 토큰 준비
SEC에서 NVIDIA 공시 수집
SEC EDGAR는 공개 API를 제공한다. 별도 인증 없이 실제 공시 원문을 내려받을 수 있다.
SEC EDGAR: 미국 상장기업 공시 저장소
SEC EDGAR는 미국 상장기업의 공시가 모두 올라오는 저장소다. NVIDIA는 여기에 10-K(연간)·10-Q(분기) 보고서를 게시한다.
API가 공개되어 있어 별도 인증 없이 수집할 수 있다. 문제는 분량이다. 공시 한 건이 수백 페이지에 달하므로, 사람이 처음부터 읽는 것보다 AI가 검색해서 답하는 구조가 효율적이다. 그 구조를 만들기 위한 첫 단계가 원문 수집이다.
AI에게 요청하기
SEC EDGAR에서 NVIDIA 공시 문서를 다운로드하는 크롤러를 만들어줘. 조건: - 라이브러리: requests, beautifulsoup4만 사용 - NVIDIA CIK: 0001045810 - API: https://data.sec.gov/submissions/CIK0001045810.json - 수집: 최근 10-K 2개, 10-Q 4개 - 저장 위치: data/raw/, 파일명 [날짜]_[공시유형].txt (예: 20240128_10-K.txt) - 헤더 포함: User-Agent "Samsung GenAI Study contact@samsung.com" - 진행 상황 print 출력 파일명: step1_crawler.py
실행
python step1_crawler.py
확인 포인트
- data/raw/ 에 .txt 파일 6개가 생성되었다
- 파일을 열면 영어 텍스트가 보인다
- 10-K 파일이 10-Q 파일보다 크다
내려받은 텍스트는 한 파일이 수십만 자에 달해 통째로 LLM에 전달할 수 없다. 질문과 관련된 부분만 골라 전달하려면 문서를 어떤 단위로 잘라 두어야 할까. 이것이 다음 단계, 청킹이다.
정제와 청킹: RAG의 데이터 단위 만들기
문서를 500~800자 조각으로 나누고 출처 메타데이터를 붙인다. 질문이 들어오면 관련 조각만 꺼내 LLM에 전달하는 구조의 토대다.
왜 자르는가: 관련 조각만 꺼내 전달하기 위해
청킹(chunking)은 긴 문서를 500~800자 단위의 조각(청크)으로 분할하는 작업이다. 각 청크에는 출처 메타데이터(파일명·섹션·날짜)를 붙인다.
질문이 들어오면 전체 문서가 아니라 관련 청크만 꺼내 LLM에 전달한다. 그래서 청크는 검색의 기본 단위이자 답변 근거의 단위가 된다. 메타데이터를 붙여 두면 나중에 "이 답의 근거가 어느 공시, 어느 섹션인가"를 보여줄 수 있다.
SEC 공시는 섹션 구조가 표준화되어 있어(Item 1 Business, Item 1A Risk Factors, Item 7 MD&A, Item 8 Financial Statements) 섹션을 자동 감지해 섹션별로 자르면 조각의 의미 단위가 살아난다.
AI에게 요청하기
data/raw/ 폴더의 텍스트 파일들을 정제하고 청킹해줘.
- SEC 공시 주요 섹션 자동 감지 (Item 1 Business, Item 1A Risk Factors, Item 7 MD&A, Item 8 Financial Statements)
- 섹션별로 500~800자 단위 청크 분할 (문장 중간에 잘리지 않게)
- 청크 메타데이터: {"source_file", "section", "filing_type": "10-K/10-Q", "filing_date", "chunk_id"}
- 결과 저장: data/nvidia_chunks.json
- 완료 후 총 청크 수, 섹션별 통계 출력
파일명: step2_preprocessor.py
실행
python step2_preprocessor.py
확인 포인트
- data/nvidia_chunks.json 이 생성되었다
- 청크가 500~800자 범위로 잘려 있다
청크를 만들었으니 이제 검색이 필요하다. 그런데 키워드 검색은 단어가 다르면 못 찾는다. "데이터센터 매출"을 물었는데 원문에 "AI 인프라"라고 쓰여 있으면 놓친다. 단어가 달라도 의미가 가까우면 찾아내는 검색, 즉 벡터 DB가 다음 단계다.
벡터 DB: 키워드가 아니라 의미로 찾는다
텍스트를 숫자 벡터로 바꾸면 의미가 가까운 내용을 검색할 수 있다. Indexing → Retrieval → Generation 세 축이 RAG의 뼈대다.
단어가 달라도 의미가 가까우면 찾는다
벡터 검색은 텍스트를 숫자 벡터(임베딩)로 바꿔, 의미가 가까운 내용을 찾는 검색이다. 6H에서 배운 임베딩·유사도 검색이 여기서 실전에 투입된다.
RAG 세 축의 구조
| 축 | 흐름 | 이 실습에서 |
|---|---|---|
| Indexing | 청크 → 벡터 → 저장 | 청크를 임베딩해 FAISS 인덱스에 저장 |
| Retrieval | 질문 → 벡터 → 유사 청크 | 질문을 임베딩해 유사 청크 5개 검색 |
| Generation | 청크 + 질문 → LLM → 답변 | 검색된 청크를 근거로 LLM이 답변 생성 |
AI에게 요청하기
data/nvidia_chunks.json으로 FAISS 벡터 검색 엔진을 구축해줘. - 임베딩: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 - FAISS IndexFlatIP + L2 정규화 - 저장: vectorstore/faiss_index.bin, vectorstore/chunks_metadata.json - 구축 후 검색 테스트 3개: "NVIDIA revenue growth data center" / "AI chip supply risk export control" / "future business strategy outlook" - 각 결과: 상위 3개 청크의 섹션명, 날짜, 내용 앞 200자 출력 파일명: step3_vectorstore.py
실행
python step3_vectorstore.py
확인 포인트
- vectorstore/ 에 인덱스 파일이 생성되었다
- Risk 관련 질문 → Risk Factors 섹션 청크가 검색된다
- Revenue 관련 질문 → MD&A 섹션 청크가 검색된다
검색 엔진은 완성됐지만 아직 터미널에서만 돌아간다. 질문 입력 → 벡터 검색 → LLM 답변 → 화면 출력을 하나의 화면으로 연결하면 누구나 쓸 수 있는 분석 도구가 된다. 다음 단계에서 Gradio로 UI를 붙인다.
Gradio 분석 도구: 질문하면 근거와 함께 답한다
질문 입력 → 벡터 검색 → LLM 답변 → 화면 출력을 UI로 연결한다. 답변 아래 참조 원문 아코디언이 근거를 보여준다.
파이프라인을 브라우저 앱으로
Step 3까지 만든 검색 엔진 위에 Gradio UI를 얹는다. 왼쪽에서 질문을 입력하면 오른쪽에 LLM 답변과 참조 원문(상위 5개 청크)이 표시된다.
답변 생성은 HF Inference API의 LLM(Qwen/Qwen3-8B)이 맡고, 검색된 청크를 [참고 문서]로 프롬프트에 넣어 한국어 답변을 요청한다. 근거 없는 답이 아니라 검색된 공시 원문에 기반한 답이 나오게 하는 구조다.
AI에게 요청하기
vectorstore/ 폴더의 FAISS 인덱스를 사용하는 NVIDIA 공시 분석 Gradio 앱을 만들어줘. - 로드: faiss_index.bin, chunks_metadata.json - 임베딩: paraphrase-multilingual-MiniLM-L12-v2 / LLM: HF Inference API, Qwen/Qwen3-8B - UI: 왼쪽 질문 입력+HF 토큰+분석 버튼 / 오른쪽 답변(gr.Markdown)+참조 원문 아코디언 - 아코디언: 상위 5개 청크의 [섹션명 | 날짜 | 공시유형] + 내용 - 프롬프트: 검색된 청크를 [참고 문서]로 전달, 한국어 답변 요청 파일명: step4_gradio_app.py
실행과 테스트
python step4_gradio_app.py
앱이 열리면 다음 질문으로 테스트한다.
NVIDIA 데이터센터 매출은 얼마이고, 주요 성장 요인은 무엇인가요?
확인 포인트
- 답변에 수치와 섹션명이 포함된다
- 참조 원문 아코디언에 검색된 청크가 표시된다
지금은 질문을 매번 직접 타이핑해야 하고, 답변 형식도 일정하지 않다. 자주 쓰는 분석 질문을 버튼으로 만들어 두고, 프롬프트에 역할·추론 단계·출력 형식을 지정하면 애널리스트 리포트 같은 구조화된 답이 나온다. 마지막 단계다.
완성 앱: 탭별 분석과 CoT 프롬프트
재무·리스크·전망 3개 탭에 분석 버튼 15개를 배치하고, CoT 프롬프트로 애널리스트 스타일의 구조화 답변을 만든다.
역할 + 추론 단계 + 출력 형식
완성 앱은 재무·리스크·전망 3개 탭 × 분석 버튼 5개 구조다. 버튼을 클릭하면 질문이 자동 입력되어 RAG 분석이 실행된다.
답변 품질을 끌어올리는 것이 CoT(Chain-of-Thought) 프롬프트다. 세 요소를 명시한다.
| 요소 | 이번 실습의 지정값 |
|---|---|
| 역할 | "15년 경력 반도체 섹터 애널리스트" |
| 추론 | 1단계 수치 파악 → 2단계 맥락 연결 → 3단계 시사점 도출 |
| 출력 형식 | 📊 핵심 수치 / 📝 분석 / ⚠️ 주의사항 |
재무 5 · 리스크 5 · 전망 5
AI에게 요청하기 전에, 각 탭에 넣을 버튼 질문 15개를 직접 설계한다(재무 5개, 리스크 5개, 전망 5개). 이 질문 목록이 그대로 앱의 분석 메뉴가 된다.
예시는 다음과 같다.
- 재무 예: "NVIDIA 최근 연간 총매출과 전년 대비 성장률은?"
- 리스크 예: "주요 사업 위험 요소는?"
- 전망 예: "경영진의 성장 전략과 핵심 투자 영역은?"
- 재무 분석 질문 5개 작성
- 리스크 분석 질문 5개 작성
- 전망 분석 질문 5개 작성
AI에게 요청하기
step4_gradio_app.py를 업그레이드해서 완성형 분석 앱을 만들어줘. - 3개 탭: [📊 재무분석] [⚠️ 리스크 분석] [🔮 전망 분석], 각 탭 버튼 5개(클릭 시 질문 자동 입력 후 분석) - 버튼 질문 목록: [직접 작성한 15개] - CoT 프롬프트: 역할 "15년 경력 반도체 섹터 애널리스트" / 추론 "1단계 수치 파악 → 2단계 맥락 연결 → 3단계 시사점 도출" / 출력 "📊 핵심 수치 / 📝 분석 / ⚠️ 주의사항" 구조, 한국어 - UI: 상단 HF 토큰+모델 선택(Qwen3-8B 기본, Qwen3-4B-Instruct-2507, Mistral-7B-Instruct-v0.3) / 각 탭 하단 참조 원문 아코디언 / gr.Progress 파일명: step5_final_app.py
실행
python step5_final_app.py
확인 포인트
- 3개 탭이 모두 동작한다
- 버튼 클릭 시 질문이 자동 입력되고 분석이 실행된다
- 답변이 📊 핵심 수치 / 📝 분석 / ⚠️ 주의사항 구조로 나온다
- 참조 원문에 날짜·섹션이 표시된다
- 모델을 바꾸면 답변 스타일 차이가 보인다
바이 인코더 vs 리랭킹: 빠른 검색 위에 정밀한 재정렬을 얹는다
지금까지 만든 벡터 검색이 바로 바이 인코더 방식이다. 빠르지만 "유사한 것"과 "질문에 답이 되는 것"은 다르다 — 그 틈을 크로스 인코더 리랭커가 메운다.
따로 인코딩하면 빠르고, 함께 인코딩하면 정확하다
Step 3에서 쓴 SentenceTransformer가 바이 인코더(Bi-Encoder, 더블/듀얼 인코더)다. 질문과 문서를 각각 따로 벡터로 바꿔 유사도를 계산한다. 크로스 인코더(Cross-Encoder)는 질문과 문서를 한 쌍으로 함께 모델에 넣어 관련도 점수 하나를 뽑는다.
정면 비교
| 구분 | 바이 인코더 | 크로스 인코더 리랭커 |
|---|---|---|
| 입력 | 질문과 문서를 따로 인코딩 | (질문, 문서) 쌍을 함께 인코딩 |
| 출력 | 벡터 → 코사인 유사도 | 쌍의 관련도 점수 |
| 사전 계산 | 문서 벡터를 미리 인덱싱 가능 | 불가 — 질문이 와야 계산 시작 |
| 속도 | 수십만 문서도 밀리초 단위 | 문서당 모델 추론 1회, 수십 개가 한계 |
| 정확도 | 의미가 비슷한 것까지 | 질문-문서 상호작용을 반영해 더 정밀 |
| 역할 | 1단계 후보 검색 (Retrieval) | 2단계 재정렬 (Re-ranking) |
그래서 실무 RAG는 2단계로 짠다
질문 ↓ 1단계 · 바이 인코더 + FAISS ← 빠르게 그물을 넓게 상위 20개 후보 청크 ↓ 2단계 · 크로스 인코더 리랭커 ← 정밀하게 순위를 다시 상위 5개 청크 ↓ LLM에 [참고 문서]로 전달 → 답변 생성
바이 인코더는 "이 두 텍스트가 비슷한 이야기를 하는가"에 답한다. 그런데 질문에 필요한 것은 "이 청크가 이 질문의 답을 담고 있는가"다. 예컨대 "수출 규제가 없었다면 매출이 어땠을까"라는 질문에는 규제를 다룬 청크가 죄다 비슷한 벡터로 잡히지만, 실제 답이 되는 청크는 그중 일부다. 부정·조건·수치 비교가 얽힌 질문일수록 질문과 문서를 함께 읽는 크로스 인코더의 이득이 커진다. 반대로 크로스 인코더만으로 전체 문서를 훑는 것은 계산상 불가능하므로, 넓게 잡고(바이) 좁게 고르는(크로스) 2단계가 표준 설계가 됐다.
SEC 분석 도구에 리랭킹 스위치 달기
Step 4 앱에 리랭킹 단계를 추가해, 켰을 때와 껐을 때 검색 순위와 답변 품질이 어떻게 달라지는지 한 화면에서 비교한다.
AI에게 요청하기
step4_gradio_app.py에 리랭킹 단계를 추가한 step6_rerank_app.py를 만들어줘.
- 1단계(기존): FAISS 벡터 검색으로 상위 20개 청크 후보를 가져와
- 2단계(추가): sentence-transformers의 CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')로
(질문, 청크) 쌍의 관련도 점수를 계산해 점수 상위 5개만 LLM에 전달해
- UI에 '리랭킹 사용' 체크박스 추가 — 끄면 기존 방식(FAISS 상위 5개 그대로)
- 참조 원문 아코디언에 두 방식의 순위를 나란히 표시:
[FAISS 순위 → 리랭킹 순위 | 리랭커 점수 | 섹션명 | 내용 앞 150자]
- 검색~답변까지 걸린 시간을 방식별로 표시해줘
파일명: step6_rerank_app.py
실행
python step6_rerank_app.py
확인 포인트
- 리랭킹을 켜면 청크 순위가 실제로 바뀐다 — FAISS 1위가 리랭킹에서 밀려나는 사례를 찾아본다
- "수출 규제가 데이터센터 매출에 미친 영향은?"처럼 조건이 얽힌 질문에서 두 방식의 답변을 비교한다
- 리랭킹을 켰을 때 응답 시간이 얼마나 늘어나는지 확인한다 — 정확도의 값이 시간이라는 것을 체감한다
ms-marco-MiniLM 계열 리랭커는 영어 검색 데이터로 학습된 모델이라 이번 실습(영문 SEC 공시)에 맞는다. 한국어 문서에 리랭킹을 얹을 때는 BAAI/bge-reranker-v2-m3 같은 다국어 리랭커로 바꿔 달라고 요청하면 된다. 그리고 검색 품질이 아쉬울 때 손대는 순서를 기억해 두자 — ① 청킹(크기·경계)을 먼저 다듬고 ② 후보 수(top-k)를 조정한 뒤 ③ 그래도 부족할 때 리랭킹을 추가한다. 리랭킹은 후보 안에 정답이 있어야 살릴 수 있다. 후보에 정답이 없으면(청킹·검색의 실패) 리랭커도 소용이 없다.
무엇을 만들었고, 어디로 확장하는가
실데이터 수집부터 구조화 답변까지, RAG 파이프라인 한 바퀴를 완주했다.
각 단계가 남긴 것
AI 크롤러 — 공개 API로 실데이터를 수집했다. 실습용 샘플이 아니라 실제 SEC 공시다.
청킹 — 긴 문서를 검색 가능한 단위로 나눴다. 메타데이터가 있어 답변의 근거를 표시할 수 있다.
벡터 DB — 키워드가 아니라 의미 기반으로 검색한다. 단어가 달라도 가까운 내용을 찾는다.
Gradio — 파이프라인을 누구나 쓸 수 있는 브라우저 앱으로 만들었다.
CoT 프롬프트 — 역할·추론 단계·출력 형식을 지정해 구조화된 애널리스트 스타일 답변을 얻었다.
리랭킹(심화) — 바이 인코더로 넓게 잡고 크로스 인코더로 좁게 골랐다. 유사도와 관련도는 다르며, 그 차이를 2단계 검색이 메운다.
더 밀어붙여 볼 방향 네 가지
- 다른 기업 적용 — TSMC·삼성전자 등 다른 기업 공시에 적용해 본다(SEC에는 외국 기업도 등록되어 있다).
- 청크 크기 실험 — 청크 크기를 조정(500→1500자)하면 검색 결과가 어떻게 달라지는지 비교한다.
- 날짜 필터 — 날짜 필터를 추가해 특정 연도 공시만 검색되게 한다.
- RAG 유무 비교 — RAG 없이 같은 질문을 던졌을 때의 답변과 품질을 비교한다.
이번 실습의 RAG는 이미 쌓인 공시를 분석한다. 그런데 공시는 계속 새로 올라온다. 새 공시(8-K)가 올라오는 순간 잡아내 분석하는 실시간 모니터링이 다음 심화편, 8-K 실시간 모니터링(08_monitor)의 주제다.
출처 원본 노션 실습 페이지: SEC NVIDIA 공시 RAG 분석 도구 만들기 (Notion)
이 자료는 단일 HTML 파일로 배포되는 실습 가이드다. 프롬프트 블록의 "복사" 버튼으로 원문을 그대로 복사해 사용할 수 있다.