diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 9334f1c28b1..2c075fdf72a 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -506,9 +506,8 @@ class OpenAIClient extends BaseClient { if (promptPrefix && this.isOmni === true) { const lastUserMessageIndex = payload.findLastIndex((message) => message.role === 'user'); if (lastUserMessageIndex !== -1) { - payload[ - lastUserMessageIndex - ].content = `${promptPrefix}\n${payload[lastUserMessageIndex].content}`; + payload[lastUserMessageIndex].content = + `${promptPrefix}\n${payload[lastUserMessageIndex].content}`; } } @@ -1072,10 +1071,24 @@ ${convo} return ''; } - const reasoningTokens = - this.streamHandler.reasoningTokens.length > 0 - ? `:::thinking\n${this.streamHandler.reasoningTokens.join('')}\n:::\n` - : ''; + let thinkMatch; + let remainingText; + let reasoningText = ''; + + if (this.streamHandler.reasoningTokens.length > 0) { + reasoningText = this.streamHandler.reasoningTokens.join(''); + thinkMatch = reasoningText.match(/([\s\S]*?)<\/think>/)?.[1]?.trim(); + if (thinkMatch != null && thinkMatch) { + const reasoningTokens = `:::thinking\n${thinkMatch}\n:::\n`; + remainingText = reasoningText.split(/<\/think>/)?.[1]?.trim() || ''; + return `${reasoningTokens}${remainingText}${this.streamHandler.tokens.join('')}`; + } else if (thinkMatch === '') { + remainingText = reasoningText.split(/<\/think>/)?.[1]?.trim() || ''; + return `${remainingText}${this.streamHandler.tokens.join('')}`; + } + } + + const reasoningTokens = reasoningText.length > 0 ? `:::thinking\n${reasoningText}\n:::\n` : ''; return `${reasoningTokens}${this.streamHandler.tokens.join('')}`; } @@ -1449,7 +1462,7 @@ ${convo} this.options.context !== 'title' && message.content.startsWith('') ) { - return message.content.replace('', ':::thinking').replace('', ':::'); + return this.getStreamText(); } return message.content; @@ -1473,13 +1486,17 @@ ${convo} (err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason')) ) { logger.error('[OpenAIClient] Known OpenAI error:', err); - if (intermediateReply.length > 0) { + if (this.streamHandler && this.streamHandler.reasoningTokens.length) { + return this.getStreamText(); + } else if (intermediateReply.length > 0) { return intermediateReply.join(''); } else { throw err; } } else if (err instanceof OpenAI.APIError) { - if (intermediateReply.length > 0) { + if (this.streamHandler && this.streamHandler.reasoningTokens.length) { + return this.getStreamText(); + } else if (intermediateReply.length > 0) { return intermediateReply.join(''); } else { throw err; diff --git a/api/package.json b/api/package.json index e5d830a398a..82002287604 100644 --- a/api/package.json +++ b/api/package.json @@ -45,7 +45,7 @@ "@langchain/google-genai": "^0.1.7", "@langchain/google-vertexai": "^0.1.8", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.0.3", + "@librechat/agents": "^2.0.4", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "1.7.8", "bcryptjs": "^2.4.3", diff --git a/client/package.json b/client/package.json index 10ceb258e74..c0ff976e6be 100644 --- a/client/package.json +++ b/client/package.json @@ -136,7 +136,7 @@ "tailwindcss": "^3.4.1", "ts-jest": "^29.2.5", "typescript": "^5.3.3", - "vite": "^5.4.14", + "vite": "^6.1.0", "vite-plugin-node-polyfills": "^0.17.0", "vite-plugin-pwa": "^0.21.1" } diff --git a/client/src/components/Chat/Messages/Content/ContentParts.tsx b/client/src/components/Chat/Messages/Content/ContentParts.tsx index ce77e3bfdb8..b997060c61f 100644 --- a/client/src/components/Chat/Messages/Content/ContentParts.tsx +++ b/client/src/components/Chat/Messages/Content/ContentParts.tsx @@ -50,11 +50,24 @@ const ContentParts = memo( [attachments, messageAttachmentsMap, messageId], ); - const hasReasoningParts = useMemo( - () => content?.some((part) => part?.type === ContentTypes.THINK && part.think) ?? false, - [content], - ); + const hasReasoningParts = useMemo(() => { + const hasThinkPart = content?.some((part) => part?.type === ContentTypes.THINK) ?? false; + const allThinkPartsHaveContent = + content?.every((part) => { + if (part?.type !== ContentTypes.THINK) { + return true; + } + + if (typeof part.think === 'string') { + const cleanedContent = part.think.replace(/<\/?think>/g, '').trim(); + return cleanedContent.length > 0; + } + + return false; + }) ?? false; + return hasThinkPart && allThinkPartsHaveContent; + }, [content]); if (!content) { return null; } diff --git a/client/src/components/Chat/Messages/Content/MessageContent.tsx b/client/src/components/Chat/Messages/Content/MessageContent.tsx index 21bbe231e67..1547a01d804 100644 --- a/client/src/components/Chat/Messages/Content/MessageContent.tsx +++ b/client/src/components/Chat/Messages/Content/MessageContent.tsx @@ -159,7 +159,9 @@ const MessageContent = ({ return ( <> - {thinkingContent && {thinkingContent}} + {thinkingContent.length > 0 && ( + {thinkingContent} + )} { const { isExpanded, nextType } = useMessageContext(); const reasoningText = useMemo(() => { - return reasoning.replace(/^\s*/, '').replace(/\s*<\/think>$/, ''); + return reasoning + .replace(/^\s*/, '') + .replace(/\s*<\/think>$/, '') + .trim(); }, [reasoning]); + if (!reasoningText) { + return null; + } + return (
{ }; return ( -
-
-
+
+
+
-
+
-
+