[add] ai 분석 됨. 프론트엔드만 다듬자
This commit is contained in:
parent
68e809bbc8
commit
7de2592ab2
File diff suppressed because it is too large
Load Diff
|
|
@ -119,3 +119,47 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
|
||||||
"RISK JUDGE", judge_decision, situation, returns_losses
|
"RISK JUDGE", judge_decision, situation, returns_losses
|
||||||
)
|
)
|
||||||
risk_manager_memory.add_situations([(situation, result)])
|
risk_manager_memory.add_situations([(situation, result)])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_final_report(final_state: dict) -> str:
|
||||||
|
"""Generate a final, comprehensive report from the final state."""
|
||||||
|
|
||||||
|
report_parts = []
|
||||||
|
report_parts.append(f"# 최종 분석 보고서: {final_state.get('company_of_interest', 'N/A')}")
|
||||||
|
report_parts.append(f"**분석 기준일:** {final_state.get('trade_date', 'N/A')}")
|
||||||
|
report_parts.append("---")
|
||||||
|
|
||||||
|
# 각 분석가 리포트 추가
|
||||||
|
if final_state.get('market_report'):
|
||||||
|
report_parts.append("## 시장 분석가 리포트")
|
||||||
|
report_parts.append(final_state['market_report'])
|
||||||
|
|
||||||
|
if final_state.get('sentiment_report'):
|
||||||
|
report_parts.append("## 소셜 미디어 분석가 리포트")
|
||||||
|
report_parts.append(final_state['sentiment_report'])
|
||||||
|
|
||||||
|
if final_state.get('news_report'):
|
||||||
|
report_parts.append("## 뉴스 분석가 리포트")
|
||||||
|
report_parts.append(final_state['news_report'])
|
||||||
|
|
||||||
|
if final_state.get('fundamentals_report'):
|
||||||
|
report_parts.append("## 재무 분석가 리포트")
|
||||||
|
report_parts.append(final_state['fundamentals_report'])
|
||||||
|
|
||||||
|
# 투자 토론 요약 추가
|
||||||
|
if final_state.get('investment_debate_state'):
|
||||||
|
debate = final_state['investment_debate_state']
|
||||||
|
report_parts.append("## 투자 결정 토론 요약")
|
||||||
|
report_parts.append(f"**심사위원 최종 결정:** {debate.get('judge_decision', 'N/A')}")
|
||||||
|
|
||||||
|
# 최종 투자 계획 및 결정 추가
|
||||||
|
if final_state.get('investment_plan'):
|
||||||
|
report_parts.append("## 최종 투자 계획")
|
||||||
|
report_parts.append(final_state['investment_plan'])
|
||||||
|
|
||||||
|
if final_state.get('final_trade_decision'):
|
||||||
|
report_parts.append("## 최종 거래 결정")
|
||||||
|
report_parts.append(final_state['final_trade_decision'])
|
||||||
|
|
||||||
|
report = "\n\n".join(report_parts)
|
||||||
|
return report
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# TradingAgents/graph/setup.py
|
# TradingAgents/graph/setup.py
|
||||||
|
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any, List
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langgraph.graph import END, StateGraph, START
|
from langgraph.graph import END, StateGraph, START
|
||||||
from langgraph.prebuilt import ToolNode
|
from langgraph.prebuilt import ToolNode
|
||||||
|
|
@ -40,9 +40,7 @@ class GraphSetup:
|
||||||
self.risk_manager_memory = risk_manager_memory
|
self.risk_manager_memory = risk_manager_memory
|
||||||
self.conditional_logic = conditional_logic
|
self.conditional_logic = conditional_logic
|
||||||
|
|
||||||
def setup_graph(
|
def setup_graph(self, selected_analysts: List[str]):
|
||||||
self, selected_analysts=["market", "social", "news", "fundamentals"]
|
|
||||||
):
|
|
||||||
"""Set up and compile the agent workflow graph.
|
"""Set up and compile the agent workflow graph.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from pathlib import Path
|
||||||
import json
|
import json
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Dict, Any, Tuple, List, Optional
|
from typing import Dict, Any, Tuple, List, Optional
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langgraph.prebuilt import ToolNode
|
from langgraph.prebuilt import ToolNode
|
||||||
|
|
@ -31,19 +32,20 @@ class TradingAgentsGraph:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
selected_analysts=["market", "social", "news", "fundamentals"],
|
|
||||||
debug=False,
|
|
||||||
config: Dict[str, Any] = None,
|
config: Dict[str, Any] = None,
|
||||||
|
progress_callback=None,
|
||||||
|
debug=False,
|
||||||
):
|
):
|
||||||
"""Initialize the trading agents graph and components.
|
"""Initialize the trading agents graph and components.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
selected_analysts: List of analyst types to include
|
|
||||||
debug: Whether to run in debug mode
|
|
||||||
config: Configuration dictionary. If None, uses default config
|
config: Configuration dictionary. If None, uses default config
|
||||||
|
progress_callback: Async function to send progress updates
|
||||||
|
debug: Whether to run in debug mode
|
||||||
"""
|
"""
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.config = config or DEFAULT_CONFIG
|
self.config = config or DEFAULT_CONFIG
|
||||||
|
self.progress_callback = progress_callback
|
||||||
|
|
||||||
# Update the interface's config
|
# Update the interface's config
|
||||||
set_config(self.config)
|
set_config(self.config)
|
||||||
|
|
@ -95,8 +97,9 @@ class TradingAgentsGraph:
|
||||||
self.ticker = None
|
self.ticker = None
|
||||||
self.log_states_dict = {} # date to full state dict
|
self.log_states_dict = {} # date to full state dict
|
||||||
|
|
||||||
# Set up the graph
|
# Set up the graph with default analysts initially
|
||||||
self.graph = self.graph_setup.setup_graph(selected_analysts)
|
default_analysts = ["market", "social", "news", "fundamentals"]
|
||||||
|
self.graph = self.graph_setup.setup_graph(default_analysts)
|
||||||
|
|
||||||
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
|
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
|
||||||
"""Create tool nodes for different data sources."""
|
"""Create tool nodes for different data sources."""
|
||||||
|
|
@ -143,8 +146,55 @@ class TradingAgentsGraph:
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def invoke(self, input_data: Dict) -> Dict:
|
||||||
|
"""Run the trading agents graph for a web-based request."""
|
||||||
|
|
||||||
|
self.ticker = input_data.get("ticker", "UNKNOWN")
|
||||||
|
trade_date = input_data.get("date", date.today().strftime("%Y-%m-%d"))
|
||||||
|
selected_analysts = input_data.get("selected_analysts", [])
|
||||||
|
|
||||||
|
self.graph = self.graph_setup.setup_graph(selected_analysts)
|
||||||
|
|
||||||
|
init_agent_state = self.propagator.create_initial_state(
|
||||||
|
self.ticker, trade_date
|
||||||
|
)
|
||||||
|
args = self.propagator.get_graph_args()
|
||||||
|
|
||||||
|
final_report = ""
|
||||||
|
final_state_result = None
|
||||||
|
|
||||||
|
# 진행률 계산을 위한 변수
|
||||||
|
total_steps = len(self.graph.nodes)
|
||||||
|
step_count = 0
|
||||||
|
|
||||||
|
# Stream the graph execution to get real-time updates
|
||||||
|
for chunk in self.graph.stream(init_agent_state, **args):
|
||||||
|
# 1 청크당 1단계로 간주
|
||||||
|
step_count += 1
|
||||||
|
for node_name, node_output in chunk.items():
|
||||||
|
if self.progress_callback:
|
||||||
|
agent_name = node_name.replace("_node", "").replace("_", " ").title()
|
||||||
|
message = f"Step {step_count}/{total_steps}: {agent_name} is working..."
|
||||||
|
|
||||||
|
# 계산된 진행률과 함께 콜백 호출
|
||||||
|
asyncio.run(self.progress_callback(
|
||||||
|
"agent_update",
|
||||||
|
message,
|
||||||
|
agent_name,
|
||||||
|
step=step_count,
|
||||||
|
total=total_steps
|
||||||
|
))
|
||||||
|
|
||||||
|
final_state_result = chunk
|
||||||
|
|
||||||
|
if final_state_result:
|
||||||
|
final_report = self.reflector.generate_final_report(final_state_result)
|
||||||
|
self._log_state(trade_date, final_state_result)
|
||||||
|
|
||||||
|
return {"final_report": final_report}
|
||||||
|
|
||||||
def propagate(self, company_name, trade_date):
|
def propagate(self, company_name, trade_date):
|
||||||
"""Run the trading agents graph for a company on a specific date."""
|
"""Run the trading agents graph for a company on a specific date (CLI)."""
|
||||||
|
|
||||||
self.ticker = company_name
|
self.ticker = company_name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from rest_framework import serializers
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.password_validation import validate_password
|
from django.contrib.auth.password_validation import validate_password
|
||||||
from .models import User, UserProfile, AnalysisSession
|
from .models import User, UserProfile, AnalysisSession
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -121,11 +122,17 @@ class CreateAnalysisSessionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AnalysisSession
|
model = AnalysisSession
|
||||||
fields = (
|
fields = (
|
||||||
'ticker', 'analysis_date',
|
'ticker',
|
||||||
'analysts_selected', 'research_depth',
|
'analysts_selected', 'research_depth',
|
||||||
'shallow_thinker', 'deep_thinker'
|
'shallow_thinker', 'deep_thinker'
|
||||||
)
|
)
|
||||||
|
# analysis_date는 create 시점에 자동 생성되므로 필드에서 제외
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""오늘 날짜를 추가하여 세션 생성"""
|
||||||
|
validated_data['analysis_date'] = date.today()
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
def validate_analysts_selected(self, value):
|
def validate_analysts_selected(self, value):
|
||||||
"""선택된 분석가들 검증"""
|
"""선택된 분석가들 검증"""
|
||||||
if not isinstance(value, list) or len(value) == 0:
|
if not isinstance(value, list) or len(value) == 0:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
|
from channels.db import database_sync_to_async
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
|
|
||||||
# CLI 모듈 import (경로 조정 필요)
|
# CLI 모듈 import (경로 조정 필요)
|
||||||
|
|
@ -25,41 +26,50 @@ class TradingAnalysisService:
|
||||||
self.channel_layer = get_channel_layer()
|
self.channel_layer = get_channel_layer()
|
||||||
self.user_channel_group = f"user_{user.id}"
|
self.user_channel_group = f"user_{user.id}"
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def _update_session_status(self, status: str, error_message: Optional[str] = None, final_report: Optional[str] = None):
|
||||||
|
"""세션 상태 업데이트 (비동기 안전)"""
|
||||||
|
self.session.status = status
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if status == 'running':
|
||||||
|
self.session.started_at = now
|
||||||
|
else:
|
||||||
|
self.session.completed_at = now
|
||||||
|
|
||||||
|
if error_message:
|
||||||
|
self.session.error_message = error_message
|
||||||
|
if final_report:
|
||||||
|
self.session.final_report = final_report
|
||||||
|
|
||||||
|
self.session.save()
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def _get_user_profile_and_key(self):
|
||||||
|
"""사용자 프로필 및 API 키 조회 (비동기 안전)"""
|
||||||
|
profile = self.user.profile
|
||||||
|
return profile.get_effective_openai_api_key()
|
||||||
|
|
||||||
async def run_analysis(self):
|
async def run_analysis(self):
|
||||||
"""분석 실행"""
|
"""분석 실행"""
|
||||||
try:
|
try:
|
||||||
# 세션 상태 업데이트
|
await self._update_session_status('running')
|
||||||
self.session.status = 'running'
|
|
||||||
self.session.started_at = datetime.datetime.now()
|
|
||||||
self.session.save()
|
|
||||||
|
|
||||||
# WebSocket으로 시작 알림
|
|
||||||
await self._send_websocket_message({
|
await self._send_websocket_message({
|
||||||
'type': 'analysis_started',
|
'type': 'analysis_started',
|
||||||
'session_id': self.session.id,
|
'session_id': self.session.id,
|
||||||
'message': '분석을 시작합니다...'
|
'message': '분석을 시작합니다...'
|
||||||
})
|
})
|
||||||
|
|
||||||
# 사용자 프로필에서 OpenAI API 키 가져오기
|
api_key = await self._get_user_profile_and_key()
|
||||||
profile = self.user.profile
|
|
||||||
api_key = profile.get_effective_openai_api_key()
|
|
||||||
|
|
||||||
if not api_key:
|
if not api_key:
|
||||||
raise Exception("OpenAI API 키가 설정되지 않았습니다.")
|
raise Exception("OpenAI API 키가 설정되지 않았습니다.")
|
||||||
|
|
||||||
# CLI 설정 준비
|
|
||||||
config = self._prepare_analysis_config(api_key)
|
config = self._prepare_analysis_config(api_key)
|
||||||
|
|
||||||
# 분석 실행
|
|
||||||
result = await self._execute_trading_analysis(config)
|
result = await self._execute_trading_analysis(config)
|
||||||
|
|
||||||
# 결과 저장
|
await self._update_session_status('completed', final_report=result)
|
||||||
self.session.final_report = result
|
|
||||||
self.session.status = 'completed'
|
|
||||||
self.session.completed_at = datetime.datetime.now()
|
|
||||||
self.session.save()
|
|
||||||
|
|
||||||
# WebSocket으로 완료 알림
|
|
||||||
await self._send_websocket_message({
|
await self._send_websocket_message({
|
||||||
'type': 'analysis_completed',
|
'type': 'analysis_completed',
|
||||||
'session_id': self.session.id,
|
'session_id': self.session.id,
|
||||||
|
|
@ -70,17 +80,13 @@ class TradingAnalysisService:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 에러 처리
|
error_msg = str(e)
|
||||||
self.session.status = 'failed'
|
await self._update_session_status('failed', error_message=error_msg)
|
||||||
self.session.error_message = str(e)
|
|
||||||
self.session.completed_at = datetime.datetime.now()
|
|
||||||
self.session.save()
|
|
||||||
|
|
||||||
# WebSocket으로 에러 알림
|
|
||||||
await self._send_websocket_message({
|
await self._send_websocket_message({
|
||||||
'type': 'analysis_failed',
|
'type': 'analysis_failed',
|
||||||
'session_id': self.session.id,
|
'session_id': self.session.id,
|
||||||
'message': f'분석 중 오류가 발생했습니다: {str(e)}'
|
'message': f'분석 중 오류가 발생했습니다: {error_msg}'
|
||||||
})
|
})
|
||||||
|
|
||||||
raise e
|
raise e
|
||||||
|
|
@ -115,9 +121,28 @@ class TradingAnalysisService:
|
||||||
'deep_thinking_model': config['deep_thinker'],
|
'deep_thinking_model': config['deep_thinker'],
|
||||||
})
|
})
|
||||||
|
|
||||||
# TradingAgentsGraph 초기화
|
# 진행 상황 콜백 함수 수정
|
||||||
trading_graph = TradingAgentsGraph(analysis_config)
|
async def progress_callback(message_type: str, content: str, agent: str = None, step: int = 0, total: int = 0):
|
||||||
|
# 백엔드에서 진행률 계산
|
||||||
|
progress_percent = int((step / total) * 99) if total > 0 else 0 # 100%는 완료 시에만
|
||||||
|
await self._send_websocket_message({
|
||||||
|
'type': 'analysis_progress',
|
||||||
|
'session_id': self.session.id,
|
||||||
|
'message_type': message_type,
|
||||||
|
'content': content,
|
||||||
|
'agent': agent,
|
||||||
|
'progress': progress_percent,
|
||||||
|
})
|
||||||
|
|
||||||
|
# TradingAgentsGraph 초기화 (더 상세한 예외 처리)
|
||||||
|
try:
|
||||||
|
trading_graph = TradingAgentsGraph(
|
||||||
|
config=analysis_config,
|
||||||
|
progress_callback=progress_callback
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"TradingAgentsGraph 초기화 실패: {str(e)}")
|
||||||
|
|
||||||
# 분석 입력 데이터 준비
|
# 분석 입력 데이터 준비
|
||||||
input_data = {
|
input_data = {
|
||||||
'ticker': config['ticker'],
|
'ticker': config['ticker'],
|
||||||
|
|
@ -126,50 +151,22 @@ class TradingAnalysisService:
|
||||||
'research_depth': config['research_depth'],
|
'research_depth': config['research_depth'],
|
||||||
}
|
}
|
||||||
|
|
||||||
# 진행 상황 콜백 함수
|
|
||||||
async def progress_callback(message_type: str, content: str, agent: str = None):
|
|
||||||
await self._send_websocket_message({
|
|
||||||
'type': 'analysis_progress',
|
|
||||||
'session_id': self.session.id,
|
|
||||||
'message_type': message_type,
|
|
||||||
'content': content,
|
|
||||||
'agent': agent
|
|
||||||
})
|
|
||||||
|
|
||||||
# 분석 실행 (실제 CLI 로직 호출)
|
# 분석 실행 (실제 CLI 로직 호출)
|
||||||
# 여기서는 간단화된 버전으로 구현
|
try:
|
||||||
# 실제로는 trading_graph.invoke(input_data) 형태로 호출
|
# 여기서 trading_graph.invoke를 비동기로 실행해야 합니다.
|
||||||
|
# 현재 trading_graph.invoke가 동기 함수라고 가정하고,
|
||||||
# 분석 진행 상황 시뮬레이션
|
# asyncio.to_thread를 사용해 비동기 컨텍스트에서 실행합니다.
|
||||||
analysis_steps = [
|
result = await asyncio.to_thread(
|
||||||
("Market Analyst", "시장 데이터 분석 중..."),
|
trading_graph.invoke,
|
||||||
("Social Analyst", "소셜 센티멘트 분석 중..."),
|
input_data
|
||||||
("News Analyst", "뉴스 분석 중..."),
|
)
|
||||||
("Fundamentals Analyst", "기본 분석 중..."),
|
return result['final_report'] # 결과 형식에 따라 조정 필요
|
||||||
("Research Manager", "연구 결과 종합 중..."),
|
except Exception as e:
|
||||||
("Trader", "거래 전략 수립 중..."),
|
raise Exception(f"trading_graph.invoke 실행 실패: {str(e)}")
|
||||||
("Portfolio Manager", "포트폴리오 최적화 중...")
|
|
||||||
]
|
|
||||||
|
|
||||||
final_report_parts = []
|
|
||||||
|
|
||||||
for agent, message in analysis_steps:
|
|
||||||
await progress_callback("agent_update", message, agent)
|
|
||||||
|
|
||||||
# 실제 분석 로직 호출 (여기서는 시뮬레이션)
|
|
||||||
await asyncio.sleep(2) # 실제 분석 시간 시뮬레이션
|
|
||||||
|
|
||||||
# 각 단계별 결과 생성 (실제로는 trading_graph의 결과)
|
|
||||||
step_result = f"## {agent} 분석 결과\n\n{config['ticker']} 종목에 대한 {agent.lower()} 분석을 완료했습니다.\n"
|
|
||||||
final_report_parts.append(step_result)
|
|
||||||
|
|
||||||
# 최종 보고서 생성
|
|
||||||
final_report = "\n\n".join(final_report_parts)
|
|
||||||
|
|
||||||
return final_report
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"분석 실행 중 오류 발생: {str(e)}")
|
# 에러 메시지를 명확하게 다시 던짐
|
||||||
|
raise Exception(f"분석 실행 중 오류: {str(e)}")
|
||||||
|
|
||||||
async def _send_websocket_message(self, message: Dict):
|
async def _send_websocket_message(self, message: Dict):
|
||||||
"""WebSocket으로 메시지 전송"""
|
"""WebSocket으로 메시지 전송"""
|
||||||
|
|
@ -189,24 +186,15 @@ class TradingAnalysisManager:
|
||||||
"""거래 분석 관리자"""
|
"""거래 분석 관리자"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_analysis_session(user, analysis_data: Dict) -> AnalysisSession:
|
@database_sync_to_async
|
||||||
"""분석 세션 생성"""
|
def _get_session(user, session_id):
|
||||||
session = AnalysisSession.objects.create(
|
return AnalysisSession.objects.get(id=session_id, user=user)
|
||||||
user=user,
|
|
||||||
ticker=analysis_data['ticker'],
|
|
||||||
analysis_date=analysis_data['analysis_date'],
|
|
||||||
analysts_selected=analysis_data['analysts_selected'],
|
|
||||||
research_depth=analysis_data['research_depth'],
|
|
||||||
shallow_thinker=analysis_data['shallow_thinker'],
|
|
||||||
deep_thinker=analysis_data['deep_thinker'],
|
|
||||||
)
|
|
||||||
return session
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def start_analysis(user, session_id: int):
|
async def start_analysis(user, session_id: int):
|
||||||
"""분석 시작"""
|
"""분석 시작"""
|
||||||
try:
|
try:
|
||||||
session = AnalysisSession.objects.get(id=session_id, user=user)
|
session = await TradingAnalysisManager._get_session(user, session_id)
|
||||||
service = TradingAnalysisService(user, session)
|
service = TradingAnalysisService(user, session)
|
||||||
result = await service.run_analysis()
|
result = await service.run_analysis()
|
||||||
return result
|
return result
|
||||||
|
|
@ -214,11 +202,13 @@ class TradingAnalysisManager:
|
||||||
raise Exception("분석 세션을 찾을 수 없습니다.")
|
raise Exception("분석 세션을 찾을 수 없습니다.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@database_sync_to_async
|
||||||
def get_user_analysis_sessions(user) -> List[AnalysisSession]:
|
def get_user_analysis_sessions(user) -> List[AnalysisSession]:
|
||||||
"""사용자의 분석 세션 목록 조회"""
|
"""사용자의 분석 세션 목록 조회"""
|
||||||
return AnalysisSession.objects.filter(user=user).order_by('-created_at')
|
return list(AnalysisSession.objects.filter(user=user).order_by('-created_at'))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@database_sync_to_async
|
||||||
def cancel_analysis(user, session_id: int):
|
def cancel_analysis(user, session_id: int):
|
||||||
"""분석 취소"""
|
"""분석 취소"""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from rest_framework.views import APIView
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from apps.authentication.models import AnalysisSession
|
from apps.authentication.models import AnalysisSession
|
||||||
|
|
@ -56,15 +57,15 @@ class StartAnalysisView(APIView):
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""새로운 분석 시작"""
|
"""새로운 분석 시작"""
|
||||||
|
print(f"request.data: {request.data}")
|
||||||
serializer = CreateAnalysisSessionSerializer(data=request.data)
|
serializer = CreateAnalysisSessionSerializer(data=request.data)
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
# 분석 세션 생성
|
|
||||||
session = serializer.save(user=request.user)
|
session = serializer.save(user=request.user)
|
||||||
|
|
||||||
# 백그라운드에서 분석 실행
|
# 별도의 스레드에서 비동기 작업 실행
|
||||||
# 실제 환경에서는 Celery나 다른 task queue를 사용하는 것이 좋습니다
|
thread = threading.Thread(target=self.run_async_task, args=(self._start_analysis_async(request.user, session.id),))
|
||||||
asyncio.create_task(self._start_analysis_async(request.user, session.id))
|
thread.start()
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
'message': '분석이 시작되었습니다.',
|
'message': '분석이 시작되었습니다.',
|
||||||
|
|
@ -74,6 +75,13 @@ class StartAnalysisView(APIView):
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def run_async_task(self, coro):
|
||||||
|
"""새 이벤트 루프에서 비동기 코루틴 실행"""
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_until_complete(coro)
|
||||||
|
loop.close()
|
||||||
|
|
||||||
async def _start_analysis_async(self, user, session_id):
|
async def _start_analysis_async(self, user, session_id):
|
||||||
"""비동기 분석 실행"""
|
"""비동기 분석 실행"""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,32 +1,32 @@
|
||||||
#!/bin/bash
|
# #!/bin/bash
|
||||||
|
|
||||||
echo "🚀 Django 서버 시작 - 데이터베이스 초기화"
|
# echo "🚀 Django 서버 시작 - 데이터베이스 초기화"
|
||||||
|
|
||||||
# Django 설정 모듈 환경 변수 설정
|
# # Django 설정 모듈 환경 변수 설정
|
||||||
export DJANGO_SETTINGS_MODULE=tradingagents_web.settings
|
# export DJANGO_SETTINGS_MODULE=tradingagents_web.settings
|
||||||
|
|
||||||
# 1. 데이터베이스 초기화
|
# # 1. 데이터베이스 초기화
|
||||||
echo "🔄 데이터베이스 초기화 중..."
|
# echo "🔄 데이터베이스 초기화 중..."
|
||||||
docker exec -i tradingagents_mysql mysql -u root -ppassword -e "
|
# docker exec -i tradingagents_mysql mysql -u root -ppassword -e "
|
||||||
DROP DATABASE IF EXISTS tradingagents_db;
|
# DROP DATABASE IF EXISTS tradingagents_db;
|
||||||
CREATE DATABASE tradingagents_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
# CREATE DATABASE tradingagents_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
"
|
# "
|
||||||
|
|
||||||
# 2. 마이그레이션
|
# # 2. 마이그레이션
|
||||||
echo "🔄 마이그레이션 중..."
|
# echo "🔄 마이그레이션 중..."
|
||||||
python manage.py makemigrations authentication
|
# python manage.py makemigrations authentication
|
||||||
python manage.py makemigrations
|
# python manage.py makemigrations
|
||||||
python manage.py migrate
|
# python manage.py migrate
|
||||||
|
|
||||||
# 3. 관리자 계정 생성
|
# # 3. 관리자 계정 생성
|
||||||
echo "🔄 관리자 계정 생성 중..."
|
# echo "🔄 관리자 계정 생성 중..."
|
||||||
python manage.py shell -c "
|
# python manage.py shell -c "
|
||||||
from django.contrib.auth import get_user_model;
|
# from django.contrib.auth import get_user_model;
|
||||||
User = get_user_model();
|
# User = get_user_model();
|
||||||
if not User.objects.filter(email='admin@example.com').exists():
|
# if not User.objects.filter(email='admin@example.com').exists():
|
||||||
User.objects.create_superuser('admin@example.com', 'admin', 'admin123!');
|
# User.objects.create_superuser('admin@example.com', 'admin', 'admin123!');
|
||||||
print('✅ 관리자: admin@example.com / admin123!');
|
# print('✅ 관리자: admin@example.com / admin123!');
|
||||||
"
|
# "
|
||||||
|
|
||||||
# 4. 서버 시작 (환경 변수와 함께)
|
# 4. 서버 시작 (환경 변수와 함께)
|
||||||
echo "🎉 서버 시작!"
|
echo "🎉 서버 시작!"
|
||||||
|
|
|
||||||
|
|
@ -136,9 +136,10 @@ export const WebSocketProvider = ({ children }) => {
|
||||||
...prev,
|
...prev,
|
||||||
[data.session_id]: {
|
[data.session_id]: {
|
||||||
...prev[data.session_id],
|
...prev[data.session_id],
|
||||||
|
status: 'running',
|
||||||
message: data.content,
|
message: data.content,
|
||||||
agent: data.agent,
|
agent: data.agent,
|
||||||
progress: prev[data.session_id]?.progress + 10 || 10
|
progress: data.progress,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -160,7 +161,7 @@ export const WebSocketProvider = ({ children }) => {
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
message: data.message,
|
message: data.message,
|
||||||
progress: 100,
|
progress: 100,
|
||||||
result: data.result
|
result: data.result,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
message.success(data.message);
|
message.success(data.message);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// web/frontend/src/pages/Analysis/Analysis.js
|
// web/frontend/src/pages/Analysis/Analysis.js
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, Divider, Spin, Alert, Typography } from 'antd';
|
import { Card, Divider, Spin, Alert, Typography, message } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import api from '../../services/api';
|
import api from '../../services/api';
|
||||||
import { useWebSocket } from '../../contexts/WebSocketContext';
|
import { useWebSocket } from '../../contexts/WebSocketContext';
|
||||||
|
|
@ -54,7 +54,7 @@ const Analysis = () => {
|
||||||
if(currentSessionId) clearAnalysisProgress(currentSessionId);
|
if(currentSessionId) clearAnalysisProgress(currentSessionId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/api/trading/start-analysis/', values);
|
const response = await api.post('/api/trading/start/', values);
|
||||||
const { session_id } = response.data;
|
const { session_id } = response.data;
|
||||||
|
|
||||||
setCurrentSessionId(session_id);
|
setCurrentSessionId(session_id);
|
||||||
|
|
@ -78,6 +78,20 @@ const Analysis = () => {
|
||||||
clearMessages();
|
clearMessages();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 분석 취소 핸들러
|
||||||
|
const handleCancelAnalysis = async () => {
|
||||||
|
if (!currentSessionId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.post(`/api/trading/cancel/${currentSessionId}/`);
|
||||||
|
message.success('분석이 성공적으로 중단되었습니다.');
|
||||||
|
handleNewAnalysis(); // 상태를 초기화하고 폼으로 돌아감
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err.response?.data?.error || '분석 취소에 실패했습니다.';
|
||||||
|
setError(errorMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (analysisStatus === 'starting') {
|
if (analysisStatus === 'starting') {
|
||||||
return <div style={{ textAlign: 'center', padding: '50px' }}><Spin size="large" tip="분석을 시작하고 있습니다..." /></div>;
|
return <div style={{ textAlign: 'center', padding: '50px' }}><Spin size="large" tip="분석을 시작하고 있습니다..." /></div>;
|
||||||
|
|
@ -92,6 +106,7 @@ const Analysis = () => {
|
||||||
messages={messages.filter(m => m.sessionId === currentSessionId)}
|
messages={messages.filter(m => m.sessionId === currentSessionId)}
|
||||||
finalReport={finalReport}
|
finalReport={finalReport}
|
||||||
onNewAnalysis={handleNewAnalysis}
|
onNewAnalysis={handleNewAnalysis}
|
||||||
|
onCancelAnalysis={handleCancelAnalysis}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// web/frontend/src/pages/Analysis/components/AnalysisForm.js
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Form, Input, Button, Card, Select, Slider, Checkbox, Row, Col, Typography } from 'antd';
|
import { Form, Input, Button, Card, Select, Slider, Checkbox, Row, Col, Typography } from 'antd';
|
||||||
import { FundOutlined, SendOutlined } from '@ant-design/icons';
|
import { FundOutlined, SendOutlined } from '@ant-design/icons';
|
||||||
|
|
@ -18,6 +20,24 @@ const analystsOptions = [
|
||||||
{ label: '재무 분석가 (Fundamentals)', value: 'fundamentals' },
|
{ label: '재무 분석가 (Fundamentals)', value: 'fundamentals' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const shallowThinkerOptions = [
|
||||||
|
{ value: 'gpt-4o-mini', label: 'GPT-4o-mini - 빠르고 효율적인 모델' },
|
||||||
|
{ value: 'gpt-4.1-nano', label: 'GPT-4.1-nano - 초경량 모델' },
|
||||||
|
{ value: 'gpt-4.1-mini', label: 'GPT-4.1-mini - 준수한 성능의 컴팩트 모델' },
|
||||||
|
{ value: 'gpt-4o', label: 'GPT-4o - 표준 모델' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const deepThinkerOptions = [
|
||||||
|
{ value: 'gpt-4.1-nano', label: 'GPT-4.1-nano - 초경량 모델' },
|
||||||
|
{ value: 'gpt-4.1-mini', label: 'GPT-4.1-mini - 준수한 성능의 컴팩트 모델' },
|
||||||
|
{ value: 'gpt-4o', label: 'GPT-4o - 표준 모델' },
|
||||||
|
{ value: 'o4-mini', label: 'o4-mini - 특화된 소형 추론 모델' },
|
||||||
|
{ value: 'o3-mini', label: 'o3-mini - 경량 고급 추론 모델' },
|
||||||
|
{ value: 'o3', label: 'o3 - 전체 고급 추론 모델' },
|
||||||
|
{ value: 'o1', label: 'o1 - 최상위 추론 및 문제 해결 모델' },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
const AnalysisForm = ({ onStartAnalysis, loading }) => {
|
const AnalysisForm = ({ onStartAnalysis, loading }) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
|
@ -83,17 +103,22 @@ const AnalysisForm = ({ onStartAnalysis, loading }) => {
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="shallow_thinker" label="Shallow Thinker 모델">
|
<Form.Item name="shallow_thinker" label="Shallow Thinker 모델">
|
||||||
<Select>
|
<Select>
|
||||||
<Option value="gpt-4o-mini">GPT-4o Mini</Option>
|
{shallowThinkerOptions.map(option => (
|
||||||
<Option value="gpt-4o">GPT-4o</Option>
|
<Option key={option.value} value={option.value}>
|
||||||
<Option value="gpt-4-turbo">GPT-4 Turbo</Option>
|
{option.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="deep_thinker" label="Deep Thinker 모델">
|
<Form.Item name="deep_thinker" label="Deep Thinker 모델">
|
||||||
<Select>
|
<Select>
|
||||||
<Option value="gpt-4o">GPT-4o</Option>
|
{deepThinkerOptions.map(option => (
|
||||||
<Option value="gpt-4-turbo">GPT-4 Turbo</Option>
|
<Option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
@ -116,4 +141,4 @@ const AnalysisForm = ({ onStartAnalysis, loading }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AnalysisForm;
|
export default AnalysisForm;
|
||||||
Loading…
Reference in New Issue