[add] analysis report error solved
This commit is contained in:
parent
ab1b0120c2
commit
eb5a95c74b
|
|
@ -17,6 +17,7 @@ from tradingagents.default_config import DEFAULT_CONFIG
|
|||
from analysis.application.websocket_manager import WebSocketManager
|
||||
from analysis.infra.db_models.analysis import AnalysisStatus
|
||||
|
||||
# 로거 설정 - 모듈명을 명확히 지정
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AnalysisService:
|
||||
|
|
@ -31,6 +32,9 @@ class AnalysisService:
|
|||
self.session = session
|
||||
self.ulid = ulid
|
||||
self.websocket_manager = websocket_manager
|
||||
|
||||
# 서비스 초기화 로그
|
||||
logger.info("🎯 AnalysisService 초기화 완료")
|
||||
|
||||
def get_analysis_list(
|
||||
self,
|
||||
|
|
@ -107,16 +111,19 @@ class AnalysisService:
|
|||
async def _run_analysis(self, analysis_id: str):
|
||||
"""백그라운드에서 실제 분석을 실행하는 메서드"""
|
||||
try:
|
||||
logger.info(f"🔄 분석 시작 - Analysis ID: {analysis_id}")
|
||||
logger.info(f"🔍 analysis_id type: {type(analysis_id)}, value: {repr(analysis_id)}")
|
||||
analysis = AnalysisVO(
|
||||
id=analysis_id,
|
||||
status=AnalysisStatus.RUNNING,
|
||||
updated_at=datetime.now()
|
||||
)
|
||||
|
||||
logger.info(f"🔍 Created AnalysisVO.id: {analysis.id}, type: {type(analysis.id)}")
|
||||
analysis = self.analysis_repo.update(analysis)
|
||||
if not analysis:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")
|
||||
|
||||
|
||||
await self.websocket_manager.send_analysis_update(
|
||||
analysis_id=analysis_id,
|
||||
update_type="status_changed",
|
||||
|
|
@ -125,13 +132,14 @@ class AnalysisService:
|
|||
|
||||
|
||||
|
||||
|
||||
# TradingAgentsGraph 설정 및 실행
|
||||
if analysis:
|
||||
config = self._create_config(analysis)
|
||||
|
||||
# 분석 실행 (실제 구현)
|
||||
await self._execute_trading_analysis(analysis_id, analysis, config)
|
||||
|
||||
logger.info(f"🔄 분석 완료 - Analysis ID: {analysis_id}")
|
||||
# 완료 상태로 업데이트
|
||||
completed_analysis = AnalysisVO(
|
||||
id=analysis_id,
|
||||
|
|
@ -144,8 +152,10 @@ class AnalysisService:
|
|||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"🔴 분석 실패 - Analysis ID: {analysis_id}, 오류: {str(e)}")
|
||||
now = datetime.now()
|
||||
updates = AnalysisVO(
|
||||
id=analysis_id,
|
||||
status=AnalysisStatus.FAILED,
|
||||
error_message=str(e),
|
||||
completed_at = now,
|
||||
|
|
@ -158,7 +168,7 @@ class AnalysisService:
|
|||
|
||||
def _create_config(self, analysis: AnalysisVO) -> dict:
|
||||
"""분석 설정을 생성하는 메서드"""
|
||||
config = {}
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config.update({
|
||||
"max_debate_rounds": analysis.research_depth,
|
||||
"max_risk_discuss_rounds": analysis.research_depth,
|
||||
|
|
@ -172,9 +182,9 @@ class AnalysisService:
|
|||
async def _execute_trading_analysis(self, analysis_id: str, analysis: AnalysisVO, config: dict):
|
||||
"""실제 TradingAgentsGraph를 실행하는 메서드"""
|
||||
try:
|
||||
logger.info(f"Starting trading analysis for {analysis_id} with ticker {analysis.ticker}")
|
||||
logger.info(f"Analysts selected: {analysis.analysts_selected}")
|
||||
logger.info(f"Config: {config}")
|
||||
logger.info(f"📊 거래 분석 시작 - ID: {analysis_id}, 티커: {analysis.ticker}")
|
||||
logger.info(f"👥 선택된 분석가들: {analysis.analysts_selected}")
|
||||
logger.info(f"⚙️ 설정: {config}")
|
||||
|
||||
# TradingAgentsGraph 초기화
|
||||
graph = TradingAgentsGraph(
|
||||
|
|
@ -182,7 +192,7 @@ class AnalysisService:
|
|||
config=config,
|
||||
debug=True
|
||||
)
|
||||
logger.info("TradingAgentsGraph initialized successfully")
|
||||
logger.info("✅ TradingAgentsGraph 초기화 완료")
|
||||
|
||||
# 초기 상태 생성
|
||||
init_agent_state = graph.propagator.create_initial_state(
|
||||
|
|
@ -192,12 +202,12 @@ class AnalysisService:
|
|||
args = graph.propagator.get_graph_args()
|
||||
|
||||
# 분석 실행 및 결과 처리
|
||||
logger.info("Starting graph execution...")
|
||||
logger.info("🚀 그래프 실행 시작...")
|
||||
trace = []
|
||||
chunk_count = 0
|
||||
async for chunk in graph.graph.astream(init_agent_state, **args):
|
||||
chunk_count += 1
|
||||
logger.info(f"Processing chunk {chunk_count}: {list(chunk.keys()) if chunk else 'Empty chunk'}")
|
||||
logger.info(f"📦 청크 처리 중 {chunk_count}: {list(chunk.keys()) if chunk else '빈 청크'}")
|
||||
trace.append(chunk)
|
||||
|
||||
# 실시간으로 분석 결과 업데이트
|
||||
|
|
@ -222,8 +232,11 @@ class AnalysisService:
|
|||
self.analysis_repo.update(updates)
|
||||
|
||||
self.session.commit()
|
||||
|
||||
logger.info(f"🎉 분석 완료 - ID: {analysis_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"🔴 분석 실패 - Analysis ID: {analysis_id}, 오류: {str(e)}")
|
||||
raise Exception(f"Analysis execution failed: {str(e)}")
|
||||
|
||||
async def _process_analysis_chunk(self, analysis_id: str, chunk: dict):
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ from typing import List, Dict, Union
|
|||
from analysis.infra.db_models.analysis import AnalysisStatus
|
||||
|
||||
class Analysis(BaseModel):
|
||||
id: str
|
||||
id: str | None = None
|
||||
member_id: str | None = None
|
||||
ticker: str | None = None
|
||||
analysis_date: date | None = None
|
||||
analysts_selected: list[str] = []
|
||||
research_depth: int = 3
|
||||
llm_provider: str = "openai"
|
||||
backend_url: str = "https://api.openai.com/v1"
|
||||
shallow_thinker: str = "gpt-4o"
|
||||
deep_thinker: str = "o3"
|
||||
research_depth: int = 1
|
||||
llm_provider: str = "google"
|
||||
backend_url: str = "https://generativelanguage.googleapis.com/v1"
|
||||
shallow_thinker: str = "gemini-2.5-flash-lite-preview-06-17"
|
||||
deep_thinker: str = "gemini-2.5-flash-lite-preview-06-17"
|
||||
status: AnalysisStatus = AnalysisStatus.PENDING
|
||||
|
||||
# 개별 분석가 리포트들
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
|
|||
from sqlmodel import SQLModel, Field, JSON, Relationship
|
||||
import enum
|
||||
from sqlalchemy import Column, Text
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
|
||||
# TYPE_CHECKING을 사용해서 circular import 방지
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -32,22 +33,22 @@ class Analysis(SQLModel, table=True):
|
|||
status: AnalysisStatus = Field(default=AnalysisStatus.PENDING)
|
||||
|
||||
# 개별 분석가 리포트들
|
||||
market_report: str | None = Field(default=None, sa_column=Column(Text), description="Market Analyst 리포트")
|
||||
sentiment_report: str | None = Field(default=None, sa_column=Column(Text), description="Social Analyst 리포트")
|
||||
news_report: str | None = Field(default=None, sa_column=Column(Text), description="News Analyst 리포트")
|
||||
fundamentals_report: str | None = Field(default=None, sa_column=Column(Text), description="Fundamentals Analyst 리포트")
|
||||
market_report: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="Market Analyst 리포트")
|
||||
sentiment_report: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="Social Analyst 리포트")
|
||||
news_report: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="News Analyst 리포트")
|
||||
fundamentals_report: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="Fundamentals Analyst 리포트")
|
||||
|
||||
# 팀별 의사결정 과정
|
||||
investment_debate_state: dict | None = Field(default=None, sa_column=Column(JSON), description="Research Team 토론 과정")
|
||||
trader_investment_plan: str | None = Field(default=None, sa_column=Column(Text), description="Trading Team 계획")
|
||||
trader_investment_plan: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="Trading Team 계획")
|
||||
risk_debate_state: dict | None = Field(default=None, sa_column=Column(JSON), description="Risk Management Team 토론 과정")
|
||||
|
||||
# 최종 결과물
|
||||
final_trade_decision: str | None = Field(default=None, sa_column=Column(Text), description="최종 거래 결정")
|
||||
final_report: str | None = Field(default=None, sa_column=Column(Text), description="전체 통합 리포트")
|
||||
final_trade_decision: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="최종 거래 결정")
|
||||
final_report: str | None = Field(default=None, sa_column=Column(LONGTEXT), description="전체 통합 리포트")
|
||||
|
||||
# 실행 결과 정보
|
||||
error_message: str | None = Field(default=None, sa_column=Column(Text))
|
||||
error_message: str | None = Field(default=None, sa_column=Column(LONGTEXT))
|
||||
completed_at: datetime | None = None
|
||||
created_at : datetime = Field(nullable=False)
|
||||
updated_at : datetime = Field(nullable=False)
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ from analysis.interface.dto import TradingAnalysisRequest
|
|||
from utils.db_utils import row_to_dict
|
||||
from sqlalchemy.orm import selectinload
|
||||
from datetime import datetime, date
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AnalysisRepository(IAnalysisRepository):
|
||||
def __init__(self, session: Session):
|
||||
self.session = session
|
||||
|
||||
def find_by_member_id(self, member_id: str) -> list[AnalysisVO] | None:
|
||||
query = select(Analysis).where(Analysis.member_id == member_id)
|
||||
query = select(Analysis).where(Analysis.member_id == member_id).order_by(Analysis.created_at.desc())
|
||||
analyses = self.session.exec(query).all()
|
||||
|
||||
if not analyses:
|
||||
|
|
@ -33,13 +36,14 @@ class AnalysisRepository(IAnalysisRepository):
|
|||
|
||||
self.session.add(new_analysis)
|
||||
self.session.flush()
|
||||
self.session.refresh(new_analysis)
|
||||
|
||||
|
||||
analysis.id = new_analysis.id
|
||||
return analysis
|
||||
|
||||
def update(self, analysis_vo: AnalysisVO) -> AnalysisVO | None:
|
||||
analysis = self.session.get(Analysis, analysis_vo.id)
|
||||
logger.info(f"🔄 분석 업데이트 - Analysis ID: {analysis_vo.id}")
|
||||
if not analysis:
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ def get_analysis_list_for_member(
|
|||
AnalysisSessionResponse(
|
||||
id=analysis.id,
|
||||
ticker=analysis.ticker,
|
||||
status=analysis.status
|
||||
status=analysis.status,
|
||||
shallow_thinker=analysis.shallow_thinker,
|
||||
deep_thinker=analysis.deep_thinker
|
||||
) for analysis in analyses
|
||||
]
|
||||
|
||||
|
|
@ -46,9 +48,7 @@ def start_analysis_session(
|
|||
try:
|
||||
new_analysis = analysis_service.create_analysis(current_member.id, request, background_tasks)
|
||||
return AnalysisSessionResponse(
|
||||
id=new_analysis.id,
|
||||
ticker=new_analysis.ticker,
|
||||
status=new_analysis.status
|
||||
**new_analysis.model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ class TradingAnalysisRequest(BaseModel):
|
|||
ticker: str = "NVDA"
|
||||
analysis_date: str = "2025-07-07"
|
||||
analysts: List[AnalystType] = [AnalystType.MARKET, AnalystType.SOCIAL, AnalystType.NEWS, AnalystType.FUNDAMENTALS]
|
||||
research_depth: int = 3
|
||||
llm_provider: str = "openai"
|
||||
backend_url: str = "https://api.openai.com/v1"
|
||||
shallow_thinker: str = "gpt-4o-mini"
|
||||
deep_thinker: str = "gpt-4o-mini"
|
||||
research_depth: int = 1
|
||||
llm_provider: str = "google"
|
||||
backend_url: str = "https://generativelanguage.googleapis.com/v1"
|
||||
shallow_thinker: str = "gemini-2.5-flash-lite-preview-06-17"
|
||||
deep_thinker: str = "gemini-2.5-flash-lite-preview-06-17"
|
||||
|
||||
class AnalysisSessionResponse(BaseModel):
|
||||
id : str
|
||||
ticker : str
|
||||
status : AnalysisStatus
|
||||
shallow_thinker : str
|
||||
deep_thinker : str
|
||||
|
||||
class AnalysisProgressUpdate(BaseModel):
|
||||
analysis_id: str
|
||||
|
|
|
|||
|
|
@ -2,19 +2,12 @@ from fastapi import FastAPI
|
|||
from utils.database import create_db_and_tables
|
||||
from utils.containers import Container
|
||||
|
||||
|
||||
from analysis.interface.controller.analysis_controller import router as analysis_router
|
||||
from member.interface.controller.member_controller import router as member_router
|
||||
import logging
|
||||
from utils.logger import setup_logging
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.StreamHandler(), # 콘솔 출력
|
||||
]
|
||||
)
|
||||
setup_logging()
|
||||
|
||||
|
||||
|
||||
|
|
@ -24,7 +17,15 @@ app.container = Container()
|
|||
app.include_router(analysis_router)
|
||||
app.include_router(member_router)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def startup_db_client():
|
||||
create_db_and_tables()
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("🚀 FastAPI 애플리케이션 시작")
|
||||
create_db_and_tables()
|
||||
logger.info("📊 데이터베이스 초기화 완료")
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("📍 루트 엔드포인트 호출됨")
|
||||
return {"message": "Trading Agents API"}
|
||||
|
|
@ -11,6 +11,7 @@ click-didyoumean==0.3.1
|
|||
click-plugins==1.1.1.2
|
||||
click-repl==0.3.0
|
||||
colorama==0.4.6
|
||||
colorlog==6.9.0
|
||||
cryptography==45.0.4
|
||||
dependency-injector==4.48.1
|
||||
dnspython==2.7.0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
import sys
|
||||
import logging
|
||||
import colorlog
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# 강화된 컬러 로깅 설정
|
||||
def setup_logging():
|
||||
# 루트 로거 설정
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
# 기존 핸들러 제거 (중복 방지)
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# 컬러 콘솔 핸들러 생성
|
||||
console_handler = colorlog.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
|
||||
# 컬러 포맷터 설정
|
||||
color_formatter = colorlog.ColoredFormatter(
|
||||
"%(log_color)s%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
log_colors={
|
||||
'DEBUG': 'cyan',
|
||||
'INFO': 'green',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red,bg_white',
|
||||
},
|
||||
secondary_log_colors={},
|
||||
style='%'
|
||||
)
|
||||
console_handler.setFormatter(color_formatter)
|
||||
|
||||
# 핸들러 추가
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
# FastAPI/uvicorn 로거 설정
|
||||
logging.getLogger("uvicorn").setLevel(logging.INFO)
|
||||
logging.getLogger("uvicorn.access").setLevel(logging.INFO)
|
||||
logging.getLogger("fastapi").setLevel(logging.INFO)
|
||||
|
||||
# 애플리케이션 로거들 설정
|
||||
logging.getLogger("analysis").setLevel(logging.INFO)
|
||||
logging.getLogger("analysis.application").setLevel(logging.INFO)
|
||||
logging.getLogger("analysis.application.analysis_service").setLevel(logging.INFO)
|
||||
|
||||
# 로깅 설정 완료 메시지
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("✅ 컬러 로깅 설정 완료")
|
||||
|
||||
|
||||
def get_colored_logger(
|
||||
name: str,
|
||||
level: int = logging.INFO,
|
||||
format_string: Optional[str] = None
|
||||
) -> logging.Logger:
|
||||
"""
|
||||
컬러 로거를 가져오는 함수
|
||||
|
||||
Args:
|
||||
name: 로거 이름
|
||||
level: 로깅 레벨
|
||||
format_string: 커스텀 포맷 문자열
|
||||
|
||||
Returns:
|
||||
설정된 컬러 로거
|
||||
"""
|
||||
|
||||
# 기본 포맷 설정
|
||||
if format_string is None:
|
||||
format_string = (
|
||||
"%(log_color)s%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
# 컬러 포맷터 생성
|
||||
color_formatter = colorlog.ColoredFormatter(
|
||||
format_string,
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
log_colors={
|
||||
'DEBUG': 'cyan',
|
||||
'INFO': 'green',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red,bg_white',
|
||||
},
|
||||
secondary_log_colors={},
|
||||
style='%'
|
||||
)
|
||||
|
||||
# 로거 생성
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
|
||||
# 이미 핸들러가 있으면 기존 로거 반환
|
||||
if logger.handlers:
|
||||
return logger
|
||||
|
||||
# 콘솔 핸들러 생성
|
||||
console_handler = colorlog.StreamHandler()
|
||||
console_handler.setFormatter(color_formatter)
|
||||
console_handler.setLevel(level)
|
||||
|
||||
# 핸들러 추가
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# 상위 로거로 전파 방지
|
||||
logger.propagate = False
|
||||
|
||||
return logger
|
||||
Loading…
Reference in New Issue