66#include < tgbot/TgException.h>
77
88#include < algorithm>
9+ #include < cstdint>
910#include < api/CommandModule.hpp>
1011#include < api/MessageExt.hpp>
1112#include < api/TgBotApi.hpp>
1213#include < filesystem>
14+ #include < memory>
15+ #include < mutex>
1316#include < nlohmann/json.hpp>
1417#include < string_view>
18+ #include < unordered_map>
1519
1620#include " CurlUtils.hpp"
1721#include " llm/LMStudioApi.hpp"
2125#ifdef ASK_ENABLE_LOCAL_LLM
2226#include " llm/LLMCore.hpp"
2327
24- constexpr const char * kLLMStorageKey = " LLMCoreInstance" ;
25-
2628struct LLMCoreInstance {
2729 LLMCore llm;
2830 LLMCore::Model model;
31+ std::filesystem::path modelPath;
2932};
3033
3134// Handler for local model LLM <i.e. With supported GPU>
3235static void localmodelhandler (TgBotApi::Ptr api, MessageExt* message,
33- const StringResLoader::PerLocaleMap* res,
34- const Providers* provider,
3536 const std::filesystem::path& modelPath) {
36- LLMCoreInstance* instance = nullptr ;
37+ static std::mutex llm_mutex;
38+ static std::unique_ptr<LLMCoreInstance> instance;
3739
3840 auto sent = api->sendReplyMessage (message->message (),
3941 " Processing your query, please wait..." );
4042
41- if (provider->globalStorage ->hasStorage (kLLMStorageKey )) {
42- instance = provider->globalStorage ->getStorage (kLLMStorageKey )
43- .getAs <LLMCoreInstance>();
43+ std::unique_lock<std::mutex> lock (llm_mutex, std::defer_lock);
44+ if (!lock.try_lock ()) {
45+ api->editMessage (sent,
46+ " Another LLM request is running. Waiting for it to "
47+ " finish..." );
48+ lock.lock ();
49+ }
4450
51+ if (instance != nullptr && instance->modelPath == modelPath) {
4552 if (!message->has <MessageAttrs::ExtraText>()) {
4653 auto info = instance->model .info ();
4754 api->editMessage (
@@ -57,19 +64,23 @@ File Size: {} bytes)",
5764 return ;
5865 }
5966 } else {
60- provider->globalStorage ->getStorage (kLLMStorageKey ) =
61- SharedMalloc::fromType<LLMCoreInstance>();
62-
63- instance = provider->globalStorage ->getStorage (kLLMStorageKey )
64- .getAs <LLMCoreInstance>();
67+ if (!message->has <MessageAttrs::ExtraText>()) {
68+ api->editMessage (
69+ sent,
70+ " Please provide a query after the command to ask the LLM." );
71+ return ;
72+ }
6573
6674 api->editMessage (sent, " Initializing LLM core, please wait..." );
67- if (!instance->model .load (modelPath)) {
75+ instance.reset ();
76+ auto new_instance = std::make_unique<LLMCoreInstance>();
77+ if (!new_instance->model .load (modelPath)) {
6878 LOG (ERROR ) << " Failed to initialize LLM core." ;
6979 api->editMessage (sent, " Error: Unable to initialize LLM core." );
70- provider->globalStorage ->removeStorage (kLLMStorageKey );
7180 return ;
7281 }
82+ new_instance->modelPath = modelPath;
83+ instance = std::move (new_instance);
7384 }
7485
7586 if (!message->has <MessageAttrs::ExtraText>()) {
@@ -79,9 +90,17 @@ File Size: {} bytes)",
7990 }
8091
8192 std::string query = message->get <MessageAttrs::ExtraText>();
82- auto response_opt = instance->llm .query (&instance->model , query, 512 );
93+ LLMCore::QueryError query_error = LLMCore::QueryError::None;
94+ auto response_opt =
95+ instance->llm .query (&instance->model , query, 512 , &query_error);
8396 if (!response_opt) {
8497 LOG (ERROR ) << " LLM query failed." ;
98+ if (query_error == LLMCore::QueryError::PromptTooLong) {
99+ api->editMessage (sent,
100+ " Error: Prompt is too long for the local LLM "
101+ " context. Please shorten it and try again." );
102+ return ;
103+ }
85104 api->editMessage (sent, " Error: LLM query failed." );
86105 return ;
87106 }
@@ -107,8 +126,6 @@ File Size: {} bytes)",
107126}
108127#endif // ASK_ENABLE_LOCAL_LLM
109128
110- constexpr float kTemperature = 0 .7F ;
111-
112129static void localnetmodelhandler (TgBotApi::Ptr api, MessageExt* message,
113130 const StringResLoader::PerLocaleMap* res,
114131 const Providers* provider,
@@ -165,7 +182,6 @@ static void localnetmodelhandler(TgBotApi::Ptr api, MessageExt* message,
165182 return m.type == LMStudioApi::LLMType::llm;
166183 });
167184
168- static std::string response_key;
169185 if (that == modelResponse.models .end ()) {
170186 LOG (ERROR ) << " No LLM type models available from LLM server." ;
171187 api->editMessage (
@@ -174,19 +190,18 @@ static void localnetmodelhandler(TgBotApi::Ptr api, MessageExt* message,
174190 }
175191 chatRequest.model = that->key ;
176192 LOG (INFO ) << " Using model " << chatRequest.model << " for query." ;
177- chatRequest.temperature = 0 .2f ;
178193 chatRequest.system_prompt = SYSTEM_PROMPT ;
179194 chatRequest.input = query;
180- std::vector<LMStudioApi::ChatRequest::Plugin> plugins{};
181- plugins. emplace_back (LMStudioApi::ChatRequest::Plugin{
182- . type = " plugin " ,
183- . id = " mcp/playwright " ,
184- });
185- chatRequest. integrations = plugins ;
186- chatRequest. context_length = 64000 ;
187- chatRequest. reasoning = LMStudioApi::Reasoning::medium;
188- if (!response_key. empty ()) {
189- chatRequest. previous_response_id = response_key;
195+
196+ const std:: int64_t chat_id = message-> get <MessageAttrs::Chat>()-> id ;
197+ static std::mutex response_keys_mutex;
198+ static std::unordered_map<std:: int64_t , std::string> response_keys;
199+ {
200+ std::lock_guard<std::mutex> lock (response_keys_mutex) ;
201+ if ( auto it = response_keys. find (chat_id) ;
202+ it != response_keys. end () && !it-> second . empty ()) {
203+ chatRequest. previous_response_id = it-> second ;
204+ }
190205 }
191206
192207 nlohmann::json payload = chatRequest;
@@ -220,7 +235,14 @@ static void localnetmodelhandler(TgBotApi::Ptr api, MessageExt* message,
220235 " Error: LLM server response has no 'message' type output." );
221236 return ;
222237 }
223- response_key = chatResponse.response_id .value_or (" " );
238+ {
239+ std::lock_guard<std::mutex> lock (response_keys_mutex);
240+ if (chatResponse.response_id ) {
241+ response_keys[chat_id] = *chatResponse.response_id ;
242+ } else {
243+ response_keys.erase (chat_id);
244+ }
245+ }
224246 std::initializer_list<
225247 std::pair<absl::string_view, absl::string_view>>
226248 replacements = {
@@ -266,9 +288,12 @@ DECLARE_COMMAND_HANDLER(ask) {
266288
267289 if (type == " local" ) {
268290#ifdef ASK_ENABLE_LOCAL_LLM
269- localmodelhandler (api, message, res, provider, location);
291+ localmodelhandler (api, message, location);
270292#else
271293 LOG (ERROR ) << " Local LLM support is not enabled in this build." ;
294+ api->sendMessage (
295+ message->get <MessageAttrs::Chat>(),
296+ " Error: Local LLM support is not enabled in this build." );
272297#endif
273298 } else if (type == " localnet" ) {
274299 if (authkey) {
@@ -280,6 +305,8 @@ DECLARE_COMMAND_HANDLER(ask) {
280305 }
281306 } else {
282307 LOG (ERROR ) << " Unsupported LLM configuration type: " << type;
308+ api->sendMessage (message->get <MessageAttrs::Chat>(),
309+ " Error: Unsupported LLM configuration type." );
283310 }
284311}
285312
0 commit comments