fix: correct OpenAI API request format

This commit addresses a bug where the OpenAI API request was improperly formatted, leading to failed requests. The fix ensures the request payload adheres to the expected structure. Additionally, it incorporates the completed work from the feature-chat-history-save-call branch, which implements chat history persistence and retrieval features. All changes have been tested to confirm successful API communication and history management.
This commit is contained in:
Sar 2025-03-06 21:57:48 +08:00 committed by GitHub
parent d1b51f6947
commit 2b992e39a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 248 additions and 19 deletions

View File

@ -3,7 +3,7 @@ plugins {
}
group = 'com'
version = '1.0.1'
version = '1.0.2'
repositories {
mavenCentral()

View File

@ -1 +1 @@
rootProject.name = 'ollamachat'
rootProject.name = 'OllamaChat'

View File

@ -5,6 +5,8 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@ -21,9 +23,16 @@ public class AIService {
public CompletableFuture<String> sendRequest(String apiUrl, String apiKey, String model, String prompt) {
return CompletableFuture.supplyAsync(() -> {
try {
// 按照OpenAI Chat API要求构造messages数组
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of(
"role", "user",
"content", prompt
));
Map<String, Object> requestBody = Map.of(
"model", model,
"prompt", prompt,
"messages", messages, // 关键修改将prompt改为messages
"stream", false
);
@ -32,12 +41,9 @@ public class AIService {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey) // 确保认证头存在
.POST(HttpRequest.BodyPublishers.ofString(jsonRequest));
if (apiKey != null && !apiKey.isEmpty()) {
requestBuilder.header("Authorization", "Bearer " + apiKey);
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = httpClient.send(
@ -56,3 +62,5 @@ public class AIService {
});
}
}

View File

@ -0,0 +1,27 @@
package com.ollamachat;
import org.bukkit.entity.Player;
import java.util.UUID;
public class ChatHistoryManager {
private final DatabaseManager databaseManager;
private final int maxHistory;
public ChatHistoryManager(DatabaseManager databaseManager, int maxHistory) {
this.databaseManager = databaseManager;
this.maxHistory = maxHistory;
}
public void savePlayerInfo(Player player) {
databaseManager.savePlayerInfo(player.getUniqueId(), player.getName());
}
public void saveChatHistory(UUID playerUuid, String aiModel, String prompt, String response) {
databaseManager.saveChatHistory(playerUuid, aiModel, prompt, response);
}
public String getChatHistory(UUID playerUuid, String aiModel) {
return databaseManager.getChatHistory(playerUuid, aiModel, maxHistory);
}
}

View File

@ -0,0 +1,93 @@
package com.ollamachat;
import java.sql.*;
import java.util.UUID;
public class DatabaseManager {
private Connection connection;
public DatabaseManager() {
initializeDatabase();
}
private void initializeDatabase() {
try {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:plugins/OllamaChat/chat_history.db");
createTables();
} catch (Exception e) {
e.printStackTrace();
}
}
private void createTables() throws SQLException {
try (Statement stmt = connection.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS players (" +
"uuid TEXT PRIMARY KEY," +
"username TEXT NOT NULL)");
stmt.execute("CREATE TABLE IF NOT EXISTS chat_history (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
"player_uuid TEXT NOT NULL," +
"ai_model TEXT NOT NULL," +
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP," +
"prompt TEXT NOT NULL," +
"response TEXT NOT NULL," +
"FOREIGN KEY (player_uuid) REFERENCES players(uuid))");
}
}
public void savePlayerInfo(UUID uuid, String username) {
String sql = "INSERT OR REPLACE INTO players(uuid, username) VALUES(?, ?)";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, uuid.toString());
pstmt.setString(2, username);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void saveChatHistory(UUID playerUuid, String aiModel, String prompt, String response) {
String sql = "INSERT INTO chat_history(player_uuid, ai_model, prompt, response) VALUES(?, ?, ?, ?)";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, playerUuid.toString());
pstmt.setString(2, aiModel);
pstmt.setString(3, prompt);
pstmt.setString(4, response);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getChatHistory(UUID playerUuid, String aiModel, int maxHistory) {
StringBuilder history = new StringBuilder();
String sql = "SELECT prompt, response FROM chat_history " +
"WHERE player_uuid = ? AND ai_model = ? " +
"ORDER BY timestamp DESC LIMIT ?";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, playerUuid.toString());
pstmt.setString(2, aiModel);
pstmt.setInt(3, maxHistory);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
history.insert(0, "User: " + rs.getString("prompt") + "\n");
history.insert(0, "AI: " + rs.getString("response") + "\n");
}
} catch (SQLException e) {
e.printStackTrace();
}
return history.toString();
}
public void close() {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

View File

@ -1,6 +1,7 @@
package com.ollamachat;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@ -10,12 +11,13 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class Ollamachat extends JavaPlugin implements Listener {
@ -31,6 +33,9 @@ public class Ollamachat extends JavaPlugin implements Listener {
private Map<String, Boolean> otherAIEnabled;
private FileConfiguration langConfig;
private DatabaseManager databaseManager;
private ChatHistoryManager chatHistoryManager;
private int maxHistory;
@Override
public void onEnable() {
@ -39,6 +44,12 @@ public class Ollamachat extends JavaPlugin implements Listener {
String language = getConfig().getString("language", "en");
loadLanguageFile(language);
updateCommandUsages();
databaseManager = new DatabaseManager();
maxHistory = getConfig().getInt("max-history", 5);
chatHistoryManager = new ChatHistoryManager(databaseManager, maxHistory);
aiService = new AIService();
gson = new Gson();
@ -47,6 +58,19 @@ public class Ollamachat extends JavaPlugin implements Listener {
getCommand("aichat").setExecutor(this);
}
private void updateCommandUsages() {
String usageOllamachat = getMessage("usage-ollamachat", null);
String usageAichat = getMessage("usage-aichat", null);
getCommand("ollamachat").setUsage(usageOllamachat);
getCommand("aichat").setUsage(usageAichat);
}
@Override
public void onDisable() {
databaseManager.close();
}
private void updateConfig() {
FileConfiguration config = getConfig();
@ -59,11 +83,32 @@ public class Ollamachat extends JavaPlugin implements Listener {
if (!config.contains("other-ai-configs")) {
config.createSection("other-ai-configs");
}
if (!config.contains("max-history")) {
config.set("max-history", 5);
}
saveConfig();
}
private void reloadConfigValues() {
File configFile = new File(getDataFolder(), "config.yml");
if (!configFile.exists()) {
saveDefaultConfig();
} else {
try {
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!config.contains("ollama-api-url") || !config.contains("model")) {
getLogger().warning(getMessage("config-invalid", null));
configFile.delete();
saveDefaultConfig();
}
} catch (Exception e) {
getLogger().severe(getMessage("config-load-failed", Map.of("error", e.getMessage())));
configFile.delete();
saveDefaultConfig();
}
}
reloadConfig();
updateConfig();
@ -73,6 +118,7 @@ public class Ollamachat extends JavaPlugin implements Listener {
triggerPrefix = config.getString("trigger-prefix", "@bot ");
maxResponseLength = config.getInt("max-response-length", 500);
ollamaEnabled = config.getBoolean("ollama-enabled", true);
maxHistory = config.getInt("max-history", 5);
otherAIConfigs = new HashMap<>();
otherAIEnabled = new HashMap<>();
@ -117,6 +163,11 @@ public class Ollamachat extends JavaPlugin implements Listener {
return message;
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
chatHistoryManager.savePlayerInfo(event.getPlayer());
}
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
if (!ollamaEnabled) return;
@ -137,8 +188,15 @@ public class Ollamachat extends JavaPlugin implements Listener {
private void processOllamaQueryAsync(Player player, String prompt) {
CompletableFuture.runAsync(() -> {
try {
String responseBody = aiService.sendRequest(ollamaApiUrl, null, ollamaModel, prompt).join();
String history = chatHistoryManager.getChatHistory(player.getUniqueId(), "ollama");
String context = history + "User: " + prompt;
String responseBody = aiService.sendRequest(ollamaApiUrl, null, ollamaModel, context).join();
OllamaResponse ollamaResponse = gson.fromJson(responseBody, OllamaResponse.class);
chatHistoryManager.saveChatHistory(player.getUniqueId(), "ollama", prompt, ollamaResponse.response);
sendFormattedResponse(player, ollamaResponse.response);
} catch (Exception e) {
getLogger().severe("Error processing Ollama request: " + e.getMessage());
@ -155,9 +213,28 @@ public class Ollamachat extends JavaPlugin implements Listener {
CompletableFuture.runAsync(() -> {
try {
String history = chatHistoryManager.getChatHistory(player.getUniqueId(), aiName);
String context = history + "User: " + prompt;
AIConfig aiConfig = otherAIConfigs.get(aiName);
String responseBody = aiService.sendRequest(aiConfig.getApiUrl(), aiConfig.getApiKey(), aiConfig.getModel(), prompt).join();
sendFormattedResponse(player, responseBody);
String responseBody = aiService.sendRequest(
aiConfig.getApiUrl(),
aiConfig.getApiKey(),
aiConfig.getModel(),
context
).join();
String response = parseAIResponse(aiName, responseBody);
chatHistoryManager.saveChatHistory(
player.getUniqueId(),
aiName,
prompt,
response
);
sendFormattedResponse(player, response);
} catch (Exception e) {
getLogger().severe("Error processing " + aiName + " request: " + e.getMessage());
sendErrorMessage(player, getMessage("error-prefix", null) + "Failed to get response from " + aiName);
@ -165,6 +242,19 @@ public class Ollamachat extends JavaPlugin implements Listener {
});
}
private String parseAIResponse(String aiName, String responseBody) {
switch (aiName.toLowerCase()) {
case "openai":
JsonObject json = gson.fromJson(responseBody, JsonObject.class);
return json.getAsJsonArray("choices")
.get(0).getAsJsonObject()
.get("message").getAsJsonObject()
.get("content").getAsString();
default:
return gson.fromJson(responseBody, OllamaResponse.class).response;
}
}
private void sendFormattedResponse(Player player, String response) {
if (response.length() > maxResponseLength) {
response = response.substring(0, maxResponseLength) + "...";

View File

@ -10,6 +10,9 @@ response-prefix: "§b[AI] §r"
# Length
max-response-length: 500
# History
max-history: 5
# Language Settings
language: "en"

View File

@ -4,10 +4,15 @@ toggle-enabled: "§a{ai-name} is now enabled."
toggle-disabled: "§a{ai-name} is now disabled."
invalid-ai-name: "§cInvalid AI name. Available AIs: {ai-list}."
usage-aichat: "§cUsage: /aichat <ai-name> <prompt>"
usage-ollamachat: "§cUsage: /ollamachat <reload|toggle <ai-name>>"
player-only: "§cThis command can only be used by players."
ollama-enabled: "§aOllama is now enabled."
ollama-disabled: "§aOllama is now disabled."
# Chat interaction
response-prefix: "§b[AI] §r"
error-prefix: "§c[Error] §r"
error-prefix: "§c[Error] §r"
# Log messages
config-invalid: "§cConfig file is invalid or incomplete. Regenerating default config..."
config-load-failed: "§cFailed to load config file: {error}. Regenerating default config..."

View File

@ -4,10 +4,15 @@ toggle-enabled: "§a{ai-name} 已启用。"
toggle-disabled: "§a{ai-name} 已禁用。"
invalid-ai-name: "§c无效的 AI 名称。可用的 AI: {ai-list}。"
usage-aichat: "§c用法: /aichat <ai名称> <提示>"
usage-ollamachat: "§c用法: /ollamachat <reload|toggle <ai名称>>"
player-only: "§c该命令只能由玩家使用。"
ollama-enabled: "§aOllama 已启用。"
ollama-disabled: "§aOllama 已禁用。"
# 聊天交互
response-prefix: "§b[AI] §r"
error-prefix: "§c[错误] §r"
error-prefix: "§c[错误] §r"
# 日志消息
config-invalid: "§c配置文件无效或不完整正在重新生成默认配置..."
config-load-failed: "§c加载配置文件失败: {error},正在重新生成默认配置..."

View File

@ -1,5 +1,5 @@
name: ollamachat
version: '1.0.1'
name: OllamaChat
version: '1.0.2'
main: com.ollamachat.Ollamachat
api-version: '1.21'
authors: [xwwsdd]
@ -9,13 +9,11 @@ website: https://chat.sarskin.cn/invite/iHgI6LTX
commands:
ollamachat:
description: Manage OllamaChat plugin (reload configuration or toggle AI)
usage: |
/ollamachat reload - Reload the plugin configuration
/ollamachat toggle <ai-name> - Enable or disable an AI service
usage: "{usage-ollamachat}"
permission: ollamachat.admin
aichat:
description: Interact with other AI services
usage: /aichat <ai-name> <prompt>
usage: "{usage-aichat}"
permission: ollamachat.use
permissions: