"use client"; import { ApiPath, Alibaba, ALIBABA_BASE_URL } from "@/app/constant"; import { useAccessStore, useAppConfig, useChatStore, ChatMessageTool, usePluginStore, } from "@/app/store"; import { preProcessImageContentForAlibabaDashScope, streamWithThink, } from "@/app/utils/chat"; import { ChatOptions, getHeaders, LLMApi, LLMModel, SpeechOptions, MultimodalContent, } from "../api"; import { getClientConfig } from "@/app/config/client"; import { getMessageTextContent, getMessageTextContentWithoutThinking, getTimeoutMSByModel, isVisionModel, } from "@/app/utils"; import { fetch } from "@/app/utils/stream"; export interface OpenAIListModelResponse { object: string; data: Array<{ id: string; object: string; root: string; }>; } interface RequestInput { messages: { role: "system" | "user" | "assistant"; content: string | MultimodalContent[]; }[]; } interface RequestParam { result_format: string; incremental_output?: boolean; temperature: number; repetition_penalty?: number; top_p: number; max_tokens?: number; } interface RequestPayload { model: string; input: RequestInput; parameters: RequestParam; } export class QwenApi implements LLMApi { path(path: string): string { const accessStore = useAccessStore.getState(); let baseUrl = ""; if (accessStore.useCustomConfig) { baseUrl = accessStore.alibabaUrl; } if (baseUrl.length === 0) { const isApp = !!getClientConfig()?.isApp; baseUrl = isApp ? ALIBABA_BASE_URL : ApiPath.Alibaba; } if (baseUrl.endsWith("/")) { baseUrl = baseUrl.slice(0, baseUrl.length - 1); } if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Alibaba)) { baseUrl = "https://" + baseUrl; } console.log("[Proxy Endpoint] ", baseUrl, path); return [baseUrl, path].join("/"); } extractMessage(res: any) { return res?.output?.choices?.at(0)?.message?.content ?? ""; } speech(options: SpeechOptions): Promise { throw new Error("Method not implemented."); } async chat(options: ChatOptions) { const modelConfig = { ...useAppConfig.getState().modelConfig, ...useChatStore.getState().currentSession().mask.modelConfig, ...{ model: options.config.model, }, }; const visionModel = isVisionModel(options.config.model); const messages: ChatOptions["messages"] = []; for (const v of options.messages) { const content = ( visionModel ? await preProcessImageContentForAlibabaDashScope(v.content) : v.role === "assistant" ? getMessageTextContentWithoutThinking(v) : getMessageTextContent(v) ) as any; messages.push({ role: v.role, content }); } const shouldStream = !!options.config.stream; const requestPayload: RequestPayload = { model: modelConfig.model, input: { messages, }, parameters: { result_format: "message", incremental_output: shouldStream, temperature: modelConfig.temperature, // max_tokens: modelConfig.max_tokens, top_p: modelConfig.top_p === 1 ? 0.99 : modelConfig.top_p, // qwen top_p is should be < 1 }, }; const controller = new AbortController(); options.onController?.(controller); try { const headers = { ...getHeaders(), "X-DashScope-SSE": shouldStream ? "enable" : "disable", }; const chatPath = this.path(Alibaba.ChatPath(modelConfig.model)); const chatPayload = { method: "POST", body: JSON.stringify(requestPayload), signal: controller.signal, headers: headers, }; // make a fetch request const requestTimeoutId = setTimeout( () => controller.abort(), getTimeoutMSByModel(options.config.model), ); if (shouldStream) { // Lấy danh sách các công cụ (tools) và hàm (funcs) từ plugin hiện tại của phiên chat const [tools, funcs] = usePluginStore .getState() .getAsTools( useChatStore.getState().currentSession().mask?.plugin || [], ); // Gọi hàm streamWithThink để xử lý chat dạng stream (dòng sự kiện server-sent events) return streamWithThink( chatPath, requestPayload, headers, tools as any, funcs, controller, // Updated SSE parse callback for new JSON structure (text: string, runTools: ChatMessageTool[]) => { // Parse the JSON response const json = JSON.parse(text); // console.log("[Alibaba] SSE response", json); // Extract content from the new structure const output = json.output; const content = output?.text ?? ""; const reasoning = output?.reasoning_content ?? ""; // If exists in your new structure // If both are empty, return default if ( (!reasoning || reasoning.length === 0) && (!content || content.length === 0) ) { return { isThinking: false, content: "", }; } // If reasoning_content exists, treat as "thinking" if (reasoning && reasoning.length > 0) { return { isThinking: true, content: reasoning, }; } // Otherwise, return the main content else if (content && content.length > 0) { return { isThinking: false, content: content, }; } // Fallback return { isThinking: false, content: "", }; }, // Hàm xử lý message liên quan đến tool_call và kết quả trả về từ tool_call ( requestPayload: RequestPayload, toolCallMessage: any, toolCallResult: any[], ) => { // Thêm message gọi tool và kết quả trả về vào cuối mảng messages trong payload gửi lên API requestPayload?.input?.messages?.splice( requestPayload?.input?.messages?.length, 0, toolCallMessage, ...toolCallResult, ); }, options, // Các tuỳ chọn khác cho hàm streamWithThink ); } 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 { return []; } } export { Alibaba };