Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat: Add Google Parameters, Ollama/Openrouter Reasoning, & UI Optimizations #5456

Merged
merged 8 commits into from
Jan 24, 2025
14 changes: 10 additions & 4 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,12 @@ ${convo}
delete modelOptions.stop;
}

let reasoningKey = 'reasoning_content';
if (this.useOpenRouter) {
modelOptions.include_reasoning = true;
reasoningKey = 'reasoning';
}

if (modelOptions.stream) {
streamPromise = new Promise((resolve) => {
streamResolve = resolve;
Expand Down Expand Up @@ -1291,14 +1297,14 @@ ${convo}

let reasoningCompleted = false;
for await (const chunk of stream) {
if (chunk?.choices?.[0]?.delta?.reasoning_content) {
if (chunk?.choices?.[0]?.delta?.[reasoningKey]) {
if (reasoningTokens.length === 0) {
const thinkingDirective = ':::thinking\n';
const thinkingDirective = '<think>\n';
intermediateReply.push(thinkingDirective);
reasoningTokens.push(thinkingDirective);
onProgress(thinkingDirective);
}
const reasoning_content = chunk?.choices?.[0]?.delta?.reasoning_content || '';
const reasoning_content = chunk?.choices?.[0]?.delta?.[reasoningKey] || '';
intermediateReply.push(reasoning_content);
reasoningTokens.push(reasoning_content);
onProgress(reasoning_content);
Expand All @@ -1307,7 +1313,7 @@ ${convo}
const token = chunk?.choices?.[0]?.delta?.content || '';
if (!reasoningCompleted && reasoningTokens.length > 0 && token) {
reasoningCompleted = true;
const separatorTokens = '\n:::\n';
const separatorTokens = '\n</think>\n';
reasoningTokens.push(separatorTokens);
onProgress(separatorTokens);
}
Expand Down
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"react-speech-recognition": "^3.10.0",
"react-textarea-autosize": "^8.4.0",
"react-transition-group": "^4.4.5",
"react-virtualized": "^9.22.6",
"recoil": "^0.7.7",
"regenerator-runtime": "^0.14.1",
"rehype-highlight": "^6.0.0",
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/Artifacts/Thinking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const Thinking = ({ children }: ThinkingProps) => {
setIsExpanded(!isExpanded);
};

if (children == null) {
return null;
}

return (
<div className="mb-3">
<button
Expand Down
20 changes: 9 additions & 11 deletions client/src/components/Chat/Messages/Content/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,13 @@ type TContentProps = {

const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentProps) => {
const LaTeXParsing = useRecoilValue<boolean>(store.LaTeXParsing);
const codeArtifacts = useRecoilValue<boolean>(store.codeArtifacts);

const isInitializing = content === '';

let currentContent = content;
if (!isInitializing) {
currentContent = currentContent.replace('z-index: 1;', '') || '';
currentContent = currentContent.replace('<think>', ':::thinking') || '';
currentContent = currentContent.replace('</think>', ':::') || '';
currentContent = LaTeXParsing ? preprocessLaTeX(currentContent) : currentContent;
}

Expand All @@ -189,15 +189,13 @@ const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentPr
);
}

const remarkPlugins: Pluggable[] = codeArtifacts
? [
supersub,
remarkGfm,
[remarkMath, { singleDollarTextMath: true }],
remarkDirective,
artifactPlugin,
]
: [supersub, remarkGfm, [remarkMath, { singleDollarTextMath: true }]];
const remarkPlugins: Pluggable[] = [
supersub,
remarkGfm,
remarkDirective,
artifactPlugin,
[remarkMath, { singleDollarTextMath: true }],
];

return (
<ArtifactProvider>
Expand Down
5 changes: 2 additions & 3 deletions client/src/components/Conversations/Fork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {
HoverCardContent,
} from '~/components/ui';
import OptionHover from '~/components/SidePanel/Parameters/OptionHover';
import { useToastContext, useChatContext } from '~/Providers';
import { useLocalize, useNavigateToConvo } from '~/hooks';
import { useForkConvoMutation } from '~/data-provider';
import { useToastContext } from '~/Providers';
import { ESide } from '~/common';
import { cn } from '~/utils';
import store from '~/store';
Expand Down Expand Up @@ -112,10 +112,9 @@ export default function Fork({
latestMessageId?: string;
}) {
const localize = useLocalize();
const { index } = useChatContext();
const { showToast } = useToastContext();
const [remember, setRemember] = useState(false);
const { navigateToConvo } = useNavigateToConvo(index);
const { navigateToConvo } = useNavigateToConvo();
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const [forkSetting, setForkSetting] = useRecoilState(store.forkSetting);
const [activeSetting, setActiveSetting] = useState(optionLabels.default);
Expand Down
10 changes: 5 additions & 5 deletions client/src/components/SidePanel/Parameters/DynamicInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { OptionTypes } from 'librechat-data-provider';
import type { DynamicSettingProps } from 'librechat-data-provider';
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
import { Label, Input, HoverCard, HoverCardTrigger } from '~/components/ui';
import { cn, defaultTextProps } from '~/utils';
import { useChatContext } from '~/Providers';
import OptionHover from './OptionHover';
import { ESide } from '~/common';
import { cn } from '~/utils';

function DynamicInput({
label = '',
Expand Down Expand Up @@ -50,7 +50,7 @@ function DynamicInput({
const value = e.target.value;
if (type === 'number') {
if (!isNaN(Number(value))) {
setInputValue(e);
setInputValue(e, true);
}
} else {
setInputValue(e);
Expand All @@ -70,7 +70,7 @@ function DynamicInput({
htmlFor={`${settingKey}-dynamic-input`}
className="text-left text-sm font-medium"
>
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
{labelCode ? localize(label) || label : label || settingKey}{' '}
{showDefault && (
<small className="opacity-40">
(
Expand All @@ -87,15 +87,15 @@ function DynamicInput({
disabled={readonly}
value={inputValue ?? ''}
onChange={handleInputChange}
placeholder={placeholderCode ? localize(placeholder) ?? placeholder : placeholder}
placeholder={placeholderCode ? localize(placeholder) || placeholder : placeholder}
className={cn(
'flex h-10 max-h-10 w-full resize-none border-none bg-surface-secondary px-3 py-2',
)}
/>
</HoverCardTrigger>
{description && (
<OptionHover
description={descriptionCode ? localize(description) ?? description : description}
description={descriptionCode ? localize(description) || description : description}
side={ESide.Left}
/>
)}
Expand Down
87 changes: 87 additions & 0 deletions client/src/components/SidePanel/Parameters/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ImageDetail,
EModelEndpoint,
openAISettings,
googleSettings,
BedrockProviders,
anthropicSettings,
} from 'librechat-data-provider';
Expand Down Expand Up @@ -352,6 +353,87 @@ const meta: Record<string, SettingDefinition> = {
}),
};

const google: Record<string, SettingDefinition> = {
temperature: createDefinition(baseDefinitions.temperature, {
default: googleSettings.temperature.default,
range: {
min: googleSettings.temperature.min,
max: googleSettings.temperature.max,
step: googleSettings.temperature.step,
},
}),
topP: createDefinition(baseDefinitions.topP, {
default: googleSettings.topP.default,
range: {
min: googleSettings.topP.min,
max: googleSettings.topP.max,
step: googleSettings.topP.step,
},
}),
topK: {
key: 'topK',
label: 'com_endpoint_top_k',
labelCode: true,
description: 'com_endpoint_google_topk',
descriptionCode: true,
type: 'number',
default: googleSettings.topK.default,
range: {
min: googleSettings.topK.min,
max: googleSettings.topK.max,
step: googleSettings.topK.step,
},
component: 'slider',
optionType: 'model',
columnSpan: 4,
},
maxOutputTokens: {
key: 'maxOutputTokens',
label: 'com_endpoint_max_output_tokens',
labelCode: true,
type: 'number',
component: 'input',
description: 'com_endpoint_google_maxoutputtokens',
descriptionCode: true,
placeholder: 'com_nav_theme_system',
placeholderCode: true,
default: googleSettings.maxOutputTokens.default,
range: {
min: googleSettings.maxOutputTokens.min,
max: googleSettings.maxOutputTokens.max,
step: googleSettings.maxOutputTokens.step,
},
optionType: 'model',
columnSpan: 2,
},
};

const googleConfig: SettingsConfiguration = [
librechat.modelLabel,
librechat.promptPrefix,
librechat.maxContextTokens,
google.maxOutputTokens,
google.temperature,
google.topP,
google.topK,
librechat.resendFiles,
];

const googleCol1: SettingsConfiguration = [
baseDefinitions.model as SettingDefinition,
librechat.modelLabel,
librechat.promptPrefix,
];

const googleCol2: SettingsConfiguration = [
librechat.maxContextTokens,
google.maxOutputTokens,
google.temperature,
google.topP,
google.topK,
librechat.resendFiles,
];

const openAI: SettingsConfiguration = [
openAIParams.chatGptLabel,
librechat.promptPrefix,
Expand Down Expand Up @@ -529,6 +611,7 @@ export const settings: Record<string, SettingsConfiguration | undefined> = {
[`${EModelEndpoint.bedrock}-${BedrockProviders.Meta}`]: bedrockGeneral,
[`${EModelEndpoint.bedrock}-${BedrockProviders.AI21}`]: bedrockGeneral,
[`${EModelEndpoint.bedrock}-${BedrockProviders.Amazon}`]: bedrockGeneral,
[EModelEndpoint.google]: googleConfig,
};

const openAIColumns = {
Expand Down Expand Up @@ -571,6 +654,10 @@ export const presetSettings: Record<
[`${EModelEndpoint.bedrock}-${BedrockProviders.Meta}`]: bedrockGeneralColumns,
[`${EModelEndpoint.bedrock}-${BedrockProviders.AI21}`]: bedrockGeneralColumns,
[`${EModelEndpoint.bedrock}-${BedrockProviders.Amazon}`]: bedrockGeneralColumns,
[EModelEndpoint.google]: {
col1: googleCol1,
col2: googleCol2,
},
};

export const agentSettings: Record<string, SettingsConfiguration | undefined> = Object.entries(
Expand Down
72 changes: 50 additions & 22 deletions client/src/components/ui/ControlCombobox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Ariakit from '@ariakit/react';
import { matchSorter } from 'match-sorter';
import { AutoSizer, List } from 'react-virtualized';
import { startTransition, useMemo, useState, useEffect, useRef, memo } from 'react';
import { cn } from '~/utils';
import type { OptionWithIcon } from '~/common';
Expand All @@ -17,6 +18,8 @@ interface ControlComboboxProps {
SelectIcon?: React.ReactNode;
}

const ROW_HEIGHT = 36;

function ControlCombobox({
selectedValue,
displayValue,
Expand Down Expand Up @@ -45,6 +48,39 @@ function ControlCombobox({
}
}, [isCollapsed]);

const rowRenderer = ({
index,
key,
style,
}: {
index: number;
key: string;
style: React.CSSProperties;
}) => {
const item = matches[index];
return (
<Ariakit.SelectItem
key={key}
value={`${item.value ?? ''}`}
aria-label={`${item.label ?? item.value ?? ''}`}
className={cn(
'flex cursor-pointer items-center px-3 text-sm',
'text-text-primary hover:bg-surface-tertiary',
'data-[active-item]:bg-surface-tertiary',
)}
render={<Ariakit.ComboboxItem />}
style={style}
>
{item.icon != null && (
<div className="assistant-item mr-2 flex h-5 w-5 items-center justify-center overflow-hidden rounded-full">
{item.icon}
</div>
)}
<span className="flex-grow truncate text-left">{item.label}</span>
</Ariakit.SelectItem>
);
};

return (
<div className="flex w-full items-center justify-center px-1">
<Ariakit.ComboboxProvider
Expand Down Expand Up @@ -93,28 +129,20 @@ function ControlCombobox({
/>
</div>
</div>
<Ariakit.ComboboxList className="max-h-[50vh] overflow-auto">
{matches.map((item) => (
<Ariakit.SelectItem
key={item.value}
value={`${item.value ?? ''}`}
aria-label={`${item.label ?? item.value ?? ''}`}
className={cn(
'flex cursor-pointer items-center px-3 py-2 text-sm',
'text-text-primary hover:bg-surface-tertiary',
'data-[active-item]:bg-surface-tertiary',
)}
render={<Ariakit.ComboboxItem />}
>
{item.icon != null && (
<div className="assistant-item mr-2 flex h-5 w-5 items-center justify-center overflow-hidden rounded-full">
{item.icon}
</div>
)}
<span className="flex-grow truncate text-left">{item.label}</span>
</Ariakit.SelectItem>
))}
</Ariakit.ComboboxList>
<div className="max-h-[50vh]">
<AutoSizer disableHeight>
{({ width }) => (
<List
width={width}
height={Math.min(matches.length * ROW_HEIGHT, 300)}
rowCount={matches.length}
rowHeight={ROW_HEIGHT}
rowRenderer={rowRenderer}
overscanRowCount={5}
/>
)}
</AutoSizer>
</div>
</Ariakit.SelectPopover>
</Ariakit.SelectProvider>
</Ariakit.ComboboxProvider>
Expand Down
Loading
Loading