mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-28 12:44:28 +00:00
🌐 fix: Expose Gemini Models to Vertex AI Agents (#15234)
* 🌐 fix: Expose Gemini Models to Vertex AI Agents * ♻️ refactor: Resolve Shared Vertex Model Catalogs * fix: Preserve Exact Vertex Model Catalogs * style: Format Agent Model Selection * test: Preserve Native FS in Stable Diffusion Spec
This commit is contained in:
parent
3d808dc906
commit
8b1fcc0fc2
16 changed files with 287 additions and 34 deletions
|
|
@ -1,7 +1,12 @@
|
|||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, alternateName, isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
QueryKeys,
|
||||
alternateName,
|
||||
isAgentsEndpoint,
|
||||
resolveModelCatalogKey,
|
||||
} from 'librechat-data-provider';
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
|
|
@ -77,7 +82,9 @@ const EditPresetDialog = ({
|
|||
return;
|
||||
}
|
||||
|
||||
const models = modelsConfig[presetEndpoint] as string[] | undefined;
|
||||
const models = modelsConfig[resolveModelCatalogKey(presetEndpoint, modelsConfig)] as
|
||||
| string[]
|
||||
| undefined;
|
||||
if (!models) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { resolveModelCatalogKey } from 'librechat-data-provider';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import type { TSetOption } from '~/common';
|
||||
|
|
@ -29,7 +30,7 @@ export default function ModelSelect({
|
|||
}
|
||||
|
||||
const { endpoint: _endpoint, endpointType } = conversation;
|
||||
const models = modelsQuery.data?.[_endpoint] ?? [];
|
||||
const models = modelsQuery.data?.[resolveModelCatalogKey(_endpoint, modelsQuery.data)] ?? [];
|
||||
const endpoint = endpointType ?? _endpoint;
|
||||
|
||||
const OptionComponent = multiChatOptions[endpoint];
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
LocalStorageKeys,
|
||||
PermissionBits,
|
||||
removeCodeExecutionCaller,
|
||||
resolveModelCatalogKey,
|
||||
resolveStatefulCodeEnvironment,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
|
|
@ -613,7 +614,7 @@ export default function AgentPanel() {
|
|||
status: 'error',
|
||||
});
|
||||
}
|
||||
if (!(models[provider] ?? []).includes(model)) {
|
||||
if (!(models[resolveModelCatalogKey(provider, models)] ?? []).includes(model)) {
|
||||
return showToast({
|
||||
message: localize('com_error_model_not_found'),
|
||||
status: 'error',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* @jest-environment jsdom
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Providers } from 'librechat-data-provider';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import type { AgentForm } from '~/common';
|
||||
|
|
@ -146,6 +147,54 @@ describe('ModelPanel', () => {
|
|||
expect(localStorage.getItem('lastAgentModel')).toBe('alternate-model');
|
||||
});
|
||||
|
||||
it('selects the Google catalog for a Vertex AI provider', () => {
|
||||
const providers = [
|
||||
{ label: 'Original', value: 'original' },
|
||||
{ label: 'Vertex AI', value: Providers.VERTEXAI },
|
||||
];
|
||||
const { getByTestId } = render(
|
||||
<TestForm
|
||||
defaultProvider="original"
|
||||
defaultModel="original-model"
|
||||
models={{ original: ['original-model'], google: ['gemini-3.7-flash'] }}
|
||||
modelsReady={true}
|
||||
providers={providers}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId(`com_ui_provider-${Providers.VERTEXAI}`));
|
||||
|
||||
expect(getByTestId('com_ui_model-selected')).toHaveTextContent('gemini-3.7-flash');
|
||||
expect(localStorage.getItem('lastAgentProvider')).toBe(Providers.VERTEXAI);
|
||||
expect(localStorage.getItem('lastAgentModel')).toBe('gemini-3.7-flash');
|
||||
});
|
||||
|
||||
it('selects an exact Vertex AI catalog when configured', () => {
|
||||
const providers = [
|
||||
{ label: 'Original', value: 'original' },
|
||||
{ label: 'Vertex AI', value: Providers.VERTEXAI },
|
||||
];
|
||||
const { getByTestId } = render(
|
||||
<TestForm
|
||||
defaultProvider="original"
|
||||
defaultModel="original-model"
|
||||
models={{
|
||||
original: ['original-model'],
|
||||
google: ['gemini-3.7-flash'],
|
||||
[Providers.VERTEXAI]: ['custom-vertex-model'],
|
||||
}}
|
||||
modelsReady={true}
|
||||
providers={providers}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId(`com_ui_provider-${Providers.VERTEXAI}`));
|
||||
|
||||
expect(getByTestId('com_ui_model-selected')).toHaveTextContent('custom-vertex-model');
|
||||
expect(localStorage.getItem('lastAgentProvider')).toBe(Providers.VERTEXAI);
|
||||
expect(localStorage.getItem('lastAgentModel')).toBe('custom-vertex-model');
|
||||
});
|
||||
|
||||
it('preserves the model when the current provider is selected again', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestForm
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
getSettingsKeys,
|
||||
getEndpointField,
|
||||
LocalStorageKeys,
|
||||
resolveModelCatalogKey,
|
||||
SettingDefinition,
|
||||
agentParamSettings,
|
||||
applyModelAwareDefaults,
|
||||
|
|
@ -58,7 +59,7 @@ export default function ModelPanel({
|
|||
return value ?? '';
|
||||
}, [providerOption]);
|
||||
const models = useMemo(
|
||||
() => (provider ? (modelsData[provider] ?? []) : []),
|
||||
() => (provider ? (modelsData[resolveModelCatalogKey(provider, modelsData)] ?? []) : []),
|
||||
[modelsData, provider],
|
||||
);
|
||||
const modelsPending = !modelsReady && !modelsError;
|
||||
|
|
@ -158,7 +159,8 @@ export default function ModelPanel({
|
|||
if (value === provider) {
|
||||
return;
|
||||
}
|
||||
const nextModel = modelsData[value]?.[0] ?? '';
|
||||
const nextModel =
|
||||
modelsData[resolveModelCatalogKey(value, modelsData)]?.[0] ?? '';
|
||||
field.onChange(value);
|
||||
setValue('model', nextModel);
|
||||
localStorage.setItem(LocalStorageKeys.LAST_AGENT_PROVIDER, value);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Providers, EModelEndpoint, isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { isAgentsEndpoint, resolveModelCatalogKey } from 'librechat-data-provider';
|
||||
import type { TConversation, TModelTokenomics } from 'librechat-data-provider';
|
||||
import { useGetStartupConfig, useTokenConfigQuery, useGetAgentByIdQuery } from '~/data-provider';
|
||||
import { getModelSpec } from '~/utils';
|
||||
|
||||
/** Gemini tokenomics are advertised under the `google` endpoint, so a
|
||||
* Vertex-backed agent (`provider: 'vertexai'`) must look up there. */
|
||||
function normalizeTokenConfigKey(endpoint: string): string {
|
||||
return endpoint === Providers.VERTEXAI ? EModelEndpoint.google : endpoint;
|
||||
}
|
||||
|
||||
export interface TokenLimits {
|
||||
/** Statically resolved max context; live snapshots override this at run time */
|
||||
maxContextTokens?: number;
|
||||
|
|
@ -52,7 +46,7 @@ export default function useTokenLimits(conversation: TConversation | null): Toke
|
|||
lookupEndpoint = specPreset.endpoint ?? lookupEndpoint;
|
||||
lookupModel = lookupModel || (specPreset.model ?? '');
|
||||
}
|
||||
lookupEndpoint = normalizeTokenConfigKey(lookupEndpoint);
|
||||
lookupEndpoint = resolveModelCatalogKey(lookupEndpoint, tokenConfig);
|
||||
|
||||
const rates = tokenConfig?.[lookupEndpoint]?.[lookupModel];
|
||||
const maxContextTokens =
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import { excludedKeys, getDefaultParamsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
excludedKeys,
|
||||
getDefaultParamsEndpoint,
|
||||
resolveModelCatalogKey,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
TEndpointsConfig,
|
||||
TModelsConfig,
|
||||
|
|
@ -30,7 +34,7 @@ const useDefaultConvo = () => {
|
|||
endpointsConfig,
|
||||
});
|
||||
|
||||
const models = modelsConfig[endpoint ?? ''] || [];
|
||||
const models = modelsConfig[resolveModelCatalogKey(endpoint, modelsConfig)] || [];
|
||||
const conversation = { ..._convo };
|
||||
if (cleanInput === true) {
|
||||
for (const key in conversation) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { resolveModelCatalogKey } from 'librechat-data-provider';
|
||||
|
||||
type ProviderOption = string | { value?: string | number | null };
|
||||
|
||||
export function getAvailableModelSelection(model: string, models: readonly string[]): string {
|
||||
|
|
@ -16,7 +18,7 @@ export function getAvailableAgentSelection({
|
|||
models: Record<string, string[] | undefined>;
|
||||
}): { provider: string; model: string } {
|
||||
const providerExists =
|
||||
models[provider] != null &&
|
||||
models[resolveModelCatalogKey(provider, models)] != null &&
|
||||
providers.some((option) =>
|
||||
typeof option === 'string' ? option === provider : option.value === provider,
|
||||
);
|
||||
|
|
@ -27,6 +29,9 @@ export function getAvailableAgentSelection({
|
|||
|
||||
return {
|
||||
provider,
|
||||
model: getAvailableModelSelection(model, models[provider] ?? []),
|
||||
model: getAvailableModelSelection(
|
||||
model,
|
||||
models[resolveModelCatalogKey(provider, models)] ?? [],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue