mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix(core): Keep n8n-docs always loaded so n8n questions do not route to web search (#35537)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
daba594654
commit
e73f0a4654
|
|
@ -66,6 +66,7 @@ vi.mock('../../tools', () => ({
|
|||
['workflows', mockBuiltTool(`workflows-${context.runLabel ?? 'unknown'}`)],
|
||||
['evals', mockBuiltTool(`evals-${context.runLabel ?? 'unknown'}`)],
|
||||
['research', mockBuiltTool(`research-${context.runLabel ?? 'unknown'}`)],
|
||||
['n8n-docs', mockBuiltTool(`n8n-docs-${context.runLabel ?? 'unknown'}`)],
|
||||
['nodes', mockBuiltTool(`nodes-${context.runLabel ?? 'unknown'}`)],
|
||||
['executions', mockBuiltTool(`executions-${context.runLabel ?? 'unknown'}`)],
|
||||
['build-workflow', mockBuiltTool(`build-workflow-${context.runLabel ?? 'unknown'}`)],
|
||||
|
|
@ -290,6 +291,39 @@ describe('createInstanceAgent', () => {
|
|||
}
|
||||
});
|
||||
|
||||
// INS-749: `research` (web-search) is always loaded while `n8n-docs` used to be
|
||||
// deferred, so answering an n8n question from n8n's own docs cost a search_tools +
|
||||
// load_tool round trip that web search did not. That price gap pushed the agent to
|
||||
// web-search things the docs already answer.
|
||||
it('keeps n8n-docs always loaded so it is no costlier to reach than web search', async () => {
|
||||
await createInstanceAgent({
|
||||
modelId: 'test-model',
|
||||
context: {
|
||||
runLabel: 'docs-parity-run',
|
||||
localGatewayStatus: undefined,
|
||||
licenseHints: undefined,
|
||||
localMcpServer: undefined,
|
||||
},
|
||||
orchestrationContext: {
|
||||
runId: 'docs-parity-run',
|
||||
},
|
||||
memoryConfig: {},
|
||||
mcpManager: createMcpManagerStub(),
|
||||
} as never);
|
||||
|
||||
const attachedTools = getAttachedTools();
|
||||
const deferredTools = getDeferredTools();
|
||||
|
||||
expect(attachedTools['n8n-docs-docs-parity-run']).toMatchObject({
|
||||
name: 'n8n-docs-docs-parity-run',
|
||||
});
|
||||
expect(deferredTools['n8n-docs-docs-parity-run']).toBeUndefined();
|
||||
// Parity is the point: both routes must be one call away.
|
||||
expect(attachedTools['research-docs-parity-run']).toMatchObject({
|
||||
name: 'research-docs-parity-run',
|
||||
});
|
||||
});
|
||||
|
||||
it('attaches the orchestration workspace when provided', async () => {
|
||||
const memoryConfig = {} as never;
|
||||
const fakeWorkspace = { id: 'thread-runtime-workspace' } as never;
|
||||
|
|
|
|||
|
|
@ -167,6 +167,29 @@ describe('getSystemPrompt — browser/computer-use discoverability', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// INS-749: n8n-docs is always loaded now, so telling the orchestrator to
|
||||
// discover it via search_tools is both wrong and a nudge away from the tool
|
||||
// it should reach for first on n8n questions.
|
||||
describe('n8n-docs is presented as already available, not as something to discover', () => {
|
||||
// The Tool Discovery section only renders with tool search on, which is where
|
||||
// the stale "search for n8n docs" example lived.
|
||||
const toolSearchOptions = { ...browserCapableOptions, toolSearchEnabled: true };
|
||||
|
||||
it('does not offer n8n docs as a search_tools discovery example', () => {
|
||||
const prompt = getSystemPrompt(toolSearchOptions);
|
||||
|
||||
expect(prompt).toContain('## Tool Discovery');
|
||||
expect(prompt).not.toMatch(/search "n8n docs"/i);
|
||||
});
|
||||
|
||||
it('tells the orchestrator to answer n8n questions from n8n-docs rather than web search', () => {
|
||||
const prompt = getSystemPrompt(toolSearchOptions);
|
||||
|
||||
expect(prompt).toMatch(/prefer[^.]{0,60}n8n-docs/i);
|
||||
expect(prompt).toMatch(/n8n-docs[^.]{0,120}already (loaded|available)/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('browser availability state propagates to the prompt', () => {
|
||||
it('includes browser automation rules when browser is available', () => {
|
||||
const prompt = getSystemPrompt({
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ function getToolDiscoverySection(
|
|||
|
||||
${mcpSearchGuidance}When the available tools do not cover the user's request, remember that you have access to more tools. Use \`search_tools\` with keyword queries to find relevant tools, then \`load_tool\` to activate them. Loaded tools persist for the rest of the conversation. When a loaded skill names a tool you do not see, search for that tool name and load it before proceeding.
|
||||
|
||||
Examples: ${mcpExamples}search "n8n docs" for \`n8n-docs\`, search "create tasks" for \`create-tasks\`, search "eval" for \`evals\`.
|
||||
Examples: ${mcpExamples}search "create tasks" for \`create-tasks\`, search "eval" for \`evals\`.
|
||||
|
||||
For questions about n8n itself — how a node behaves, the shape of its output, what a parameter does, product semantics — prefer \`n8n-docs\` and the node type definitions, both already loaded and needing no search, over web search, which is for third-party services and APIs.
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ export const ALWAYS_LOADED_TOOL_NAMES = new Set<string>([
|
|||
DOMAIN_TOOL_IDS.NODES,
|
||||
ORCHESTRATION_TOOL_IDS.VERIFY_BUILT_WORKFLOW,
|
||||
DOMAIN_TOOL_IDS.RESEARCH,
|
||||
// Paired with RESEARCH on purpose: the research tool tells the agent to prefer
|
||||
// n8n's own docs for n8n questions, but deferring n8n-docs priced that route at
|
||||
// search_tools + load_tool while web search stayed one call away — so the agent
|
||||
// web-searched things the docs answer (INS-749).
|
||||
DOMAIN_TOOL_IDS.N8N_DOCS,
|
||||
DOMAIN_TOOL_IDS.AGENTS,
|
||||
// Deferring this one defeats its purpose: it exists for the case where
|
||||
// nothing is connected, which is exactly when `search_tools` has no MCP tool
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user