mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-31 15:48:51 +01:00
🎯 refactor: Centralize Agent Model Handling Across Conversation Lifecycle (#10956)
* refactor: Implement clearModelForNonEphemeralAgent utility for improved agent handling - Introduced clearModelForNonEphemeralAgent function to manage model state for non-ephemeral agents across various components. - Updated ModelSelectorContext to initialize model based on agent type. - Enhanced useNavigateToConvo, useQueryParams, and useSelectMention hooks to clear model for non-ephemeral agents. - Refactored buildDefaultConvo and endpoints utility to ensure proper handling of agent_id and model state. - Improved overall conversation logic and state management for better performance and reliability. * refactor: Enhance useNewConvo hook to improve agent conversation handling - Added logic to skip access checks for existing agent conversations, utilizing localStorage to restore conversations after refresh. - Improved handling of default endpoints for agents based on user access and existing conversation state, ensuring more reliable conversation initialization. * refactor: Update ChatRoute and useAuthRedirect to include user roles - Enhanced ChatRoute to utilize user roles for improved conversation state management. - Modified useAuthRedirect to return user roles alongside authentication status, ensuring roles are available for conditional logic in ChatRoute. - Adjusted conversation initialization logic to depend on the loaded roles, enhancing the reliability of the conversation setup. * refactor: Update BaseClient to handle non-ephemeral agents in conversation logic - Added a check for non-ephemeral agents in BaseClient, modifying the exceptions set to include 'model' when applicable. - Enhanced conversation handling to improve flexibility based on agent type. * test: Add mock for clearModelForNonEphemeralAgent in useQueryParams tests - Introduced a mock for clearModelForNonEphemeralAgent to enhance testing of query parameters related to non-ephemeral agents. - This addition supports improved test coverage and ensures proper handling of model state in relevant scenarios. * refactor: Simplify mocks in useQueryParams tests for improved clarity - Updated the mocking strategy for utilities in useQueryParams tests to use actual implementations where possible, while still suppressing test output for the logger. - Enhanced the mock for tQueryParamsSchema to minimize complexity and avoid unnecessary validation during tests, improving test reliability and maintainability. * refactor: Enhance agent identification logic in BaseClient for improved clarity * chore: Import Constants in families.ts for enhanced functionality
This commit is contained in:
parent
06719794f6
commit
b5ab32c5ae
12 changed files with 161 additions and 49 deletions
|
|
@ -9,7 +9,13 @@ import type {
|
|||
TModelsConfig,
|
||||
TConversation,
|
||||
} from 'librechat-data-provider';
|
||||
import { getDefaultEndpoint, clearMessagesCache, buildDefaultConvo, logger } from '~/utils';
|
||||
import {
|
||||
clearModelForNonEphemeralAgent,
|
||||
getDefaultEndpoint,
|
||||
clearMessagesCache,
|
||||
buildDefaultConvo,
|
||||
logger,
|
||||
} from '~/utils';
|
||||
import { useApplyModelSpecEffects } from '~/hooks/Agents';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -49,7 +55,10 @@ const useNavigateToConvo = (index = 0) => {
|
|||
dataService.getConversationById(conversationId),
|
||||
);
|
||||
logger.log('conversation', 'Fetched fresh conversation data', data);
|
||||
setConversation(data);
|
||||
|
||||
const convoData = { ...data };
|
||||
clearModelForNonEphemeralAgent(convoData);
|
||||
setConversation(convoData);
|
||||
navigate(`/c/${conversationId ?? Constants.NEW_CONVO}`, { state: { focusChat: true } });
|
||||
} catch (error) {
|
||||
console.error('Error fetching conversation data on navigation', error);
|
||||
|
|
|
|||
|
|
@ -65,38 +65,33 @@ jest.mock('~/hooks/Agents/useAgentDefaultPermissionLevel', () => ({
|
|||
default: jest.fn(() => ({})),
|
||||
}));
|
||||
|
||||
jest.mock('~/utils', () => ({
|
||||
getConvoSwitchLogic: jest.fn(() => ({
|
||||
template: {},
|
||||
shouldSwitch: false,
|
||||
isNewModular: false,
|
||||
newEndpointType: null,
|
||||
isCurrentModular: false,
|
||||
isExistingConversation: false,
|
||||
})),
|
||||
getModelSpecIconURL: jest.fn(() => 'icon-url'),
|
||||
removeUnavailableTools: jest.fn((preset) => preset),
|
||||
logger: { log: jest.fn() },
|
||||
getInitialTheme: jest.fn(() => 'light'),
|
||||
applyFontSize: jest.fn(),
|
||||
}));
|
||||
jest.mock('~/utils', () => {
|
||||
const actualUtils = jest.requireActual('~/utils');
|
||||
return {
|
||||
...actualUtils,
|
||||
// Only mock logger to suppress test output
|
||||
logger: { log: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
||||
// Mock theme utilities that interact with DOM
|
||||
getInitialTheme: jest.fn(() => 'light'),
|
||||
applyFontSize: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the tQueryParamsSchema
|
||||
jest.mock('librechat-data-provider', () => ({
|
||||
...jest.requireActual('librechat-data-provider'),
|
||||
tQueryParamsSchema: {
|
||||
shape: {
|
||||
model: { parse: jest.fn((value) => value) },
|
||||
endpoint: { parse: jest.fn((value) => value) },
|
||||
temperature: { parse: jest.fn((value) => value) },
|
||||
// Add other schema shapes as needed
|
||||
// Use actual librechat-data-provider with minimal overrides
|
||||
jest.mock('librechat-data-provider', () => {
|
||||
const actual = jest.requireActual('librechat-data-provider');
|
||||
return {
|
||||
...actual,
|
||||
// Override schema to avoid complex validation in tests
|
||||
tQueryParamsSchema: {
|
||||
shape: {
|
||||
model: { parse: jest.fn((value) => value) },
|
||||
endpoint: { parse: jest.fn((value) => value) },
|
||||
temperature: { parse: jest.fn((value) => value) },
|
||||
},
|
||||
},
|
||||
},
|
||||
isAgentsEndpoint: jest.fn(() => false),
|
||||
isAssistantsEndpoint: jest.fn(() => false),
|
||||
QueryKeys: { startupConfig: 'startupConfig', endpoints: 'endpoints' },
|
||||
EModelEndpoint: { custom: 'custom', assistants: 'assistants', agents: 'agents' },
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
// Mock data-provider hooks
|
||||
jest.mock('~/data-provider', () => ({
|
||||
|
|
|
|||
|
|
@ -11,13 +11,19 @@ import {
|
|||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
TPreset,
|
||||
AgentListResponse,
|
||||
TEndpointsConfig,
|
||||
TStartupConfig,
|
||||
AgentListResponse,
|
||||
TPreset,
|
||||
} from 'librechat-data-provider';
|
||||
import type { ZodAny } from 'zod';
|
||||
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools, logger } from '~/utils';
|
||||
import {
|
||||
clearModelForNonEphemeralAgent,
|
||||
removeUnavailableTools,
|
||||
getModelSpecIconURL,
|
||||
getConvoSwitchLogic,
|
||||
logger,
|
||||
} from '~/utils';
|
||||
import { useAuthContext, useAgentsMap, useDefaultConvo, useSubmitMessage } from '~/hooks';
|
||||
import { useChatContext, useChatFormContext } from '~/Providers';
|
||||
import { useGetAgentByIdQuery } from '~/data-provider';
|
||||
|
|
@ -194,6 +200,12 @@ export default function useQueryParams({
|
|||
newPreset = { ...newPreset, ...resetParams };
|
||||
}
|
||||
|
||||
// Sync agent_id from newPreset to template, then clear model if non-ephemeral agent
|
||||
if (newPreset.agent_id) {
|
||||
template.agent_id = newPreset.agent_id;
|
||||
}
|
||||
clearModelForNonEphemeralAgent(template);
|
||||
|
||||
const isModular = isCurrentModular && isNewModular && shouldSwitch;
|
||||
if (isExistingConversation && isModular) {
|
||||
template.endpointType = newEndpointType as EModelEndpoint | undefined;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ import type {
|
|||
TEndpointsConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { MentionOption, ConvoGenerator } from '~/common';
|
||||
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools, logger } from '~/utils';
|
||||
import {
|
||||
clearModelForNonEphemeralAgent,
|
||||
removeUnavailableTools,
|
||||
getModelSpecIconURL,
|
||||
getConvoSwitchLogic,
|
||||
logger,
|
||||
} from '~/utils';
|
||||
import { useDefaultConvo } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -154,6 +160,7 @@ export default function useSelectMention({
|
|||
if (agent_id) {
|
||||
template.agent_id = agent_id;
|
||||
}
|
||||
clearModelForNonEphemeralAgent(template);
|
||||
|
||||
template.spec = null;
|
||||
template.iconURL = null;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type {
|
|||
import type { AssistantListItem } from '~/common';
|
||||
import {
|
||||
updateLastSelectedModel,
|
||||
getLocalStorageItems,
|
||||
getDefaultModelSpec,
|
||||
getDefaultEndpoint,
|
||||
getModelSpecPreset,
|
||||
|
|
@ -112,7 +113,21 @@ const useNewConvo = (index = 0) => {
|
|||
});
|
||||
|
||||
// If the selected endpoint is agents but user doesn't have access, find an alternative
|
||||
if (defaultEndpoint && isAgentsEndpoint(defaultEndpoint) && !hasAgentAccess) {
|
||||
// Skip this check for existing agent conversations (they have agent_id set)
|
||||
// Also check localStorage for new conversations restored after refresh
|
||||
const { lastConversationSetup } = getLocalStorageItems();
|
||||
const storedAgentId =
|
||||
isAgentsEndpoint(lastConversationSetup?.endpoint) && lastConversationSetup?.agent_id;
|
||||
const isExistingAgentConvo =
|
||||
isAgentsEndpoint(defaultEndpoint) &&
|
||||
((conversation.agent_id && conversation.agent_id !== Constants.EPHEMERAL_AGENT_ID) ||
|
||||
(storedAgentId && storedAgentId !== Constants.EPHEMERAL_AGENT_ID));
|
||||
if (
|
||||
defaultEndpoint &&
|
||||
isAgentsEndpoint(defaultEndpoint) &&
|
||||
!hasAgentAccess &&
|
||||
!isExistingAgentConvo
|
||||
) {
|
||||
defaultEndpoint = Object.keys(endpointsConfig ?? {}).find(
|
||||
(ep) => !isAgentsEndpoint(ep as EModelEndpoint) && endpointsConfig?.[ep],
|
||||
) as EModelEndpoint | undefined;
|
||||
|
|
@ -121,7 +136,11 @@ const useNewConvo = (index = 0) => {
|
|||
if (!defaultEndpoint) {
|
||||
// Find first available endpoint that's not agents (if no access) or any endpoint
|
||||
defaultEndpoint = Object.keys(endpointsConfig ?? {}).find((ep) => {
|
||||
if (isAgentsEndpoint(ep as EModelEndpoint) && !hasAgentAccess) {
|
||||
if (
|
||||
isAgentsEndpoint(ep as EModelEndpoint) &&
|
||||
!hasAgentAccess &&
|
||||
!isExistingAgentConvo
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return !!endpointsConfig?.[ep];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue