refactor: MCP 공통 함수/httpx 싱글톤, CORS 수정, ML 이벤트루프 블로킹 해소 (#4~#7)
This commit is contained in:
@@ -45,5 +45,14 @@ class Settings:
|
||||
CHART_DAYS = int(os.getenv("CHART_DAYS", "100"))
|
||||
JSON_COMPACT = os.getenv("JSON_COMPACT", "false").lower() == "true"
|
||||
|
||||
# CORS (이슈 5)
|
||||
# 서버-서버 통신용: 브라우저 credentials 불필요.
|
||||
# Docker 환경: MCP_CORS_ORIGINS=http://backend:8000
|
||||
# 개발 환경: MCP_CORS_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
|
||||
MCP_CORS_ORIGINS = os.getenv(
|
||||
"MCP_CORS_ORIGINS",
|
||||
"http://localhost:8000,http://127.0.0.1:8000,http://backend:8000",
|
||||
)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -29,13 +29,22 @@ app = FastAPI(
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# CORS 설정
|
||||
# CORS 설정 (이슈 5)
|
||||
# 문제: allow_origins=["*"] + allow_credentials=True 조합은 CORS 스펙 위반 (브라우저 차단)
|
||||
# 해결: 환경변수 MCP_CORS_ORIGINS로 origin 제한 + allow_credentials=False (서버-서버 통신 불필요)
|
||||
_raw_cors = settings.MCP_CORS_ORIGINS
|
||||
_allowed_origins = (
|
||||
[o.strip() for o in _raw_cors.split(",") if o.strip()]
|
||||
if _raw_cors
|
||||
else ["http://localhost:8000", "http://127.0.0.1:8000", "http://backend:8000"]
|
||||
)
|
||||
logger.info(f"MCP CORS allowed origins: {_allowed_origins}")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_origins=_allowed_origins,
|
||||
allow_credentials=False, # 서버-서버 통신; 브라우저 세션 쿠키 불필요
|
||||
allow_methods=["GET", "POST"],
|
||||
allow_headers=["Content-Type", "Authorization"],
|
||||
)
|
||||
|
||||
|
||||
@@ -93,19 +102,15 @@ async def analyze(request: AnalysisRequest):
|
||||
try:
|
||||
analysis_type = request.analysis_type
|
||||
data = request.data
|
||||
|
||||
|
||||
if analysis_type == "RULE_VALIDATION":
|
||||
# 규칙 검증
|
||||
rule = data.get("rule")
|
||||
market_data = data.get("market_data")
|
||||
|
||||
if not rule or not market_data:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="rule and market_data are required"
|
||||
)
|
||||
|
||||
# 뉴스 정보 추가 수집
|
||||
stock_code = market_data.get("stock_code")
|
||||
news_data = []
|
||||
if stock_code:
|
||||
@@ -115,80 +120,56 @@ async def analyze(request: AnalysisRequest):
|
||||
news_data = news_result["news"]
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch news for validation: {e}")
|
||||
|
||||
result = await ai_client.validate_trading_rule(rule, market_data, news_data)
|
||||
return {
|
||||
"analysis_type": analysis_type,
|
||||
"result": result
|
||||
}
|
||||
|
||||
return {"analysis_type": analysis_type, "result": result}
|
||||
|
||||
elif analysis_type == "MARKET_ANALYSIS":
|
||||
# 시장 분석
|
||||
stock_code = data.get("stock_code")
|
||||
market_data = data.get("market_data")
|
||||
news = data.get("news", [])
|
||||
|
||||
if not stock_code or not market_data:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="stock_code and market_data are required"
|
||||
)
|
||||
|
||||
result = await ai_client.analyze_market(stock_code, market_data, news)
|
||||
return {
|
||||
"analysis_type": analysis_type,
|
||||
"result": result
|
||||
}
|
||||
|
||||
return {"analysis_type": analysis_type, "result": result}
|
||||
|
||||
elif analysis_type == "RISK_ASSESSMENT":
|
||||
# 리스크 평가
|
||||
portfolio = data.get("portfolio")
|
||||
# 이슈 4: request_mcp_risk_assessment()가 trade_plan/portfolio/signals 전달
|
||||
portfolio = data.get("portfolio") or {}
|
||||
trade_plan = data.get("trade_plan")
|
||||
|
||||
if not portfolio or not trade_plan:
|
||||
signals = data.get("signals") or {}
|
||||
if not trade_plan:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="portfolio and trade_plan are required"
|
||||
detail="trade_plan is required"
|
||||
)
|
||||
|
||||
result = await ai_client.assess_risk(portfolio, trade_plan)
|
||||
return {
|
||||
"analysis_type": analysis_type,
|
||||
"result": result
|
||||
}
|
||||
|
||||
return {"analysis_type": analysis_type, "result": result}
|
||||
|
||||
elif analysis_type == "FAIR_PRICE":
|
||||
# 적정가 분석
|
||||
stock_code = data.get("stock_code")
|
||||
market_data = data.get("market_data")
|
||||
financial_data = data.get("financial_data")
|
||||
|
||||
if not stock_code or not market_data:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="stock_code and market_data are required"
|
||||
)
|
||||
|
||||
result = await ai_client.calculate_fair_price(
|
||||
stock_code, market_data, financial_data
|
||||
)
|
||||
return {
|
||||
"analysis_type": analysis_type,
|
||||
"result": result
|
||||
}
|
||||
|
||||
return {"analysis_type": analysis_type, "result": result}
|
||||
|
||||
elif analysis_type == "RULE_OPTIMIZATION":
|
||||
# 트레이딩 규칙 최적화/생성
|
||||
stock_code = data.get("stock_code")
|
||||
market_data = data.get("market_data")
|
||||
|
||||
if not stock_code or not market_data:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="stock_code and market_data are required"
|
||||
)
|
||||
|
||||
# 뉴스 정보 추가 수집
|
||||
news_data = []
|
||||
try:
|
||||
news_result = await mcp_tools.get_news(stock_code, limit=settings.NEWS_LIMIT)
|
||||
@@ -196,36 +177,26 @@ async def analyze(request: AnalysisRequest):
|
||||
news_data = news_result["news"]
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch news for optimization: {e}")
|
||||
|
||||
result = await ai_client.optimize_trading_rules(stock_code, market_data, news_data)
|
||||
return {
|
||||
"analysis_type": analysis_type,
|
||||
"result": result
|
||||
}
|
||||
|
||||
return {"analysis_type": analysis_type, "result": result}
|
||||
|
||||
elif analysis_type == "GENERATE_JOURNAL":
|
||||
# 매매일지 생성
|
||||
trade_info = data.get("trade_info")
|
||||
market_data = data.get("market_data")
|
||||
|
||||
if not trade_info or not market_data:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="trade_info and market_data are required"
|
||||
)
|
||||
|
||||
result = await ai_client.generate_trade_journal(trade_info, market_data)
|
||||
return {
|
||||
"analysis_type": analysis_type,
|
||||
"result": result
|
||||
}
|
||||
|
||||
return {"analysis_type": analysis_type, "result": result}
|
||||
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Unknown analysis type: {analysis_type}"
|
||||
)
|
||||
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user