🔍 feat: Add Google Search Grounding Toggle (#8174)

*  feat: Add Google Search Grounding Feature and Update Agent Tool Initialization

- Introduced a new grounding option in the Google configuration to enable real-time web search results.
- Updated the agent initialization to concatenate additional tools from options.
- Enhanced translation files to include descriptions for the new grounding feature.
- Modified relevant schemas and parameter settings to support the grounding functionality.

* 🔑 chore: Update @librechat/agents dependency to version 2.4.50

*  fix: Ensure tools array is initialized before concatenation in initializeAgent function

* chore: Update version of librechat-data-provider to 0.7.899 and add GOOGLE_TOOL_CONFLICT error type

* fix: Adjust label class for better text wrapping in DynamicSwitch component

* fix: Handle Google tool conflict error and update error messages in translation

* fix: Restore grounding setting in googleCol2 configuration

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Dustin Healy 2025-07-01 15:00:18 -07:00 committed by GitHub
parent 8a5dbac0f9
commit 738d04fac4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 59 additions and 11 deletions

View file

@ -48,7 +48,7 @@
"@langchain/google-genai": "^0.2.13", "@langchain/google-genai": "^0.2.13",
"@langchain/google-vertexai": "^0.2.13", "@langchain/google-vertexai": "^0.2.13",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.4.49", "@librechat/agents": "^2.4.50",
"@librechat/api": "*", "@librechat/api": "*",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@node-saml/passport-saml": "^5.0.0", "@node-saml/passport-saml": "^5.0.0",

View file

@ -140,6 +140,14 @@ const initializeAgent = async ({
agent.provider = options.provider; agent.provider = options.provider;
} }
if (
(agent.provider === Providers.GOOGLE || agent.provider === Providers.VERTEXAI) &&
options?.tools?.length &&
tools?.length
) {
throw new Error(`{ "type": "${ErrorTypes.GOOGLE_TOOL_CONFLICT}"}`);
}
/** @type {import('@librechat/agents').ClientOptions} */ /** @type {import('@librechat/agents').ClientOptions} */
agent.model_parameters = { ...options.llmConfig }; agent.model_parameters = { ...options.llmConfig };
if (options.configOptions) { if (options.configOptions) {
@ -162,10 +170,10 @@ const initializeAgent = async ({
return { return {
...agent, ...agent,
tools,
attachments, attachments,
resendFiles, resendFiles,
toolContextMap, toolContextMap,
tools: options.tools ?? tools,
maxContextTokens: (agentMaxContextTokens - maxTokens) * 0.9, maxContextTokens: (agentMaxContextTokens - maxTokens) * 0.9,
}; };
}; };

View file

@ -62,6 +62,7 @@ const errorMessages = {
const { info } = json; const { info } = json;
return info; return info;
}, },
[ErrorTypes.GOOGLE_TOOL_CONFLICT]: 'com_error_google_tool_conflict',
[ViolationTypes.BAN]: [ViolationTypes.BAN]:
'Your account has been temporarily banned due to violations of our service.', 'Your account has been temporarily banned due to violations of our service.',
invalid_api_key: invalid_api_key:

View file

@ -50,7 +50,7 @@ function DynamicSwitch({
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label
htmlFor={`${settingKey}-dynamic-switch`} htmlFor={`${settingKey}-dynamic-switch`}
className="text-left text-sm font-medium" className="break-words text-left text-sm font-medium"
> >
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '} {labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
{showDefault && ( {showDefault && (

View file

@ -233,6 +233,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_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_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_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_google_use_search_grounding": "Use Google's search grounding feature to enhance responses with real-time web search results. This enables models to access current information and provide more accurate, up-to-date answers.",
"com_endpoint_output": "Output", "com_endpoint_output": "Output",
"com_endpoint_plug_image_detail": "Image Detail", "com_endpoint_plug_image_detail": "Image Detail",
"com_endpoint_plug_resend_files": "Resend Files", "com_endpoint_plug_resend_files": "Resend Files",
@ -280,6 +281,7 @@
"com_endpoint_top_p": "Top P", "com_endpoint_top_p": "Top P",
"com_endpoint_use_active_assistant": "Use Active Assistant", "com_endpoint_use_active_assistant": "Use Active Assistant",
"com_endpoint_use_responses_api": "Use Responses API", "com_endpoint_use_responses_api": "Use Responses API",
"com_endpoint_use_search_grounding": "Grounding with Google Search",
"com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.", "com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.",
"com_error_files_dupe": "Duplicate file detected.", "com_error_files_dupe": "Duplicate file detected.",
"com_error_files_empty": "Empty files are not allowed.", "com_error_files_empty": "Empty files are not allowed.",
@ -291,6 +293,7 @@
"com_error_heic_conversion": "Failed to convert HEIC image to JPEG. Please try converting the image manually or use a different format.", "com_error_heic_conversion": "Failed to convert HEIC image to JPEG. Please try converting the image manually or use a different format.",
"com_error_input_length": "The latest message token count is too long, exceeding the token limit, or your token limit parameters are misconfigured, adversely affecting the context window. More info: {{0}}. Please shorten your message, adjust the max context size from the conversation parameters, or fork the conversation to continue.", "com_error_input_length": "The latest message token count is too long, exceeding the token limit, or your token limit parameters are misconfigured, adversely affecting the context window. More info: {{0}}. Please shorten your message, adjust the max context size from the conversation parameters, or fork the conversation to continue.",
"com_error_invalid_agent_provider": "The \"{{0}}\" provider is not available for use with Agents. Please go to your agent's settings and select a currently available provider.", "com_error_invalid_agent_provider": "The \"{{0}}\" provider is not available for use with Agents. Please go to your agent's settings and select a currently available provider.",
"com_error_google_tool_conflict": "Usage of built-in Google tools are not supported with external tools. Please disable either the built-in tools or the external tools.",
"com_error_invalid_user_key": "Invalid key provided. Please provide a valid key and try again.", "com_error_invalid_user_key": "Invalid key provided. Please provide a valid key and try again.",
"com_error_moderation": "It appears that the content submitted has been flagged by our moderation system for not aligning with our community guidelines. We're unable to proceed with this specific topic. If you have any other questions or topics you'd like to explore, please edit your message, or create a new conversation.", "com_error_moderation": "It appears that the content submitted has been flagged by our moderation system for not aligning with our community guidelines. We're unable to proceed with this specific topic. If you have any other questions or topics you'd like to explore, please edit your message, or create a new conversation.",
"com_error_no_base_url": "No base URL found. Please provide one and try again.", "com_error_no_base_url": "No base URL found. Please provide one and try again.",

12
package-lock.json generated
View file

@ -64,7 +64,7 @@
"@langchain/google-genai": "^0.2.13", "@langchain/google-genai": "^0.2.13",
"@langchain/google-vertexai": "^0.2.13", "@langchain/google-vertexai": "^0.2.13",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.4.49", "@librechat/agents": "^2.4.50",
"@librechat/api": "*", "@librechat/api": "*",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@node-saml/passport-saml": "^5.0.0", "@node-saml/passport-saml": "^5.0.0",
@ -19436,9 +19436,9 @@
} }
}, },
"node_modules/@librechat/agents": { "node_modules/@librechat/agents": {
"version": "2.4.49", "version": "2.4.50",
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.49.tgz", "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.50.tgz",
"integrity": "sha512-Bnp/PZsg1VgnmGS80tW4ssKpcqUZ7xysKesV/8gGaUBF1VDBiYBh0gC6ugfJhltNOv93rEVSucjPlTAuHimNCg==", "integrity": "sha512-8yUndPTa5ctxGBqlzMcyBDi+c6lup37wtXXFJMyBcm2Bx4MhqrEOMdI3HRu/3CsYpRgC07wAK2AwZ593aGLWoA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@langchain/anthropic": "^0.3.23", "@langchain/anthropic": "^0.3.23",
@ -46624,7 +46624,7 @@
"typescript": "^5.0.4" "typescript": "^5.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"@librechat/agents": "^2.4.49", "@librechat/agents": "^2.4.50",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.12.3", "@modelcontextprotocol/sdk": "^1.12.3",
"axios": "^1.8.2", "axios": "^1.8.2",
@ -46717,7 +46717,7 @@
}, },
"packages/data-provider": { "packages/data-provider": {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.7.89", "version": "0.7.899",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.8.2", "axios": "^1.8.2",

View file

@ -69,7 +69,7 @@
"registry": "https://registry.npmjs.org/" "registry": "https://registry.npmjs.org/"
}, },
"peerDependencies": { "peerDependencies": {
"@librechat/agents": "^2.4.49", "@librechat/agents": "^2.4.50",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.12.3", "@modelcontextprotocol/sdk": "^1.12.3",
"axios": "^1.8.2", "axios": "^1.8.2",

View file

@ -1,6 +1,7 @@
import { Providers } from '@librechat/agents'; import { Providers } from '@librechat/agents';
import { googleSettings, AuthKeys } from 'librechat-data-provider'; import { googleSettings, AuthKeys } from 'librechat-data-provider';
import type { GoogleClientOptions, VertexAIClientOptions } from '@librechat/agents'; import type { GoogleClientOptions, VertexAIClientOptions } from '@librechat/agents';
import type { GoogleAIToolType } from '@langchain/google-common';
import type * as t from '~/types'; import type * as t from '~/types';
import { isEnabled } from '~/utils'; import { isEnabled } from '~/utils';
@ -105,6 +106,7 @@ export function getGoogleConfig(
const authHeader = options.authHeader; const authHeader = options.authHeader;
const { const {
grounding,
thinking = googleSettings.thinking.default, thinking = googleSettings.thinking.default,
thinkingBudget = googleSettings.thinkingBudget.default, thinkingBudget = googleSettings.thinkingBudget.default,
...modelOptions ...modelOptions
@ -187,8 +189,16 @@ export function getGoogleConfig(
}; };
} }
const tools: GoogleAIToolType[] = [];
if (grounding) {
tools.push({ googleSearch: {} });
}
// Return the final shape // Return the final shape
return { return {
/** @type {GoogleAIToolType[]} */
tools,
/** @type {Providers.GOOGLE | Providers.VERTEXAI} */ /** @type {Providers.GOOGLE | Providers.VERTEXAI} */
provider, provider,
/** @type {GoogleClientOptions | VertexAIClientOptions} */ /** @type {GoogleClientOptions | VertexAIClientOptions} */

View file

@ -1,6 +1,6 @@
{ {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.7.89", "version": "0.7.899",
"description": "data services for librechat apps", "description": "data services for librechat apps",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",

View file

@ -1257,6 +1257,10 @@ export enum ErrorTypes {
* Google provider returned an error * Google provider returned an error
*/ */
GOOGLE_ERROR = 'google_error', GOOGLE_ERROR = 'google_error',
/**
* Google provider does not allow custom tools with built-in tools
*/
GOOGLE_TOOL_CONFLICT = 'google_tool_conflict',
/** /**
* Invalid Agent Provider (excluded by Admin) * Invalid Agent Provider (excluded by Admin)
*/ */

View file

@ -537,6 +537,19 @@ const google: Record<string, SettingDefinition> = {
optionType: 'conversation', optionType: 'conversation',
columnSpan: 2, columnSpan: 2,
}, },
grounding: {
key: 'grounding',
label: 'com_endpoint_use_search_grounding',
labelCode: true,
description: 'com_endpoint_google_use_search_grounding',
descriptionCode: true,
type: 'boolean',
default: false,
component: 'switch',
optionType: 'model',
showDefault: false,
columnSpan: 2,
},
}; };
const googleConfig: SettingsConfiguration = [ const googleConfig: SettingsConfiguration = [
@ -550,6 +563,7 @@ const googleConfig: SettingsConfiguration = [
librechat.resendFiles, librechat.resendFiles,
google.thinking, google.thinking,
google.thinkingBudget, google.thinkingBudget,
google.grounding,
]; ];
const googleCol1: SettingsConfiguration = [ const googleCol1: SettingsConfiguration = [
@ -567,6 +581,7 @@ const googleCol2: SettingsConfiguration = [
librechat.resendFiles, librechat.resendFiles,
google.thinking, google.thinking,
google.thinkingBudget, google.thinkingBudget,
google.grounding,
]; ];
const openAI: SettingsConfiguration = [ const openAI: SettingsConfiguration = [

View file

@ -634,6 +634,8 @@ export const tConversationSchema = z.object({
reasoning_summary: eReasoningSummarySchema.optional().nullable(), reasoning_summary: eReasoningSummarySchema.optional().nullable(),
/* OpenAI: use Responses API */ /* OpenAI: use Responses API */
useResponsesApi: z.boolean().optional(), useResponsesApi: z.boolean().optional(),
/* Google: use Search Grounding */
grounding: z.boolean().optional(),
/* assistant */ /* assistant */
assistant_id: z.string().optional(), assistant_id: z.string().optional(),
/* agents */ /* agents */
@ -736,6 +738,8 @@ export const tQueryParamsSchema = tConversationSchema
reasoning_summary: true, reasoning_summary: true,
/** @endpoints openAI, custom, azureOpenAI */ /** @endpoints openAI, custom, azureOpenAI */
useResponsesApi: true, useResponsesApi: true,
/** @endpoints google */
grounding: true,
/** @endpoints google, anthropic, bedrock */ /** @endpoints google, anthropic, bedrock */
topP: true, topP: true,
/** @endpoints google, anthropic */ /** @endpoints google, anthropic */
@ -818,6 +822,7 @@ export const googleBaseSchema = tConversationSchema.pick({
topK: true, topK: true,
thinking: true, thinking: true,
thinkingBudget: true, thinkingBudget: true,
grounding: true,
iconURL: true, iconURL: true,
greeting: true, greeting: true,
spec: true, spec: true,
@ -849,6 +854,7 @@ export const googleGenConfigSchema = z
thinkingBudget: coerceNumber.optional(), thinkingBudget: coerceNumber.optional(),
}) })
.optional(), .optional(),
grounding: z.boolean().optional(),
}) })
.strip() .strip()
.optional(); .optional();

View file

@ -47,6 +47,7 @@ export interface IConversation extends Document {
reasoning_effort?: string; reasoning_effort?: string;
reasoning_summary?: string; reasoning_summary?: string;
useResponsesApi?: boolean; useResponsesApi?: boolean;
grounding?: boolean;
// Additional fields // Additional fields
files?: string[]; files?: string[];
expiredAt?: Date; expiredAt?: Date;