From 6e082ad7ac19f6124a66df3711cebe5aed696560 Mon Sep 17 00:00:00 2001 From: xsun2001 Date: Tue, 18 Feb 2025 14:30:34 +0800 Subject: [PATCH 1/2] Support reasoning for OpenRouter using OpenAI provider --- app/client/platforms/openai.ts | 29 +++++++++++++++++++++-------- app/utils/chat.ts | 16 ++++++---------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index c6f3fc4253f..0de7235153d 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -2,10 +2,10 @@ // azure and openai, using same models. so using same LLMApi. import { ApiPath, - OPENAI_BASE_URL, + Azure, DEFAULT_MODELS, + OPENAI_BASE_URL, OpenaiPath, - Azure, REQUEST_TIMEOUT_MS, ServiceProvider, } from "@/app/constant"; @@ -18,13 +18,13 @@ import { } from "@/app/store"; import { collectModelsWithDefaultModel } from "@/app/utils/model"; import { - preProcessImageContent, - uploadImage, base64Image2Blob, + preProcessImageContent, streamWithThink, + uploadImage, } from "@/app/utils/chat"; import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare"; -import { ModelSize, DalleQuality, DalleStyle } from "@/app/typing"; +import { DalleQuality, DalleStyle, ModelSize } from "@/app/typing"; import { ChatOptions, @@ -39,9 +39,9 @@ import Locale from "../../locales"; import { getClientConfig } from "@/app/config/client"; import { getMessageTextContent, - isVisionModel, - isDalle3 as _isDalle3, getTimeoutMSByModel, + isDalle3 as _isDalle3, + isVisionModel, } from "@/app/utils"; import { fetch } from "@/app/utils/stream"; @@ -294,6 +294,14 @@ export class ChatGPTApi implements LLMApi { useChatStore.getState().currentSession().mask?.plugin || [], ); // console.log("getAsTools", tools, funcs); + + // Add "include_reasoning" for OpenRouter: https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models + const isOpenRouter = chatPath.includes("openrouter.ai"); + if (isOpenRouter) { + // @ts-ignore + requestPayload["include_reasoning"] = true; + } + streamWithThink( chatPath, requestPayload, @@ -310,6 +318,7 @@ export class ChatGPTApi implements LLMApi { content: string; tool_calls: ChatMessageTool[]; reasoning_content: string | null; + reasoning: string | null; }; }>; @@ -335,7 +344,9 @@ export class ChatGPTApi implements LLMApi { } } - const reasoning = choices[0]?.delta?.reasoning_content; + const reasoning = isOpenRouter + ? choices[0]?.delta?.reasoning + : choices[0]?.delta?.reasoning_content; const content = choices[0]?.delta?.content; // Skip if both content and reasoning_content are empty or null @@ -411,6 +422,7 @@ export class ChatGPTApi implements LLMApi { options.onError?.(e as Error); } } + async usage() { const formatDate = (d: Date) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d @@ -514,4 +526,5 @@ export class ChatGPTApi implements LLMApi { })); } } + export { OpenaiPath }; diff --git a/app/utils/chat.ts b/app/utils/chat.ts index efc496f2c32..733a6f7b049 100644 --- a/app/utils/chat.ts +++ b/app/utils/chat.ts @@ -1,7 +1,7 @@ import { CACHE_URL_PREFIX, - UPLOAD_URL, REQUEST_TIMEOUT_MS, + UPLOAD_URL, } from "@/app/constant"; import { RequestMessage } from "@/app/client/api"; import Locale from "@/app/locales"; @@ -93,6 +93,7 @@ export async function preProcessImageContent( } const imageCaches: Record = {}; + export function cacheImageToBase64Image(imageUrl: string) { if (imageUrl.includes(CACHE_URL_PREFIX)) { if (!imageCaches[imageUrl]) { @@ -367,6 +368,7 @@ export function stream( openWhenHidden: true, }); } + console.debug("[ChatAPI] start"); chatApi(chatPath, headers, requestPayload, tools); // call fetchEventSource } @@ -609,16 +611,9 @@ export function streamWithThink( if (remainText.length > 0) { remainText += "\n"; } - remainText += "> " + chunk.content; - } else { - // Handle newlines in thinking content - if (chunk.content.includes("\n\n")) { - const lines = chunk.content.split("\n\n"); - remainText += lines.join("\n\n> "); - } else { - remainText += chunk.content; - } + remainText += "> "; } + remainText += chunk.content.replaceAll("\n", "\n> "); } else { // If in normal mode if (isInThinkingMode || isThinkingChanged) { @@ -644,6 +639,7 @@ export function streamWithThink( openWhenHidden: true, }); } + console.debug("[ChatAPI] start"); chatApi(chatPath, headers, requestPayload, tools); // call fetchEventSource } From a87ec75ba6b28fbb37e0d6c140ec211dc9455609 Mon Sep 17 00:00:00 2001 From: xsun2001 Date: Tue, 25 Feb 2025 16:35:03 +0800 Subject: [PATCH 2/2] Support OpenRouter reasoning when using env var --- app/api/common.ts | 15 ++++++++++----- app/client/platforms/openai.ts | 9 ++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/api/common.ts b/app/api/common.ts index b7e41fa2647..c37ebfe69b1 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -90,6 +90,14 @@ export async function requestOpenai(req: NextRequest) { const fetchUrl = cloudflareAIGatewayUrl(`${baseUrl}/${path}`); console.log("fetchUrl", fetchUrl); + + let payload = await req.text(); + if (baseUrl.includes("openrouter.ai")) { + const body = JSON.parse(payload); + body["include_reasoning"] = true; + payload = JSON.stringify(body); + } + const fetchOptions: RequestInit = { headers: { "Content-Type": "application/json", @@ -100,7 +108,7 @@ export async function requestOpenai(req: NextRequest) { }), }, method: req.method, - body: req.body, + body: payload, // to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body redirect: "manual", // @ts-ignore @@ -111,10 +119,7 @@ export async function requestOpenai(req: NextRequest) { // #1815 try to refuse gpt4 request if (serverConfig.customModels && req.body) { try { - const clonedBody = await req.text(); - fetchOptions.body = clonedBody; - - const jsonBody = JSON.parse(clonedBody) as { model?: string }; + const jsonBody = JSON.parse(payload) as { model?: string }; // not undefined and is false if ( diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 0de7235153d..abdbb82d470 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -296,8 +296,7 @@ export class ChatGPTApi implements LLMApi { // console.log("getAsTools", tools, funcs); // Add "include_reasoning" for OpenRouter: https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models - const isOpenRouter = chatPath.includes("openrouter.ai"); - if (isOpenRouter) { + if (chatPath.includes("openrouter.ai")) { // @ts-ignore requestPayload["include_reasoning"] = true; } @@ -344,9 +343,9 @@ export class ChatGPTApi implements LLMApi { } } - const reasoning = isOpenRouter - ? choices[0]?.delta?.reasoning - : choices[0]?.delta?.reasoning_content; + const reasoning = + choices[0]?.delta?.reasoning_content || + choices[0]?.delta?.reasoning; const content = choices[0]?.delta?.content; // Skip if both content and reasoning_content are empty or null