1
0

askLlm.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { Message, Screenplay } from "@/features/chat/messages";
  2. import { Chat, ChatConfig } from "@/features/chat/chat";
  3. import { getEchoChatResponseStream } from "@/features/chat/echoChat";
  4. import { getOpenAiChatResponseStream } from "@/features/chat/openAiChat";
  5. import { getLlamaCppChatResponseStream } from "@/features/chat/llamaCppChat";
  6. import { getWindowAiChatResponseStream } from "@/features/chat/windowAiChat";
  7. import { getOllamaChatResponseStream } from "@/features/chat/ollamaChat";
  8. import { getKoboldAiChatResponseStream } from "@/features/chat/koboldAiChat";
  9. import { getOpenRouterChatResponseStream } from "@/features/chat/openRouterChat";
  10. import { processResponse } from "@/utils/processResponse";
  11. import { ChatbotBackend } from "@/types/backend";
  12. // Function to ask llm with custom system prompt, if doesn't want it to speak provide the chat in params as null.
  13. export async function askLLM(
  14. config: ChatConfig,
  15. systemPrompt: string,
  16. userPrompt: string,
  17. chat: Chat | null,
  18. onChatCompleteResolver?: () => void
  19. ): Promise<string> {
  20. let streams = [];
  21. let readers = [];
  22. let currentStreamIdx = 0
  23. let setChatProcessing = (_processing: boolean) => {};
  24. chat === null ? currentStreamIdx = 0 : null;
  25. const alert = {
  26. error: (title: string, message: string) => {
  27. console.error(`${title}: ${message}`);
  28. },
  29. };
  30. const messages: Message[] = [
  31. { role: "system", content: systemPrompt },
  32. { role: "user", content: userPrompt },
  33. ];
  34. // Function to simulate fetching chat response stream based on the selected backend
  35. const getChatResponseStream = async (messages: Message[]) => {
  36. console.debug("getChatResponseStream", messages);
  37. const chatbotBackend = config.chatbot_backend;
  38. const params = config.chatbot_params;
  39. const name = config.name;
  40. const system_prompt = config.system_prompt;
  41. switch (chatbotBackend) {
  42. case "openai":
  43. return getOpenAiChatResponseStream(params as ChatbotBackend["openai"], messages);
  44. case "llamacpp":
  45. return getLlamaCppChatResponseStream(params as ChatbotBackend["llamacpp"], name, system_prompt, messages);
  46. case "windowai":
  47. return getWindowAiChatResponseStream(name, messages);
  48. case "ollama":
  49. return getOllamaChatResponseStream(params as ChatbotBackend["ollama"], messages);
  50. case "koboldai":
  51. return getKoboldAiChatResponseStream(name, system_prompt, params as ChatbotBackend["koboldai"] ,messages);
  52. case "openrouter":
  53. return getOpenRouterChatResponseStream(params as ChatbotBackend["openrouter"], messages);
  54. default:
  55. return getEchoChatResponseStream(messages);
  56. }
  57. };
  58. try {
  59. streams.push(await getChatResponseStream(messages));
  60. } catch (e: any) {
  61. const errMsg = `Error: ${e.toString()}`;
  62. console.error(errMsg);
  63. alert.error("Failed to get subconcious subroutine response", errMsg);
  64. return errMsg;
  65. }
  66. const stream = streams[streams.length - 1];
  67. if (!stream) {
  68. const errMsg = "Error: Null subconcious subroutine stream encountered.";
  69. console.error(errMsg);
  70. alert.error("Null subconcious subroutine stream encountered", errMsg);
  71. return errMsg;
  72. }
  73. if (streams.length === 0) {
  74. console.log("No stream!");
  75. return "Error: No stream";
  76. }
  77. currentStreamIdx++;
  78. chat !== null ? currentStreamIdx = chat.currentStreamIdx : null;
  79. setChatProcessing(true);
  80. console.time("Subconcious subroutine stream processing");
  81. const reader = stream.getReader();
  82. readers.push(reader);
  83. let receivedMessage = "";
  84. let sentences = new Array<string>();
  85. let aiTextLog = "";
  86. let tag = "";
  87. let isThinking = false;
  88. let rolePlay = "";
  89. let result = "";
  90. let firstTokenEncountered = false;
  91. let firstSentenceEncountered = false;
  92. console.time('performance_time_to_first_token');
  93. chat !== null ? console.time('performance_time_to_first_sentence') : null ;
  94. try {
  95. while (true) {
  96. if (currentStreamIdx !== currentStreamIdx) {
  97. console.log("Wrong stream idx");
  98. break;
  99. }
  100. const { done, value } = await reader.read();
  101. if (!firstTokenEncountered) {
  102. console.timeEnd("performance_time_to_first_token");
  103. firstTokenEncountered = true;
  104. }
  105. if (done) break;
  106. receivedMessage += value;
  107. receivedMessage = receivedMessage.trimStart();
  108. if (chat !== null) {
  109. const proc = processResponse({
  110. sentences,
  111. aiTextLog,
  112. receivedMessage,
  113. tag,
  114. isThinking,
  115. rolePlay,
  116. callback: (aiTalks: Screenplay[]): boolean => {
  117. // Generate & play audio for each sentence, display responses
  118. console.debug('enqueue tts', aiTalks);
  119. console.debug('streamIdx', currentStreamIdx, 'currentStreamIdx', chat.currentStreamIdx)
  120. if (currentStreamIdx !== chat.currentStreamIdx) {
  121. console.log('wrong stream idx');
  122. return true; // should break
  123. }
  124. chat.ttsJobs.enqueue({
  125. screenplay: aiTalks[0],
  126. streamIdx: currentStreamIdx,
  127. });
  128. if (! firstSentenceEncountered) {
  129. console.timeEnd('performance_time_to_first_sentence');
  130. firstSentenceEncountered = true;
  131. }
  132. return false; // normal processing
  133. }
  134. });
  135. sentences = proc.sentences;
  136. aiTextLog = proc.aiTextLog;
  137. receivedMessage = proc.receivedMessage;
  138. tag = proc.tag;
  139. rolePlay = proc.rolePlay;
  140. if (proc.shouldBreak) {
  141. break;
  142. }
  143. }
  144. }
  145. // Wait for TTS jobs to complete
  146. if (onChatCompleteResolver && chat) {
  147. const waitForTTS = () =>
  148. new Promise<void>((resolve) => {
  149. const interval = setInterval(() => {
  150. if (chat.speakJobs.size() === 0 && chat.ttsJobs.size() === 0) {
  151. clearInterval(interval);
  152. resolve();
  153. }
  154. }, 100);
  155. });
  156. await waitForTTS();
  157. onChatCompleteResolver();
  158. }
  159. } catch (e: any) {
  160. const errMsg = e.toString();
  161. console.error(errMsg);
  162. } finally {
  163. if (!reader.closed) {
  164. reader.releaseLock();
  165. }
  166. console.timeEnd("Subconcious subroutine stream processing");
  167. if (currentStreamIdx === currentStreamIdx) {
  168. setChatProcessing(false);
  169. }
  170. }
  171. chat !== null ? result = aiTextLog : result = receivedMessage;
  172. return result;
  173. }
  174. export default askLLM;