moonshot.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. "use client";
  2. // azure and openai, using same models. so using same LLMApi.
  3. import {
  4. ApiPath,
  5. DEFAULT_API_HOST,
  6. DEFAULT_MODELS,
  7. Moonshot,
  8. REQUEST_TIMEOUT_MS,
  9. ServiceProvider,
  10. } from "@/app/constant";
  11. import {
  12. useAccessStore,
  13. useAppConfig,
  14. useChatStore,
  15. ChatMessageTool,
  16. usePluginStore,
  17. } from "@/app/store";
  18. import { collectModelsWithDefaultModel } from "@/app/utils/model";
  19. import { preProcessImageContent, stream } from "@/app/utils/chat";
  20. import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
  21. import {
  22. ChatOptions,
  23. getHeaders,
  24. LLMApi,
  25. LLMModel,
  26. LLMUsage,
  27. MultimodalContent,
  28. SpeechOptions,
  29. TranscriptionOptions,
  30. } from "../api";
  31. import Locale from "../../locales";
  32. import {
  33. EventStreamContentType,
  34. fetchEventSource,
  35. } from "@fortaine/fetch-event-source";
  36. import { prettyObject } from "@/app/utils/format";
  37. import { getClientConfig } from "@/app/config/client";
  38. import { getMessageTextContent } from "@/app/utils";
  39. import { OpenAIListModelResponse, RequestPayload } from "./openai";
  40. export class MoonshotApi implements LLMApi {
  41. private disableListModels = true;
  42. path(path: string): string {
  43. const accessStore = useAccessStore.getState();
  44. let baseUrl = "";
  45. if (accessStore.useCustomConfig) {
  46. baseUrl = accessStore.moonshotUrl;
  47. }
  48. if (baseUrl.length === 0) {
  49. const isApp = !!getClientConfig()?.isApp;
  50. const apiPath = ApiPath.Moonshot;
  51. baseUrl = isApp ? DEFAULT_API_HOST + "/proxy" + apiPath : apiPath;
  52. }
  53. if (baseUrl.endsWith("/")) {
  54. baseUrl = baseUrl.slice(0, baseUrl.length - 1);
  55. }
  56. if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Moonshot)) {
  57. baseUrl = "https://" + baseUrl;
  58. }
  59. console.log("[Proxy Endpoint] ", baseUrl, path);
  60. return [baseUrl, path].join("/");
  61. }
  62. extractMessage(res: any) {
  63. return res.choices?.at(0)?.message?.content ?? "";
  64. }
  65. speech(options: SpeechOptions): Promise<ArrayBuffer> {
  66. throw new Error("Method not implemented.");
  67. }
  68. transcription(options: TranscriptionOptions): Promise<string> {
  69. throw new Error("Method not implemented.");
  70. }
  71. async chat(options: ChatOptions) {
  72. const messages: ChatOptions["messages"] = [];
  73. for (const v of options.messages) {
  74. const content = getMessageTextContent(v);
  75. messages.push({ role: v.role, content });
  76. }
  77. const modelConfig = {
  78. ...useAppConfig.getState().modelConfig,
  79. ...useChatStore.getState().currentSession().mask.modelConfig,
  80. ...{
  81. model: options.config.model,
  82. providerName: options.config.providerName,
  83. },
  84. };
  85. const requestPayload: RequestPayload = {
  86. messages,
  87. stream: options.config.stream,
  88. model: modelConfig.model,
  89. temperature: modelConfig.temperature,
  90. presence_penalty: modelConfig.presence_penalty,
  91. frequency_penalty: modelConfig.frequency_penalty,
  92. top_p: modelConfig.top_p,
  93. // max_tokens: Math.max(modelConfig.max_tokens, 1024),
  94. // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
  95. };
  96. console.log("[Request] openai payload: ", requestPayload);
  97. const shouldStream = !!options.config.stream;
  98. const controller = new AbortController();
  99. options.onController?.(controller);
  100. try {
  101. const chatPath = this.path(Moonshot.ChatPath);
  102. const chatPayload = {
  103. method: "POST",
  104. body: JSON.stringify(requestPayload),
  105. signal: controller.signal,
  106. headers: getHeaders(),
  107. };
  108. // make a fetch request
  109. const requestTimeoutId = setTimeout(
  110. () => controller.abort(),
  111. REQUEST_TIMEOUT_MS,
  112. );
  113. if (shouldStream) {
  114. const [tools, funcs] = usePluginStore
  115. .getState()
  116. .getAsTools(
  117. useChatStore.getState().currentSession().mask?.plugin || [],
  118. );
  119. return stream(
  120. chatPath,
  121. requestPayload,
  122. getHeaders(),
  123. tools as any,
  124. funcs,
  125. controller,
  126. // parseSSE
  127. (text: string, runTools: ChatMessageTool[]) => {
  128. // console.log("parseSSE", text, runTools);
  129. const json = JSON.parse(text);
  130. const choices = json.choices as Array<{
  131. delta: {
  132. content: string;
  133. tool_calls: ChatMessageTool[];
  134. };
  135. }>;
  136. const tool_calls = choices[0]?.delta?.tool_calls;
  137. if (tool_calls?.length > 0) {
  138. const index = tool_calls[0]?.index;
  139. const id = tool_calls[0]?.id;
  140. const args = tool_calls[0]?.function?.arguments;
  141. if (id) {
  142. runTools.push({
  143. id,
  144. type: tool_calls[0]?.type,
  145. function: {
  146. name: tool_calls[0]?.function?.name as string,
  147. arguments: args,
  148. },
  149. });
  150. } else {
  151. // @ts-ignore
  152. runTools[index]["function"]["arguments"] += args;
  153. }
  154. }
  155. return choices[0]?.delta?.content;
  156. },
  157. // processToolMessage, include tool_calls message and tool call results
  158. (
  159. requestPayload: RequestPayload,
  160. toolCallMessage: any,
  161. toolCallResult: any[],
  162. ) => {
  163. // @ts-ignore
  164. requestPayload?.messages?.splice(
  165. // @ts-ignore
  166. requestPayload?.messages?.length,
  167. 0,
  168. toolCallMessage,
  169. ...toolCallResult,
  170. );
  171. },
  172. options,
  173. );
  174. } else {
  175. const res = await fetch(chatPath, chatPayload);
  176. clearTimeout(requestTimeoutId);
  177. const resJson = await res.json();
  178. const message = this.extractMessage(resJson);
  179. options.onFinish(message);
  180. }
  181. } catch (e) {
  182. console.log("[Request] failed to make a chat request", e);
  183. options.onError?.(e as Error);
  184. }
  185. }
  186. async usage() {
  187. return {
  188. used: 0,
  189. total: 0,
  190. };
  191. }
  192. async models(): Promise<LLMModel[]> {
  193. return [];
  194. }
  195. }