mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🧠 fix: Handle Reasoning Chunk Edge Cases (#5800)
* refactor: better reasoning parsing * style: better model selector mobile styling * chore: bump vite
This commit is contained in:
parent
404b27d045
commit
4de9619bd9
9 changed files with 1515 additions and 600 deletions
|
|
@ -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(/<think>([\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('<think>')
|
||||
) {
|
||||
return message.content.replace('<think>', ':::thinking').replace('</think>', ':::');
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,9 @@ const MessageContent = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{thinkingContent && <Thinking key={`thinking-${messageId}`}>{thinkingContent}</Thinking>}
|
||||
{thinkingContent.length > 0 && (
|
||||
<Thinking key={`thinking-${messageId}`}>{thinkingContent}</Thinking>
|
||||
)}
|
||||
<DisplayMessage
|
||||
key={`display-${messageId}`}
|
||||
showCursor={showRegularCursor}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,16 @@ type ReasoningProps = {
|
|||
const Reasoning = memo(({ reasoning }: ReasoningProps) => {
|
||||
const { isExpanded, nextType } = useMessageContext();
|
||||
const reasoningText = useMemo(() => {
|
||||
return reasoning.replace(/^<think>\s*/, '').replace(/\s*<\/think>$/, '');
|
||||
return reasoning
|
||||
.replace(/^<think>\s*/, '')
|
||||
.replace(/\s*<\/think>$/, '')
|
||||
.trim();
|
||||
}, [reasoning]);
|
||||
|
||||
if (!reasoningText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -40,13 +40,15 @@ export const TemporaryChat = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="sticky bottom-0 border-none bg-surface-tertiary px-6 py-4 ">
|
||||
<div className="flex items-center">
|
||||
<div className={cn('flex flex-1 items-center gap-2', isActiveConvo && 'opacity-40')}>
|
||||
<div className="sticky bottom-0 mt-auto w-full border-none bg-surface-tertiary px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={cn('flex items-center gap-2', isActiveConvo && 'opacity-40')}>
|
||||
<MessageCircleDashed className="icon-sm" aria-hidden="true" />
|
||||
<span className="text-sm text-text-primary">{localize('com_ui_temporary_chat')}</span>
|
||||
<span className="truncate text-sm text-text-primary">
|
||||
{localize('com_ui_temporary_chat')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
<Switch
|
||||
id="temporary-chat-switch"
|
||||
checked={isTemporary}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ function SelectDropDownPop({
|
|||
|
||||
return (
|
||||
<Root>
|
||||
<div className={'flex items-center justify-center gap-2 '}>
|
||||
<div className={'flex items-center justify-center gap-2'}>
|
||||
<div className={'relative w-full'}>
|
||||
<Trigger asChild>
|
||||
<button
|
||||
|
|
@ -64,23 +64,24 @@ function SelectDropDownPop({
|
|||
className={cn(
|
||||
'pointer-cursor relative flex flex-col rounded-lg border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:ring-0 focus:ring-offset-0 dark:border-gray-700 dark:bg-gray-800 sm:text-sm',
|
||||
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-gray-700 dark:radix-state-open:bg-gray-700',
|
||||
'min-w-[200px] max-w-[215px] sm:min-w-full sm:max-w-full',
|
||||
)}
|
||||
aria-label={`Select ${title}`}
|
||||
aria-haspopup="false"
|
||||
>
|
||||
{' '}
|
||||
{showLabel && (
|
||||
<label className="block text-xs text-gray-700 dark:text-gray-500 ">{title}</label>
|
||||
<label className="block text-xs text-gray-700 dark:text-gray-500">{title}</label>
|
||||
)}
|
||||
<span className="inline-flex w-full ">
|
||||
<span className="inline-flex w-full">
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-6 items-center gap-1 text-sm text-gray-800 dark:text-white',
|
||||
'flex h-6 items-center gap-1 text-sm text-text-primary',
|
||||
!showLabel ? 'text-xs' : '',
|
||||
'min-w-[75px] font-normal',
|
||||
)}
|
||||
>
|
||||
{typeof value !== 'string' && value ? value.label ?? '' : value ?? ''}
|
||||
{typeof value !== 'string' && value ? (value.label ?? '') : (value ?? '')}
|
||||
</span>
|
||||
</span>
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
|
|
@ -91,7 +92,7 @@ function SelectDropDownPop({
|
|||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4 text-gray-400"
|
||||
className="h-4 w-4 text-gray-400"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -107,7 +108,7 @@ function SelectDropDownPop({
|
|||
side="bottom"
|
||||
align="start"
|
||||
className={cn(
|
||||
'mt-2 max-h-[52vh] min-w-full overflow-hidden overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white lg:max-h-[52vh]',
|
||||
'mr-3 mt-2 max-h-[52vh] w-full max-w-[85vw] overflow-hidden overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white sm:max-w-full lg:max-h-[52vh]',
|
||||
hasSearchRender && 'relative',
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
2013
package-lock.json
generated
2013
package-lock.json
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue