Skip to content

mu-jeong/financial-report-agent

Repository files navigation

Finance LLM

Version: 0.5.0

Quick Start: 간편하게 실행하기

Windows에서 처음 실행할 때는 RUN_QUICKSTART.bat을 더블클릭하면 설치, OpenRouter API 키 설정, 실행일 포함 이전 7일 범위(총 최대 8일)의 리포트 수집, 임베딩 생성, 웹 화면 실행까지 자동으로 진행됩니다.

  1. OpenRouter API 키 발급 방법을 따라 API 키와 크레딧을 준비합니다.
  2. 프로젝트 폴더에서 RUN_QUICKSTART.bat을 더블클릭합니다.
  3. 처음 실행 시 API 키를 붙여넣고 Enter를 누릅니다.
  4. 브라우저가 열리면 바로 질문을 입력합니다.

초기 준비가 끝난 뒤 앱만 다시 열 때는 RUN_APP.bat을 사용하세요. RUN_APP.bat.venv.env가 있는지 확인한 뒤 Streamlit GUI만 실행하므로, 매번 패키지 설치·리포트 수집·임베딩을 반복하지 않습니다.

Quick Start는 매번 실행하는 날짜를 기준으로 실행일과 그 이전 7일(총 최대 8일)의 리포트를 준비합니다. 자세한 실행 방법과 RUN_APP.bat 사용 구분은 docs/QUICK_START.md를 참고하세요.


증권사 리포트 PDF를 수집하고 SQLite + FAISS에 색인한 뒤, LangGraph 기반 RAG 파이프라인으로 재무 질문에 답하는 프로젝트입니다. 생성 모델, 임베딩, 선택형 rerank는 OpenRouter API를 기준으로 연동합니다.

이 프로젝트는 투자 조언이나 매수/매도 추천을 제공하지 않습니다. 답변은 수집·색인된 리포트와 공개 데이터 기반의 참고 정보로만 사용하세요.

주요 기능

  • 증권사 리포트 PDF 다운로드 및 파일명 기반 메타데이터 파싱
  • company, industry, economy 카테고리별 리포트 수집
  • SQLite reports 테이블과 FAISS 벡터 인덱스 동기화
  • PyMuPDF, OpenDataLoader, Marker, Docling, pdf-to-markdown 중 선택 가능한 PDF 텍스트 추출 엔진
  • Parent-Child Chunking 기반 문맥 확장 검색
  • LangGraph 기반 query rewrite, routing, RDB 검색, VectorDB 검색, 답변 생성
  • SQL guardrail: SELECTreports 테이블 중심의 read-only SQLite 접근
  • OpenRouter 임베딩(baai/bge-m3) 지원
  • 선택형 OpenRouter rerank(cohere/rerank-v3.5) 또는 FlashRank fallback
  • report_date 기준 날짜/월/분기/연도 필터링과 최신성 가중치(RECENCY_WEIGHT) 지원
  • KRX 상장법인 업종 CSV 기반 섹터/분야 질문의 회사 universe lookup 지원
  • VectorDB 검색 실패 시 short-term memory 영향을 제거하고 원질문으로 재검색
  • 답변의 [숫자] citation과 기본 접힘 상태의 참고 문서 목록 연동
  • Streamlit GUI 대화 기록 저장, 백그라운드 답변 생성, 대화 이름 변경/삭제, 참고 PDF 열기
  • FinanceDataReader 기반 주가 조회 tool calling
  • Streamlit GUI 실행 (CLI는 유지보수 전용 deprecated 모드)

설치

git clone <repository-url>
cd finance_llm
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

Python 3.10 이상을 권장합니다.

환경 변수 설정

수정 가능한 설정의 기본값, 타입, 설명은 src/configs/settings.py에서 한 번만 관리합니다. .env.example은 이 파일에서 자동 생성되는 템플릿이고, .env는 실제 실행값만 저장합니다.

수동으로 환경을 준비할 때는 루트의 .env.example.env로 복사한 뒤 OPENROUTER_API_KEY를 채웁니다.

Copy-Item .env.example .env

주요 기본값은 다음과 같습니다.

GENERATION_MODEL=deepseek/deepseek-v4-flash
EMBEDDING_MODEL=baai/bge-m3
USE_RERANKER=false
RERANK_PROVIDER=openrouter
RERANK_MODEL=cohere/rerank-v3.5
SEARCH_TOP_K=20
RECENCY_WEIGHT=0.15
PDF_EXTRACTION_ENGINE=pymupdf
UNEMBEDDED_PDF_EXTRACTION_ENGINE=opendataloader

약 2,000건의 리포트를 임베딩 벡터화하는 데 약 $0.05가 소요되었습니다. 실제 비용은 문서 길이, 청크 수, 호출량, 모델 가격에 따라 달라질 수 있습니다.

.env.example을 다시 생성해야 할 때는 아래 명령을 실행합니다.

python -m src.configs.generate_env_example

자세한 설정은 docs/API_SETUP.md를 참고하세요.

PDF 파일명 규칙

다운로드된 PDF는 기본적으로 data/downloaded/ 아래에 저장됩니다. 파일명은 DB 메타데이터 파싱에 사용하므로 아래 규칙을 따릅니다.

[카테고리]_[YYYY-MM-DD]_[대상]_[증권사]_[제목].pdf

예시:

company_2026-05-29_NAVER_미래에셋증권_기업 분석.pdf
industry_2026-05-20_반도체_신한투자증권_HBM 전망.pdf
economy_2026-05-15_null_한국투자증권_금리 전망.pdf

리포트 다운로드

python -m src.core.report_crawler

주요 옵션은 src/configs/settings.py.env.example에서 확인합니다. 기본 Quick Start 정책은 실행일을 기준일로 자동 설정하고, 실행일과 그 이전 7일(총 최대 8일) 범위의 리포트를 수집하는 것입니다.

CRAWLER_MODE=LATEST
CRAWLER_CATEGORIES=company
CRAWLER_TARGET_DATE=
CRAWLER_TARGET_COUNT=0
CRAWLER_LOOKBACK_DAYS=7
CRAWLER_MAX_LOOKBACK_DAYS=7
  • CRAWLER_MODE=LATEST: 실행 시점의 KST 기준일을 사용합니다.
  • CRAWLER_MODE=SPECIFIC_DATE: CRAWLER_TARGET_DATE에 지정한 날짜를 사용합니다.
  • CRAWLER_CATEGORIES: company, industry, economy, 쉼표 구분 목록, 또는 all을 사용할 수 있습니다.
  • CRAWLER_LOOKBACK_DAYS=7: 기준일 포함 최대 8일 범위를 조회합니다.
  • CRAWLER_TARGET_COUNT=0: 개수 제한 없이 가능한 리포트를 수집합니다.

상장기업 업종 데이터

섹터나 분야에 속한 기업을 묻는 질문은 레포에 포함된 KRX 상장법인 업종 CSV를 회사 universe lookup에 사용합니다. 원본 데이터는 KRX 상장법인목록 페이지에서 내려받은 Excel을 company_name,industry,main_products CSV로 변환한 것입니다.

이 데이터는 리포트 본문 검색용 VectorDB가 아니라 회사명/업종/주요제품 구조화 lookup에 사용합니다. 예를 들어 “반도체 섹터에 속한 기업”은 먼저 업종 CSV에서 관련 회사를 찾고, 현재 날짜/report_type scope의 reports DB와 교집합을 낸 뒤 해당 file_name 범위로 VectorDB 검색을 좁힙니다.

임베딩 인덱스 생성

python -m src.core.embed_pipeline          # TEST_LIMIT 적용
python -m src.core.embed_pipeline --all    # pending 전체 처리
python -m src.core.embed_pipeline --limit 100

Quick Start는 pending 문서 전체 처리를 위해 --all을 사용합니다. 직접 실행할 때 .envTEST_LIMIT=10이 남아 있으면 일부 문서만 처리될 수 있습니다.

모델, chunk 크기, PDF 추출 엔진을 바꾼 뒤에는 기존 FAISS 인덱스를 재생성하는 것이 안전합니다.

Remove-Item -Recurse -Force data\vector_db
python - <<'PY'
from src.core.db_manager import get_connection

conn = get_connection()
conn.execute("UPDATE reports SET is_embedded = 0")
conn.execute("DELETE FROM parent_chunks")
conn.commit()
conn.close()
PY
python -m src.core.embed_pipeline --all

PDF 추출 엔진 비교는 docs/PDF_EXTRACTION_COMPARISON.md를 참고하세요.

실행

Streamlit GUI

권장 실행 방식입니다.

streamlit run apps/gui/app.py

CLI (deprecated)

CLI 모드는 유지보수 전용입니다. 신규 기능 개발은 중단하며, 일반 사용은 Quick Start 또는 Streamlit GUI를 권장합니다. 기존 자동화 호환성을 위해 --status 등 최소 동작만 유지합니다.

python apps/cli/app.py --status

GUI 대화 이력은 data/conversations.db에 저장됩니다. deprecated CLI도 기존 호환성을 위해 같은 저장소를 사용합니다. Streamlit 사이드바의 대화 목록에서 각 대화 오른쪽의 연필 버튼으로 이름을 변경하고 × 버튼으로 삭제할 수 있습니다. 삭제 후에는 다음 대화가 자동 선택되고, 삭제 UI가 화면에 남지 않도록 즉시 rerun합니다. 이 파일은 로컬 상태 파일이며 일반적으로 Git에 포함하지 않습니다.

GUI 답변 생성은 백그라운드 thread에서 실행됩니다. 답변 생성 중인 대화는 입력창이 잠기고, 다른 대화로 이동해도 작업은 계속되며 완료/실패 상태가 toast와 대화 목록 배지로 표시됩니다.

채팅 입력창 아래의 ⚠ 신고 버튼은 현재 대화에서 발생한 문제를 텍스트 파일로 저장합니다. 신고 파일은 기본적으로 debug/issue_report_*.txt에 생성되며, debug/ 폴더 내용은 Git에 포함하지 않습니다(debug/.gitkeep만 폴더 유지용). 민감정보가 포함될 수 있으므로 외부로 전달하기 전에 내용을 확인하세요.

참고 문서의 열기 버튼은 브라우저 링크가 아니라 Streamlit 서버가 실행 중인 PC에서 PDF를 직접 엽니다. 파일은 REPORT_PDF_DIR 환경 변수의 폴더와 참고 문서의 파일명을 조합해 찾습니다. REPORT_PDF_DIR은 임베딩 파이프라인이 문서 폴더의 절대경로를 기준으로 .env에 자동 생성하거나 기존 값만 갱신합니다. 로컬 사용에는 적합하지만, 원격 서버에 배포한 경우에는 서버 PC에서 파일이 열립니다.

사이드바 캘린더는 임베딩 완료 날짜를 데이터 있음으로 표시합니다. 데이터 업데이트에서는 company, industry, economy 카테고리를 선택할 수 있고, 선택한 카테고리 중 하나라도 비어 있는 평일은 업데이트 대상으로 포함합니다. 필요한 다운로드와 임베딩은 백그라운드 작업으로 실행됩니다.

대화 후속 질문과 참고 문서 표시

GUI 채팅은 성공한 assistant 답변의 검색 범위를 메시지 metadata에 저장합니다. 저장되는 범위에는 VectorDB 필터, 상대 날짜 해석 결과, 실제 답변에 사용된 참고 PDF 파일명, 답변 섹션별 answer_scope_index가 포함됩니다. 사용자가 "주요 내용 정리", "방금 내용 요약", "위 내용 핵심"처럼 직전 답변을 가리키는 후속 질문을 하면 같은 문서 범위를 재사용해 답변이 다른 리포트로 새지 않도록 합니다.

직전 답변의 특정 섹션을 더 자세히 묻는 질문도 별도로 처리합니다. 예를 들어 "개별 종목/주요 기업 리포트를 상세하게 정리해줘", "섹터 부분을 자세히 알려줘", "거시경제 내용을 더 알려줘" 같은 질문은 직전 답변의 날짜 범위를 유지하면서 report_type=company|industry|economy 필터를 추가합니다. 이때 직전 답변의 전체 PDF 파일명 목록은 섹션 범위를 과도하게 제한하지 않도록 제거하고, 어떤 섹션 alias와 필터가 적용됐는지는 scope_decision metadata로 남깁니다. 다만 "5월 주요 내용"처럼 새 날짜 조건을 명시한 질문은 현재 질문의 조건을 우선합니다.

참고 문서 목록은 답변 안의 citation 링크와 연결되지만, 화면에서는 기본적으로 접힌 상태로 표시됩니다. 필요한 경우 사용자가 직접 펼쳐서 문서명과 열기 버튼을 확인합니다.

검색 및 답변 흐름

  1. query_rewrite: 질문을 검색 친화적으로 정리합니다.
    • 후속 질문 여부를 판단해 followup_scope_intent를 상태에 남깁니다.
    • 직전 답변 섹션을 가리키는 deep-dive 질문은 src/core/followup_scope.py의 섹션 alias(company, industry, economy)로 감지합니다.
  2. search_scope: 날짜/메타데이터 필터와 직전 답변 scope 재사용 여부를 결정합니다.
    • followup_scope_intent가 켜져 있고 새 날짜 조건이 없으면 직전 VectorDB 답변의 검색 필터, 날짜 범위, 실제 참고 파일명을 prior_search_scope로 재사용합니다.
    • 섹션 follow-up이면 직전 날짜 범위는 유지하고 섹션별 report_type 필터를 추가하며, scope_decision에 적용 근거를 기록합니다.
    • 섹션 follow-up은 "top company"류 rewrite가 끼어들어도 단일 기업 선택으로 축소하지 않습니다.
  3. router: RDB 질문인지 VectorDB 질문인지 판단합니다.
  4. RDB 검색: LLM이 SQL을 생성하고 guardrail을 통과한 read-only SELECT만 실행합니다.
  5. VectorDB 검색: FAISS 후보를 넉넉히 가져온 뒤 날짜/종목/증권사/리포트 유형/파일명 필터와 최신성 가중치를 적용합니다.
    • 복수 문서 의도, 명시 파일 scope, 섹션 follow-up에서는 특정 PDF chunk에 결과가 쏠리지 않도록 문서 coverage를 적용합니다.
  6. USE_RERANKER=true일 때 OpenRouter rerank를 추가로 적용합니다. 기본값은 비용을 고려해 false입니다.
  7. VectorDB 검색 결과가 없으면 해당 대화의 short-term memory 영향을 제거하고 원질문으로 한 번 더 검색합니다.

PDF 추출 엔진 비교

PDF_EXTRACTION_ENGINEpymupdf, marker, opendataloader, docling, pdf-to-markdown 중 하나로 설정할 수 있습니다. pymupdf가 기본값이고, doclingpdf-to-markdown은 각각 별도 설치/CLI가 필요한 선택형 엔진입니다. UNEMBEDDED_PDF_EXTRACTION_ENGINE을 설정하면 미임베딩/재시도 문서만 별도 엔진으로 처리할 수 있습니다. 빈 값이면 PDF_EXTRACTION_ENGINE을 그대로 사용합니다. 기존 EXTRACTION_ENGINE, UNEMBEDDED_EXTRACTION_ENGINE 환경변수도 alias로 동작합니다. 모든 엔진 출력은 downstream 색인 전에 공통 표 제거 로직을 통과합니다.

python -m src.core.compare_pdf_extractors --limit 10
python -m src.core.compare_pdf_extractors --engines pymupdf opendataloader marker --limit 5
# 선택형 엔진까지 비교하려면 런타임 요구사항을 설치한 뒤 실행합니다.
python -m src.core.compare_pdf_extractors --engines pymupdf opendataloader marker docling pdf-to-markdown --limit 5

자세한 내용은 docs/PDF_EXTRACTION_COMPARISON.md를 참고하세요.

테스트

python -m pytest -q

현재 테스트는 파일명 파싱, SQL guardrail, 상태 요약, metadata filter와 날짜 해석, query rewrite, 후속 질문 검색 범위 재사용, 답변 섹션 기반 follow-up scope 결정, 섹션 follow-up의 문서 coverage, OpenRouter embedding/rerank payload, PDF 추출 엔진/표 제거 계약, conversation store, citation 링크 변환, 문서 단위 citation 재번호, Quick Start, 백그라운드 데이터 업데이트, VectorDB no-result 재시도 로직을 검증합니다.

평가용 테스트셋

현재 로컬 data/reports.db 메타데이터에서 뽑은 평가용 fixture는 tests/fixtures/evaluation_dataset.json에 있습니다. PDF 본문은 포함하지 않고, 질문/기대 라우팅/기대 필터/기대 출처 파일명/RDB 기대 집계값만 담았습니다.

테스트셋은 한 번 기준선으로 정하면 변경 사유가 생기기 전까지 그대로 유지합니다. 선정 기준과 고정 정책은 fixture의 selection_criteria, stability_policy, docs/EVALUATION_DATASET.md에 함께 저장합니다. scripts/build_evaluation_dataset.py는 source가 사라졌거나 지표 축이 바뀌는 등 명시적 변경 사유가 있을 때만 재생성에 사용합니다.

python -m pytest tests/test_evaluation_dataset.py -q

이 테스트셋은 향후 parsing, chunking, retrieval/rerank, 모델 변경에 따른 답변 변화, RDB 라우팅, 날짜별 데이터 캘린더, latency/비용 측정 회귀 평가에 사용할 고정 기준 데이터입니다.

주의사항

  • 이 프로젝트는 투자 조언이나 매수/매도 추천을 제공하지 않습니다.
  • 오래된 PDF와 잘못 추출된 표, 누락된 리포트는 답변 품질에 영향을 줄 수 있습니다.
  • .env에는 실제 API 키가 들어가므로 커밋하지 마세요.
  • FAISS의 index.pkl은 pickle 역직렬화를 사용하므로 신뢰할 수 없는 파일을 로드하지 마세요.
  • 로컬 GUI에서 PDF 열기 기능은 Streamlit 서버가 실행 중인 PC 기준으로 동작합니다. 원격 서버에 배포하면 서버 PC에서 파일을 열려고 시도합니다.

Monitoring Mode

Monitoring Mode는 성능 개선과 회귀 확인을 위한 개발자용 지표 모니터링 모드입니다. 일반 채팅 UX와 분리된 Monitoring 탭에서 데이터/설정 상태, 고정 평가 테스트셋 커버리지, 대화별 route/source/latency metadata, PDF parsing engine 비교 결과를 확인합니다. 일반 GUI에서는 이 진단 정보를 숨기고, .env에서 명시적으로 켰을 때만 노출합니다. 전체 Monitoring과 개별 Chat Monitoring의 구현 세부 내용은 docs/MONITORING.md에 별도로 정리되어 있습니다.

실행 방법

Monitoring Mode UI는 .env에서 명시적으로 켰을 때만 Streamlit에 표시됩니다.

MONITORING_MODE=true

이후 평소처럼 GUI를 실행합니다.

streamlit run apps/gui/app.py

활성화되면 상단에 Chat / Monitoring 탭이 생기고, Monitoring 탭에서 데이터/설정 상태, 고정 평가 테스트셋 커버리지, PDF parsing engine 비교 실행/결과, 현재 대화의 route/source/latency 지표를 확인할 수 있습니다. MONITORING_MODE=false이거나 설정이 없으면 일반 채팅 UI만 동작합니다.

테스트 방법

python -m pytest tests/test_settings.py tests/test_monitoring.py tests/test_evaluation_dataset.py -q
python -m pytest -q

목표

  • 검색/답변 품질 저하 원인을 빠르게 찾는 성능개선 지표 대시보드 제공
  • 수집 → 추출 → 임베딩 → 검색 → rerank → 답변 생성까지 단계별 병목 확인
  • parsing/chunking/retrieval/rerank/model 변경 전후의 답변 변화와 품질 지표 추적
  • API 비용, latency, Top-K 품질, 필터 적용 결과를 한 화면에서 추적
  • 일반 사용자 UX와 개발자 진단 UX를 분리

현재 구현된 화면

  • Data status: 다운로드 PDF, DB row, 임베딩 완료/대기, FAISS 파일, 주요 설정 상태
  • Evaluation dataset: 고정 평가셋 case 수, route coverage, monitoring dimension 분포
  • Parsing engine evaluation: 선택한 PDF/폴더를 여러 엔진으로 추출해 latency, 문자 수, block 수, table-like line 등 지표를 CSV/JSON/sample로 저장
  • Conversation metrics: 현재 대화의 route, source 수, latency, scope_decision, retrieval coverage 등 monitoring metadata 요약
  • Chat Monitoring trace viewer: assistant 응답 row에서 특정 턴을 선택해 Trace summary, Scope / routing, Advanced diagnostics 세 tab으로 확인합니다. 기본 화면에는 핵심 trace, 직전 성공 응답 대비 diff, 자동 debug hint를 모으고, retrieval/rerank, sources, answer/citation raw detail은 필요할 때만 펼쳐 봅니다. 선택한 trace는 issue report로 바로 저장할 수 있습니다.

TODO

  • 일반 사용자 UX와 분리된 Monitoring Mode의 진입 방식과 노출 범위를 정합니다.
  • 데이터 준비 상태와 검색 가능 여부를 한눈에 파악할 수 있는 대시보드 방향을 잡습니다.
  • 질문 처리 흐름을 추적해 검색 실패, 라우팅 오류, 답변 품질 저하 원인을 확인할 수 있게 합니다.
  • parsing·chunking·retrieval·rerank·모델 변경의 품질, 답변 변화량, 비용/latency를 비교할 수 있는 관측 지표를 정리합니다.
  • 설정 변경이나 파이프라인 개선 전후를 비교할 수 있는 실험·평가 흐름을 마련합니다.
  • Monitoring Mode가 일반 실행 경로에 영향을 주지 않는지 회귀 테스트로 보호합니다.

About

로컬 디바이스에 저장되어있는 증권사 리포트(PDF)의 내용을 검색할 수 있는 LangChain, LangGraph기반 AI agent

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Packages

 
 
 

Contributors