1package com.runehive.content.ai;
3import com.openai.client.OpenAIClient;
4import com.openai.client.okhttp.OpenAIOkHttpClient;
5import com.openai.models.ChatModel;
6import com.openai.models.chat.completions.ChatCompletion;
7import com.openai.models.chat.completions.ChatCompletionCreateParams;
8import com.openai.models.chat.completions.ChatCompletionMessage;
9import org.slf4j.LoggerFactory;
10import org.slf4j.Logger;
12import java.io.IOException;
13import java.nio.file.Files;
14import java.nio.file.Path;
15import java.nio.file.Paths;
16import java.time.Duration;
17import java.util.ArrayList;
19import java.util.concurrent.CompletableFuture;
20import java.util.concurrent.ConcurrentHashMap;
21import java.util.concurrent.ExecutorService;
22import java.util.concurrent.Executors;
23import java.util.concurrent.atomic.AtomicBoolean;
38 private final AtomicBoolean
initialized =
new AtomicBoolean(
false);
43 this.executorService = Executors.newFixedThreadPool(4);
44 this.conversationHistory =
new ConcurrentHashMap<>();
53 logger.info(
"Initializing Official OpenAI Service with GPT-4o-mini model");
60 .timeout(Duration.ofSeconds(30))
64 logger.info(
"Official OpenAI Service initialized and ready for GPT-4o-mini requests");
65 }
catch (Exception e) {
66 logger.error(
"Failed to initialize Official OpenAI Service", e);
68 throw new RuntimeException(
"OpenAI Service initialization failed", e);
78 if (
apiKey.matches(
"^sk-[a-zA-Z0-9\\-_]{20,}$")) {
79 logger.info(
"OpenAI API key loaded from environment variable");
82 logger.warn(
"Invalid API key format in environment variable - must match pattern: sk-[alphanumeric]{20+}");
89 if (Files.exists(keyFile)) {
90 byte[] bytes = Files.readAllBytes(keyFile);
91 apiKey =
new String(bytes,
"UTF-8").trim();
92 if (
apiKey.isEmpty() || !
apiKey.matches(
"^sk-[a-zA-Z0-9\\-_]{20,}$")) {
93 throw new IllegalArgumentException(
"Invalid API key format in file - must match pattern: sk-[alphanumeric]{20+}");
95 logger.warn(
"OpenAI API key loaded from file - consider using environment variable {} for better security",
API_KEY_ENV);
98 }
catch (IOException e) {
102 throw new IOException(
"OpenAI API key not found. Set environment variable " +
API_KEY_ENV +
103 " or create file " +
API_KEY_FILE +
" with a valid API key starting with 'sk-'");
107 return CompletableFuture.supplyAsync(() -> {
110 logger.warn(
"OpenAI Service not initialized");
111 return "The mystical networks are not yet connected. Please try again.";
115 k ->
new ArrayList<>());
120 history =
new ArrayList<>(history.subList(
125 ChatCompletionCreateParams.Builder paramsBuilder = ChatCompletionCreateParams.builder()
126 .model(ChatModel.GPT_4O_MINI)
129 .addDeveloperMessage(
130 "You are a helpful AI assistant. Provide accurate, concise, and direct answers to questions. " +
131 "Keep responses under 150 tokens while ensuring they fully answer the question."
135 if (
"user".equals(msg.getRole())) {
136 paramsBuilder.addUserMessage(msg.getContent());
137 }
else if (
"assistant".equals(msg.getRole())) {
138 paramsBuilder.addAssistantMessage(msg.getContent());
142 ChatCompletionCreateParams params = paramsBuilder.build();
145 ChatCompletion completion =
openAIClient.chat().completions().create(params);
147 if (completion.choices().isEmpty()) {
148 logger.warn(
"No choices returned from OpenAI API");
149 return "The mystical spirits did not respond. Please try again.";
152 String response = completion.choices().get(0).message().content().orElse(
"");
153 if (response.isEmpty()) {
154 logger.warn(
"Empty response from OpenAI API");
155 return "The AI spirits whispered too quietly. Please try again.";
158 history.add(
new ChatMessage(
"assistant", response));
160 completion.usage().ifPresent(usage -> {
161 logger.info(
"OpenAI API usage for {}: prompt_tokens={}, completion_tokens={}, total_tokens={}",
162 hashUsername(username), usage.promptTokens(), usage.completionTokens(), usage.totalTokens());
165 logger.debug(
"Successfully processed OpenAI request for user: {}",
hashUsername(username));
168 }
catch (Exception e) {
169 logger.error(
"Error processing OpenAI request for user: " + username, e);
170 return "The mystical connection encountered turbulence. Please try again later.";
182 if (username ==
null)
return "null";
183 int hash = username.hashCode();
184 return "user_" + Integer.toHexString(hash);
200 logger.info(
"Shutting down Official OpenAI Service...");
211 logger.info(
"Official OpenAI Service shutdown complete");
ChatMessage(String role, String content)
CompletableFuture< String > processPlayerMessage(String username, String message, int npcId)
static final String API_KEY_ENV
final ConcurrentHashMap< String, List< ChatMessage > > conversationHistory
static final OpenAIService INSTANCE
static final int MAX_TOKENS
static final Logger logger
final ExecutorService executorService
String hashUsername(String username)
static final int MAX_CONVERSATION_HISTORY
static final String API_KEY_FILE
static OpenAIService getInstance()
static final double TEMPERATURE
void clearConversationHistory(String username)
int getActiveSessionCount()
OpenAIClient openAIClient
final AtomicBoolean initialized