....
This commit is contained in:
parent
9803b27741
commit
681472df58
@ -77,8 +77,23 @@ fun getLlamaBinPath(): String {
|
|||||||
else -> "$basePath/llama-server"
|
else -> "$basePath/llama-server"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun main() = application {
|
|
||||||
|
|
||||||
|
fun initLogger(isDebug: Boolean) {
|
||||||
|
val root = org.slf4j.LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as ch.qos.logback.classic.Logger
|
||||||
|
|
||||||
|
if (isDebug) {
|
||||||
|
root.level = ch.qos.logback.classic.Level.DEBUG
|
||||||
|
println("🛠️ 디버그 모드: 상세 로그를 출력합니다.")
|
||||||
|
} else {
|
||||||
|
root.level = ch.qos.logback.classic.Level.ERROR
|
||||||
|
// 특정 라이브러리(Exposed)만 콕 집어서 끄기
|
||||||
|
(org.slf4j.LoggerFactory.getLogger("Exposed") as ch.qos.logback.classic.Logger).level = ch.qos.logback.classic.Level.OFF
|
||||||
|
println("🤫 운영 모드: 에러 로그만 출력합니다.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
initLogger(AutoTradingManager.DETAILLOG)
|
||||||
val trayState = rememberTrayState()
|
val trayState = rememberTrayState()
|
||||||
var isWindowOpen by remember { mutableStateOf(false) } // 창의 표시 상태 관리
|
var isWindowOpen by remember { mutableStateOf(false) } // 창의 표시 상태 관리
|
||||||
|
|
||||||
@ -193,10 +208,10 @@ fun main() = application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.modelPath.isNotEmpty()) {
|
if (config.modelPath.isNotEmpty()) {
|
||||||
LlamaServerManager.startServer(binPath, config.modelPath,port = 8080)
|
LlamaServerManager.startServer(binPath, config.modelPath,port = AutoTradingManager.LLM_PORT)
|
||||||
}
|
}
|
||||||
if (config.embedModelPath.isNotEmpty()) {
|
if (config.embedModelPath.isNotEmpty()) {
|
||||||
LlamaServerManager.startServer(binPath, config.embedModelPath, port = 8081)
|
LlamaServerManager.startServer(binPath, config.embedModelPath, port = AutoTradingManager.EMBEDDING_PORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 대시보드로 화면 전환
|
// 대시보드로 화면 전환
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import okhttp3.Request
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.apache.lucene.store.MMapDirectory
|
import org.apache.lucene.store.MMapDirectory
|
||||||
import org.slf4j.MDC.put
|
import org.slf4j.MDC.put
|
||||||
|
import service.AutoTradingManager
|
||||||
import service.FinancialAnalyzer
|
import service.FinancialAnalyzer
|
||||||
import service.InvestmentScores
|
import service.InvestmentScores
|
||||||
import service.TechnicalAnalyzer
|
import service.TechnicalAnalyzer
|
||||||
@ -55,12 +56,12 @@ object RagService {
|
|||||||
|
|
||||||
// 임베딩 모델 (8081) 및 채팅 모델 (8080) 설정
|
// 임베딩 모델 (8081) 및 채팅 모델 (8080) 설정
|
||||||
private val embeddingModel = OpenAiEmbeddingModel.builder()
|
private val embeddingModel = OpenAiEmbeddingModel.builder()
|
||||||
.baseUrl("http://127.0.0.1:8081/v1")
|
.baseUrl("http://127.0.0.1:${AutoTradingManager.EMBEDDING_PORT}/v1")
|
||||||
.apiKey("unused")
|
.apiKey("unused")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val chatModel = OpenAiChatModel.builder()
|
private val chatModel = OpenAiChatModel.builder()
|
||||||
.baseUrl("http://127.0.0.1:8080/v1")
|
.baseUrl("http://127.0.0.1:${AutoTradingManager.LLM_PORT}/v1")
|
||||||
.apiKey("unused")
|
.apiKey("unused")
|
||||||
.temperature(0.0) // [중요] 0.0으로 설정하여 결정론적 응답 유도
|
.temperature(0.0) // [중요] 0.0으로 설정하여 결정론적 응답 유도
|
||||||
.timeout(Duration.ofSeconds(60))
|
.timeout(Duration.ofSeconds(60))
|
||||||
|
|||||||
@ -49,6 +49,9 @@ import kotlin.math.*
|
|||||||
// service/AutoTradingManager.kt
|
// service/AutoTradingManager.kt
|
||||||
typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit
|
typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit
|
||||||
object AutoTradingManager {
|
object AutoTradingManager {
|
||||||
|
val DETAILLOG = true
|
||||||
|
val LLM_PORT = 13080
|
||||||
|
val EMBEDDING_PORT = 13081
|
||||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||||
private var discoveryJob: Job? = null
|
private var discoveryJob: Job? = null
|
||||||
|
|
||||||
@ -418,10 +421,10 @@ object AutoTradingManager {
|
|||||||
val config = KisSession.config
|
val config = KisSession.config
|
||||||
// LLM 서버 시작 (설정된 모델 경로 사용)
|
// LLM 서버 시작 (설정된 모델 경로 사용)
|
||||||
if (config.modelPath.isNotEmpty()) {
|
if (config.modelPath.isNotEmpty()) {
|
||||||
LlamaServerManager.startServer(binPath, config.modelPath,port = 8080)
|
LlamaServerManager.startServer(binPath, config.modelPath,port = LLM_PORT)
|
||||||
}
|
}
|
||||||
if (config.embedModelPath.isNotEmpty()) {
|
if (config.embedModelPath.isNotEmpty()) {
|
||||||
LlamaServerManager.startServer(binPath, config.embedModelPath, port = 8081)
|
LlamaServerManager.startServer(binPath, config.embedModelPath, port = EMBEDDING_PORT)
|
||||||
}
|
}
|
||||||
KisWebSocketManager.connect()
|
KisWebSocketManager.connect()
|
||||||
isSystemReadyToday = true
|
isSystemReadyToday = true
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.RagService
|
import network.RagService
|
||||||
import util.HardwareDetector
|
import util.HardwareDetector
|
||||||
|
import util.NetworkPortDiagnostic
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
@ -50,6 +51,7 @@ object LlamaServerManager {
|
|||||||
|
|
||||||
fun checkPortStatus(port: Int): String {
|
fun checkPortStatus(port: Int): String {
|
||||||
return try {
|
return try {
|
||||||
|
|
||||||
// netstat 명령어로 해당 포트를 점유 중인 프로세스 확인
|
// netstat 명령어로 해당 포트를 점유 중인 프로세스 확인
|
||||||
val process = Runtime.getRuntime().exec("cmd /c netstat -ano | findstr :$port")
|
val process = Runtime.getRuntime().exec("cmd /c netstat -ano | findstr :$port")
|
||||||
val reader = process.inputStream.bufferedReader()
|
val reader = process.inputStream.bufferedReader()
|
||||||
@ -67,110 +69,107 @@ object LlamaServerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startServer(binPath: String, modelPath: String, port: Int) {
|
fun startServer(binPath: String, modelPath: String, port: Int) {
|
||||||
|
|
||||||
if (processes.containsKey(port) || modelPath.isBlank()) return
|
|
||||||
val os = System.getProperty("os.name").lowercase()
|
val os = System.getProperty("os.name").lowercase()
|
||||||
val arch = System.getProperty("os.arch").lowercase()
|
val arch = System.getProperty("os.arch").lowercase()
|
||||||
val isWin = os.contains("win")
|
val isWin = os.contains("win")
|
||||||
val isMacArm = os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64"))
|
val isMacArm = os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64"))
|
||||||
|
|
||||||
val cpuCores = Runtime.getRuntime().availableProcessors() // HardwareDetector.getCpuCores()와 동일
|
val canUsePort = if (isWin) NetworkPortDiagnostic.testPortAvailability(port) else true
|
||||||
val hasGpu = HardwareDetector.hasNvidiaGpu()
|
if (canUsePort) {
|
||||||
|
if (processes.containsKey(port) || modelPath.isBlank()) return
|
||||||
|
val cpuCores = Runtime.getRuntime().availableProcessors() // HardwareDetector.getCpuCores()와 동일
|
||||||
|
val hasGpu = HardwareDetector.hasNvidiaGpu()
|
||||||
|
val ratio = if (isWin) 0.5 else 0.7
|
||||||
|
val optimalThreads = (cpuCores * ratio).toInt().coerceIn(4, 16)
|
||||||
|
|
||||||
// 1. optimalThreads: 할당 비율 적용 및 최소/최대 범위 제한(Safety Boundary)
|
var optimalGpuLayers = if ((isWin && hasGpu) || isMacArm) 99 else 4
|
||||||
// 과도한 스레드 할당은 오히려 컨텍스트 스위칭 비용을 높여 성능을 저하시킬 수 있습니다.
|
if(HardwareDetector.getCpuName().contains("i7")) {
|
||||||
val ratio = if (isWin) 0.5 else 0.7
|
optimalGpuLayers = 0
|
||||||
val optimalThreads = (cpuCores * ratio).toInt().coerceIn(4, 16)
|
}
|
||||||
|
println("🖥️ OS: $os / Arch: $arch")
|
||||||
|
println("⚙️ 할당 스레드: $optimalThreads (Core: $cpuCores, Ratio: $ratio)")
|
||||||
|
println("🚀 GPU 레이어: $optimalGpuLayers (NVIDIA/MacArm: ${if(optimalGpuLayers == 99) "YES" else "NO"})")
|
||||||
|
|
||||||
// 2. optimalGpuLayers: GPU 가속 조건 (윈도우 NVIDIA 또는 맥 ARM)
|
val command = mutableListOf(
|
||||||
var optimalGpuLayers = if ((isWin && hasGpu) || isMacArm) 99 else 4
|
binPath,
|
||||||
if(HardwareDetector.getCpuName().contains("i7")) {
|
"-m", modelPath,
|
||||||
optimalGpuLayers = 0
|
"--port", port.toString(),
|
||||||
}
|
"-c", if (port == AutoTradingManager.EMBEDDING_PORT) "512" else "8192",
|
||||||
println("🖥️ OS: $os / Arch: $arch")
|
"-ngl", optimalGpuLayers.toString(),
|
||||||
println("⚙️ 할당 스레드: $optimalThreads (Core: $cpuCores, Ratio: $ratio)")
|
"-t", optimalThreads.toString(),
|
||||||
println("🚀 GPU 레이어: $optimalGpuLayers (NVIDIA/MacArm: ${if(optimalGpuLayers == 99) "YES" else "NO"})")
|
"--embedding"
|
||||||
|
)
|
||||||
|
if (port != AutoTradingManager.EMBEDDING_PORT) { // 텍스트 생성용 모델에만 적용
|
||||||
|
command.addAll(listOf(
|
||||||
|
"-b", "512", // Batch size (토큰 병렬 처리량 제한으로 연산 안정화)
|
||||||
|
"--threads-batch", optimalThreads.toString(),
|
||||||
|
"-fa","on" // Flash Attention 활성화 (메모리 절약 및 긴 컨텍스트 연산 안정성 증가)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val pb = ProcessBuilder(command)
|
||||||
|
|
||||||
// val (nGpuLayers, threads) = when {
|
// 2. 윈도우 Vulkan 환경 변수 설정
|
||||||
// os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64")) -> 99 to 8
|
if (isWin && binPath.contains("win-x64")) {
|
||||||
// isWin -> optimalGpuLayers to optimalThreads // NUC Core Ultra 7: GPU 레이어 40 내외, 스레드 12 권장
|
val env = pb.environment()
|
||||||
// else -> 0 to 4 // 인텔 맥 2017 등
|
// 특정 GPU 선택 (내장 GPU가 여러 개일 경우)
|
||||||
// }
|
// env["GGML_VULKAN_DEVICE"] = "0"
|
||||||
|
|
||||||
val command = mutableListOf(
|
// DLL 로드 경로 강제 지정 (bin 폴더 내 dll 참조)
|
||||||
binPath,
|
val libraryPath = File(binPath).parentFile.absolutePath
|
||||||
"-m", modelPath,
|
val currentPath = System.getenv("PATH") ?: ""
|
||||||
"--port", port.toString(),
|
env["PATH"] = "$libraryPath;$currentPath"
|
||||||
"-c", if (port == 8081) "512" else "8192",
|
|
||||||
"-ngl", optimalGpuLayers.toString(),
|
|
||||||
"-t", optimalThreads.toString(),
|
|
||||||
"--embedding"
|
|
||||||
)
|
|
||||||
if (port != 8081) { // 텍스트 생성용 모델에만 적용
|
|
||||||
command.addAll(listOf(
|
|
||||||
"-b", "512", // Batch size (토큰 병렬 처리량 제한으로 연산 안정화)
|
|
||||||
"--threads-batch", optimalThreads.toString(),
|
|
||||||
"-fa","on" // Flash Attention 활성화 (메모리 절약 및 긴 컨텍스트 연산 안정성 증가)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
val pb = ProcessBuilder(command)
|
|
||||||
|
|
||||||
// 2. 윈도우 Vulkan 환경 변수 설정
|
println("🔧 [Vulkan] 환경 변수 설정 완료: $libraryPath")
|
||||||
if (isWin && binPath.contains("win-x64")) {
|
}
|
||||||
val env = pb.environment()
|
|
||||||
// 특정 GPU 선택 (내장 GPU가 여러 개일 경우)
|
|
||||||
// env["GGML_VULKAN_DEVICE"] = "0"
|
|
||||||
|
|
||||||
// DLL 로드 경로 강제 지정 (bin 폴더 내 dll 참조)
|
pb.redirectErrorStream(true)
|
||||||
val libraryPath = File(binPath).parentFile.absolutePath
|
File(binPath).setExecutable(true)
|
||||||
val currentPath = System.getenv("PATH") ?: ""
|
|
||||||
env["PATH"] = "$libraryPath;$currentPath"
|
|
||||||
|
|
||||||
println("🔧 [Vulkan] 환경 변수 설정 완료: $libraryPath")
|
val process = pb.start()
|
||||||
}
|
processes[port] = process
|
||||||
|
println("✅ AI 서버 시작 시도 (Port: $port, Model: ${File(modelPath).name})")
|
||||||
|
if (isWin) {
|
||||||
|
delay(3000)
|
||||||
|
|
||||||
pb.redirectErrorStream(true)
|
val status = checkPortStatus(port)
|
||||||
File(binPath).setExecutable(true)
|
println(status) // 콘솔 로그
|
||||||
|
TradingLogStore.addAnalyzer("System", "Port:$port", status, status.contains("✅"))
|
||||||
|
}
|
||||||
|
|
||||||
val process = pb.start()
|
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
||||||
processes[port] = process
|
var line: String?
|
||||||
println("✅ AI 서버 시작 시도 (Port: $port, Model: ${File(modelPath).name})")
|
while (reader.readLine().also { line = it } != null) {
|
||||||
|
// 로그 출력 (디버깅용)
|
||||||
|
if (AutoTradingManager.DETAILLOG) println("[Server $port] $line")
|
||||||
|
|
||||||
delay(3000)
|
if (line?.contains("server is listening") == true) {
|
||||||
|
println("🚀 AI 서버 준비 완료 (Port: $port)")
|
||||||
val status = checkPortStatus(port)
|
if (port == AutoTradingManager.LLM_PORT){
|
||||||
println(status) // 콘솔 로그
|
AutoTradingManager.llmAnalyser = true
|
||||||
|
}
|
||||||
// UI 로그 스토어에도 기록 (TradingDecisionLog 등에서 확인 가능)
|
if (port == AutoTradingManager.EMBEDDING_PORT){
|
||||||
TradingLogStore.addAnalyzer("System", "Port:$port", status, status.contains("✅"))
|
AutoTradingManager.llmNews = true
|
||||||
|
}
|
||||||
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
if (processes.size > 1) {
|
||||||
var line: String?
|
println("[Cache] ${processes.size}")
|
||||||
while (reader.readLine().also { line = it } != null) {
|
RagService.active()
|
||||||
// 로그 출력 (디버깅용)
|
}
|
||||||
// println("[Server $port] $line")
|
|
||||||
if (line?.contains("server is listening") == true) {
|
|
||||||
println("🚀 AI 서버 준비 완료 (Port: $port)")
|
|
||||||
if (port == 8080){
|
|
||||||
AutoTradingManager.llmAnalyser = true
|
|
||||||
}
|
|
||||||
if (port == 8081){
|
|
||||||
AutoTradingManager.llmNews = true
|
|
||||||
}
|
|
||||||
if (processes.size > 1) {
|
|
||||||
println("[Cache] ${processes.size}")
|
|
||||||
RagService.active()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ AI 서버 실행 실패 (Port: $port): ${e.message}")
|
||||||
|
processes.remove(port)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
println("❌ AI 서버 실행 실패 (Port: $port): ${e.message}")
|
|
||||||
processes.remove(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("🚨 포트 $port 가 보안 정책에 의해 막혀있어 서버 기동을 중단합니다.")
|
||||||
|
TradingLogStore.addAnalyzer("System", "Port:$port", "보안 정책에 의한 포트 차단 감지", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopAll(): Boolean {
|
fun stopAll(): Boolean {
|
||||||
|
|||||||
53
src/main/kotlin/util/NetworkPortDiagnostic.kt
Normal file
53
src/main/kotlin/util/NetworkPortDiagnostic.kt
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
object NetworkPortDiagnostic {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 포트에 소켓을 직접 열어보고 닫음으로써 네트워크 가용성을 테스트합니다.
|
||||||
|
*/
|
||||||
|
fun testPortAvailability(port: Int): Boolean {
|
||||||
|
return try {
|
||||||
|
// 1. ServerSocket 생성 및 바인딩 시도
|
||||||
|
val serverSocket = ServerSocket()
|
||||||
|
// 타임아웃 2초 설정하여 무한 대기 방지
|
||||||
|
serverSocket.reuseAddress = true
|
||||||
|
serverSocket.bind(InetSocketAddress("127.0.0.1", port))
|
||||||
|
|
||||||
|
println("✅ [Diagnosis] 포트 $port 개방 성공: 시스템 및 보안 프로그램에서 허용됨.")
|
||||||
|
|
||||||
|
// 2. 테스트 성공 후 즉시 포트 닫기
|
||||||
|
serverSocket.close()
|
||||||
|
println("✅ [Diagnosis] 테스트 종료 후 포트 $port 정상적으로 다시 닫힘.")
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ [Diagnosis] 포트 $port 개방 실패: ${e.message}")
|
||||||
|
// 에러 메시지에 'Permission denied'가 포함되면 보안 프로그램(안랩 등) 차단일 확률이 높음
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기존에 구현하신 netstat 방식을 결합하여 상세 로그를 남깁니다.
|
||||||
|
*/
|
||||||
|
fun runFullDiagnostic(port: Int) {
|
||||||
|
val isHardwareReady = testPortAvailability(port)
|
||||||
|
|
||||||
|
if (!isHardwareReady) {
|
||||||
|
println("⚠️ [Alert] OS 레벨에서 포트 $port 사용이 거부되었습니다. 보안 프로그램 설정을 확인하세요.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netstat 실행 로직 (기존 NetworkChecker 활용)
|
||||||
|
val process = Runtime.getRuntime().exec("cmd /c netstat -ano | findstr :$port")
|
||||||
|
val result = process.inputStream.bufferedReader().readText()
|
||||||
|
|
||||||
|
if (result.contains("LISTENING")) {
|
||||||
|
println("ℹ️ 현재 포트 $port 를 다른 프로세스가 점유 중입니다.")
|
||||||
|
} else {
|
||||||
|
println("✅ 포트 $port 는 현재 깨끗하며 사용 가능한 상태입니다.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,10 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<logger name="Exposed" level="OFF" />
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO"> <appender-ref ref="STDOUT" />
|
||||||
<appender-ref ref="STDOUT" />
|
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
Loading…
x
Reference in New Issue
Block a user