siliconflow.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. "use client";
  2. // azure and openai, using same models. so using same LLMApi.
  3. import {
  4. ApiPath,
  5. SILICONFLOW_BASE_URL,
  6. SiliconFlow,
  7. DEFAULT_MODELS,
  8. } from "@/app/constant";
  9. import {
  10. useAccessStore,
  11. useAppConfig,
  12. useChatStore,
  13. ChatMessageTool,
  14. usePluginStore,
  15. } from "@/app/store";
  16. import { preProcessImageContent, streamWithThink } from "@/app/utils/chat";
  17. import {
  18. ChatOptions,
  19. getHeaders,
  20. LLMApi,
  21. LLMModel,
  22. SpeechOptions,
  23. } from "../api";
  24. import { getClientConfig } from "@/app/config/client";
  25. import {
  26. getMessageTextContent,
  27. getMessageTextContentWithoutThinking,
  28. isVisionModel,
  29. getTimeoutMSByModel,
  30. } from "@/app/utils";
  31. import { RequestPayload } from "./openai";
  32. import { fetch } from "@/app/utils/stream";
  33. export interface SiliconFlowListModelResponse {
  34. object: string;
  35. data: Array<{
  36. id: string;
  37. object: string;
  38. root: string;
  39. }>;
  40. }
  41. export class SiliconflowApi implements LLMApi {
  42. private disableListModels = false;
  43. path(path: string): string {
  44. const accessStore = useAccessStore.getState();
  45. let baseUrl = "";
  46. if (accessStore.useCustomConfig) {
  47. baseUrl = accessStore.siliconflowUrl;
  48. }
  49. if (baseUrl.length === 0) {
  50. const isApp = !!getClientConfig()?.isApp;
  51. const apiPath = ApiPath.SiliconFlow;
  52. baseUrl = isApp ? SILICONFLOW_BASE_URL : apiPath;
  53. }
  54. if (baseUrl.endsWith("/")) {
  55. baseUrl = baseUrl.slice(0, baseUrl.length - 1);
  56. }
  57. if (
  58. !baseUrl.startsWith("http") &&
  59. !baseUrl.startsWith(ApiPath.SiliconFlow)
  60. ) {
  61. baseUrl = "https://" + baseUrl;
  62. }
  63. console.log("[Proxy Endpoint] ", baseUrl, path);
  64. return [baseUrl, path].join("/");
  65. }
  66. extractMessage(res: any) {
  67. return res.choices?.at(0)?.message?.content ?? "";
  68. }
  69. speech(options: SpeechOptions): Promise<ArrayBuffer> {
  70. throw new Error("Method not implemented.");
  71. }
  72. async chat(options: ChatOptions) {
  73. const visionModel = isVisionModel(options.config.model);
  74. const messages: ChatOptions["messages"] = [];
  75. for (const v of options.messages) {
  76. if (v.role === "assistant") {
  77. const content = getMessageTextContentWithoutThinking(v);
  78. messages.push({ role: v.role, content });
  79. } else {
  80. const content = visionModel
  81. ? await preProcessImageContent(v.content)
  82. : getMessageTextContent(v);
  83. messages.push({ role: v.role, content });
  84. }
  85. }
  86. const modelConfig = {
  87. ...useAppConfig.getState().modelConfig,
  88. ...useChatStore.getState().currentSession().mask.modelConfig,
  89. ...{
  90. model: options.config.model,
  91. providerName: options.config.providerName,
  92. },
  93. };
  94. const requestPayload: RequestPayload = {
  95. messages,
  96. stream: options.config.stream,
  97. model: modelConfig.model,
  98. temperature: modelConfig.temperature,
  99. presence_penalty: modelConfig.presence_penalty,
  100. frequency_penalty: modelConfig.frequency_penalty,
  101. top_p: modelConfig.top_p,
  102. // max_tokens: Math.max(modelConfig.max_tokens, 1024),
  103. // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
  104. };
  105. console.log("[Request] openai payload: ", requestPayload);
  106. const shouldStream = !!options.config.stream;
  107. const controller = new AbortController();
  108. options.onController?.(controller);
  109. try {
  110. const chatPath = this.path(SiliconFlow.ChatPath);
  111. const chatPayload = {
  112. method: "POST",
  113. body: JSON.stringify(requestPayload),
  114. signal: controller.signal,
  115. headers: getHeaders(),
  116. };
  117. // console.log(chatPayload);
  118. // Use extended timeout for thinking models as they typically require more processing time
  119. const requestTimeoutId = setTimeout(
  120. () => controller.abort(),
  121. getTimeoutMSByModel(options.config.model),
  122. );
  123. if (shouldStream) {
  124. const [tools, funcs] = usePluginStore
  125. .getState()
  126. .getAsTools(
  127. useChatStore.getState().currentSession().mask?.plugin || [],
  128. );
  129. return streamWithThink(
  130. chatPath,
  131. requestPayload,
  132. getHeaders(),
  133. tools as any,
  134. funcs,
  135. controller,
  136. // parseSSE
  137. (text: string, runTools: ChatMessageTool[]) => {
  138. // console.log("parseSSE", text, runTools);
  139. const json = JSON.parse(text);
  140. const choices = json.choices as Array<{
  141. delta: {
  142. content: string | null;
  143. tool_calls: ChatMessageTool[];
  144. reasoning_content: string | null;
  145. };
  146. }>;
  147. const tool_calls = choices[0]?.delta?.tool_calls;
  148. if (tool_calls?.length > 0) {
  149. const index = tool_calls[0]?.index;
  150. const id = tool_calls[0]?.id;
  151. const args = tool_calls[0]?.function?.arguments;
  152. if (id) {
  153. runTools.push({
  154. id,
  155. type: tool_calls[0]?.type,
  156. function: {
  157. name: tool_calls[0]?.function?.name as string,
  158. arguments: args,
  159. },
  160. });
  161. } else {
  162. // @ts-ignore
  163. runTools[index]["function"]["arguments"] += args;
  164. }
  165. }
  166. const reasoning = choices[0]?.delta?.reasoning_content;
  167. const content = choices[0]?.delta?.content;
  168. // Skip if both content and reasoning_content are empty or null
  169. if (
  170. (!reasoning || reasoning.length === 0) &&
  171. (!content || content.length === 0)
  172. ) {
  173. return {
  174. isThinking: false,
  175. content: "",
  176. };
  177. }
  178. if (reasoning && reasoning.length > 0) {
  179. return {
  180. isThinking: true,
  181. content: reasoning,
  182. };
  183. } else if (content && content.length > 0) {
  184. return {
  185. isThinking: false,
  186. content: content,
  187. };
  188. }
  189. return {
  190. isThinking: false,
  191. content: "",
  192. };
  193. },
  194. // processToolMessage, include tool_calls message and tool call results
  195. (
  196. requestPayload: RequestPayload,
  197. toolCallMessage: any,
  198. toolCallResult: any[],
  199. ) => {
  200. // @ts-ignore
  201. requestPayload?.messages?.splice(
  202. // @ts-ignore
  203. requestPayload?.messages?.length,
  204. 0,
  205. toolCallMessage,
  206. ...toolCallResult,
  207. );
  208. },
  209. options,
  210. );
  211. } else {
  212. const res = await fetch(chatPath, chatPayload);
  213. clearTimeout(requestTimeoutId);
  214. const resJson = await res.json();
  215. const message = this.extractMessage(resJson);
  216. options.onFinish(message, res);
  217. }
  218. } catch (e) {
  219. console.log("[Request] failed to make a chat request", e);
  220. options.onError?.(e as Error);
  221. }
  222. }
  223. async usage() {
  224. return {
  225. used: 0,
  226. total: 0,
  227. };
  228. }
  229. async models(): Promise<LLMModel[]> {
  230. if (this.disableListModels) {
  231. return DEFAULT_MODELS.slice();
  232. }
  233. const res = await fetch(this.path(SiliconFlow.ListModelPath), {
  234. method: "GET",
  235. headers: {
  236. ...getHeaders(),
  237. },
  238. });
  239. const resJson = (await res.json()) as SiliconFlowListModelResponse;
  240. const chatModels = resJson.data;
  241. console.log("[Models]", chatModels);
  242. if (!chatModels) {
  243. return [];
  244. }
  245. let seq = 1000; //同 Constant.ts 中的排序保持一致
  246. return chatModels.map((m) => ({
  247. name: m.id,
  248. available: true,
  249. sorted: seq++,
  250. provider: {
  251. id: "siliconflow",
  252. providerName: "SiliconFlow",
  253. providerType: "siliconflow",
  254. sorted: 14,
  255. },
  256. }));
  257. }
  258. }