From e3fc9eef8f19fd3522787d2eb5ad3e0971561602 Mon Sep 17 00:00:00 2001 From: quangdn-ght Date: Tue, 24 Jun 2025 12:00:46 +0700 Subject: [PATCH] chuyen ve api dashscope compatible - toi uu vision qwen-vl --- app/api/alibaba.ts | 52 ++++++++++++++++++++++++-- app/client/platforms/alibaba.ts | 65 +++++++++++++-------------------- app/constant.ts | 17 ++++++--- git.sh | 2 +- 4 files changed, 87 insertions(+), 49 deletions(-) diff --git a/app/api/alibaba.ts b/app/api/alibaba.ts index 9e6a34fd0..55d234b5f 100644 --- a/app/api/alibaba.ts +++ b/app/api/alibaba.ts @@ -36,7 +36,9 @@ async function request(req: NextRequest) { const controller = new AbortController(); // alibaba use base url or just remove the path - let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Alibaba, ""); + let path = `${req.nextUrl.pathname}` + .replaceAll(ApiPath.Alibaba, "") + .replace("/api", ""); let baseUrl = serverConfig.alibabaUrl || ALIBABA_BASE_URL; @@ -59,6 +61,9 @@ async function request(req: NextRequest) { ); const fetchUrl = `${baseUrl}${path}`; + + console.log("[Alibaba] fetchUrl", fetchUrl); + const fetchOptions: RequestInit = { headers: { "Content-Type": "application/json", @@ -81,13 +86,54 @@ async function request(req: NextRequest) { try { jsonBody = JSON.parse(clonedBody); - delete jsonBody.model; // Remove the model key + + // Move input.messages to messages at the root level if present + if (jsonBody.input && Array.isArray(jsonBody.input.messages)) { + jsonBody.messages = jsonBody.input.messages; + + // Remove input.messages to avoid duplication + delete jsonBody.input; + + jsonBody.stream = true; + } + + const current_model = jsonBody?.model; + console.log("[Alibaba] custom models", current_model); + + //kiem tra xem model co phai la qwen-vl hay khong (vision model) + if (current_model && current_model.startsWith("qwen-vl")) { + console.log("[Alibaba] current model is qwen-vl"); + console.log("xu ly hinh anh trong message"); + + // Reformat image objects in messages + if (Array.isArray(jsonBody.messages)) { + jsonBody.messages = jsonBody.messages.map((msg: any) => { + if (Array.isArray(msg.content)) { + msg.content = msg.content.map((item: any) => { + if (item && typeof item === "object" && "image" in item) { + return { + type: "image_url", + image_url: { + url: item.image, + }, + }; + } + return item; + }); + } + return msg; + }); + } + } + + // console.log("[Alibaba] request body json", jsonBody); + fetchOptions.body = JSON.stringify(jsonBody); } catch (e) { fetchOptions.body = clonedBody; // fallback if not JSON } - console.log("[Alibaba] request body", fetchOptions.body); + // console.log("[Alibaba] request body", fetchOptions.body); // not undefined and is false // if ( diff --git a/app/client/platforms/alibaba.ts b/app/client/platforms/alibaba.ts index e9a7a493e..a9f335d75 100644 --- a/app/client/platforms/alibaba.ts +++ b/app/client/platforms/alibaba.ts @@ -169,57 +169,29 @@ export class QwenApi implements LLMApi { tools as any, funcs, controller, - // Updated SSE parse callback for new JSON structure + // SSE parse callback for OpenAI-style streaming (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: "", - }; + // Each `text` is a line like: data: {...} + let json: any; + try { + json = JSON.parse(text); + } catch { + return { isThinking: false, content: "" }; } + const delta = json.choices?.[0]?.delta; + const content = delta?.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 + // You can accumulate content outside if needed return { isThinking: false, - content: "", + 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, @@ -227,7 +199,20 @@ export class QwenApi implements LLMApi { ...toolCallResult, ); }, - options, // Các tuỳ chọn khác cho hàm streamWithThink + { + ...options, + // Accumulate and render result as it streams + onUpdate: (() => { + let accumulated = ""; + return (chunk: string) => { + accumulated += chunk; + options.onUpdate?.(accumulated, chunk); + }; + })(), + onFinish: (final: string, res: any) => { + options.onFinish?.(final, res); + }, + }, ); } else { const res = await fetch(chatPath, chatPayload); diff --git a/app/constant.ts b/app/constant.ts index c71bfa0d7..53ccd7695 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -224,12 +224,19 @@ export const ByteDance = { export const Alibaba = { ExampleEndpoint: ALIBABA_BASE_URL, - ChatPath: (modelName: string) => { - const URL = `api/v1/apps/${ALIBABA_APP_ID}/completion`; - if (modelName.includes("vl") || modelName.includes("omni")) { - return "v1/services/aigc/multimodal-generation/generation"; - } + ChatPath: (modelName: string) => { + // CHUYEN DUNG CHO ALIBABA APP ID + // const URL = `api/v1/apps/${ALIBABA_APP_ID}/completion`; + console.log("[Alibaba] modelName", modelName); + + // https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions + + const URL = "compatible-mode/v1/chat/completions"; + + // if (modelName.includes("vl") || modelName.includes("omni")) { + // return "v1/services/aigc/multimodal-generation/generation"; + // } // return `v1/services/aigc/text-generation/generation`; return URL; }, diff --git a/git.sh b/git.sh index 03c9c6653..b29ef73c8 100644 --- a/git.sh +++ b/git.sh @@ -2,7 +2,7 @@ # git config --global user.name "quangdn-ght" git add . -git commit -m "thay doi alibaba module mac dinh - chebichat" +git commit -m "chuyen ve api dashscope compatible - toi uu vision qwen-vl" git push # mdZddHXcuzsB0Akk \ No newline at end of file