Cannot make web search work without reranker #8725
Replies: 6 comments 1 reply
-
According to the docs, reranker is needed - so this is not a bug. You can disable it though by setting an invalid key in your .env file. Something like this works for me COHERE_API_KEY=not_needed and configure the websearch reranker to use cohere. Same could be done for jina as well |
Beta Was this translation helpful? Give feedback.
-
const { logger } = require('@librechat/data-schemas');
const { SerpAPI } = require('@langchain/community/tools/serpapi');
const { Calculator } = require('@langchain/community/tools/calculator');
const { mcpToolPattern, loadWebSearchAuth } = require('@librechat/api');
const { EnvVar, createCodeExecutionTool } = require('@librechat/agents');
const { Tools, EToolResources, replaceSpecialVars } = require('librechat-data-provider');
const {
availableTools,
manifestToolMap,
// Basic Tools
GoogleSearchAPI,
// Structured Tools
DALLE3,
FluxAPI,
OpenWeather,
StructuredSD,
StructuredACS,
TraversaalSearch,
StructuredWolfram,
createYouTubeTools,
TavilySearchResults,
createOpenAIImageTools,
} = require('../');
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch');
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { getCachedTools } = require('~/server/services/Config');
const { createMCPTool } = require('~/server/services/MCP');
/**
* Validates the availability and authentication of tools for a user based on environment variables or user-specific plugin authentication values.
* Tools without required authentication or with valid authentication are considered valid.
*
* @param {Object} user The user object for whom to validate tool access.
* @param {Array<string>} tools An array of tool identifiers to validate. Defaults to an empty array.
* @returns {Promise<Array<string>>} A promise that resolves to an array of valid tool identifiers.
*/
const validateTools = async (user, tools = []) => {
try {
const validToolsSet = new Set(tools);
const availableToolsToValidate = availableTools.filter((tool) =>
validToolsSet.has(tool.pluginKey),
);
/**
* Validates the credentials for a given auth field or set of alternate auth fields for a tool.
* If valid admin or user authentication is found, the function returns early. Otherwise, it removes the tool from the set of valid tools.
*
* @param {string} authField The authentication field or fields (separated by "||" for alternates) to validate.
* @param {string} toolName The identifier of the tool being validated.
*/
const validateCredentials = async (authField, toolName) => {
const fields = authField.split('||');
for (const field of fields) {
const adminAuth = process.env[field];
if (adminAuth && adminAuth.length > 0) {
return;
}
let userAuth = null;
try {
userAuth = await getUserPluginAuthValue(user, field);
} catch (err) {
if (field === fields[fields.length - 1] && !userAuth) {
throw err;
}
}
if (userAuth && userAuth.length > 0) {
return;
}
}
validToolsSet.delete(toolName);
};
for (const tool of availableToolsToValidate) {
if (!tool.authConfig || tool.authConfig.length === 0) {
continue;
}
for (const auth of tool.authConfig) {
await validateCredentials(auth.authField, tool.pluginKey);
}
}
return Array.from(validToolsSet.values());
} catch (err) {
logger.error('[validateTools] There was a problem validating tools', err);
throw new Error(err);
}
};
/** @typedef {typeof import('@langchain/core/tools').Tool} ToolConstructor */
/** @typedef {import('@langchain/core/tools').Tool} Tool */
/**
* Initializes a tool with authentication values for the given user, supporting alternate authentication fields.
* Authentication fields can have alternates separated by "||", and the first defined variable will be used.
*
* @param {string} userId The user ID for which the tool is being loaded.
* @param {Array<string>} authFields Array of strings representing the authentication fields. Supports alternate fields delimited by "||".
* @param {ToolConstructor} ToolConstructor The constructor function for the tool to be initialized.
* @param {Object} options Optional parameters to be passed to the tool constructor alongside authentication values.
* @returns {() => Promise<Tool>} An Async function that, when called, asynchronously initializes and returns an instance of the tool with authentication.
*/
const loadToolWithAuth = (userId, authFields, ToolConstructor, options = {}) => {
return async function () {
const authValues = await loadAuthValues({ userId, authFields });
return new ToolConstructor({ ...options, ...authValues, userId });
};
};
/**
* @param {string} toolKey
* @returns {Array<string>}
*/
const getAuthFields = (toolKey) => {
return manifestToolMap[toolKey]?.authConfig.map((auth) => auth.authField) ?? [];
};
/**
*
* @param {object} object
* @param {string} object.user
* @param {Pick<Agent, 'id' | 'provider' | 'model'>} [object.agent]
* @param {string} [object.model]
* @param {EModelEndpoint} [object.endpoint]
* @param {LoadToolOptions} [object.options]
* @param {boolean} [object.useSpecs]
* @param {Array<string>} object.tools
* @param {boolean} [object.functions]
* @param {boolean} [object.returnMap]
* @returns {Promise<{ loadedTools: Tool[], toolContextMap: Object<string, any> } | Record<string,Tool>>}
*/
const loadTools = async ({
user,
agent,
model,
endpoint,
tools = [],
options = {},
functions = true,
returnMap = false,
}) => {
const toolConstructors = {
flux: FluxAPI,
calculator: Calculator,
google: GoogleSearchAPI,
open_weather: OpenWeather,
wolfram: StructuredWolfram,
'stable-diffusion': StructuredSD,
'azure-ai-search': StructuredACS,
traversaal_search: TraversaalSearch,
tavily_search_results_json: TavilySearchResults,
};
const customConstructors = {
serpapi: async (_toolContextMap) => {
const authFields = getAuthFields('serpapi');
let envVar = authFields[0] ?? '';
let apiKey = process.env[envVar];
if (!apiKey) {
apiKey = await getUserPluginAuthValue(user, envVar);
}
return new SerpAPI(apiKey, {
location: 'Austin,Texas,United States',
hl: 'en',
gl: 'us',
});
},
youtube: async (_toolContextMap) => {
const authFields = getAuthFields('youtube');
const authValues = await loadAuthValues({ userId: user, authFields });
return createYouTubeTools(authValues);
},
image_gen_oai: async (toolContextMap) => {
const authFields = getAuthFields('image_gen_oai');
const authValues = await loadAuthValues({ userId: user, authFields });
const imageFiles = options.tool_resources?.[EToolResources.image_edit]?.files ?? [];
let toolContext = '';
for (let i = 0; i < imageFiles.length; i++) {
const file = imageFiles[i];
if (!file) {
continue;
}
if (i === 0) {
toolContext =
'Image files provided in this request (their image IDs listed in order of appearance) available for image editing:';
}
toolContext += `\n\t- ${file.file_id}`;
if (i === imageFiles.length - 1) {
toolContext += `\n\nInclude any you need in the \`image_ids\` array when calling \`${EToolResources.image_edit}_oai\`. You may also include previously referenced or generated image IDs.`;
}
}
if (toolContext) {
toolContextMap.image_edit_oai = toolContext;
}
return createOpenAIImageTools({
...authValues,
isAgent: !!agent,
req: options.req,
imageFiles,
});
},
};
const requestedTools = {};
if (functions === true) {
toolConstructors.dalle = DALLE3;
}
/** @type {ImageGenOptions} */
const imageGenOptions = {
isAgent: !!agent,
req: options.req,
fileStrategy: options.fileStrategy,
processFileURL: options.processFileURL,
returnMetadata: options.returnMetadata,
uploadImageBuffer: options.uploadImageBuffer,
};
const toolOptions = {
flux: imageGenOptions,
dalle: imageGenOptions,
'stable-diffusion': imageGenOptions,
serpapi: { location: 'Austin,Texas,United States', hl: 'en', gl: 'us' },
};
/** @type {Record<string, string>} */
const toolContextMap = {};
const cachedTools = (await getCachedTools({ userId: user, includeGlobal: true })) ?? {};
for (const tool of tools) {
if (tool === Tools.execute_code) {
requestedTools[tool] = async () => {
const authValues = await loadAuthValues({
userId: user,
authFields: [EnvVar.CODE_API_KEY],
});
const codeApiKey = authValues[EnvVar.CODE_API_KEY];
const { files, toolContext } = await primeCodeFiles(
{
...options,
agentId: agent?.id,
},
codeApiKey,
);
if (toolContext) {
toolContextMap[tool] = toolContext;
}
const CodeExecutionTool = createCodeExecutionTool({
user_id: user,
files,
...authValues,
});
CodeExecutionTool.apiKey = codeApiKey;
return CodeExecutionTool;
};
continue;
} else if (tool === Tools.file_search) {
requestedTools[tool] = async () => {
const { files, toolContext } = await primeSearchFiles({
...options,
agentId: agent?.id,
});
if (toolContext) {
toolContextMap[tool] = toolContext;
}
return createFileSearchTool({ req: options.req, files, entity_id: agent?.id });
};
continue;
} else if (tool === Tools.web_search) {
const { onSearchResults, onGetHighlights } = options?.[Tools.web_search] ?? {};
requestedTools[tool] = async () => {
toolContextMap[tool] = `# \`${tool}\`:
Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}
1. **Execute immediately without preface** when using \`${tool}\`.
2. **After the search, begin with a brief summary** that directly addresses the query without headers or explaining your process.
3. **Structure your response clearly** using Markdown formatting (Level 2 headers for sections, lists for multiple points, tables for comparisons).
4. **Cite sources properly** according to the citation anchor format, utilizing group anchors when appropriate.
5. **Tailor your approach to the query type** (academic, news, coding, etc.) while maintaining an expert, journalistic, unbiased tone.
6. **Provide comprehensive information** with specific details, examples, and as much relevant context as possible from search results.
7. **Avoid moralizing language.**
`.trim();
// Create a working web search tool using Serper API
const { tool: createTool } = require('@langchain/core/tools');
const axios = require('axios');
const { z } = require('zod');
return createTool(
async ({ query, date, country, images, videos, news }) => {
try {
// Get API key from environment
const apiKey = process.env.SERPER_API_KEY;
if (!apiKey || apiKey === '${SERPER_API_KEY}') {
return 'Web search is not configured. Please set SERPER_API_KEY in your environment.';
}
console.log('Web search using API key:', apiKey.substring(0, 10) + '...');
// Build search parameters
const payload = {
q: query,
safe: 'moderate',
num: 8,
};
if (country && country !== '') {
payload.gl = country.toLowerCase();
}
if (date) {
payload.tbs = `qdr:${date}`;
}
// Determine API endpoint based on type
let apiEndpoint = 'https://google.serper.dev/search';
if (images) {
apiEndpoint = 'https://google.serper.dev/images';
} else if (videos) {
apiEndpoint = 'https://google.serper.dev/videos';
} else if (news) {
apiEndpoint = 'https://google.serper.dev/news';
}
console.log('Making request to:', apiEndpoint);
console.log('Payload:', payload);
const response = await axios.post(apiEndpoint, payload, {
headers: {
'X-API-KEY': apiKey,
'Content-Type': 'application/json',
},
timeout: 10000,
});
console.log('Response status:', response.status);
const data = response.data;
console.log('Response data keys:', Object.keys(data));
const results = {
organic: data.organic || [],
images: data.images || [],
videos: data.videos || [],
news: data.news || [],
topStories: data.topStories || [],
knowledgeGraph: data.knowledgeGraph,
answerBox: data.answerBox,
peopleAlsoAsk: data.peopleAlsoAsk || [],
relatedSearches: data.relatedSearches || [],
};
// Call onSearchResults if provided
if (onSearchResults) {
onSearchResults({ success: true, data: results }, {
metadata: {
user_id: user,
thread_id: 'web_search',
run_id: 'web_search'
},
toolCall: {
id: 'web_search_tool',
name: 'web_search',
turn: 0
}
});
}
// Format the results for the LLM
let formattedResult = `Search Results for: "${query}"\n\n`;
if (results.organic && results.organic.length > 0) {
formattedResult += '**Web Results:**\n';
results.organic.forEach((result, index) => {
formattedResult += `${index + 1}. [${result.title || 'No title'}](${result.link})\n`;
if (result.snippet) {
formattedResult += ` ${result.snippet}\n`;
}
formattedResult += '\n';
});
}
if (results.topStories && results.topStories.length > 0) {
formattedResult += '**Top Stories:**\n';
results.topStories.forEach((result, index) => {
formattedResult += `${index + 1}. [${result.title || 'No title'}](${result.link})\n`;
if (result.snippet) {
formattedResult += ` ${result.snippet}\n`;
}
formattedResult += '\n';
});
}
if (results.answerBox) {
formattedResult += '**Answer Box:**\n';
formattedResult += `${results.answerBox.title || ''}\n`;
if (results.answerBox.snippet) {
formattedResult += `${results.answerBox.snippet}\n`;
}
formattedResult += '\n';
}
if (results.knowledgeGraph) {
formattedResult += '**Knowledge Graph:**\n';
formattedResult += `${results.knowledgeGraph.title || ''}\n`;
if (results.knowledgeGraph.description) {
formattedResult += `${results.knowledgeGraph.description}\n`;
}
formattedResult += '\n';
}
if (results.peopleAlsoAsk && results.peopleAlsoAsk.length > 0) {
formattedResult += '**People Also Ask:**\n';
results.peopleAlsoAsk.forEach((item, index) => {
formattedResult += `${index + 1}. ${item.question}\n`;
if (item.answer) {
formattedResult += ` ${item.answer}\n`;
}
formattedResult += '\n';
});
}
console.log('Formatted result length:', formattedResult.length);
return formattedResult;
} catch (error) {
console.error('Web search error:', error.message);
console.error('Error stack:', error.stack);
if (error.response) {
console.error('Response data:', error.response.data);
console.error('Response status:', error.response.status);
}
const errorMessage = error instanceof Error ? error.message : String(error);
return `Search failed: ${errorMessage}`;
}
},
{
name: 'web_search',
description: 'Search the web for current information. Use this tool to find recent news, facts, or information that may not be in your training data.',
schema: z.object({
query: z.string().describe('The search query to look up on the web'),
date: z.string().optional().describe('Optional date range filter (h, d, w, m, y)'),
country: z.string().optional().describe('Optional country code for localized results'),
images: z.boolean().optional().describe('Search for images'),
videos: z.boolean().optional().describe('Search for videos'),
news: z.boolean().optional().describe('Search for news')
})
}
);
};
continue;
} else if (tool && cachedTools && mcpToolPattern.test(tool)) {
requestedTools[tool] = async () =>
createMCPTool({
req: options.req,
res: options.res,
toolKey: tool,
model: agent?.model ?? model,
provider: agent?.provider ?? endpoint,
});
continue;
}
if (customConstructors[tool]) {
requestedTools[tool] = async () => customConstructors[tool](toolContextMap);
continue;
}
if (toolConstructors[tool]) {
const options = toolOptions[tool] || {};
const toolInstance = loadToolWithAuth(
user,
getAuthFields(tool),
toolConstructors[tool],
options,
);
requestedTools[tool] = toolInstance;
continue;
}
}
if (returnMap) {
return requestedTools;
}
const toolPromises = [];
for (const tool of tools) {
const validTool = requestedTools[tool];
if (validTool) {
toolPromises.push(
validTool().catch((error) => {
logger.error(`Error loading tool ${tool}:`, error);
return null;
}),
);
}
}
const loadedTools = (await Promise.all(toolPromises)).flatMap((plugin) => plugin || []);
return { loadedTools, toolContextMap };
};
module.exports = {
loadToolWithAuth,
validateTools,
loadTools,
}; If anybody wants this to work, then handleTools.js above. This should be written in TypeScript to prevent maintenance issues. |
Beta Was this translation helpful? Give feedback.
-
Okay, I made it work like that by setting bogus FIRECRAWL_API_KEY and COHERE_API_KEY. It is unclear that the ENV vars would work, because setting those bogus values into the UI API dialogue box does not make it work. Can this please be documented? Even better, can we please change the dialogue box to have a checkbox to disable these unnecessary services? It works just fine with only Serper. In my time using Firecrawl I consider it a liability due to the number of fields in their own SDK which do not work or error out when used, the lack of proper typing and other limitations. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the reply by the way. |
Beta Was this translation helpful? Give feedback.
-
Here is a patch to override the model names (generated with From 61bccf134105ce61658ec0c791e781f51be9c068 Mon Sep 17 00:00:00 2001
From: Nicholas Johnson <[email protected]>
Date: Tue, 29 Jul 2025 18:43:02 +0800
Subject: [PATCH] Add ability to override model names
---
.../services/Config/loadConfigEndpoints.js | 15 +++++
.../src/components/Chat/Input/AddedConvo.tsx | 4 +-
.../Chat/Menus/Endpoints/ModelSelector.tsx | 3 +-
.../Endpoints/components/EndpointItem.tsx | 2 +
.../components/EndpointModelItem.tsx | 8 +++
.../Endpoints/components/SearchResults.tsx | 56 +++++++++++--------
.../components/Chat/Menus/Endpoints/utils.ts | 15 +++++
.../src/hooks/Conversations/useGetSender.ts | 9 ++-
client/src/utils/presets.ts | 19 +++++--
client/vite.config.ts | 4 +-
packages/data-provider/src/config.ts | 1 +
packages/data-provider/src/types.ts | 1 +
12 files changed, 102 insertions(+), 35 deletions(-)
diff --git a/api/server/services/Config/loadConfigEndpoints.js b/api/server/services/Config/loadConfigEndpoints.js
index 2e80fb42..accdc06e 100644
--- a/api/server/services/Config/loadConfigEndpoints.js
+++ b/api/server/services/Config/loadConfigEndpoints.js
@@ -35,6 +35,7 @@ async function loadConfigEndpoints(req) {
name: configName,
iconURL,
modelDisplayLabel,
+ modelDisplayNames,
customParams,
} = endpoint;
const name = normalizeEndpointName(configName);
@@ -47,6 +48,7 @@ async function loadConfigEndpoints(req) {
userProvide: isUserProvided(resolvedApiKey),
userProvideURL: isUserProvided(resolvedBaseURL),
modelDisplayLabel,
+ modelDisplayNames,
iconURL,
customParams,
};
@@ -58,6 +60,19 @@ async function loadConfigEndpoints(req) {
endpointsConfig[EModelEndpoint.azureOpenAI] = {
userProvide: false,
};
+
+ if (customConfig?.endpoints?.azureOpenAI) {
+ const azureConfig = customConfig.endpoints.azureOpenAI;
+ if (azureConfig.tools) {
+ endpointsConfig[EModelEndpoint.azureOpenAI].tools = azureConfig.tools;
+ }
+ if (azureConfig.modelDisplayNames) {
+ endpointsConfig[EModelEndpoint.azureOpenAI].modelDisplayNames = azureConfig.modelDisplayNames;
+ }
+ if (azureConfig.modelDisplayLabel) {
+ endpointsConfig[EModelEndpoint.azureOpenAI].modelDisplayLabel = azureConfig.modelDisplayLabel;
+ }
+ }
}
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
diff --git a/client/src/components/Chat/Input/AddedConvo.tsx b/client/src/components/Chat/Input/AddedConvo.tsx
index 96d534cf..cd9fd744 100644
--- a/client/src/components/Chat/Input/AddedConvo.tsx
+++ b/client/src/components/Chat/Input/AddedConvo.tsx
@@ -17,9 +17,9 @@ export default function AddedConvo({
const { data: endpointsConfig } = useGetEndpointsQuery();
const title = useMemo(() => {
const sender = getSender(addedConvo as TEndpointOption);
- const title = getPresetTitle(addedConvo as TPreset);
+ const title = getPresetTitle(addedConvo as TPreset, false, endpointsConfig);
return `+ ${sender}: ${title}`;
- }, [addedConvo, getSender]);
+ }, [addedConvo, getSender, endpointsConfig]);
if (!addedConvo) {
return null;
diff --git a/client/src/components/Chat/Menus/Endpoints/ModelSelector.tsx b/client/src/components/Chat/Menus/Endpoints/ModelSelector.tsx
index 1a1463a4..26211e2a 100644
--- a/client/src/components/Chat/Menus/Endpoints/ModelSelector.tsx
+++ b/client/src/components/Chat/Menus/Endpoints/ModelSelector.tsx
@@ -46,8 +46,9 @@ function ModelSelectorContent() {
modelSpecs,
selectedValues,
mappedEndpoints,
+ endpointsConfig,
}),
- [localize, modelSpecs, selectedValues, mappedEndpoints],
+ [localize, modelSpecs, selectedValues, mappedEndpoints, endpointsConfig],
);
const trigger = (
diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx
index 19a17316..a2ef94c7 100644
--- a/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx
+++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx
@@ -63,6 +63,7 @@ export function EndpointItem({ endpoint }: EndpointItemProps) {
endpointSearchValues,
setEndpointSearchValue,
endpointRequiresUserKey,
+ endpointsConfig,
} = useModelSelectorContext();
const { model: selectedModel, endpoint: selectedEndpoint } = selectedValues;
@@ -107,6 +108,7 @@ export function EndpointItem({ endpoint }: EndpointItemProps) {
searchValue,
agentsMap,
assistantsMap,
+ endpointsConfig,
)
: null;
const placeholder =
diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx
index 6a9b6fd3..672d2e1d 100644
--- a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx
+++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx
@@ -4,6 +4,7 @@ import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider'
import type { Endpoint } from '~/common';
import { useModelSelectorContext } from '../ModelSelectorContext';
import { CustomMenuItem as MenuItem } from '../CustomMenu';
+import { useGetEndpointsQuery } from '~/data-provider';
interface EndpointModelItemProps {
modelId: string | null;
@@ -13,10 +14,14 @@ interface EndpointModelItemProps {
export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointModelItemProps) {
const { handleSelectModel } = useModelSelectorContext();
+ const { data: endpointsConfig = {} } = useGetEndpointsQuery();
let isGlobal = false;
let modelName = modelId;
const avatarUrl = endpoint?.modelIcons?.[modelId ?? ''] || null;
+ // Get modelDisplayNames mapping from endpoints config
+ const modelDisplayNames = endpointsConfig[endpoint.value]?.modelDisplayNames;
+
// Use custom names if available
if (endpoint && modelId && isAgentsEndpoint(endpoint.value) && endpoint.agentNames?.[modelId]) {
modelName = endpoint.agentNames[modelId];
@@ -30,6 +35,9 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod
endpoint.assistantNames?.[modelId]
) {
modelName = endpoint.assistantNames[modelId];
+ } else if (modelDisplayNames && modelId && modelDisplayNames[modelId]) {
+ // Use modelDisplayNames mapping from configuration
+ modelName = modelDisplayNames[modelId];
}
return (
diff --git a/client/src/components/Chat/Menus/Endpoints/components/SearchResults.tsx b/client/src/components/Chat/Menus/Endpoints/components/SearchResults.tsx
index ffefbc44..60a56f65 100644
--- a/client/src/components/Chat/Menus/Endpoints/components/SearchResults.tsx
+++ b/client/src/components/Chat/Menus/Endpoints/components/SearchResults.tsx
@@ -1,16 +1,15 @@
-import React, { Fragment } from 'react';
-import { EarthIcon } from 'lucide-react';
+import { Fragment } from 'react';
+import { useModelSelectorContext } from '../ModelSelectorContext';
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
+import { EarthIcon } from 'lucide-react';
+import { useGetEndpointsQuery } from '~/data-provider';
import type { TModelSpec } from 'librechat-data-provider';
import type { Endpoint } from '~/common';
-import { useModelSelectorContext } from '../ModelSelectorContext';
import { CustomMenuItem as MenuItem } from '../CustomMenu';
-import SpecIcon from './SpecIcon';
-import { cn } from '~/utils';
interface SearchResultsProps {
results: (TModelSpec | Endpoint)[] | null;
- localize: (phraseKey: any, options?: any) => string;
+ localize: (key: string) => string;
searchValue: string;
}
@@ -20,8 +19,8 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
handleSelectSpec,
handleSelectModel,
handleSelectEndpoint,
- endpointsConfig,
} = useModelSelectorContext();
+ const { data: endpointsConfigData = {} } = useGetEndpointsQuery();
const {
modelSpec: selectedSpec,
@@ -50,22 +49,9 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
<MenuItem
key={spec.name}
onClick={() => handleSelectSpec(spec)}
- className={cn(
- 'flex w-full cursor-pointer justify-between rounded-lg px-2 text-sm',
- spec.description ? 'items-start' : 'items-center',
- )}
+ className="flex w-full cursor-pointer justify-between rounded-lg px-2 text-sm"
>
- <div
- className={cn(
- 'flex w-full min-w-0 gap-2 px-1 py-1',
- spec.description ? 'items-start' : 'items-center',
- )}
- >
- {(spec.showIconInMenu ?? true) && (
- <div className="flex-shrink-0">
- <SpecIcon currentSpec={spec} endpointsConfig={endpointsConfig} />
- </div>
- )}
+ <div className="flex w-full min-w-0 gap-2 px-1 py-1">
<div className="flex min-w-0 flex-col gap-1">
<span className="truncate text-left">{spec.label}</span>
{spec.description && (
@@ -74,7 +60,7 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
</div>
</div>
{selectedSpec === spec.name && (
- <div className={cn('flex-shrink-0', spec.description ? 'pt-1' : '')}>
+ <div className="flex-shrink-0">
<svg
width="16"
height="16"
@@ -115,6 +101,18 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
endpoint.assistantNames[model.name]
) {
modelName = endpoint.assistantNames[model.name];
+ } else {
+ // Check for modelDisplayNames mapping from configuration
+ const endpointConfig = endpointsConfigData?.[endpoint.value];
+ const modelDisplayNames = (endpointConfig as any)?.modelDisplayNames;
+ console.log('SearchResults (1) - endpoint:', endpoint.value);
+ console.log('SearchResults (1) - modelId:', model.name);
+ console.log('SearchResults (1) - endpointConfig:', endpointConfig);
+ console.log('SearchResults (1) - modelDisplayNames:', modelDisplayNames);
+ if (modelDisplayNames && modelDisplayNames[model.name]) {
+ modelName = modelDisplayNames[model.name];
+ console.log('SearchResults (1) - Using custom name:', modelName);
+ }
}
return modelName.toLowerCase().includes(lowerQuery);
});
@@ -152,6 +150,18 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
endpoint.assistantNames[modelId]
) {
modelName = endpoint.assistantNames[modelId];
+ } else {
+ // Check for modelDisplayNames mapping from configuration
+ const endpointConfig = endpointsConfigData?.[endpoint.value];
+ const modelDisplayNames = (endpointConfig as any)?.modelDisplayNames;
+ console.log('SearchResults (2) - endpoint:', endpoint.value);
+ console.log('SearchResults (2) - modelId:', modelId);
+ console.log('SearchResults (2) - endpointConfig:', endpointConfig);
+ console.log('SearchResults (2) - modelDisplayNames:', modelDisplayNames);
+ if (modelDisplayNames && modelDisplayNames[modelId]) {
+ modelName = modelDisplayNames[modelId];
+ console.log('SearchResults (2) - Using custom name:', modelName);
+ }
}
return (
diff --git a/client/src/components/Chat/Menus/Endpoints/utils.ts b/client/src/components/Chat/Menus/Endpoints/utils.ts
index 5ed155c6..fcd6086f 100644
--- a/client/src/components/Chat/Menus/Endpoints/utils.ts
+++ b/client/src/components/Chat/Menus/Endpoints/utils.ts
@@ -73,6 +73,7 @@ export function filterModels(
searchValue: string,
agentsMap: TAgentsMap | undefined,
assistantsMap: TAssistantsMap | undefined,
+ endpointsConfig?: TEndpointsConfig,
): string[] {
const searchTermLower = searchValue.trim().toLowerCase();
if (!searchTermLower) {
@@ -92,6 +93,12 @@ export function filterModels(
const assistant = assistantsMap[endpoint.value][modelId];
modelName =
typeof assistant.name === 'string' && assistant.name ? (assistant.name as string) : modelId;
+ } else {
+ // Check for modelDisplayNames mapping from configuration
+ const modelDisplayNames = (endpointsConfig?.[endpoint.value] as any)?.modelDisplayNames;
+ if (modelDisplayNames && modelDisplayNames[modelId]) {
+ modelName = modelDisplayNames[modelId];
+ }
}
return modelName.toLowerCase().includes(searchTermLower);
@@ -167,11 +174,13 @@ export const getDisplayValue = ({
mappedEndpoints,
selectedValues,
modelSpecs,
+ endpointsConfig,
}: {
localize: ReturnType<typeof useLocalize>;
selectedValues: SelectedValues;
mappedEndpoints: Endpoint[];
modelSpecs: TModelSpec[];
+ endpointsConfig?: TEndpointsConfig;
}) => {
if (selectedValues.modelSpec) {
const spec = modelSpecs.find((s) => s.name === selectedValues.modelSpec);
@@ -200,6 +209,12 @@ export const getDisplayValue = ({
return endpoint.assistantNames[selectedValues.model];
}
+ // Check for modelDisplayNames mapping from configuration
+ const modelDisplayNames = (endpointsConfig?.[selectedValues.endpoint] as any)?.modelDisplayNames;
+ if (modelDisplayNames && modelDisplayNames[selectedValues.model]) {
+ return modelDisplayNames[selectedValues.model];
+ }
+
return selectedValues.model;
}
diff --git a/client/src/hooks/Conversations/useGetSender.ts b/client/src/hooks/Conversations/useGetSender.ts
index cced4ca4..eb5fc5f0 100644
--- a/client/src/hooks/Conversations/useGetSender.ts
+++ b/client/src/hooks/Conversations/useGetSender.ts
@@ -7,7 +7,14 @@ export default function useGetSender() {
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
return useCallback(
(endpointOption: TEndpointOption) => {
- const { modelDisplayLabel } = endpointsConfig?.[endpointOption.endpoint ?? ''] ?? {};
+ const endpointConfig = endpointsConfig?.[endpointOption.endpoint ?? ''];
+ const { modelDisplayLabel, modelDisplayNames } = endpointConfig ?? {};
+
+ // Check if we have a custom model name mapping
+ if (modelDisplayNames && endpointOption.model && modelDisplayNames[endpointOption.model]) {
+ return modelDisplayNames[endpointOption.model];
+ }
+
return getResponseSender({ ...endpointOption, modelDisplayLabel });
},
[endpointsConfig],
diff --git a/client/src/utils/presets.ts b/client/src/utils/presets.ts
index cf693384..237c2d08 100644
--- a/client/src/utils/presets.ts
+++ b/client/src/utils/presets.ts
@@ -1,19 +1,26 @@
-import type { TPreset, TPlugin } from 'librechat-data-provider';
+import type { TPreset, TPlugin, TEndpointsConfig } from 'librechat-data-provider';
import { EModelEndpoint } from 'librechat-data-provider';
-type TEndpoints = Array<string | EModelEndpoint>;
-
-export const getPresetTitle = (preset: TPreset, mention?: boolean) => {
+export const getPresetTitle = (preset: TPreset, mention?: boolean, endpointsConfig?: TEndpointsConfig) => {
const {
endpoint,
title: presetTitle,
model,
tools,
promptPrefix,
- chatGptLabel,
modelLabel,
} = preset;
- const modelInfo = model ?? '';
+
+ // Get the custom model name if available
+ let modelInfo = model ?? '';
+ if (endpointsConfig && endpoint && model) {
+ const endpointConfig = endpointsConfig[endpoint];
+ const modelDisplayNames = (endpointConfig as any)?.modelDisplayNames;
+ if (modelDisplayNames && modelDisplayNames[model]) {
+ modelInfo = modelDisplayNames[model];
+ }
+ }
+
let title = '';
let label = '';
diff --git a/client/vite.config.ts b/client/vite.config.ts
index 152cf425..1db8e726 100644
--- a/client/vite.config.ts
+++ b/client/vite.config.ts
@@ -14,11 +14,11 @@ export default defineConfig(({ command }) => ({
strictPort: false,
proxy: {
'/api': {
- target: 'http://localhost:3080',
+ target: 'http://localhost:3001',
changeOrigin: true,
},
'/oauth': {
- target: 'http://localhost:3080',
+ target: 'http://localhost:3001',
changeOrigin: true,
},
},
diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts
index 89a3aa13..f5023fd6 100644
--- a/packages/data-provider/src/config.ts
+++ b/packages/data-provider/src/config.ts
@@ -287,6 +287,7 @@ export const endpointSchema = baseEndpointSchema.merge(
summaryModel: z.string().optional(),
forcePrompt: z.boolean().optional(),
modelDisplayLabel: z.string().optional(),
+ modelDisplayNames: z.record(z.string()).optional(),
headers: z.record(z.any()).optional(),
addParams: z.record(z.any()).optional(),
dropParams: z.array(z.string()).optional(),
diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts
index 5799ca50..6225a5f8 100644
--- a/packages/data-provider/src/types.ts
+++ b/packages/data-provider/src/types.ts
@@ -328,6 +328,7 @@ export type TConfig = {
iconURL?: string;
version?: string;
modelDisplayLabel?: string;
+ modelDisplayNames?: Record<string, string>;
userProvide?: boolean | null;
userProvideURL?: boolean | null;
disableBuilder?: boolean;
--
2.39.5 (Apple Git-154)
|
Beta Was this translation helpful? Give feedback.
-
Are you interested in a pull request for adding model names override? I have found more places where it needs to be overridden. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
What happened?
Reranker is not inherently needed. Why is it insisting on me entering a key? I just want to disable reranker but none of the yaml options do so.
The modal for saving the keys in the UI also flickers another modal for one frame then disappears when you hit save.
Version Information
Latest git pulled today
Steps to Reproduce
What browsers are you seeing the problem on?
No response
Relevant log output
Screenshots
Code of Conduct
Beta Was this translation helpful? Give feedback.
All reactions