Skip to content

Commit 58cd614

Browse files
authored
tools: trigger tool grouping on 128 limit automatically (#385)
Triggers tool grouping when users hit the 128 limit. Reworks an EXP setting which allows the limit to be configurable (lowered or raised arbitrarily high.) Users of the setting keep the existing 64 tool limit. Will have a corresponding VS Code PR.
1 parent d78902d commit 58cd614

File tree

8 files changed

+67
-20
lines changed

8 files changed

+67
-20
lines changed

src/extension/intents/node/agentIntent.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ export class AgentIntent extends EditCodeIntent {
9595
@IExperimentationService expService: IExperimentationService,
9696
@ICodeMapperService codeMapperService: ICodeMapperService,
9797
@IWorkspaceService workspaceService: IWorkspaceService,
98-
@IConfigurationService private readonly _configurationService: IConfigurationService,
99-
@IExperimentationService private readonly _experimentationService: IExperimentationService,
10098
@IToolGroupingService private readonly _toolGroupingService: IToolGroupingService,
10199
) {
102100
super(instantiationService, endpointProvider, configurationService, expService, codeMapperService, workspaceService, { intentInvocation: AgentIntentInvocation, processCodeblocks: false });
@@ -113,7 +111,8 @@ export class AgentIntent extends EditCodeIntent {
113111

114112
private async listTools(request: vscode.ChatRequest, stream: vscode.ChatResponseStream, token: CancellationToken) {
115113
const editingTools = await getTools(this.instantiationService, request);
116-
if (!this._configurationService.getExperimentBasedConfig(ConfigKey.VirtualTools, this._experimentationService)) {
114+
const grouping = this._toolGroupingService.create(editingTools);
115+
if (!grouping.isEnabled) {
117116
stream.markdown(`Available tools: \n${editingTools.map(tool => `- ${tool.name}`).join('\n')}\n`);
118117
return;
119118
}
@@ -137,7 +136,7 @@ export class AgentIntent extends EditCodeIntent {
137136
}
138137
}
139138

140-
const tools = await this._toolGroupingService.create(editingTools).computeAll(token);
139+
const tools = await grouping.computeAll(token);
141140
tools.forEach(t => printTool(t));
142141
stream.markdown(str);
143142

src/extension/prompt/node/defaultIntentRequestHandler.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { IAuthenticationChatUpgradeService } from '../../../platform/authenticat
1111
import { ICopilotTokenStore } from '../../../platform/authentication/common/copilotTokenStore';
1212
import { CanceledResult, ChatFetchResponseType, ChatLocation, ChatResponse, getErrorDetailsFromChatFetchError } from '../../../platform/chat/common/commonTypes';
1313
import { IConversationOptions } from '../../../platform/chat/common/conversationOptions';
14-
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
1514
import { IEditSurvivalTrackerService, IEditSurvivalTrackingSession, NullEditSurvivalTrackingSession } from '../../../platform/editSurvivalTracking/common/editSurvivalTrackerService';
1615
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1716
import { HAS_IGNORED_FILES_MESSAGE } from '../../../platform/ignore/common/ignoreService';
@@ -508,7 +507,6 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
508507
@IAuthenticationChatUpgradeService authenticationChatUpgradeService: IAuthenticationChatUpgradeService,
509508
@ITelemetryService telemetryService: ITelemetryService,
510509
@IToolGroupingService private readonly toolGroupingService: IToolGroupingService,
511-
@IConfigurationService private readonly _configurationService: IConfigurationService,
512510
@IExperimentationService private readonly _experimentationService: IExperimentationService,
513511
@ICopilotTokenStore private readonly _copilotTokenStore: ICopilotTokenStore,
514512
@IThinkingDataService thinkingDataService: IThinkingDataService,
@@ -557,7 +555,7 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
557555
private async _doMirroredCallWithVirtualTools(delta: IResponseDelta, messages: Raw.ChatMessage[], requestOptions: OptionalChatRequestParams) {
558556
const shouldDo = !this._didParallelToolCallLoop
559557
&& this._copilotTokenStore.copilotToken?.isInternal
560-
&& !DefaultToolCallingLoop.toolGrouping;
558+
&& !DefaultToolCallingLoop.toolGrouping?.isEnabled;
561559
if (!shouldDo) {
562560
return;
563561
}
@@ -692,16 +690,16 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
692690

693691
protected override async getAvailableTools(outputStream: ChatResponseStream | undefined, token: CancellationToken): Promise<LanguageModelToolInformation[]> {
694692
const tools = await this.options.invocation.getAvailableTools?.() ?? [];
695-
if (!this._configurationService.getExperimentBasedConfig(ConfigKey.VirtualTools, this._experimentationService)) {
696-
return tools;
697-
}
698-
699693
if (DefaultToolCallingLoop.toolGrouping) {
700694
DefaultToolCallingLoop.toolGrouping.tools = tools;
701695
} else {
702696
DefaultToolCallingLoop.toolGrouping = this.toolGroupingService.create(tools);
703697
}
704698

699+
if (!DefaultToolCallingLoop.toolGrouping.isEnabled) {
700+
return tools;
701+
}
702+
705703
const computePromise = DefaultToolCallingLoop.toolGrouping.compute(token);
706704

707705
// Show progress if this takes a moment...

src/extension/tools/common/virtualTools/toolGrouping.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import type { LanguageModelToolInformation } from 'vscode';
7-
import { HARD_TOOL_LIMIT } from '../../../../platform/configuration/common/configurationService';
7+
import { ConfigKey, HARD_TOOL_LIMIT, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
8+
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
89
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
910
import { equals as arraysEqual } from '../../../../util/vs/base/common/arrays';
1011
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
@@ -16,6 +17,19 @@ import { VirtualToolGrouper } from './virtualToolGrouper';
1617
import * as Constant from './virtualToolsConstants';
1718
import { IToolCategorization, IToolGrouping } from './virtualToolTypes';
1819

20+
export function computeToolGroupingMinThreshold(experimentationService: IExperimentationService, configurationService: IConfigurationService): number {
21+
let threshold = HARD_TOOL_LIMIT;
22+
23+
const override = experimentationService.getTreatmentVariable<number>('vscode', 'copilotchat.virtualToolThreshold');
24+
if (override) {
25+
threshold = override;
26+
} else if (configurationService.getExperimentBasedConfig(ConfigKey.VirtualTools, experimentationService)) {
27+
threshold = Constant.START_GROUPING_AFTER_TOOL_COUNT;
28+
}
29+
30+
return threshold;
31+
}
32+
1933
export class ToolGrouping implements IToolGrouping {
2034

2135
private readonly _root = new VirtualTool(VIRTUAL_TOOL_NAME_PREFIX, '', Infinity, { groups: [], toolsetKey: '', preExpanded: true });
@@ -36,10 +50,16 @@ export class ToolGrouping implements IToolGrouping {
3650
}
3751
}
3852

53+
public get isEnabled() {
54+
return this._tools.length >= computeToolGroupingMinThreshold(this._experimentationService, this._configurationService);
55+
}
56+
3957
constructor(
4058
private _tools: readonly LanguageModelToolInformation[],
4159
@IInstantiationService private readonly _instantiationService: IInstantiationService,
4260
@ITelemetryService private readonly _telemetryService: ITelemetryService,
61+
@IConfigurationService private readonly _configurationService: IConfigurationService,
62+
@IExperimentationService private readonly _experimentationService: IExperimentationService
4363
) {
4464
this._root.isExpanded = true;
4565
}

src/extension/tools/common/virtualTools/toolGroupingService.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import type { LanguageModelToolInformation } from 'vscode';
7+
import { IConfigurationService } from '../../../../platform/configuration/common/configurationService';
8+
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
79
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
8-
import { ToolGrouping } from './toolGrouping';
10+
import { computeToolGroupingMinThreshold, ToolGrouping } from './toolGrouping';
911
import { IToolGrouping, IToolGroupingService } from './virtualToolTypes';
1012

1113
export class ToolGroupingService implements IToolGroupingService {
1214
declare readonly _serviceBrand: undefined;
1315

14-
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
16+
constructor(
17+
@IInstantiationService private readonly _instantiationService: IInstantiationService,
18+
@IConfigurationService private readonly _configurationService: IConfigurationService,
19+
@IExperimentationService private readonly _experimentationService: IExperimentationService
20+
) { }
21+
22+
public get threshold() {
23+
return computeToolGroupingMinThreshold(this._experimentationService, this._configurationService);
24+
}
1525

1626
create(tools: readonly LanguageModelToolInformation[]): IToolGrouping {
1727
return this._instantiationService.createInstance(ToolGrouping, tools);

src/extension/tools/common/virtualTools/virtualToolTypes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export interface IToolGrouping {
1414
*/
1515
tools: readonly LanguageModelToolInformation[];
1616

17+
/**
18+
* Whether tool grouping logic is enabled at the current tool threshold.
19+
*/
20+
isEnabled: boolean;
21+
1722
/**
1823
* Should be called for each model tool call. Returns a tool result if the
1924
* call was a virtual tool call that was expanded.
@@ -52,6 +57,10 @@ export interface IToolGrouping {
5257

5358
export interface IToolGroupingService {
5459
_serviceBrand: undefined;
60+
/**
61+
* The current tool count threshold for grouping to kick in.
62+
*/
63+
threshold: number;
5564
/**
5665
* Creates a tool grouping for a request, based on its conversation and the
5766
* initial set of tools.

src/extension/tools/test/node/virtualTools/virtualToolGrouping.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
77
import type { LanguageModelTextPart, LanguageModelToolInformation } from 'vscode';
8-
import { HARD_TOOL_LIMIT } from '../../../../../platform/configuration/common/configurationService';
8+
import { HARD_TOOL_LIMIT, IConfigurationService } from '../../../../../platform/configuration/common/configurationService';
99
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry';
1010
import { ITestingServicesAccessor } from '../../../../../platform/test/node/services';
1111
import { shuffle } from '../../../../../util/vs/base/common/arrays';
@@ -17,6 +17,7 @@ import { ToolGrouping } from '../../../common/virtualTools/toolGrouping';
1717
import { VIRTUAL_TOOL_NAME_PREFIX, VirtualTool } from '../../../common/virtualTools/virtualTool';
1818
import { TRIM_THRESHOLD } from '../../../common/virtualTools/virtualToolsConstants';
1919
import { IToolCategorization } from '../../../common/virtualTools/virtualToolTypes';
20+
import { IExperimentationService } from '../../../../../platform/telemetry/common/nullExperimentationService';
2021

2122
describe('Virtual Tools - Grouping', () => {
2223
let accessor: ITestingServicesAccessor;
@@ -28,8 +29,10 @@ describe('Virtual Tools - Grouping', () => {
2829
_tools: readonly LanguageModelToolInformation[],
2930
@IInstantiationService _instantiationService: IInstantiationService,
3031
@ITelemetryService _telemetryService: ITelemetryService,
32+
@IConfigurationService _configurationService: IConfigurationService,
33+
@IExperimentationService _experimentationService: IExperimentationService
3134
) {
32-
super(_tools, _instantiationService, _telemetryService);
35+
super(_tools, _instantiationService, _telemetryService, _configurationService, _experimentationService);
3336
this._grouper = mockGrouper;
3437
}
3538

src/extension/tools/vscode-node/tools.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import { Disposable } from '../../../util/vs/base/common/lifecycle';
88
import { getContributedToolName } from '../common/toolNames';
99
import { IToolsService } from '../common/toolsService';
1010

11-
import { IToolGroupingCache } from '../common/virtualTools/virtualToolTypes';
11+
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
12+
import { IToolGroupingCache, IToolGroupingService } from '../common/virtualTools/virtualToolTypes';
1213
import '../node/allTools';
1314
import './allTools';
1415

1516
export class ToolsContribution extends Disposable {
1617
constructor(
1718
@IToolsService toolsService: IToolsService,
1819
@IToolGroupingCache toolGrouping: IToolGroupingCache,
20+
@IToolGroupingService toolGroupingService: IToolGroupingService,
21+
@IExperimentationService experimentationService: IExperimentationService
1922
) {
2023
super();
2124

@@ -27,5 +30,9 @@ export class ToolsContribution extends Disposable {
2730
await toolGrouping.clear();
2831
vscode.window.showInformationMessage('Tool groups have been reset. They will be regenerated on the next agent request.');
2932
}));
33+
34+
experimentationService.initializePromise.then(() => {
35+
vscode.commands.executeCommand('setContext', 'chat.toolGroupingThreshold', toolGroupingService.threshold);
36+
});
3037
}
3138
}

test/base/extHostContext/simulationExtHostToolsService.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ import type { CancellationToken, ChatRequest, LanguageModelTool, LanguageModelTo
99
import { getToolName, ToolName } from '../../../src/extension/tools/common/toolNames';
1010
import { ICopilotTool } from '../../../src/extension/tools/common/toolsRegistry';
1111
import { BaseToolsService, IToolsService } from '../../../src/extension/tools/common/toolsService';
12+
import { getPackagejsonToolsForTest } from '../../../src/extension/tools/node/test/testToolsService';
1213
import { ToolsContribution } from '../../../src/extension/tools/vscode-node/tools';
1314
import { ToolsService } from '../../../src/extension/tools/vscode-node/toolsService';
1415
import { packageJson } from '../../../src/platform/env/common/packagejson';
1516
import { ILogService } from '../../../src/platform/log/common/logService';
17+
import { NullExperimentationService } from '../../../src/platform/telemetry/common/nullExperimentationService';
18+
import { raceTimeout } from '../../../src/util/vs/base/common/async';
1619
import { CancellationError } from '../../../src/util/vs/base/common/errors';
1720
import { Iterable } from '../../../src/util/vs/base/common/iterator';
1821
import { IInstantiationService } from '../../../src/util/vs/platform/instantiation/common/instantiation';
1922
import { logger } from '../../simulationLogger';
20-
import { raceTimeout } from '../../../src/util/vs/base/common/async';
21-
import { getPackagejsonToolsForTest } from '../../../src/extension/tools/node/test/testToolsService';
2223

2324
export class SimulationExtHostToolsService extends BaseToolsService implements IToolsService {
2425
declare readonly _serviceBrand: undefined;
@@ -63,7 +64,7 @@ export class SimulationExtHostToolsService extends BaseToolsService implements I
6364
}
6465

6566
private ensureToolsRegistered() {
66-
this._lmToolRegistration ??= new ToolsContribution(this, {} as any);
67+
this._lmToolRegistration ??= new ToolsContribution(this, {} as any, { threshold: Infinity } as any, new NullExperimentationService());
6768
}
6869

6970
getCopilotTool(name: string): ICopilotTool<any> | undefined {

0 commit comments

Comments
 (0)