CUDA 없는 CPU 전용 환경(i7-12700H, 16GB RAM)에서 Ollama로 Llama 3.1 8B를 로컬 실행하고, LangChain·FastAPI·yfinance·pykrx까지 금융 AI Agent 전체 스택을 세팅합니다.
이 시리즈는 아래 사양의 노트북을 기준으로 작성됐습니다.
| 항목 | 사양 | AI 추론 영향 |
|---|---|---|
| CPU | Intel i7-12700H (14코어/20스레드) | llama.cpp 최적, 병렬 토큰 생성 |
| RAM | 16GB LPDDR5 6400MHz | 8B Q4 모델 완전 로드 가능 |
| GPU | Intel Arc A370M 4GB | CUDA 없음 → CPU 추론 사용 |
| 스토리지 | D: 519GB 여유 | 모델 파일 저장 충분 |
| 모델 | 크기(Q4_K_M) | RAM 점유 | 속도 | 용도 |
|---|---|---|---|---|
| llama3.1:8b | ~5.0GB | 7GB | ~5 tok/s | 메인 에이전트 추론 |
| phi3.5:mini | ~2.7GB | 4GB | ~15 tok/s | 빠른 분류·라우팅 |
| qwen2.5:7b | ~4.8GB | 7GB | ~5 tok/s | 한국어 공시 분석 |
Claude API 보조: 복잡한 포트폴리오 분석·멀티 에이전트 오케스트레이션은 claude-sonnet-4-6을 API로 호출합니다. 로컬 모델과 API를 용도에 따라 혼용합니다.
Ollama는 GGUF 양자화 모델을 CPU에서 실행하는 가장 간단한 방법입니다. Windows 설치 후 PowerShell에서 실행합니다.
# 1. Ollama 설치 (Windows)
# https://ollama.com/download 에서 다운로드 후 설치
# 2. 설치 확인
ollama --version
# 3. 메인 모델 풀 (약 5GB — D드라이브에 저장됨)
ollama pull llama3.1:8b
# 4. 한국어 특화 모델 (선택)
ollama pull qwen2.5:7b
# 5. 빠른 라우팅용 소형 모델
ollama pull phi3.5:mini
# 6. 설치된 모델 확인
ollama list# Ollama 서버 실행 (별도 터미널)
ollama serve
# → http://localhost:11434 에서 OpenAI 호환 API 제공
# 동작 테스트
curl http://localhost:11434/api/generate -d '{
"model": "llama3.1:8b",
"prompt": "삼성전자 주가가 오르는 이유 3가지는?",
"stream": false
}'모델 저장 위치 변경: 기본 경로는 C:\Users\[user]\.ollama입니다. D드라이브로 변경하려면 환경변수 OLLAMA_MODELS=D:\ollama-models를 설정하세요.
# 프로젝트 폴더 생성
mkdir D:\ai-agent-finance
cd D:\ai-agent-finance
# 가상환경 생성 (Python 3.13)
python -m venv venv
.\venv\Scripts\activate
# 패키지 설치
pip install --upgrade pip# LangChain 코어
langchain==0.3.25
langchain-community==0.3.25
langchain-ollama==0.3.3
langchain-anthropic==0.3.15
langgraph==0.4.8
# 웹 서버
fastapi==0.115.12
uvicorn[standard]==0.34.3
python-dotenv==1.1.0
pydantic==2.11.5
# 금융 데이터
yfinance==0.2.61
pykrx==1.0.48
pandas==2.2.3
pandas-ta==0.3.14b0
# RAG / 벡터 DB
chromadb==1.0.12
sentence-transformers==3.4.1
# 유틸리티
requests==2.32.3
beautifulsoup4==4.13.4
httpx==0.28.1pip install -r requirements.txt
# Anthropic SDK (Claude API)
pip install anthropicYahoo Finance API를 래핑한 라이브러리로 미국 주식·ETF·지수 데이터를 무료로 가져옵니다.
import yfinance as yf
import pandas as pd
# 단일 종목
ticker = yf.Ticker("AAPL")
# 기본 정보
info = ticker.info
print(f"회사명: {info.get('longName')}")
print(f"현재가: ${info.get('currentPrice')}")
print(f"시총: ${info.get('marketCap'):,}")
print(f"PER: {info.get('trailingPE'):.2f}")
# 6개월 일봉 데이터
hist = ticker.history(period="6mo")
print(hist[['Open', 'High', 'Low', 'Close', 'Volume']].tail())
# 여러 종목 동시 조회
tickers = yf.download(["AAPL", "MSFT", "NVDA", "GOOGL"], period="1mo")
print(tickers['Close'].tail())
# 지수
sp500 = yf.Ticker("^GSPC") # S&P 500
nasdaq = yf.Ticker("^IXIC") # NASDAQ
print(f"S&P500: ${sp500.info.get('regularMarketPrice')}")한국거래소(KRX) 데이터를 파이썬으로 조회합니다. 별도 API 키 없이 사용 가능합니다.
from pykrx import stock
from pykrx import bond
import pandas as pd
from datetime import datetime, timedelta
today = datetime.today().strftime("%Y%m%d")
yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y%m%d")
# 종목 코드로 종목명 조회
name = stock.get_market_ticker_name("005930")
print(f"005930 = {name}") # 삼성전자
# 삼성전자 최근 30일 OHLCV
df = stock.get_market_ohlcv_by_date("20250101", today, "005930")
print(df.tail())
# KOSPI 전체 종목 시세
kospi = stock.get_market_ohlcv_by_ticker(today, market="KOSPI")
print(f"KOSPI 종목 수: {len(kospi)}")
print(kospi.sort_values("거래량", ascending=False).head(5))
# 시장 지수
index_df = stock.get_index_ohlcv_by_date("20250101", today, "1001") # KOSPI
print(index_df.tail())
# 외국인 순매수 상위 종목
foreigner = stock.get_market_net_purchases_of_equities_by_ticker(
today, today, "KOSPI", "외국인"
)
print(foreigner.head(10))pykrx 주의사항: 장 마감 후(오후 4시 이후) 당일 데이터가 업데이트됩니다. 실시간 체결가가 아닌 종가 기준 데이터를 제공합니다.
금융감독원 DART에서 무료 API 키를 발급받아 공시 데이터를 수집합니다.
# 1. https://opendart.fss.or.kr 회원가입
# 2. API 신청 → 인증키 발급 (즉시)
# 3. .env 파일에 저장DART_API_KEY=your_dart_api_key_here
ANTHROPIC_API_KEY=your_anthropic_api_key_here
OLLAMA_BASE_URL=http://localhost:11434import requests
import os
from dotenv import load_dotenv
load_dotenv()
DART_KEY = os.getenv("DART_API_KEY")
def get_company_info(corp_name: str) -> dict:
"""기업명으로 DART 기업 코드 조회"""
url = "https://opendart.fss.or.kr/api/company.json"
params = {"crtfc_key": DART_KEY, "corp_name": corp_name}
res = requests.get(url, params=params)
return res.json()
def get_disclosures(corp_code: str, days: int = 30) -> list:
"""최근 공시 목록 조회"""
from datetime import datetime, timedelta
bgn = (datetime.today() - timedelta(days=days)).strftime("%Y%m%d")
url = "https://opendart.fss.or.kr/api/list.json"
params = {
"crtfc_key": DART_KEY,
"corp_code": corp_code,
"bgn_de": bgn,
"sort": "date",
"sort_mth": "desc",
}
return requests.get(url, params=params).json().get("list", [])
# 테스트
company = get_company_info("삼성전자")
print(company)from langchain_ollama import ChatOllama
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage
import os
from dotenv import load_dotenv
load_dotenv()
# 로컬 Ollama 모델 (무료, CPU 추론)
local_llm = ChatOllama(
model="llama3.1:8b",
base_url="http://localhost:11434",
temperature=0, # 금융 분석: 재현성 우선
num_predict=2048, # 최대 출력 토큰
)
# Claude API (복잡한 분석용)
claude_llm = ChatAnthropic(
model="claude-sonnet-4-6",
api_key=os.getenv("ANTHROPIC_API_KEY"),
temperature=0,
max_tokens=4096,
)
def ask_local(question: str, system: str = "") -> str:
messages = []
if system:
messages.append(SystemMessage(content=system))
messages.append(HumanMessage(content=question))
return local_llm.invoke(messages).content
def ask_claude(question: str, system: str = "") -> str:
messages = []
if system:
messages.append(SystemMessage(content=system))
messages.append(HumanMessage(content=question))
return claude_llm.invoke(messages).content
# 테스트
if __name__ == "__main__":
print("=== Ollama (로컬) ===")
print(ask_local("NVIDIA 주가에 영향을 주는 핵심 요소 3가지는?"))
print("\n=== Claude (API) ===")
print(ask_claude("삼성전자 반도체 사업 리스크를 분석해주세요."))from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from llm_setup import ask_local
app = FastAPI(title="금융 AI Agent API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
class QueryRequest(BaseModel):
question: str
model: str = "local" # "local" | "claude"
class QueryResponse(BaseModel):
answer: str
model_used: str
@app.get("/health")
async def health():
return {"status": "ok"}
@app.post("/ask", response_model=QueryResponse)
async def ask(req: QueryRequest):
if req.model == "claude":
from llm_setup import ask_claude
answer = ask_claude(req.question)
else:
answer = ask_local(req.question)
return QueryResponse(answer=answer, model_used=req.model)# 서버 실행
uvicorn main:app --reload --port 8000
# API 테스트
curl -X POST http://localhost:8000/ask \
-H "Content-Type: application/json" \
-d '{"question": "애플 주식 현재 PER은?"}'
# Swagger UI
# http://localhost:8000/docs"""전체 환경 동작 검증 스크립트"""
import sys
def check(label, fn):
try:
fn()
print(f" ✓ {label}")
except Exception as e:
print(f" ✗ {label}: {e}")
sys.exit(1)
print("=== 금융 AI Agent 환경 검증 ===\n")
# 1. Ollama
def test_ollama():
import requests
r = requests.get("http://localhost:11434/api/tags")
assert r.status_code == 200
models = [m["name"] for m in r.json().get("models", [])]
assert any("llama3.1" in m for m in models), f"llama3.1 모델 없음: {models}"
check("Ollama + llama3.1:8b", test_ollama)
# 2. yfinance
def test_yfinance():
import yfinance as yf
t = yf.Ticker("AAPL")
assert t.info.get("currentPrice") is not None
check("yfinance (미국 주식)", test_yfinance)
# 3. pykrx
def test_pykrx():
from pykrx import stock
from datetime import datetime, timedelta
d = (datetime.today() - timedelta(days=3)).strftime("%Y%m%d")
df = stock.get_market_ohlcv_by_date(d, d, "005930")
assert not df.empty
check("pykrx (한국 주식)", test_pykrx)
# 4. DART API
def test_dart():
import requests, os
from dotenv import load_dotenv
load_dotenv()
key = os.getenv("DART_API_KEY", "")
assert key, "DART_API_KEY 미설정"
r = requests.get(f"https://opendart.fss.or.kr/api/company.json",
params={"crtfc_key": key, "corp_name": "삼성전자"})
assert r.status_code == 200
check("DART API", test_dart)
# 5. LangChain + Ollama
def test_langchain():
from llm_setup import ask_local
ans = ask_local("1+1=?")
assert len(ans) > 0
check("LangChain + Ollama", test_langchain)
# 6. ChromaDB
def test_chroma():
import chromadb
client = chromadb.Client()
col = client.create_collection("test")
col.add(documents=["test doc"], ids=["1"])
assert col.count() == 1
check("ChromaDB", test_chroma)
print("\n✅ 모든 환경 검증 완료. 02 시장 데이터 에이전트로 이동하세요.")# 검증 실행 (Ollama 서버가 실행 중이어야 함)
python verify_all.py
# 기대 출력:
# === 금융 AI Agent 환경 검증 ===
# ✓ Ollama + llama3.1:8b
# ✓ yfinance (미국 주식)
# ✓ pykrx (한국 주식)
# ✓ DART API
# ✓ LangChain + Ollama
# ✓ ChromaDB
# ✅ 모든 환경 검증 완료.