feat(Google): Support all Text/Chat Models, Response streaming, PaLM -> Google 🤖 (#1316)

* feat: update PaLM icons

* feat: add additional google models

* POC: formatting inputs for Vertex AI streaming

* refactor: move endpoints services outside of /routes dir to /services/Endpoints

* refactor: shorten schemas import

* refactor: rename PALM to GOOGLE

* feat: make Google editable endpoint

* feat: reusable Ask and Edit controllers based off Anthropic

* chore: organize imports/logic

* fix(parseConvo): include examples in googleSchema

* fix: google only allows odd number of messages to be sent

* fix: pass proxy to AnthropicClient

* refactor: change `google` altName to `Google`

* refactor: update getModelMaxTokens and related functions to handle maxTokensMap with nested endpoint model key/values

* refactor: google Icon and response sender changes (Codey and Google logo instead of PaLM in all cases)

* feat: google support for maxTokensMap

* feat: google updated endpoints with Ask/Edit controllers, buildOptions, and initializeClient

* feat(GoogleClient): now builds prompt for text models and supports real streaming from Vertex AI through langchain

* chore(GoogleClient): remove comments, left before for reference in git history

* docs: update google instructions (WIP)

* docs(apis_and_tokens.md): add images to google instructions

* docs: remove typo apis_and_tokens.md

* Update apis_and_tokens.md

* feat(Google): use default settings map, fully support context for both text and chat models, fully support examples for chat models

* chore: update more PaLM references to Google

* chore: move playwright out of workflows to avoid failing tests
This commit is contained in:
Danny Avila 2023-12-10 14:54:13 -05:00 committed by GitHub
parent 8a1968b2f8
commit 583e978a82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 1613 additions and 784 deletions

View file

@ -5,7 +5,7 @@ import {
AnthropicIcon,
AzureMinimalIcon,
BingAIMinimalIcon,
PaLMinimalIcon,
GoogleMinimalIcon,
LightningIcon,
} from '~/components/svg';
import { cn } from '~/utils';
@ -16,7 +16,7 @@ export const icons = {
[EModelEndpoint.gptPlugins]: MinimalPlugin,
[EModelEndpoint.anthropic]: AnthropicIcon,
[EModelEndpoint.chatGPTBrowser]: LightningIcon,
[EModelEndpoint.google]: PaLMinimalIcon,
[EModelEndpoint.google]: GoogleMinimalIcon,
[EModelEndpoint.bingAI]: BingAIMinimalIcon,
[EModelEndpoint.assistant]: ({ className = '' }) => (
<svg

View file

@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
import { useGetEndpointsQuery } from 'librechat-data-provider';
import { cn, defaultTextProps, removeFocusOutlines, mapEndpoints } from '~/utils';
import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/';
import PopoverButtons from '~/components/Endpoints/PopoverButtons';
import PopoverButtons from '~/components/Chat/Input/PopoverButtons';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { useSetIndexOptions, useLocalize } from '~/hooks';
import { EndpointSettings } from '~/components/Endpoints';
@ -90,6 +90,7 @@ const EditPresetDialog = ({
conversation={preset}
setOption={setOption}
isPreset={true}
isMultiChat={true}
className="h-full md:mb-4 md:h-[440px]"
/>
</div>

View file

@ -1,5 +1,5 @@
import { useRef } from 'react';
import { useUpdateMessageMutation } from 'librechat-data-provider';
import { useUpdateMessageMutation, EModelEndpoint } from 'librechat-data-provider';
import Container from '~/components/Messages/Content/Container';
import { useChatContext } from '~/Providers';
import type { TEditProps } from '~/common';
@ -18,6 +18,7 @@ const EditMessage = ({
const textEditor = useRef<HTMLDivElement | null>(null);
const { conversationId, parentMessageId, messageId } = message;
const { endpoint } = conversation ?? { endpoint: null };
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
const localize = useLocalize();
@ -94,7 +95,9 @@ const EditMessage = ({
<div className="mt-2 flex w-full justify-center text-center">
<button
className="btn btn-primary relative mr-2"
disabled={isSubmitting}
disabled={
isSubmitting || (endpoint === EModelEndpoint.google && !message.isCreatedByUser)
}
onClick={resubmitMessage}
>
{localize('com_ui_save')} {'&'} {localize('com_ui_submit')}

View file

@ -1,11 +1,18 @@
import { EModelEndpoint } from 'librechat-data-provider';
import { Plugin, GPTIcon, AnthropicIcon, AzureMinimalIcon } from '~/components/svg';
import {
Plugin,
GPTIcon,
AnthropicIcon,
AzureMinimalIcon,
PaLMIcon,
CodeyIcon,
} from '~/components/svg';
import { useAuthContext } from '~/hooks/AuthContext';
import { IconProps } from '~/common';
import { cn } from '~/utils';
const Icon: React.FC<IconProps> = (props) => {
const { size = 30, isCreatedByUser, button, model = true, endpoint, error, jailbreak } = props;
const { size = 30, isCreatedByUser, button, model = '', endpoint, error, jailbreak } = props;
const { user } = useAuthContext();
@ -52,8 +59,12 @@ const Icon: React.FC<IconProps> = (props) => {
name: 'Plugins',
},
[EModelEndpoint.google]: {
icon: <img src="/assets/google-palm.svg" alt="Palm Icon" />,
name: 'PaLM2',
icon: model?.includes('code') ? (
<CodeyIcon size={size * 0.75} />
) : (
<PaLMIcon size={size * 0.7} />
),
name: model?.includes('code') ? 'Codey' : 'PaLM2',
},
[EModelEndpoint.anthropic]: {
icon: <AnthropicIcon size={size * 0.5555555555555556} />,

View file

@ -5,7 +5,7 @@ import {
LightningIcon,
PluginMinimalIcon,
BingAIMinimalIcon,
PaLMinimalIcon,
GoogleMinimalIcon,
AnthropicIcon,
} from '~/components/svg';
import { cn } from '~/utils';
@ -27,7 +27,7 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
},
[EModelEndpoint.openAI]: { icon: <OpenAIMinimalIcon />, name: props.chatGptLabel || 'ChatGPT' },
[EModelEndpoint.gptPlugins]: { icon: <PluginMinimalIcon />, name: 'Plugins' },
[EModelEndpoint.google]: { icon: <PaLMinimalIcon />, name: props.modelLabel || 'PaLM2' },
[EModelEndpoint.google]: { icon: <GoogleMinimalIcon />, name: props.modelLabel || 'Google' },
[EModelEndpoint.anthropic]: {
icon: <AnthropicIcon className="icon-md shrink-0 dark:text-white" />,
name: props.modelLabel || 'Claude',

View file

@ -1,5 +1,6 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { EModelEndpoint, endpointSettings } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common';
import { ESide } from '~/common';
import {
@ -31,7 +32,8 @@ export default function Settings({ conversation, setOption, models, readonly }:
const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens');
const codeChat = model?.startsWith('codechat-');
const isTextModel = !model?.includes('chat') && /code|text/.test(model ?? '');
const google = endpointSettings[EModelEndpoint.google];
return (
<div className="grid grid-cols-5 gap-6">
@ -46,45 +48,41 @@ export default function Settings({ conversation, setOption, models, readonly }:
containerClassName="flex w-full resize-none"
/>
</div>
{!codeChat && (
<>
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
{localize('com_endpoint_custom_name')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value ?? null)}
placeholder={localize('com_endpoint_google_custom_name_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize('com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
placeholder={localize('com_endpoint_prompt_prefix_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</>
)}
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
{localize('com_endpoint_custom_name')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value ?? null)}
placeholder={localize('com_endpoint_google_custom_name_placeholder')}
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines,
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
{localize('com_endpoint_prompt_prefix')}{' '}
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
placeholder={localize('com_endpoint_prompt_prefix_placeholder')}
className={cn(
defaultTextProps,
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2 ',
)}
/>
</div>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
@ -92,16 +90,18 @@ export default function Settings({ conversation, setOption, models, readonly }:
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize('com_endpoint_temperature')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 0.2)</small>
<small className="opacity-40">
({localize('com_endpoint_default')}: {google.temperature.default})
</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value ?? 0.2)}
max={1}
min={0}
step={0.01}
onChange={(value) => setTemperature(value ?? google.temperature.default)}
max={google.temperature.max}
min={google.temperature.min}
step={google.temperature.step}
controls={false}
className={cn(
defaultTextProps,
@ -114,18 +114,18 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
<Slider
disabled={readonly}
value={[temperature ?? 0.2]}
value={[temperature ?? google.temperature.default]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(0.2)}
max={1}
min={0}
step={0.01}
doubleClickHandler={() => setTemperature(google.temperature.default)}
max={google.temperature.max}
min={google.temperature.min}
step={google.temperature.step}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={ESide.Left} />
</HoverCard>
{!codeChat && (
{!isTextModel && (
<>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
@ -133,17 +133,17 @@ export default function Settings({ conversation, setOption, models, readonly }:
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_p')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '0.95')})
({localize('com_endpoint_default_with_num', google.topP.default + '')})
</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value ?? '0.95')}
max={1}
min={0}
step={0.01}
onChange={(value) => setTopP(value ?? google.topP.default)}
max={google.topP.max}
min={google.topP.min}
step={google.topP.step}
controls={false}
className={cn(
defaultTextProps,
@ -156,12 +156,12 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
<Slider
disabled={readonly}
value={[topP ?? 0.95]}
value={[topP ?? google.topP.default]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(0.95)}
max={1}
min={0}
step={0.01}
doubleClickHandler={() => setTopP(google.topP.default)}
max={google.topP.max}
min={google.topP.min}
step={google.topP.step}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
@ -174,17 +174,17 @@ export default function Settings({ conversation, setOption, models, readonly }:
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_k')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '40')})
({localize('com_endpoint_default_with_num', google.topK.default + '')})
</small>
</Label>
<InputNumber
id="top-k-int"
disabled={readonly}
value={topK}
onChange={(value) => setTopK(value ?? 40)}
max={40}
min={1}
step={0.01}
onChange={(value) => setTopK(value ?? google.topK.default)}
max={google.topK.max}
min={google.topK.min}
step={google.topK.step}
controls={false}
className={cn(
defaultTextProps,
@ -197,12 +197,12 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
<Slider
disabled={readonly}
value={[topK ?? 40]}
value={[topK ?? google.topK.default]}
onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(40)}
max={40}
min={1}
step={0.01}
doubleClickHandler={() => setTopK(google.topK.default)}
max={google.topK.max}
min={google.topK.min}
step={google.topK.step}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
@ -216,17 +216,17 @@ export default function Settings({ conversation, setOption, models, readonly }:
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
{localize('com_endpoint_max_output_tokens')}{' '}
<small className="opacity-40">
({localize('com_endpoint_default_with_num', '1024')})
({localize('com_endpoint_default_with_num', google.maxOutputTokens.default + '')})
</small>
</Label>
<InputNumber
id="max-tokens-int"
disabled={readonly}
value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(value ?? 1024)}
max={1024}
min={1}
step={1}
onChange={(value) => setMaxOutputTokens(value ?? google.maxOutputTokens.default)}
max={google.maxOutputTokens.max}
min={google.maxOutputTokens.min}
step={google.maxOutputTokens.step}
controls={false}
className={cn(
defaultTextProps,
@ -239,12 +239,12 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
<Slider
disabled={readonly}
value={[maxOutputTokens ?? 1024]}
value={[maxOutputTokens ?? google.maxOutputTokens.default]}
onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(1024)}
max={1024}
min={1}
step={1}
doubleClickHandler={() => setMaxOutputTokens(google.maxOutputTokens.default)}
max={google.maxOutputTokens.max}
min={google.maxOutputTokens.min}
step={google.maxOutputTokens.step}
className="flex h-4 w-full"
/>
</HoverCardTrigger>

View file

@ -16,7 +16,7 @@ export default function GoogleView({ conversation, models, isPreset = false }) {
const { showExamples, isCodeChat } = optionSettings;
return showExamples && !isCodeChat ? (
<Examples
examples={examples ?? []}
examples={examples ?? [{ input: { content: '' }, output: { content: '' } }]}
setExample={setExample}
addExample={addExample}
removeExample={removeExample}

View file

@ -0,0 +1,26 @@
import { cn } from '~/utils';
export default function CodeyIcon({
size = 25,
className = '',
}: {
size?: number;
className?: string;
}) {
return (
<svg
// width="100%"
// height="100%"
width={size}
height={size}
className={cn('dark:fill-white', className)}
viewBox="0 0 18 18"
preserveAspectRatio="xMidYMid meet"
focusable="false"
>
<path
d="M2 4.006C2 2.898 2.897 2 4.006 2h9.988C15.102 2 16 2.897 16 4.006v9.988A2.005 2.005 0 0 1 13.994 16H4.006A2.005 2.005 0 0 1 2 13.994V4.006zM13.992 9l.003-.003L10.997 6 9.75 7.247 11.503 9 9.75 10.753 10.997 12l2.997-2.997L13.992 9zm-9.99 0L4 8.997 6.997 6l1.247 1.247L6.492 9l1.753 1.753L6.997 12 4 9.003 4.003 9z"
fillRule="evenodd"
/>
</svg>
);
}

View file

@ -0,0 +1,15 @@
import { cn } from '~/utils';
export default function GoogleMinimalIcon({ className = '' }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width="800px"
height="800px"
viewBox="0 0 512 512"
className={cn('h-4 w-4', className)}
>
<path d="M473.16,221.48l-2.26-9.59H262.46v88.22H387c-12.93,61.4-72.93,93.72-121.94,93.72-35.66,0-73.25-15-98.13-39.11a140.08,140.08,0,0,1-41.8-98.88c0-37.16,16.7-74.33,41-98.78s61-38.13,97.49-38.13c41.79,0,71.74,22.19,82.94,32.31l62.69-62.36C390.86,72.72,340.34,32,261.6,32h0c-60.75,0-119,23.27-161.58,65.71C58,139.5,36.25,199.93,36.25,256S56.83,369.48,97.55,411.6C141.06,456.52,202.68,480,266.13,480c57.73,0,112.45-22.62,151.45-63.66,38.34-40.4,58.17-96.3,58.17-154.9C475.75,236.77,473.27,222.12,473.16,221.48Z" />
</svg>
);
}

View file

@ -0,0 +1,50 @@
export default function PaLMIcon({
size = 25,
className = '',
}: {
size?: number;
className?: string;
}) {
return (
<svg
// width="100%"
// height="100%"
width={size}
height={size}
className={className}
viewBox="0 0 19 17"
fill="none"
preserveAspectRatio="xMidYMid meet"
focusable="false"
>
<path
d="M9.62674 16.2202H9.7049C10.4225 16.2202 11.0016 15.6412 11.0016 14.9236V4.04224H8.33008V14.92C8.33008 15.6376 8.90914 16.2202 9.62674 16.2202Z"
fill="#F9AB00"
/>
<path
d="M14.6819 8.02813C13.3249 6.66752 11.2964 6.39398 9.66577 7.2004L15.0585 12.5931C15.2823 12.8169 15.6624 12.7281 15.7583 12.4297C16.2308 10.927 15.8756 9.21822 14.6819 8.02813Z"
fill="#5BB974"
/>
<path
d="M4.64953 8.02813C6.00659 6.66752 8.03507 6.39398 9.66567 7.2004L4.27297 12.5931C4.04916 12.8169 3.66904 12.7281 3.57312 12.4297C3.10064 10.927 3.45589 9.21822 4.64953 8.02813Z"
fill="#129EAF"
/>
<path
d="M14.284 3.84326C12.1383 3.84326 10.3159 5.25005 9.66577 7.20038H18.1918C18.5399 7.20038 18.7744 6.83092 18.6145 6.5183C17.8081 4.93033 16.1704 3.84326 14.284 3.84326Z"
fill="#AF5CF7"
/>
<path
d="M10.5574 1.55901C9.04053 3.07593 8.74567 5.36019 9.66577 7.20039L15.6944 1.17179C15.943 0.923113 15.8436 0.496814 15.5132 0.390239C13.8151 -0.1604 11.8896 0.226822 10.5574 1.55901Z"
fill="#FF8BCB"
/>
<path
d="M8.77408 1.55901C10.291 3.07593 10.5859 5.36019 9.66576 7.20039L3.63716 1.17179C3.38848 0.923113 3.48795 0.496814 3.81833 0.390239C5.51643 -0.1604 7.44189 0.226822 8.77408 1.55901Z"
fill="#FA7B17"
/>
<path
d="M5.04752 3.84326C7.19323 3.84326 9.01566 5.25005 9.66577 7.20038H1.13976C0.791616 7.20038 0.55715 6.83092 0.717013 6.5183C1.52343 4.93033 3.16114 3.84326 5.04752 3.84326Z"
fill="#4285F4"
/>
</svg>
);
}

View file

@ -1,6 +1,5 @@
import React from 'react';
export default function PaLMinimalIcon() {
import { cn } from '~/utils';
export default function PaLMinimalIcon({ className = '' }: { className?: string }) {
return (
<svg
stroke="currentColor"
@ -9,7 +8,7 @@ export default function PaLMinimalIcon() {
viewBox="0 0 32 32"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
className={cn('h-4 w-4', className)}
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"

View file

@ -34,5 +34,8 @@ export { default as ChatGPTMinimalIcon } from './ChatGPTMinimalIcon';
export { default as PluginMinimalIcon } from './PluginMinimalIcon';
export { default as BingAIMinimalIcon } from './BingAIMinimalIcon';
export { default as PaLMinimalIcon } from './PaLMinimalIcon';
export { default as PaLMIcon } from './PaLMIcon';
export { default as CodeyIcon } from './CodeyIcon';
export { default as GoogleMinimalIcon } from './GoogleMinimalIcon';
export { default as AnthropicMinimalIcon } from './AnthropicMinimalIcon';
export { default as SendMessageIcon } from './SendMessageIcon';