diff --git a/api/server/services/Endpoints/agents/agent.js b/api/server/services/Endpoints/agents/agent.js index 469cacd0e4..b262847846 100644 --- a/api/server/services/Endpoints/agents/agent.js +++ b/api/server/services/Endpoints/agents/agent.js @@ -85,7 +85,7 @@ const initializeAgent = async ({ }); const provider = agent.provider; - const { tools, toolContextMap } = + const { tools: structuredTools, toolContextMap } = (await loadTools?.({ req, res, @@ -140,12 +140,20 @@ const initializeAgent = async ({ agent.provider = options.provider; } + /** @type {import('@librechat/agents').GenericTool[]} */ + let tools = options.tools ?? structuredTools; if ( (agent.provider === Providers.GOOGLE || agent.provider === Providers.VERTEXAI) && - options?.tools?.length && - tools?.length + options.tools?.length && + structuredTools?.length ) { throw new Error(`{ "type": "${ErrorTypes.GOOGLE_TOOL_CONFLICT}"}`); + } else if ( + (agent.provider === Providers.OPENAI || agent.provider === Providers.AZURE) && + options.tools?.length && + structuredTools?.length + ) { + tools = structuredTools.concat(options.tools); } /** @type {import('@librechat/agents').ClientOptions} */ @@ -173,7 +181,7 @@ const initializeAgent = async ({ attachments, resendFiles, toolContextMap, - tools: options.tools ?? tools, + tools, maxContextTokens: (agentMaxContextTokens - maxTokens) * 0.9, }; }; diff --git a/client/src/components/SidePanel/Parameters/DynamicSlider.tsx b/client/src/components/SidePanel/Parameters/DynamicSlider.tsx index d1b9bd9678..9651110842 100644 --- a/client/src/components/SidePanel/Parameters/DynamicSlider.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicSlider.tsx @@ -197,7 +197,7 @@ function DynamicSlider({ defaultTextProps, cn( optionText, - 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 py-1 text-xs group-hover/temp:border-gray-200', + 'reset-rc-number-input h-auto w-14 border-0 py-1 pl-1 text-center text-xs group-hover/temp:border-gray-200', ), )} /> diff --git a/client/src/hooks/Conversations/useSetIndexOptions.ts b/client/src/hooks/Conversations/useSetIndexOptions.ts index 8e3f3e6363..59bcad5572 100644 --- a/client/src/hooks/Conversations/useSetIndexOptions.ts +++ b/client/src/hooks/Conversations/useSetIndexOptions.ts @@ -30,6 +30,14 @@ const useSetIndexOptions: TUseSetOptions = (preset = false) => { }; } + // Auto-enable Responses API when web search is enabled + if (param === 'web_search' && newValue === true) { + const currentUseResponsesApi = conversation?.useResponsesApi ?? false; + if (!currentUseResponsesApi) { + update['useResponsesApi'] = true; + } + } + setConversation( (prevState) => tConvoUpdateSchema.parse({ diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index d85b1c5f8d..b751bde05c 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -234,6 +234,7 @@ "com_endpoint_openai_temp": "Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.", "com_endpoint_openai_topp": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.", "com_endpoint_openai_use_responses_api": "Use the Responses API instead of Chat Completions, which includes extended features from OpenAI. Required for o1-pro, o3-pro, and to enable reasoning summaries.", + "com_endpoint_openai_use_web_search": "Enable web search functionality using OpenAI's built-in search capabilities. This allows the model to search the web for up-to-date information and provide more accurate, current responses.", "com_endpoint_output": "Output", "com_endpoint_plug_image_detail": "Image Detail", "com_endpoint_plug_resend_files": "Resend Files", diff --git a/packages/api/src/endpoints/openai/llm.ts b/packages/api/src/endpoints/openai/llm.ts index e5251bb83e..2d6ac4a226 100644 --- a/packages/api/src/endpoints/openai/llm.ts +++ b/packages/api/src/endpoints/openai/llm.ts @@ -1,5 +1,6 @@ import { ProxyAgent } from 'undici'; import { KnownEndpoints, removeNullishValues } from 'librechat-data-provider'; +import type { BindToolsInput } from '@langchain/core/language_models/chat_models'; import type { AzureOpenAIInput } from '@langchain/openai'; import type { OpenAI } from 'openai'; import type * as t from '~/types'; @@ -176,6 +177,13 @@ export function getOpenAIConfig( delete llmConfig.max_tokens; } + const tools: BindToolsInput[] = []; + + if (modelOptions.web_search) { + llmConfig.useResponsesApi = true; + tools.push({ type: 'web_search_preview' }); + } + /** * Note: OpenAI Web Search models do not support any known parameters besides `max_tokens` */ @@ -216,5 +224,6 @@ export function getOpenAIConfig( return { llmConfig, configOptions, + tools, }; } diff --git a/packages/api/src/types/openai.ts b/packages/api/src/types/openai.ts index 5f609a641a..b9886fa51b 100644 --- a/packages/api/src/types/openai.ts +++ b/packages/api/src/types/openai.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { openAISchema, EModelEndpoint } from 'librechat-data-provider'; import type { TEndpointOption, TAzureConfig, TEndpoint } from 'librechat-data-provider'; +import type { BindToolsInput } from '@langchain/core/language_models/chat_models'; import type { OpenAIClientOptions } from '@librechat/agents'; import type { AzureOptions } from './azure'; @@ -33,6 +34,7 @@ export type ClientOptions = OpenAIClientOptions & { export interface LLMConfigResult { llmConfig: ClientOptions; configOptions: OpenAIConfiguration; + tools?: BindToolsInput[]; } /** diff --git a/packages/data-provider/src/parameterSettings.ts b/packages/data-provider/src/parameterSettings.ts index 1b28e9cc3c..393533466e 100644 --- a/packages/data-provider/src/parameterSettings.ts +++ b/packages/data-provider/src/parameterSettings.ts @@ -247,6 +247,19 @@ const openAIParams: Record = { showDefault: false, columnSpan: 2, }, + web_search: { + key: 'web_search', + label: 'com_ui_web_search', + labelCode: true, + description: 'com_endpoint_openai_use_web_search', + descriptionCode: true, + type: 'boolean', + default: false, + component: 'switch', + optionType: 'model', + showDefault: false, + columnSpan: 2, + }, reasoning_summary: { key: 'reasoning_summary', label: 'com_endpoint_reasoning_summary', @@ -596,6 +609,7 @@ const openAI: SettingsConfiguration = [ baseDefinitions.stop, librechat.resendFiles, baseDefinitions.imageDetail, + openAIParams.web_search, openAIParams.reasoning_effort, openAIParams.useResponsesApi, openAIParams.reasoning_summary, @@ -618,8 +632,9 @@ const openAICol2: SettingsConfiguration = [ librechat.resendFiles, baseDefinitions.imageDetail, openAIParams.reasoning_effort, - openAIParams.useResponsesApi, openAIParams.reasoning_summary, + openAIParams.useResponsesApi, + openAIParams.web_search, ]; const anthropicConfig: SettingsConfiguration = [ diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index de51c35bf2..36ea3bd100 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -634,6 +634,8 @@ export const tConversationSchema = z.object({ reasoning_summary: eReasoningSummarySchema.optional().nullable(), /* OpenAI: use Responses API */ useResponsesApi: z.boolean().optional(), + /* OpenAI: use Responses API with Web Search */ + web_search: z.boolean().optional(), /* Google: use Search Grounding */ grounding: z.boolean().optional(), /* assistant */ @@ -1071,6 +1073,7 @@ export const openAIBaseSchema = tConversationSchema.pick({ reasoning_effort: true, reasoning_summary: true, useResponsesApi: true, + web_search: true, }); export const openAISchema = openAIBaseSchema diff --git a/packages/data-schemas/src/types/convo.ts b/packages/data-schemas/src/types/convo.ts index 1d9ba8749e..177c140fb9 100644 --- a/packages/data-schemas/src/types/convo.ts +++ b/packages/data-schemas/src/types/convo.ts @@ -47,6 +47,7 @@ export interface IConversation extends Document { reasoning_effort?: string; reasoning_summary?: string; useResponsesApi?: boolean; + web_search?: boolean; grounding?: boolean; // Additional fields files?: string[];