From 92609142778b00864a3e3306e3fc0da194c85191 Mon Sep 17 00:00:00 2001 From: gkirkendall <61368993+gkirkendall@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:57:56 +0000 Subject: [PATCH 1/2] assigns random personalities to the existing bots This file assigns random personalities to the existing bots. Once the playerbots are created and the personalities loaded, running this sql script will randomly assign a personality to each playerbot. If the script is left in place, any new playerbots will be assigned a randomly selected personality on server startup. The existing personalities will not be altered by this script. --- ..._ollama_chat_randomly_assign_personality.sql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 data/sql/characters/updates/acore_user_mod_ollama_chat_randomly_assign_personality.sql diff --git a/data/sql/characters/updates/acore_user_mod_ollama_chat_randomly_assign_personality.sql b/data/sql/characters/updates/acore_user_mod_ollama_chat_randomly_assign_personality.sql new file mode 100644 index 0000000000..a4875d445c --- /dev/null +++ b/data/sql/characters/updates/acore_user_mod_ollama_chat_randomly_assign_personality.sql @@ -0,0 +1,17 @@ +INSERT INTO mod_ollama_chat_personality (guid, personality) +SELECT + c.guid, + ( + SELECT `key` + FROM mod_ollama_chat_personality_templates + -- Exclude templates meant only for manual assignment + WHERE manual_only = 0 + -- This dummy condition forces MySQL to evaluate the subquery per row + AND c.guid IS NOT NULL + ORDER BY RAND() + LIMIT 1 + ) AS random_personality +FROM characters c +LEFT JOIN mod_ollama_chat_personality p + ON c.guid = p.guid +WHERE p.guid IS NULL; \ No newline at end of file From 04f5404a419c0f2101eebc94c2388660aa5a4086 Mon Sep 17 00:00:00 2001 From: gkirkendall Date: Fri, 19 Jun 2026 17:28:17 -0400 Subject: [PATCH 2/2] Add support for llama.cpp and OpenAI-compatible API providers --- conf/mod_ollama_chat.conf.dist | 13 +- src/mod-ollama-chat_api.cpp | 337 ++++++++++++++++++++++++--------- src/mod-ollama-chat_config.cpp | 6 +- src/mod-ollama-chat_config.h | 1 + 4 files changed, 261 insertions(+), 96 deletions(-) diff --git a/conf/mod_ollama_chat.conf.dist b/conf/mod_ollama_chat.conf.dist index b6000322e2..0ea7b18c71 100644 --- a/conf/mod_ollama_chat.conf.dist +++ b/conf/mod_ollama_chat.conf.dist @@ -33,9 +33,20 @@ OllamaChat.DebugShowFullPrompt = 0 # OLLAMA LLM CONNECTION AND INFERENCE # -------------------------------------------- +# OllamaChat.ApiProvider +# Description: The API backend provider to use for LLM inference. +# "ollama" - Ollama's native generation endpoint (/api/generate). +# "llamacpp" - llama.cpp's native completion endpoint (/completion). +# "openai" - OpenAI-compatible chat completions endpoint (/v1/chat/completions). +# Supported by llama.cpp, LM Studio, Ollama, and cloud hosts. +# Default: "ollama" +OllamaChat.ApiProvider = "ollama" + # OllamaChat.Url # Description: The URL used to query the Ollama API. -# Default: http://localhost:11434/api/generate +# Default: http://localhost:11434/api/generate (For Ollama) +# http://localhost:8080/completion (For llama.cpp native) +# http://localhost:8080/v1/chat/completions (For OpenAI-compatible) OllamaChat.Url = http://localhost:11434/api/generate # OllamaChat.Model diff --git a/src/mod-ollama-chat_api.cpp b/src/mod-ollama-chat_api.cpp index 271077d9dd..13c15ed001 100644 --- a/src/mod-ollama-chat_api.cpp +++ b/src/mod-ollama-chat_api.cpp @@ -42,97 +42,194 @@ std::string QueryOllamaAPI(const std::string& prompt) // Sanitize the prompt to ensure it's valid UTF-8 before creating JSON std::string sanitizedPrompt = SanitizeUTF8(prompt); + std::string requestDataStr; - nlohmann::json requestData = { - {"model", model}, - {"prompt", sanitizedPrompt}, - {"stream", false} - }; + if (g_OllamaApiProvider == "ollama") + { + nlohmann::json requestData = { + {"model", model}, + {"prompt", sanitizedPrompt}, + {"stream", false} + }; - // Create options object for model parameters - nlohmann::json options; - bool hasOptions = false; + // Create options object for model parameters + nlohmann::json options; + bool hasOptions = false; - // Only include if set (do not send defaults if user did not set them) - if (g_OllamaNumPredict > 0) { - options["num_predict"] = g_OllamaNumPredict; - hasOptions = true; - } - if (g_OllamaTemperature != 0.8f) { - options["temperature"] = g_OllamaTemperature; - hasOptions = true; - } - if (g_OllamaTopP != 0.95f) { - options["top_p"] = g_OllamaTopP; - hasOptions = true; - } - if (g_OllamaRepeatPenalty != 1.1f) { - options["repeat_penalty"] = g_OllamaRepeatPenalty; - hasOptions = true; - } - if (g_OllamaNumCtx > 0) { - options["num_ctx"] = g_OllamaNumCtx; - hasOptions = true; - } - if (g_OllamaNumThreads > 0) { - options["num_thread"] = g_OllamaNumThreads; - hasOptions = true; - if(g_DebugEnabled) { - //LOG_INFO("server.loading", "[Ollama Chat] Setting num_thread to: {}", g_OllamaNumThreads); - } - } else if(g_DebugEnabled) { - //LOG_INFO("server.loading", "[Ollama Chat] g_OllamaNumThreads is: {} (not sending num_thread)", g_OllamaNumThreads); - } - if (!g_OllamaSeed.empty()) { - try { - int seedValue = std::stoi(g_OllamaSeed); - options["seed"] = seedValue; + // Only include if set (do not send defaults if user did not set them) + if (g_OllamaNumPredict > 0) { + options["num_predict"] = g_OllamaNumPredict; + hasOptions = true; + } + if (g_OllamaTemperature != 0.8f) { + options["temperature"] = g_OllamaTemperature; + hasOptions = true; + } + if (g_OllamaTopP != 0.95f) { + options["top_p"] = g_OllamaTopP; hasOptions = true; - } catch (const std::exception& e) { - if(g_DebugEnabled) { - LOG_INFO("server.loading", "[Ollama Chat] Invalid seed value: {}", g_OllamaSeed); + } + if (g_OllamaRepeatPenalty != 1.1f) { + options["repeat_penalty"] = g_OllamaRepeatPenalty; + hasOptions = true; + } + if (g_OllamaNumCtx > 0) { + options["num_ctx"] = g_OllamaNumCtx; + hasOptions = true; + } + if (g_OllamaNumThreads > 0) { + options["num_thread"] = g_OllamaNumThreads; + hasOptions = true; + } + if (!g_OllamaSeed.empty()) { + try { + int seedValue = std::stoi(g_OllamaSeed); + options["seed"] = seedValue; + hasOptions = true; + } catch (...) {} + } + + // Add options object if any options were set + if (hasOptions) { + requestData["options"] = options; + } + + // Root-level parameters (these stay at root level) + if (!g_OllamaStop.empty()) { + // If comma-separated, convert to array + std::vector stopSeqs; + std::stringstream ss(g_OllamaStop); + std::string item; + while (std::getline(ss, item, ',')) { + // trim whitespace + size_t start = item.find_first_not_of(" \t"); + size_t end = item.find_last_not_of(" \t"); + if (start != std::string::npos && end != std::string::npos) + stopSeqs.push_back(item.substr(start, end - start + 1)); } + if (!stopSeqs.empty()) + requestData["stop"] = stopSeqs; + } + if (!g_OllamaSystemPrompt.empty()) + { + // Sanitize system prompt as well + requestData["system"] = SanitizeUTF8(g_OllamaSystemPrompt); } - } - // Add options object if any options were set - if (hasOptions) { - requestData["options"] = options; - } + if (g_ThinkModeEnableForModule) + { + if(g_DebugEnabled) + { + LOG_INFO("server.loading", "[Ollama Chat] LLM set to Think mode."); + } + requestData["think"] = true; + requestData["hidethinking"] = true; + } - // Root-level parameters (these stay at root level) - if (!g_OllamaStop.empty()) { - // If comma-separated, convert to array - std::vector stopSeqs; - std::stringstream ss(g_OllamaStop); - std::string item; - while (std::getline(ss, item, ',')) { - // trim whitespace - size_t start = item.find_first_not_of(" \t"); - size_t end = item.find_last_not_of(" \t"); - if (start != std::string::npos && end != std::string::npos) - stopSeqs.push_back(item.substr(start, end - start + 1)); - } - if (!stopSeqs.empty()) - requestData["stop"] = stopSeqs; + requestDataStr = requestData.dump(); } - if (!g_OllamaSystemPrompt.empty()) + else if (g_OllamaApiProvider == "llamacpp") { - // Sanitize system prompt as well - requestData["system"] = SanitizeUTF8(g_OllamaSystemPrompt); - } + nlohmann::json requestData = { + {"prompt", sanitizedPrompt}, + {"stream", false} + }; - if (g_ThinkModeEnableForModule) - { - if(g_DebugEnabled) + if (g_OllamaNumPredict > 0) { + requestData["n_predict"] = g_OllamaNumPredict; + } + if (g_OllamaTemperature != 0.8f) { + requestData["temperature"] = g_OllamaTemperature; + } + if (g_OllamaTopP != 0.95f) { + requestData["top_p"] = g_OllamaTopP; + } + if (g_OllamaRepeatPenalty != 1.1f) { + requestData["repeat_penalty"] = g_OllamaRepeatPenalty; + } + if (g_OllamaNumCtx > 0) { + requestData["n_ctx"] = g_OllamaNumCtx; + } + if (!g_OllamaSeed.empty()) { + try { + int seedValue = std::stoi(g_OllamaSeed); + requestData["seed"] = seedValue; + } catch (...) {} + } + if (!g_OllamaStop.empty()) { + std::vector stopSeqs; + std::stringstream ss(g_OllamaStop); + std::string item; + while (std::getline(ss, item, ',')) { + size_t start = item.find_first_not_of(" \t"); + size_t end = item.find_last_not_of(" \t"); + if (start != std::string::npos && end != std::string::npos) + stopSeqs.push_back(item.substr(start, end - start + 1)); + } + if (!stopSeqs.empty()) + requestData["stop"] = stopSeqs; + } + if (!g_OllamaSystemPrompt.empty()) { - LOG_INFO("server.loading", "[Ollama Chat] LLM set to Think mode."); + requestData["system_prompt"] = SanitizeUTF8(g_OllamaSystemPrompt); } - requestData["think"] = true; - requestData["hidethinking"] = true; + + requestDataStr = requestData.dump(); } + else if (g_OllamaApiProvider == "openai") + { + nlohmann::json messages = nlohmann::json::array(); + if (!g_OllamaSystemPrompt.empty()) { + messages.push_back({ + {"role", "system"}, + {"content", SanitizeUTF8(g_OllamaSystemPrompt)} + }); + } + messages.push_back({ + {"role", "user"}, + {"content", sanitizedPrompt} + }); + + nlohmann::json requestData = { + {"model", model}, + {"messages", messages}, + {"stream", false} + }; + + if (g_OllamaNumPredict > 0) { + requestData["max_tokens"] = g_OllamaNumPredict; + } + if (g_OllamaTemperature != 0.8f) { + requestData["temperature"] = g_OllamaTemperature; + } + if (g_OllamaTopP != 0.95f) { + requestData["top_p"] = g_OllamaTopP; + } + if (g_OllamaRepeatPenalty != 1.1f) { + requestData["frequency_penalty"] = std::min(2.0f, std::max(-2.0f, g_OllamaRepeatPenalty - 1.0f)); + } + if (!g_OllamaSeed.empty()) { + try { + int seedValue = std::stoi(g_OllamaSeed); + requestData["seed"] = seedValue; + } catch (...) {} + } + if (!g_OllamaStop.empty()) { + std::vector stopSeqs; + std::stringstream ss(g_OllamaStop); + std::string item; + while (std::getline(ss, item, ',')) { + size_t start = item.find_first_not_of(" \t"); + size_t end = item.find_last_not_of(" \t"); + if (start != std::string::npos && end != std::string::npos) + stopSeqs.push_back(item.substr(start, end - start + 1)); + } + if (!stopSeqs.empty()) + requestData["stop"] = stopSeqs; + } - std::string requestDataStr = requestData.dump(); + requestDataStr = requestData.dump(); + } // Make HTTP POST request using our custom client std::string responseBuffer = httpClient.Post(url, requestDataStr); @@ -147,37 +244,91 @@ std::string QueryOllamaAPI(const std::string& prompt) return ""; } - std::stringstream ss(responseBuffer); - std::string line; - std::ostringstream extractedResponse; + std::string botReply; try { - while (std::getline(ss, line)) - { - if (line.empty() || std::all_of(line.begin(), line.end(), isspace)) - continue; - - nlohmann::json jsonResponse = nlohmann::json::parse(line); + nlohmann::json jsonResponse = nlohmann::json::parse(responseBuffer); - if (jsonResponse.contains("response") && !jsonResponse["response"].get().empty()) + if (g_OllamaApiProvider == "ollama") + { + if (jsonResponse.contains("response")) + { + botReply = jsonResponse["response"].get(); + } + else { - extractedResponse << jsonResponse["response"].get(); + // Fallback to line-by-line parsing if Ollama outputs streaming-like JSON rows + std::stringstream ss(responseBuffer); + std::string line; + std::ostringstream extractedResponse; + while (std::getline(ss, line)) + { + if (line.empty() || std::all_of(line.begin(), line.end(), isspace)) + continue; + nlohmann::json lineJson = nlohmann::json::parse(line); + if (lineJson.contains("response") && !lineJson["response"].get().empty()) + extractedResponse << lineJson["response"].get(); + } + botReply = extractedResponse.str(); + } + } + else if (g_OllamaApiProvider == "llamacpp") + { + if (jsonResponse.contains("content")) + { + botReply = jsonResponse["content"].get(); + } + } + else if (g_OllamaApiProvider == "openai") + { + if (jsonResponse.contains("choices") && jsonResponse["choices"].is_array() && !jsonResponse["choices"].empty()) + { + auto firstChoice = jsonResponse["choices"][0]; + if (firstChoice.contains("message") && firstChoice["message"].contains("content")) + { + botReply = firstChoice["message"]["content"].get(); + } } } } catch (const std::exception& e) { - LOG_ERROR("server.loading", "[OllamaChat] ERROR: JSON parsing failed. Exception: {}", e.what()); - if(g_DebugEnabled) + // Fallback for line-by-line streaming parse for Ollama + if (g_OllamaApiProvider == "ollama") + { + try + { + std::stringstream ss(responseBuffer); + std::string line; + std::ostringstream extractedResponse; + while (std::getline(ss, line)) + { + if (line.empty() || std::all_of(line.begin(), line.end(), isspace)) + continue; + nlohmann::json lineJson = nlohmann::json::parse(line); + if (lineJson.contains("response") && !lineJson["response"].get().empty()) + extractedResponse << lineJson["response"].get(); + } + botReply = extractedResponse.str(); + } + catch (...) + { + LOG_ERROR("server.loading", "[OllamaChat] ERROR: JSON parsing failed. Exception: {}", e.what()); + return ""; + } + } + else { - LOG_INFO("server.loading", "[OllamaChat] Debug: Response buffer content: {}", responseBuffer); + LOG_ERROR("server.loading", "[OllamaChat] ERROR: JSON parsing failed. Exception: {}", e.what()); + if(g_DebugEnabled) + { + LOG_INFO("server.loading", "[OllamaChat] Debug: Response buffer content: {}", responseBuffer); + } + return ""; } - return ""; } - std::string botReply = extractedResponse.str(); - botReply = ExtractTextBetweenDoubleQuotes(botReply); // Check for unclosed think tags diff --git a/src/mod-ollama-chat_config.cpp b/src/mod-ollama-chat_config.cpp index 8bc1b1da33..485cb7fea2 100644 --- a/src/mod-ollama-chat_config.cpp +++ b/src/mod-ollama-chat_config.cpp @@ -40,6 +40,7 @@ uint32_t g_EventChatterMaxBotsPerPlayer = 2; // -------------------------------------------- // Ollama LLM API Configuration // -------------------------------------------- +std::string g_OllamaApiProvider = "ollama"; std::string g_OllamaUrl = "http://localhost:11434/api/generate"; std::string g_OllamaModel = "llama3.2:1b"; uint32_t g_OllamaNumPredict = 40; @@ -384,6 +385,7 @@ void LoadOllamaChatConfig() g_BotReplyChance_Guild = sConfigMgr->GetOption("OllamaChat.BotReplyChance.Guild", 5); g_MaxBotsToPick = sConfigMgr->GetOption("OllamaChat.MaxBotsToPick", 2); + g_OllamaApiProvider = sConfigMgr->GetOption("OllamaChat.ApiProvider", "ollama"); g_OllamaUrl = sConfigMgr->GetOption("OllamaChat.Url", "http://localhost:11434/api/generate"); g_OllamaModel = sConfigMgr->GetOption("OllamaChat.Model", "llama3.2:1b"); g_OllamaNumPredict = sConfigMgr->GetOption("OllamaChat.NumPredict", 40); @@ -632,11 +634,11 @@ void LoadOllamaChatConfig() g_DisableForParty = sConfigMgr->GetOption("OllamaChat.DisableForParty", false); LOG_INFO("server.loading", - "[Ollama Chat] Config loaded: Enabled = {}, SayDistance = {}, YellDistance = {}, " + "[Ollama Chat] Config loaded: Enabled = {}, ApiProvider = {}, SayDistance = {}, YellDistance = {}, " "Reply Chances - Say: P{}%/B{}%, Channel: P{}%/B{}%, Party: P{}%/B{}%, Guild: P{}%/B{}%, MaxBotsToPick = {}, " "Url = {}, Model = {}, MaxConcurrentQueries = {}, EnableRandomChatter = {}, MinRandInt = {}, MaxRandInt = {}, RandomChatterRealPlayerDistance = {}, " "RandomChatterBotCommentChance = {}. MaxConcurrentQueries = {}. Extra blacklist commands: {}", - g_Enable, g_SayDistance, g_YellDistance, + g_Enable, g_OllamaApiProvider, g_SayDistance, g_YellDistance, g_PlayerReplyChance_Say, g_BotReplyChance_Say, g_PlayerReplyChance_Channel, g_BotReplyChance_Channel, g_PlayerReplyChance_Party, g_BotReplyChance_Party, diff --git a/src/mod-ollama-chat_config.h b/src/mod-ollama-chat_config.h index 37e66e26e8..86786cd012 100644 --- a/src/mod-ollama-chat_config.h +++ b/src/mod-ollama-chat_config.h @@ -41,6 +41,7 @@ extern uint32_t g_EventChatterMaxBotsPerPlayer; // -------------------------------------------- // Ollama LLM API Configuration // -------------------------------------------- +extern std::string g_OllamaApiProvider; extern std::string g_OllamaUrl; extern std::string g_OllamaModel; extern uint32_t g_OllamaNumPredict;