229b09b895
Build & Deploy to K8s / build-and-deploy (push) Failing after 7m14s
Epic A: ai-assistant 디렉토리/Spring 프록시/프론트엔드 메뉴 완전 제거 Epic B: Flyway 도입 + 13 신규 테이블 마이그레이션 Epic C: 9 서비스 + 7 컨트롤러 + LlmClient 추상화 (Java 21/Spring/MyBatis) Epic D: ApiKey 인증 필터 (sk-pipe-* 키 SHA-256 검증) Epic E: OpenClaw 외부 엔진 docker-compose 통합 Epic F: Next.js 7 페이지 + lib/api/aiAgent.ts 이식 Epic G: 화면 그룹/메뉴 등록 마이그레이션 (V014) Epic H: 통합 빌드 검증 - DB: invyone PostgreSQL에 ai_agents/ai_agent_groups/... 13 테이블 + Quartz - 멀티테넌시: 모든 테이블에 company_code 강제 필터 - LLM: Anthropic/OpenAI/Google/Ollama 직접 클라이언트 (Spring AI 미도입) - 스케줄러: Quartz JDBC JobStore (cron 기반) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.5 KiB
Java
68 lines
2.5 KiB
Java
package com.erp.ai.client;
|
|
|
|
import com.erp.ai.exception.LlmClientException;
|
|
import com.erp.ai.mapper.AiLlmProviderMapper;
|
|
import com.erp.ai.model.AiLlmProvider;
|
|
import com.erp.ai.util.AesGcmCipher;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.client.RestClient;
|
|
import org.springframework.web.client.RestClientResponseException;
|
|
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* OpenAI LLM 클라이언트 (DeepSeek 등 OpenAI 호환 프로바이더 포함).
|
|
*/
|
|
@Slf4j
|
|
@Component
|
|
@RequiredArgsConstructor
|
|
public class OpenAiLlmClient implements LlmClient {
|
|
|
|
private static final String DEFAULT_BASE_URL = "https://api.openai.com";
|
|
|
|
@Qualifier("llmRestClient")
|
|
private final RestClient httpClient;
|
|
private final AiLlmProviderMapper providerMapper;
|
|
private final AesGcmCipher cipher;
|
|
|
|
@Override
|
|
public boolean supports(String model) {
|
|
return model != null && (model.startsWith("gpt-") || model.startsWith("o1-") || model.startsWith("o3-"));
|
|
}
|
|
|
|
@Override
|
|
public String providerName() {
|
|
return "openai";
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public Map<String, Object> chat(Map<String, Object> request) {
|
|
AiLlmProvider provider = providerMapper.getByName("openai");
|
|
if (provider == null || !Boolean.TRUE.equals(provider.getIs_active())) {
|
|
throw new LlmClientException("OpenAI 프로바이더가 활성화되지 않았습니다.");
|
|
}
|
|
String apiKey = cipher.decrypt(provider.getApi_key_encrypted());
|
|
String baseUrl = provider.getEndpoint() != null ? provider.getEndpoint() : DEFAULT_BASE_URL;
|
|
|
|
try {
|
|
return httpClient.post()
|
|
.uri(baseUrl + "/v1/chat/completions")
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.header("Authorization", "Bearer " + apiKey)
|
|
.body(request)
|
|
.retrieve()
|
|
.body(Map.class);
|
|
} catch (RestClientResponseException e) {
|
|
log.error("OpenAI API 오류 {}: {}", e.getStatusCode().value(), e.getResponseBodyAsString());
|
|
throw new LlmClientException("OpenAI API 오류: " + e.getMessage(), e.getStatusCode().value());
|
|
} catch (Exception e) {
|
|
throw new LlmClientException("OpenAI 호출 실패: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
}
|