🚀 feat: o1 Tool Calling & reasoning_effort (#5553)

* fix: Update @librechat/agents to version 1.9.98

* feat: o1 tool calling

* fix: Improve error logging in RouteErrorBoundary

* refactor: Move extractContent function to utils and clean up Artifact component

* refactor: optimize reasoning UI post-streaming and deprecate plugins rendering

* feat: reasoning_effort support

* fix: update request content type handling in openapiToFunction to remove default 'application/x-www-form-urlencoded'

* chore: bump v0.7.696 data-provider
This commit is contained in:
Danny Avila 2025-01-30 12:36:35 -05:00 committed by GitHub
parent 591a019766
commit 587d46a20b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 752 additions and 685 deletions

View file

@ -17,7 +17,6 @@ import {
import { Artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
import { langSubset, preprocessLaTeX, handleDoubleClick } from '~/utils';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
import Thinking from '~/components/Artifacts/Thinking';
import { useFileDownload } from '~/data-provider';
import useLocalize from '~/hooks/useLocalize';
import store from '~/store';
@ -223,7 +222,6 @@ const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentPr
a,
p,
artifact: Artifact,
thinking: Thinking,
} as {
[nodeType: string]: React.ElementType;
}

View file

@ -1,9 +1,9 @@
import { Fragment, Suspense, useMemo } from 'react';
import { memo, Suspense, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import type { TMessage, TResPlugin } from 'librechat-data-provider';
import type { TMessage } from 'librechat-data-provider';
import type { TMessageContentProps, TDisplayProps } from '~/common';
import Plugin from '~/components/Messages/Content/Plugin';
import Error from '~/components/Messages/Content/Error';
import Thinking from '~/components/Artifacts/Thinking';
import { DelayedRender } from '~/components/ui';
import { useChatContext } from '~/Providers';
import MarkdownLite from './MarkdownLite';
@ -117,7 +117,6 @@ export const UnfinishedMessage = ({ message }: { message: TMessage }) => (
/>
);
// Content Component
const MessageContent = ({
text,
edit,
@ -127,72 +126,49 @@ const MessageContent = ({
isLast,
...props
}: TMessageContentProps) => {
const { message } = props;
const { messageId } = message;
const { thinkingContent, regularContent } = useMemo(() => {
const thinkingMatch = text.match(/:::thinking([\s\S]*?):::/);
return {
thinkingContent: thinkingMatch ? thinkingMatch[1].trim() : '',
regularContent: thinkingMatch ? text.replace(/:::thinking[\s\S]*?:::/, '').trim() : text,
};
}, [text]);
const showRegularCursor = useMemo(() => isLast && isSubmitting, [isLast, isSubmitting]);
const unfinishedMessage = useMemo(
() =>
!isSubmitting && unfinished ? (
<Suspense>
<DelayedRender delay={250}>
<UnfinishedMessage message={message} />
</DelayedRender>
</Suspense>
) : null,
[isSubmitting, unfinished, message],
);
if (error) {
return <ErrorMessage message={props.message} text={text} />;
} else if (edit) {
return <EditMessage text={text} isSubmitting={isSubmitting} {...props} />;
} else {
const marker = ':::plugin:::\n';
const splitText = text.split(marker);
const { message } = props;
const { plugins, messageId } = message;
const displayedIndices = new Set<number>();
// Function to get the next non-empty text index
const getNextNonEmptyTextIndex = (currentIndex: number) => {
for (let i = currentIndex + 1; i < splitText.length; i++) {
// Allow the last index to be last in case it has text
// this may need to change if I add back streaming
if (i === splitText.length - 1) {
return currentIndex;
}
if (splitText[i].trim() !== '' && !displayedIndices.has(i)) {
return i;
}
}
return currentIndex; // If no non-empty text is found, return the current index
};
return splitText.map((text, idx) => {
let currentText = text.trim();
let plugin: TResPlugin | null = null;
if (plugins) {
plugin = plugins[idx];
}
// If the current text is empty, get the next non-empty text index
const displayTextIndex = currentText === '' ? getNextNonEmptyTextIndex(idx) : idx;
currentText = splitText[displayTextIndex];
const isLastIndex = displayTextIndex === splitText.length - 1;
const isEmpty = currentText.trim() === '';
const showText =
(currentText && !isEmpty && !displayedIndices.has(displayTextIndex)) ||
(isEmpty && isLastIndex);
displayedIndices.add(displayTextIndex);
return (
<Fragment key={idx}>
{plugin && <Plugin key={`plugin-${messageId}-${idx}`} plugin={plugin} />}
{showText ? (
<DisplayMessage
key={`display-${messageId}-${idx}`}
showCursor={isLastIndex && isLast && isSubmitting}
text={currentText}
{...props}
/>
) : null}
{!isSubmitting && unfinished && (
<Suspense>
<DelayedRender delay={250}>
<UnfinishedMessage message={message} key={`unfinished-${messageId}-${idx}`} />
</DelayedRender>
</Suspense>
)}
</Fragment>
);
});
}
return (
<>
{thinkingContent && <Thinking key={`thinking-${messageId}`}>{thinkingContent}</Thinking>}
<DisplayMessage
key={`display-${messageId}`}
showCursor={showRegularCursor}
text={regularContent}
{...props}
/>
{unfinishedMessage}
</>
);
};
export default MessageContent;
export default memo(MessageContent);

View file

@ -18,7 +18,7 @@ const Reasoning = memo(({ reasoning }: ReasoningProps) => {
<div
className={cn(
'grid transition-all duration-300 ease-out',
nextType !== ContentTypes.THINK && isExpanded && 'mb-10',
nextType !== ContentTypes.THINK && isExpanded && 'mb-8',
)}
style={{
gridTemplateRows: isExpanded ? '1fr' : '0fr',