"use client"; // azure and openai, using same models. so using same LLMApi. import { ApiPath, SILICONFLOW_BASE_URL, SiliconFlow, DEFAULT_MODELS, } from "@/app/constant"; import { useAccessStore, useAppConfig, useChatStore, ChatMessageTool, usePluginStore, } from "@/app/store"; import { preProcessImageContent, streamWithThink } from "@/app/utils/chat"; import { ChatOptions, getHeaders, LLMApi, LLMModel, SpeechOptions, } from "../api"; import { getClientConfig } from "@/app/config/client"; import { getMessageTextContent, getMessageTextContentWithoutThinking, isVisionModel, getTimeoutMSByModel, } from "@/app/utils"; import { RequestPayload } from "./openai"; import { fetch } from "@/app/utils/stream"; export interface SiliconFlowListModelResponse { object: string; data: Array<{ id: string; object: string; root: string; }>; } export class SiliconflowApi implements LLMApi { private disableListModels = false; path(path: string): string { const accessStore = useAccessStore.getState(); let baseUrl = ""; if (accessStore.useCustomConfig) { baseUrl = accessStore.siliconflowUrl; } if (baseUrl.length === 0) { const isApp = !!getClientConfig()?.isApp; const apiPath = ApiPath.SiliconFlow; baseUrl = isApp ? SILICONFLOW_BASE_URL : apiPath; } if (baseUrl.endsWith("/")) { baseUrl = baseUrl.slice(0, baseUrl.length - 1); } if ( !baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.SiliconFlow) ) { baseUrl = "https://" + baseUrl; } console.log("[Proxy Endpoint] ", baseUrl, path); return [baseUrl, path].join("/"); } extractMessage(res: any) { return res.choices?.at(0)?.message?.content ?? ""; } speech(options: SpeechOptions): Promise { throw new Error("Method not implemented."); } async chat(options: ChatOptions) { const visionModel = isVisionModel(options.config.model); const messages: ChatOptions["messages"] = []; for (const v of options.messages) { if (v.role === "assistant") { const content = getMessageTextContentWithoutThinking(v); messages.push({ role: v.role, content }); } else { const content = visionModel ? await preProcessImageContent(v.content) : getMessageTextContent(v); messages.push({ role: v.role, content }); } } const modelConfig = { ...useAppConfig.getState().modelConfig, ...useChatStore.getState().currentSession().mask.modelConfig, ...{ model: options.config.model, providerName: options.config.providerName, }, }; const requestPayload: RequestPayload = { messages, stream: options.config.stream, model: modelConfig.model, temperature: modelConfig.temperature, presence_penalty: modelConfig.presence_penalty, frequency_penalty: modelConfig.frequency_penalty, top_p: modelConfig.top_p, // max_tokens: Math.max(modelConfig.max_tokens, 1024), // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore. }; console.log("[Request] openai payload: ", requestPayload); const shouldStream = !!options.config.stream; const controller = new AbortController(); options.onController?.(controller); try { const chatPath = this.path(SiliconFlow.ChatPath); const chatPayload = { method: "POST", body: JSON.stringify(requestPayload), signal: controller.signal, headers: getHeaders(), }; // console.log(chatPayload); // Use extended timeout for thinking models as they typically require more processing time const requestTimeoutId = setTimeout( () => controller.abort(), getTimeoutMSByModel(options.config.model), ); if (shouldStream) { const [tools, funcs] = usePluginStore .getState() .getAsTools( useChatStore.getState().currentSession().mask?.plugin || [], ); return streamWithThink( chatPath, requestPayload, getHeaders(), tools as any, funcs, controller, // parseSSE mới cho SiliconFlow response (text: string, runTools: ChatMessageTool[]) => { // Parse chuỗi JSON trả về thành đối tượng const json = JSON.parse(text); // Lấy nội dung trả lời từ output.text const content = json?.output?.text ?? ""; // Nếu không có nội dung trả lời, trả về trạng thái không suy nghĩ và nội dung rỗng if (!content || content.length === 0) { return { isThinking: false, content: "", }; } // Trả về trạng thái không suy nghĩ và nội dung trả lời return { isThinking: false, content: content, }; }, // processToolMessage: SiliconFlow không có tool_call nên giữ nguyên hoặc để rỗng ( requestPayload: RequestPayload, toolCallMessage: any, toolCallResult: any[], ) => { // Không cần xử lý tool_call, có thể để trống hoặc giữ nguyên nếu muốn tương thích }, options, ); } else { const res = await fetch(chatPath, chatPayload); clearTimeout(requestTimeoutId); const resJson = await res.json(); const message = this.extractMessage(resJson); options.onFinish(message, res); } } catch (e) { console.log("[Request] failed to make a chat request", e); options.onError?.(e as Error); } } async usage() { return { used: 0, total: 0, }; } async models(): Promise { if (this.disableListModels) { return DEFAULT_MODELS.slice(); } const res = await fetch(this.path(SiliconFlow.ListModelPath), { method: "GET", headers: { ...getHeaders(), }, }); const resJson = (await res.json()) as SiliconFlowListModelResponse; const chatModels = resJson.data; console.log("[Models]", chatModels); if (!chatModels) { return []; } let seq = 1000; //同 Constant.ts 中的排序保持一致 return chatModels.map((m) => ({ name: m.id, available: true, sorted: seq++, provider: { id: "siliconflow", providerName: "SiliconFlow", providerType: "siliconflow", sorted: 14, }, })); } }