diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 8f673c11bc..f1db6d325a 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -647,6 +647,12 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { return; } if (subagentGraphIds.size >= MAX_SUBAGENT_GRAPH_NODES) { + logger.warn('[initializeClient] Subagent graph node limit exceeded', { + agentId, + primaryAgentId: primaryConfig.id, + loadedSubagentCount: subagentGraphIds.size, + maxSubagentGraphNodes: MAX_SUBAGENT_GRAPH_NODES, + }); throw new Error( `Subagent graph exceeds the maximum of ${MAX_SUBAGENT_GRAPH_NODES} unique agents.`, ); @@ -670,6 +676,13 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { if (loadedSubagentConfigIds.has(config.id)) { if ((config.subagentAgentConfigs?.length ?? 0) > 0 && depth >= MAX_SUBAGENT_DEPTH) { + logger.warn('[initializeClient] Subagent graph depth limit exceeded', { + agentId: config.id, + primaryAgentId: primaryConfig.id, + depth, + maxSubagentDepth: MAX_SUBAGENT_DEPTH, + childCount: config.subagentAgentConfigs.length, + }); throw new Error( `Subagent graph exceeds the maximum depth of ${MAX_SUBAGENT_DEPTH} at agent ${config.id}.`, ); @@ -690,6 +703,13 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { ); if (explicitSubagentIds.length > 0 && depth >= MAX_SUBAGENT_DEPTH) { + logger.warn('[initializeClient] Subagent graph depth limit exceeded', { + agentId: config.id, + primaryAgentId: primaryConfig.id, + depth, + maxSubagentDepth: MAX_SUBAGENT_DEPTH, + childCount: explicitSubagentIds.length, + }); throw new Error( `Subagent graph exceeds the maximum depth of ${MAX_SUBAGENT_DEPTH} at agent ${config.id}.`, ); diff --git a/api/server/services/Endpoints/agents/initialize.spec.js b/api/server/services/Endpoints/agents/initialize.spec.js index 61f5bdfcf0..3bf6a67e50 100644 --- a/api/server/services/Endpoints/agents/initialize.spec.js +++ b/api/server/services/Endpoints/agents/initialize.spec.js @@ -68,9 +68,12 @@ jest.mock('~/cache', () => ({ })); const { initializeClient } = require('./initialize'); +const { logger } = require('@librechat/data-schemas'); const { User, AclEntry } = require('~/db/models'); const { createAgent } = require('~/models'); +jest.spyOn(logger, 'warn').mockImplementation(() => {}); + const PRIMARY_ID = 'agent_primary'; const TARGET_ID = 'agent_target'; const AUTHORIZED_ID = 'agent_authorized'; @@ -504,6 +507,16 @@ describe('initializeClient — subagent loading', () => { endpointOption: makeEndpointOption(), }), ).rejects.toThrow(`maximum depth of ${MAX_SUBAGENT_DEPTH}`); + expect(logger.warn).toHaveBeenCalledWith( + '[initializeClient] Subagent graph depth limit exceeded', + expect.objectContaining({ + agentId: `agent_depth_${MAX_SUBAGENT_DEPTH - 1}`, + primaryAgentId: PRIMARY_ID, + depth: MAX_SUBAGENT_DEPTH, + maxSubagentDepth: MAX_SUBAGENT_DEPTH, + childCount: 1, + }), + ); expect(agentClientArgs).toBeUndefined(); }); @@ -542,6 +555,15 @@ describe('initializeClient — subagent loading', () => { endpointOption: makeEndpointOption(), }), ).rejects.toThrow(`maximum depth of ${MAX_SUBAGENT_DEPTH}`); + expect(logger.warn).toHaveBeenCalledWith( + '[initializeClient] Subagent graph depth limit exceeded', + expect.objectContaining({ + primaryAgentId: PRIMARY_ID, + depth: MAX_SUBAGENT_DEPTH, + maxSubagentDepth: MAX_SUBAGENT_DEPTH, + childCount: 1, + }), + ); expect(agentClientArgs).toBeUndefined(); }); @@ -581,6 +603,14 @@ describe('initializeClient — subagent loading', () => { endpointOption: makeEndpointOption(), }), ).rejects.toThrow(`maximum of ${MAX_SUBAGENT_GRAPH_NODES} unique agents`); + expect(logger.warn).toHaveBeenCalledWith( + '[initializeClient] Subagent graph node limit exceeded', + expect.objectContaining({ + primaryAgentId: PRIMARY_ID, + loadedSubagentCount: MAX_SUBAGENT_GRAPH_NODES, + maxSubagentGraphNodes: MAX_SUBAGENT_GRAPH_NODES, + }), + ); expect(agentClientArgs).toBeUndefined(); }); diff --git a/packages/api/src/agents/__tests__/run-summarization.test.ts b/packages/api/src/agents/__tests__/run-summarization.test.ts index b3fb1577dd..1aa8e0d58d 100644 --- a/packages/api/src/agents/__tests__/run-summarization.test.ts +++ b/packages/api/src/agents/__tests__/run-summarization.test.ts @@ -1,3 +1,4 @@ +import { logger } from '@librechat/data-schemas'; import type { AppConfig } from '@librechat/data-schemas'; import type { SummarizationConfig, TEndpoint } from 'librechat-data-provider'; import { @@ -26,6 +27,16 @@ jest.mock('~/utils/env', () => ({ createSafeUser: jest.fn(() => ({})), })); +jest.mock('@librechat/data-schemas', () => ({ + ...jest.requireActual('@librechat/data-schemas'), + logger: { + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + info: jest.fn(), + }, +})); + // Mock Run.create to capture the graphConfig it receives jest.mock('@librechat/agents', () => { const actual = jest.requireActual('@librechat/agents'); @@ -1007,6 +1018,14 @@ describe('subagentConfigs', () => { }), ).rejects.toThrow(`maximum depth of ${MAX_SUBAGENT_DEPTH}`); + expect(logger.warn).toHaveBeenCalledWith( + '[createRun] Subagent graph depth limit exceeded', + expect.objectContaining({ + agentId: `agent_chain_${MAX_SUBAGENT_DEPTH + 1}`, + depth: MAX_SUBAGENT_DEPTH + 1, + maxSubagentDepth: MAX_SUBAGENT_DEPTH, + }), + ); expect(Run.create).not.toHaveBeenCalled(); }); @@ -1020,6 +1039,14 @@ describe('subagentConfigs', () => { }), ).rejects.toThrow(`maximum of ${MAX_SUBAGENT_RUN_CONFIGS} expanded entries`); + expect(logger.warn).toHaveBeenCalledWith( + '[createRun] Subagent run configuration limit exceeded', + expect.objectContaining({ + expandedConfigCount: MAX_SUBAGENT_RUN_CONFIGS + 1, + maxSubagentRunConfigs: MAX_SUBAGENT_RUN_CONFIGS, + rootAgentIds: ['agent_dag_root'], + }), + ); expect(Run.create).not.toHaveBeenCalled(); }); }); diff --git a/packages/api/src/agents/run.ts b/packages/api/src/agents/run.ts index 8e5f80f7e6..d90a623d27 100644 --- a/packages/api/src/agents/run.ts +++ b/packages/api/src/agents/run.ts @@ -551,11 +551,17 @@ const SELF_SUBAGENT_TYPE = 'self'; interface SubagentBuildState { configCount: number; + rootAgentIds: string[]; } function countSubagentConfig(state: SubagentBuildState): void { state.configCount += 1; if (state.configCount > MAX_SUBAGENT_RUN_CONFIGS) { + logger.warn('[createRun] Subagent run configuration limit exceeded', { + expandedConfigCount: state.configCount, + maxSubagentRunConfigs: MAX_SUBAGENT_RUN_CONFIGS, + rootAgentIds: state.rootAgentIds, + }); throw new Error( `Subagent run configuration exceeds the maximum of ${MAX_SUBAGENT_RUN_CONFIGS} expanded entries.`, ); @@ -564,6 +570,11 @@ function countSubagentConfig(state: SubagentBuildState): void { function assertSubagentDepth(depth: number, agentId: string): void { if (depth > MAX_SUBAGENT_DEPTH) { + logger.warn('[createRun] Subagent graph depth limit exceeded', { + agentId, + depth, + maxSubagentDepth: MAX_SUBAGENT_DEPTH, + }); throw new Error( `Subagent graph exceeds the maximum depth of ${MAX_SUBAGENT_DEPTH} at agent ${agentId}.`, ); @@ -922,7 +933,10 @@ export async function createRun({ }; const agentInputs: AgentInputs[] = []; - const subagentBuildState: SubagentBuildState = { configCount: 0 }; + const subagentBuildState: SubagentBuildState = { + configCount: 0, + rootAgentIds: agents.map((agent) => agent.id), + }; for (const agent of agents) { const agentInput = buildAgentInput(agent); const subagentConfigs = buildSubagentConfigs(