From 83328d0bd7b86742cfa34a8072eb6aab4c10c64f Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Sun, 3 Mar 2024 21:05:22 +0100 Subject: [PATCH] Inital workign ollama version (w/o Preferences dialog - configured in .md) #1539 --- app/src/qt/main_window_presenter.cpp | 3 +- build/Makefile | 6 +- lib/src/config/configuration.cpp | 90 +++++++++---- lib/src/config/configuration.h | 31 +++-- lib/src/mind/ai/llm/ollama_wingman.cpp | 118 +++++------------- lib/src/mind/ai/llm/ollama_wingman.h | 4 +- lib/src/mind/ai/llm/openai_wingman.cpp | 1 + lib/src/mind/mind.cpp | 34 +++-- lib/src/mind/mind.h | 1 + .../markdown_configuration_representation.cpp | 25 +++- 10 files changed, 172 insertions(+), 141 deletions(-) diff --git a/app/src/qt/main_window_presenter.cpp b/app/src/qt/main_window_presenter.cpp index 8f752736..f829e917 100644 --- a/app/src/qt/main_window_presenter.cpp +++ b/app/src/qt/main_window_presenter.cpp @@ -2197,7 +2197,8 @@ void MainWindowPresenter::slotRunWingmanFromDialog(bool showDialog) auto duration = std::chrono::duration_cast(end - start); // wingmanProgressDialog->hide(); string answerDescriptor{ - "[model: " + commandWingmanChat.answerLlmModel + + "[provider: " + config.getWingmanLlmProviderAsString(config.getWingmanLlmProvider()) + + ", model: " + commandWingmanChat.answerLlmModel + ", tokens (prompt/answer): " + std::to_string(commandWingmanChat.promptTokens) + "/" + std::to_string(commandWingmanChat.answerTokens) + ", time: " + diff --git a/build/Makefile b/build/Makefile index 5834c155..6222e0d4 100644 --- a/build/Makefile +++ b/build/Makefile @@ -35,7 +35,11 @@ MF_LANG := "en" # Ubuntu distro: trusty xenial bionic focal jammy kinetic DISTRO := "bionic" # CPU cores thant can be used to build the project -CPU_CORES := 7 +ifeq ($(shell hostname), skunkworks) + CPU_CORES := 10 +else + CPU_CORES := 7 +endif # Qt version to be used by MindForger # MF_QT_VERSION := 5.9.9 MF_QT_VERSION := 5.15.2 diff --git a/lib/src/config/configuration.cpp b/lib/src/config/configuration.cpp index 310d3fe2..ea25bc3c 100644 --- a/lib/src/config/configuration.cpp +++ b/lib/src/config/configuration.cpp @@ -38,7 +38,8 @@ const string Configuration::DEFAULT_UI_THEME_NAME = string{UI_DEFAULT_THEME}; const string Configuration::DEFAULT_UI_HTML_CSS_THEME = string{UI_DEFAULT_HTML_CSS_THEME}; const string Configuration::DEFAULT_EDITOR_FONT= string{UI_DEFAULT_EDITOR_FONT}; const string Configuration::DEFAULT_TIME_SCOPE = string{"0y0m0d0h0m"}; -const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OPENAI = string{"gpt-3.5-turbo"}; +const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OPENAI = string{"gpt-3.5-turbo"}; // "gpt-3.5-turbo" and "gpt-4" are symbolic names +const string Configuration::DEFAULT_WINGMAN_LLM_MODEL_OLLAMA = string{"llama2"}; Configuration::Configuration() : asyncMindThreshold{}, @@ -51,9 +52,10 @@ Configuration::Configuration() autolinkingColonSplit{}, autolinkingCaseInsensitive{}, wingmanProvider{DEFAULT_WINGMAN_LLM_PROVIDER}, - wingmanApiKey{}, wingmanOpenAiApiKey{}, - wingmanLlmModel{DEFAULT_WINGMAN_LLM_MODEL_OPENAI}, + wingmanOpenAiLlm{DEFAULT_WINGMAN_LLM_MODEL_OPENAI}, + wingmanOllamaUrl{}, + wingmanOllamaLlm{DEFAULT_WINGMAN_LLM_MODEL_OLLAMA}, md2HtmlOptions{}, distributorSleepInterval{DEFAULT_DISTRIBUTOR_SLEEP_INTERVAL}, markdownQuoteSections{}, @@ -150,9 +152,10 @@ void Configuration::clear() autolinkingColonSplit = DEFAULT_AUTOLINKING_COLON_SPLIT; autolinkingCaseInsensitive = DEFAULT_AUTOLINKING_CASE_INSENSITIVE; wingmanProvider = DEFAULT_WINGMAN_LLM_PROVIDER; - wingmanApiKey.clear(); wingmanOpenAiApiKey.clear(); - wingmanLlmModel.clear(); + wingmanOpenAiLlm = DEFAULT_WINGMAN_LLM_MODEL_OPENAI; + wingmanOllamaUrl.clear(); + wingmanOllamaLlm = DEFAULT_WINGMAN_LLM_MODEL_OLLAMA; timeScopeAsString.assign(DEFAULT_TIME_SCOPE); tagsScope.clear(); markdownQuoteSections = DEFAULT_MD_QUOTE_SECTIONS; @@ -403,6 +406,15 @@ bool Configuration::canWingmanOpenAi() return false; } +bool Configuration::canWingmanOllama() +{ + if(wingmanOllamaUrl.size() > 0) { + return true; + } + + return false; +} + void Configuration::setWingmanLlmProvider(WingmanLlmProviders provider) { MF_DEBUG( @@ -410,17 +422,11 @@ void Configuration::setWingmanLlmProvider(WingmanLlmProviders provider) << std::to_string(provider) << endl); wingmanProvider = provider; - - // try to initialize Wingman @ given LLM provider, - // if it fails, then set it to false ~ disabled Wingman - initWingman(); } bool Configuration::initWingmanMock() { if(canWingmanMock()) { - wingmanApiKey.clear(); - wingmanLlmModel.clear(); return true; } @@ -436,35 +442,57 @@ bool Configuration::initWingmanMock() bool Configuration::initWingmanOpenAi() { MF_DEBUG(" Configuration::initWingmanOpenAi()" << endl); if(canWingmanOpenAi()) { + // API key MF_DEBUG( " Wingman OpenAI API key found in the shell environment variable " "MINDFORGER_OPENAI_API_KEY or set in MF config" << endl); - if(wingmanOpenAiApiKey.size() > 0) { - wingmanApiKey = wingmanOpenAiApiKey; - } else { + if(wingmanOpenAiApiKey.size() <= 0) { const char* apiKeyEnv = std::getenv(ENV_VAR_OPENAI_API_KEY); MF_DEBUG(" Wingman API key loaded from the env: " << apiKeyEnv << endl); - wingmanApiKey = apiKeyEnv; + wingmanOpenAiApiKey = apiKeyEnv; } + // LLM model const char* llmModelEnv = std::getenv(ENV_VAR_OPENAI_LLM_MODEL); if(llmModelEnv) { MF_DEBUG(" Wingman LLM model loaded from the env: " << llmModelEnv << endl); - wingmanLlmModel = llmModelEnv; + wingmanOpenAiLlm = llmModelEnv; } else { MF_DEBUG(" Wingman LLM model set to default: " << DEFAULT_WINGMAN_LLM_MODEL_OPENAI << endl); - wingmanLlmModel = DEFAULT_WINGMAN_LLM_MODEL_OPENAI; + wingmanOpenAiLlm = DEFAULT_WINGMAN_LLM_MODEL_OPENAI; } - wingmanProvider = WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI; return true; } MF_DEBUG( " Wingman OpenAI API key NEITHER found in the environment variable " "MINDFORGER_OPENAI_API_KEY, NOR set in MF configuration" << endl); - wingmanApiKey.clear(); - wingmanLlmModel.clear(); - wingmanProvider = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; + if(wingmanProvider == WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI) { + wingmanProvider = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; + } + return false; +} + +/** + * @brief Check whether ollama Wingman requirements are satisfied. + */ +bool Configuration::initWingmanOllama() { + MF_DEBUG(" Configuration::initWingmanOllama()" << endl); + if(canWingmanOllama()) { + // OPTIONAL: LLM model + if(wingmanOllamaLlm.size() <= 0) { + MF_DEBUG(" Wingman LLM model for ollama set to default: " << DEFAULT_WINGMAN_LLM_MODEL_OLLAMA << endl); + wingmanOpenAiLlm = DEFAULT_WINGMAN_LLM_MODEL_OLLAMA; + } + wingmanProvider = WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA; + return true; + } + + MF_DEBUG( + " Wingman ollama URL not set in the configuration" << endl); + if(wingmanProvider == WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA) { + wingmanProvider = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; + } return false; } @@ -474,7 +502,11 @@ bool Configuration::initWingman() " BEFORE Configuration::initWingman():" << endl << " LLM provider: " << wingmanProvider << endl << " OpenAI API key env var name: " << ENV_VAR_OPENAI_API_KEY << endl << - " Wingman provider API key : " << wingmanApiKey << endl + " OpenAI API key : " << wingmanOpenAiApiKey << endl << + " OpenAI LLM env var name : " << ENV_VAR_OPENAI_LLM_MODEL << endl << + " OpenAI LLM : " << wingmanOpenAiLlm << endl << + " ollama URL : " << wingmanOllamaUrl << endl << + " ollama LLM : " << wingmanOllamaLlm << endl ); bool initialized = false; @@ -493,6 +525,10 @@ bool Configuration::initWingman() MF_DEBUG(" OpenAI Wingman provider CONFIGURED" << endl); initialized = initWingmanOpenAi(); break; + case WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA: + MF_DEBUG(" ollama Wingman provider CONFIGURED" << endl); + initialized = initWingmanOllama(); + break; default: MF_DEBUG( " ERROR: unable to CONFIGURE UNKNOWN Wingman provider: " @@ -502,15 +538,17 @@ bool Configuration::initWingman() if(!initialized) { wingmanProvider = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; - wingmanApiKey.clear(); - wingmanLlmModel.clear(); } MF_DEBUG( - " BEFORE Configuration::initWingman():" << endl << + " AFTER Configuration::initWingman():" << endl << " LLM provider: " << wingmanProvider << endl << " OpenAI API key env var name: " << ENV_VAR_OPENAI_API_KEY << endl << - " Wingman provider API key : " << wingmanApiKey << endl + " OpenAI API key : " << wingmanOpenAiApiKey << endl << + " OpenAI LLM env var name : " << ENV_VAR_OPENAI_LLM_MODEL << endl << + " OpenAI LLM : " << wingmanOpenAiLlm << endl << + " ollama URL : " << wingmanOllamaUrl << endl << + " ollama LLM : " << wingmanOllamaLlm << endl ); return initialized; diff --git a/lib/src/config/configuration.h b/lib/src/config/configuration.h index 83a8e271..0eb1528f 100644 --- a/lib/src/config/configuration.h +++ b/lib/src/config/configuration.h @@ -50,7 +50,6 @@ enum WingmanLlmProviders { WINGMAN_PROVIDER_MOCK, WINGMAN_PROVIDER_OPENAI, WINGMAN_PROVIDER_OLLAMA - // TODO WINGMAN_PROVIDER_GOOGLE, }; // const in constexpr makes value const @@ -260,12 +259,14 @@ class Configuration { static const std::string DEFAULT_ACTIVE_REPOSITORY_PATH; static const std::string DEFAULT_TIME_SCOPE; + static const std::string DEFAULT_WINGMAN_LLM_MODEL_OPENAI; + static const std::string DEFAULT_WINGMAN_LLM_MODEL_OLLAMA; + static constexpr const WingmanLlmProviders DEFAULT_WINGMAN_LLM_PROVIDER = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; static constexpr const bool DEFAULT_AUTOLINKING = false; static constexpr const bool DEFAULT_AUTOLINKING_COLON_SPLIT = true; static constexpr const bool DEFAULT_AUTOLINKING_CASE_INSENSITIVE = true; - static constexpr const WingmanLlmProviders DEFAULT_WINGMAN_LLM_PROVIDER = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; static constexpr const bool DEFAULT_SAVE_READS_METADATA = true; static constexpr const bool UI_DEFAULT_NERD_TARGET_AUDIENCE = true; @@ -374,10 +375,11 @@ class Configuration { - on change: re-init Wingman DIALOG (refresh pre-defined prompts) */ WingmanLlmProviders wingmanProvider; // "none", "Mock", "OpenAI", ... - std::string wingmanApiKey; // API key of the currently configured Wingman LLM provider std::string wingmanOpenAiApiKey; // OpenAI API specified by user in the config, env or UI - std::string wingmanLlmModel; // preferred LLM model the currently configured provider, like "gpt-3.5-turbo" - + std::string wingmanOpenAiLlm; + std::string wingmanOllamaUrl; // base URL like http://localhost:11434 + std::string wingmanOllamaLlm; + TimeScope timeScope; std::string timeScopeAsString; std::vector tagsScope; @@ -553,6 +555,8 @@ class Configuration { return "mock"; } else if(provider == WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI) { return "openai"; + } else if(provider == WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA) { + return "ollama"; } return "none"; @@ -563,9 +567,11 @@ class Configuration { bool canWingmanMock() { return false; } #endif bool canWingmanOpenAi(); + bool canWingmanOllama(); private: bool initWingmanMock(); bool initWingmanOpenAi(); + bool initWingmanOllama(); /** * @brief Initialize Wingman's LLM provider. */ @@ -573,14 +579,13 @@ class Configuration { public: std::string getWingmanOpenAiApiKey() const { return wingmanOpenAiApiKey; } void setWingmanOpenAiApiKey(std::string apiKey) { wingmanOpenAiApiKey = apiKey; } - /** - * @brief Get API key of the currently configured Wingman LLM provider. - */ - std::string getWingmanApiKey() const { return wingmanApiKey; } - /** - * @brief Get preferred Wingman LLM provider model name. - */ - std::string getWingmanLlmModel() const { return wingmanLlmModel; } + std::string getWingmanOpenAiLlm() const { return wingmanOpenAiLlm; } + void setWingmanOpenAiLlm(std::string llm) { wingmanOpenAiLlm = llm; } + std::string getWingmanOllamaUrl() const { return wingmanOllamaUrl; } + void setWingmanOllamaUrl(std::string url) { wingmanOllamaUrl = url; } + std::string getWingmanOllamaLlm() const { return wingmanOllamaLlm; } + void setWingmanOllamaLlm(std::string llm) { wingmanOllamaLlm = llm; } + /** * @brief Check whether a Wingman LLM provider is ready from * the configuration perspective. diff --git a/lib/src/mind/ai/llm/ollama_wingman.cpp b/lib/src/mind/ai/llm/ollama_wingman.cpp index af986bb1..90f029fb 100644 --- a/lib/src/mind/ai/llm/ollama_wingman.cpp +++ b/lib/src/mind/ai/llm/ollama_wingman.cpp @@ -41,11 +41,11 @@ size_t ollamaCurlWriteCallback(void* contents, size_t size, size_t nmemb, std::s */ OllamaWingman::OllamaWingman( - const string& apiKey, + const string& url, const std::string& llmModel ) : Wingman(WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA), - apiKey{apiKey}, + url{url + "/api/generate"}, llmModel{llmModel} { } @@ -81,25 +81,11 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { }' */ - nlohmann::json messageSystemJSon{}; - messageSystemJSon["role"] = "system"; // system (instruct GPT who it is), user (user prompts), assistant (GPT answers) - messageSystemJSon["content"] = - // "You are a helpful assistant that returns HTML-formatted answers to the user's prompts." - "You are a helpful assistant." - ; - // ... more messages like above (with chat history) can be created to provide context - nlohmann::json messageUserJSon{}; - messageUserJSon["role"] = "user"; - messageUserJSon["content"] = escapedPrompt; - nlohmann::json requestJSon; requestJSon["model"] = llmModel; - requestJSon["messages"] = nlohmann::json::array( - { - messageSystemJSon, - messageUserJSon, - } - ); + requestJSon["prompt"] = escapedPrompt; + requestJSon["stream"] = false; + string requestJSonStr = requestJSon.dump(4); MF_DEBUG( @@ -112,13 +98,10 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { #if defined(_WIN32) || defined(__APPLE__) QNetworkAccessManager networkManager; - QNetworkRequest request(QUrl("https://api.openai.com/v1/chat/completions")); + QNetworkRequest request(QUrl(this->url.c_str()); request.setHeader( QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader( - "Authorization", - "Bearer " + QString::fromStdString(apiKey).toUtf8()); QNetworkReply* reply = networkManager.post( request, @@ -135,7 +118,7 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { auto error = reply->error(); if(error != QNetworkReply::NoError) { command.errorMessage = - "Error: request to OpenAI Wingman provider failed due a network error - " + + "Error: request to ollama Wingman provider failed due a network error - " + reply->errorString().toStdString(); MF_DEBUG(command.errorMessage << endl); command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; @@ -146,7 +129,7 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { if(read.isEmpty()) { command.errorMessage = - "Error: Request to OpenAI Wingman provider failed - response is empty'"; + "Error: Request to ollama Wingman provider failed - response is empty'"; MF_DEBUG(command.errorMessage << endl); command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; } @@ -159,7 +142,7 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { command.errorMessage.clear(); command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; MF_DEBUG( - "Successful OpenAI Wingman provider response:" << endl << + "Successful ollama Wingman provider response:" << endl << " '" << command.httpResponse << "'" << endl); } #else @@ -167,7 +150,7 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { command.httpResponse.clear(); curl_easy_setopt( curl, CURLOPT_URL, - "https://api.openai.com/v1/chat/completions"); + this->url.c_str()); curl_easy_setopt( curl, CURLOPT_POSTFIELDS, requestJSonStr.c_str()); @@ -178,17 +161,11 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { curl, CURLOPT_WRITEDATA, &command.httpResponse); - struct curl_slist* headers = NULL; - headers = curl_slist_append(headers, ("Authorization: Bearer " + apiKey).c_str()); - headers = curl_slist_append(headers, "Content-Type: application/json"); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - // perform the request CURLcode res = curl_easy_perform(curl); // clean up curl_easy_cleanup(curl); - curl_slist_free_all(headers); if (res != CURLE_OK) { command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; @@ -201,7 +178,7 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { // finish error handling (shared by QNetwork/CURL) if(command.status == WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR) { std::cerr << - "Error: Wingman OpenAI cURL/QtNetwork request failed (error message/HTTP response):" << endl << + "Error: Wingman ollama cURL/QtNetwork request failed (error message/HTTP response):" << endl << " '" << command.errorMessage << "'" << endl << " '" << command.httpResponse << "'" << endl; @@ -245,7 +222,7 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { ); command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; - command.errorMessage = "Error: unable to parse OpenAI JSon response: '" + command.httpResponse + "'"; + command.errorMessage = "Error: unable to parse ollama JSon response: '" + command.httpResponse + "'"; command.answerMarkdown.clear(); command.answerTokens = 0; command.answerLlmModel = llmModel; @@ -265,69 +242,36 @@ void OllamaWingman::curlGet(CommandWingmanChat& command) { httpResponseJSon["model"].get_to(command.answerLlmModel); MF_DEBUG(" model: " << command.answerLlmModel << endl); } - if(httpResponseJSon.contains("usage")) { - if(httpResponseJSon["usage"].contains("prompt_tokens")) { - httpResponseJSon["usage"]["prompt_tokens"].get_to(command.promptTokens); - MF_DEBUG(" prompt_tokens: " << command.promptTokens << endl); - } - if(httpResponseJSon["usage"].contains("completion_tokens")) { - httpResponseJSon["usage"]["completion_tokens"].get_to(command.answerTokens); - MF_DEBUG(" answer_tokens: " << command.answerTokens << endl); - } - } - if(httpResponseJSon.contains("choices") - && httpResponseJSon["choices"].size() > 0 - ) { - // TODO get the last choice rather than the first one - auto choice = httpResponseJSon["choices"][0]; - if(choice.contains("message") - && choice["message"].contains("content") - ) { - choice["message"]["content"].get_to(command.answerMarkdown); - // TODO ask GPT for HTML formatted response - m8r::replaceAll( - "\n", - "
", - command.answerMarkdown); - MF_DEBUG(" answer (HTML): " << command.answerMarkdown << endl); - } - if(choice.contains("finish_reason")) { - string statusStr{}; - choice["finish_reason"].get_to(statusStr); - if(statusStr == "stop") { - command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK; - } else { - command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; - command.errorMessage.assign( - "OpenAI API HTTP required failed with finish_reason: " - + statusStr); - command.answerMarkdown.clear(); - command.answerTokens = 0; - command.answerLlmModel = llmModel; - } - MF_DEBUG(" status: " << command.status << endl); - } + if(httpResponseJSon.contains("response")) { + httpResponseJSon["response"].get_to(command.answerMarkdown); + // TODO ask GPT for HTML formatted response + m8r::replaceAll( + "\n", + "
", + command.answerMarkdown); + MF_DEBUG(" response (HTML): " << command.answerMarkdown << endl); } else { command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; + command.errorMessage.assign( + "No response in the ollama API HTTP response"); command.answerMarkdown.clear(); command.answerTokens = 0; command.answerLlmModel = llmModel; - if( - httpResponseJSon.contains("error") - && httpResponseJSon["error"].contains("message") - ) { - httpResponseJSon["error"]["message"].get_to(command.errorMessage); - } else { - command.errorMessage.assign( - "No choices in the OpenAI API HTTP response"); - } + } + if(httpResponseJSon.contains("prompt_eval_count")) { + httpResponseJSon["prompt_eval_count"].get_to(command.promptTokens); + MF_DEBUG(" prompt_eval_count: " << command.promptTokens << endl); + } + if(httpResponseJSon.contains("eval_count")) { + httpResponseJSon["eval_count"].get_to(command.answerTokens); + MF_DEBUG(" eval_count: " << command.answerTokens << endl); } #if !defined(__APPLE__) && !defined(_WIN32) } else { command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR; command.errorMessage.assign( - "OpenAI API HTTP request failed: unable to initialize cURL"); + "ollama API HTTP request failed: unable to initialize cURL"); } #endif } diff --git a/lib/src/mind/ai/llm/ollama_wingman.h b/lib/src/mind/ai/llm/ollama_wingman.h index 5b93ff25..2b160a60 100644 --- a/lib/src/mind/ai/llm/ollama_wingman.h +++ b/lib/src/mind/ai/llm/ollama_wingman.h @@ -42,13 +42,13 @@ namespace m8r { class OllamaWingman: Wingman { private: - std::string apiKey; + std::string url; std::string llmModel; void curlGet(CommandWingmanChat& command); public: - explicit OllamaWingman(const std::string& apiKey, const std::string& llmModel); + explicit OllamaWingman(const std::string& url, const std::string& llmModel); OllamaWingman(const OllamaWingman&) = delete; OllamaWingman(const OllamaWingman&&) = delete; OllamaWingman& operator =(const OllamaWingman&) = delete; diff --git a/lib/src/mind/ai/llm/openai_wingman.cpp b/lib/src/mind/ai/llm/openai_wingman.cpp index e862ec8b..d5215a39 100644 --- a/lib/src/mind/ai/llm/openai_wingman.cpp +++ b/lib/src/mind/ai/llm/openai_wingman.cpp @@ -55,6 +55,7 @@ OpenAiWingman::~OpenAiWingman() { } +// TODO refactor to parent class so that all wingmans can use it /** * OpenAI cURL GET request. * diff --git a/lib/src/mind/mind.cpp b/lib/src/mind/mind.cpp index aebf753a..29e91ed6 100644 --- a/lib/src/mind/mind.cpp +++ b/lib/src/mind/mind.cpp @@ -49,13 +49,11 @@ Mind::Mind(Configuration &configuration) exclusiveMind{}, timeScopeAspect{}, tagsScopeAspect{ontology}, - scopeAspect{timeScopeAspect, tagsScopeAspect} + scopeAspect{timeScopeAspect, tagsScopeAspect}, + wingman{nullptr} { ai = new Ai{memory, *this}; - // TODO BEGIN: code before Wingman config persisted - config.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI); - // TODO END: code before Wingman config persisted initWingman(); deleteWatermark = 0; @@ -1458,19 +1456,32 @@ void Mind::initWingman() "MIND Wingman init: " << boolalpha << config.isWingman() << endl ); if(config.isWingman()) { - MF_DEBUG("MIND Wingman INIT: instantiation..." << endl); + MF_DEBUG("MIND Wingman initialization..." << endl); switch(config.getWingmanLlmProvider()) { case WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI: MF_DEBUG(" MIND Wingman init: OpenAI" << endl); + if(wingman) { + delete wingman; + wingman = nullptr; + } wingman = (Wingman*)new OpenAiWingman{ - config.getWingmanApiKey(), - config.getWingmanLlmModel() + config.getWingmanOpenAiApiKey(), + config.getWingmanOpenAiLlm() + }; + wingmanLlmProvider = config.getWingmanLlmProvider(); + return; + case WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA: + MF_DEBUG(" MIND Wingman init: ollama" << endl); + if(wingman) { + delete wingman; + wingman = nullptr; + } + wingman = (Wingman*)new OllamaWingman{ + config.getWingmanOllamaUrl(), + config.getWingmanOllamaLlm() }; wingmanLlmProvider = config.getWingmanLlmProvider(); return; - // case BARD: - // wingman = (Wingman*)new BardWingman{}; - // return; case WingmanLlmProviders::WINGMAN_PROVIDER_MOCK: MF_DEBUG(" MIND Wingman init: MOCK" << endl); wingman = (Wingman*)new MockWingman{ @@ -1485,6 +1496,9 @@ void Mind::initWingman() } MF_DEBUG("MIND Wingman init: DISABLED" << endl); + if(wingman) { + delete wingman; + } wingman = nullptr; wingmanLlmProvider = WingmanLlmProviders::WINGMAN_PROVIDER_NONE; } diff --git a/lib/src/mind/mind.h b/lib/src/mind/mind.h index 6d8e55d8..8b6b9061 100644 --- a/lib/src/mind/mind.h +++ b/lib/src/mind/mind.h @@ -30,6 +30,7 @@ #include "ai/ai.h" #include "ai/llm/wingman.h" #include "ai/llm/openai_wingman.h" +#include "ai/llm/ollama_wingman.h" #include "ai/llm/mock_wingman.h" #include "associated_notes.h" #include "ontology/thing_class_rel_triple.h" diff --git a/lib/src/representations/markdown/markdown_configuration_representation.cpp b/lib/src/representations/markdown/markdown_configuration_representation.cpp index 3c6c6e84..1cfd8a96 100644 --- a/lib/src/representations/markdown/markdown_configuration_representation.cpp +++ b/lib/src/representations/markdown/markdown_configuration_representation.cpp @@ -36,6 +36,9 @@ constexpr const auto CONFIG_SETTING_MIND_DISTRIBUTOR_INTERVAL = "* Async refresh constexpr const auto CONFIG_SETTING_MIND_AUTOLINKING = "* Autolinking: "; constexpr const auto CONFIG_SETTING_MIND_WINGMAN_PROVIDER = "* Wingman LLM provider: "; constexpr const auto CONFIG_SETTING_MIND_OPENAI_KEY = "* Wingman's OpenAI API key: "; +constexpr const auto CONFIG_SETTING_MIND_OPENAI_LLM = "* Wingman's OpenAI LLM model: "; +constexpr const auto CONFIG_SETTING_MIND_OLLAMA_URL = "* Wingman's ollama URL: "; +constexpr const auto CONFIG_SETTING_MIND_OLLAMA_LLM = "* Wingman's ollama LLM model: "; // application constexpr const auto CONFIG_SETTING_STARTUP_VIEW_LABEL = "* Startup view: "; @@ -408,12 +411,26 @@ void MarkdownConfigurationRepresentation::configurationSection( WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI)) != std::string::npos ) { c.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI); + } else if(line->find( + c.getWingmanLlmProviderAsString( + WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA)) != std::string::npos + ) { + c.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA); } else { c.setWingmanLlmProvider(WingmanLlmProviders::WINGMAN_PROVIDER_NONE); } } else if(line->find(CONFIG_SETTING_MIND_OPENAI_KEY) != std::string::npos) { string k = line->substr(strlen(CONFIG_SETTING_MIND_OPENAI_KEY)); c.setWingmanOpenAiApiKey(k); + } else if(line->find(CONFIG_SETTING_MIND_OPENAI_LLM) != std::string::npos) { + string k = line->substr(strlen(CONFIG_SETTING_MIND_OPENAI_LLM)); + c.setWingmanOpenAiLlm(k); + } else if(line->find(CONFIG_SETTING_MIND_OLLAMA_URL) != std::string::npos) { + string k = line->substr(strlen(CONFIG_SETTING_MIND_OLLAMA_URL)); + c.setWingmanOllamaUrl(k); + } else if(line->find(CONFIG_SETTING_MIND_OLLAMA_LLM) != std::string::npos) { + string k = line->substr(strlen(CONFIG_SETTING_MIND_OLLAMA_LLM)); + c.setWingmanOllamaLlm(k); } } } @@ -506,9 +523,15 @@ string& MarkdownConfigurationRepresentation::to(Configuration* c, string& md) CONFIG_SETTING_MIND_AUTOLINKING << (c?(c->isAutolinking()?"yes":"no"):(Configuration::DEFAULT_AUTOLINKING?"yes":"no")) << endl << " * Examples: yes, no" << endl << CONFIG_SETTING_MIND_WINGMAN_PROVIDER << Configuration::getWingmanLlmProviderAsString(c?c->getWingmanLlmProvider():Configuration::DEFAULT_WINGMAN_LLM_PROVIDER) << endl << - " * Examples: none, openai" << endl << + " * Examples: none, openai, ollama" << endl << CONFIG_SETTING_MIND_OPENAI_KEY << (c?c->getWingmanOpenAiApiKey():"") << endl << " * OpenAI API key generated at https://platform.openai.com/api-keys to be used by Wingman as LLM provider" << endl << + CONFIG_SETTING_MIND_OPENAI_LLM << (c?c->getWingmanOpenAiLlm():"") << endl << + " * Preferred Open AI LLM model: gpt-4, gpt-3.5-turbo, ... " << endl << + CONFIG_SETTING_MIND_OLLAMA_URL << (c?c->getWingmanOllamaUrl():"") << endl << + " * Base URL of the ollama service like http://localhost:11434" << endl << + CONFIG_SETTING_MIND_OLLAMA_LLM << (c?c->getWingmanOllamaLlm():"") << endl << + " * Preferred ollama LLM model: llama2, mistral, phi, ... " << endl << endl << "# " << CONFIG_SECTION_APP << endl <<