fix(core): Web search misconfigs in agents as UserError (#41257)

This commit is contained in:
Anne Aguirre 2026-10-16 12:00:35 +00:00 committed by GitHub
parent 333a010112
commit f84413516a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 31 deletions

View File

@ -8,6 +8,7 @@ import {
type AgentJsonConfig,
} from '@n8n/api-types';
import type { JSONSchema7 } from 'json-schema';
import { UserError } from 'n8n-workflow';
import { buildFromJson, buildProviderToolsForModel } from '../json-config/from-json-config';
import type { ToolExecutor } from '../json-config/from-json-config';
@ -826,21 +827,24 @@ describe('buildFromJson()', () => {
});
it('rejects native web search config for unsupported providers without fallback settings', async () => {
await expect(
buildFromJson(
makeConfig({
model: 'google/gemini-2.5-flash',
config: { webSearch: { enabled: true } },
providerTools: { 'anthropic.web_search': { maxUses: 5 } },
}),
{},
{
toolExecutor: makeMockToolExecutor(),
credentialProvider: makeMockCredentialProvider(),
memoryFactory: makeMockMemoryFactory(),
},
),
).rejects.toThrow('Web search is enabled but no fallback search provider is configured.');
const rejected = buildFromJson(
makeConfig({
model: 'google/gemini-2.5-flash',
config: { webSearch: { enabled: true } },
providerTools: { 'anthropic.web_search': { maxUses: 5 } },
}),
{},
{
toolExecutor: makeMockToolExecutor(),
credentialProvider: makeMockCredentialProvider(),
memoryFactory: makeMockMemoryFactory(),
},
);
await expect(rejected).rejects.toThrow(UserError);
await expect(rejected).rejects.toThrow(
'Web search is enabled but no fallback search provider is configured.',
);
});
it('does not enable native web search when config is sparse', async () => {
@ -1008,20 +1012,23 @@ describe('buildFromJson()', () => {
});
it('requires fallback web search credentials for providers without native web search', async () => {
await expect(
buildFromJson(
makeConfig({
model: 'deepseek/deepseek-chat',
config: { webSearch: { enabled: true, provider: 'brave' } },
}),
{},
{
toolExecutor: makeMockToolExecutor(),
credentialProvider: makeMockCredentialProvider(),
memoryFactory: makeMockMemoryFactory(),
},
),
).rejects.toThrow('Web search is enabled but no search credential is configured.');
const rejected = buildFromJson(
makeConfig({
model: 'deepseek/deepseek-chat',
config: { webSearch: { enabled: true, provider: 'brave' } },
}),
{},
{
toolExecutor: makeMockToolExecutor(),
credentialProvider: makeMockCredentialProvider(),
memoryFactory: makeMockMemoryFactory(),
},
);
await expect(rejected).rejects.toThrow(UserError);
await expect(rejected).rejects.toThrow(
'Web search is enabled but no search credential is configured.',
);
});
it('adds fallback web search tool for the n8n Connect managed credential (brave)', async () => {

View File

@ -326,10 +326,10 @@ function buildFallbackWebSearchTool(
};
}
if (webSearchConfig.provider !== 'brave' && webSearchConfig.provider !== 'searxng') {
throw new Error('Web search is enabled but no fallback search provider is configured.');
throw new UserError('Web search is enabled but no fallback search provider is configured.');
}
if (!webSearchConfig.credential) {
throw new Error('Web search is enabled but no search credential is configured.');
throw new UserError('Web search is enabled but no search credential is configured.');
}
const credentialId = webSearchConfig.credential;