Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/extension/extension/vscode-node/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IEndpointProvider } from '../../../platform/endpoint/common/endpointPro
import { CAPIClientImpl } from '../../../platform/endpoint/node/capiClientImpl';
import { DomainService } from '../../../platform/endpoint/node/domainServiceImpl';
import { INativeEnvService, isScenarioAutomation } from '../../../platform/env/common/envService';
import { NativeEnvServiceImpl } from '../../../platform/env/vscode-node/nativeEnvServiceImpl';
import { IGitCommitMessageService } from '../../../platform/git/common/gitCommitMessageService';
import { IGitDiffService } from '../../../platform/git/common/gitDiffService';
import { IGithubRepositoryService } from '../../../platform/github/common/githubService';
Expand Down Expand Up @@ -102,7 +103,6 @@ import { LanguageContextServiceImpl } from '../../typescriptContext/vscode-node/
import { IWorkspaceListenerService } from '../../workspaceRecorder/common/workspaceListenerService';
import { WorkspacListenerService } from '../../workspaceRecorder/vscode-node/workspaceListenerService';
import { registerServices as registerCommonServices } from '../vscode/services';
import { NativeEnvServiceImpl } from '../../../platform/env/vscode-node/nativeEnvServiceImpl';

// ###########################################################################################
// ### ###
Expand Down
6 changes: 3 additions & 3 deletions src/extension/prompt/node/defaultIntentRequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ import { SummarizedConversationHistoryMetadata } from '../../prompts/node/agent/
import { normalizeToolSchema } from '../../tools/common/toolSchemaNormalizer';
import { ToolCallCancelledError } from '../../tools/common/toolsService';
import { IToolGrouping, IToolGroupingService } from '../../tools/common/virtualTools/virtualToolTypes';
import { ChatVariablesCollection } from '../common/chatVariablesCollection';
import { Conversation, getUniqueReferences, GlobalContextMessageMetadata, IResultMetadata, RenderedUserMessageMetadata, RequestDebugInformation, ResponseStreamParticipant, Turn, TurnStatus } from '../common/conversation';
import { IBuildPromptContext, IToolCallRound } from '../common/intents';
import { ChatTelemetry, ChatTelemetryBuilder } from './chatParticipantTelemetry';
import { IntentInvocationMetadata } from './conversation';
import { IDocumentContext } from './documentContext';
import { IBuildPromptResult, IIntent, IIntentInvocation, IResponseProcessor } from './intents';
import { ConversationalBaseTelemetryData, createTelemetryWithId, sendModelMessageTelemetry } from './telemetry';
import { ChatVariablesCollection } from '../common/chatVariablesCollection';

export interface IDefaultIntentRequestHandlerOptions {
maxToolCallIterations: number;
Expand Down Expand Up @@ -595,7 +595,7 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
const token = CancellationToken.None;
const allTools = await this.options.invocation.getAvailableTools?.() ?? [];
const grouping = this.toolGroupingService.create(this.options.conversation.sessionId, allTools);
const computed = await grouping.compute(this.options.request.prompt, token);
const computed = await grouping.compute(this.options.request.prompt, token, this.options.invocation.endpoint);

const container = grouping.getContainerFor(candidateCall.name);

Expand Down Expand Up @@ -722,7 +722,7 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
return tools;
}

const computePromise = this.toolGrouping.compute(this.options.request.prompt, token);
const computePromise = this.toolGrouping.compute(this.options.request.prompt, token, this.options.invocation.endpoint);

// Show progress if this takes a moment...
const timeout = setTimeout(() => {
Expand Down
149 changes: 149 additions & 0 deletions src/extension/tools/common/virtualTools/builtInToolGroupHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { LanguageModelToolInformation } from 'vscode';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
import { VIRTUAL_TOOL_NAME_PREFIX, VirtualTool } from './virtualTool';
import * as Constant from './virtualToolsConstants';

const BUILT_IN_GROUP = 'builtin';
const SUMMARY_PREFIX = 'Call this tool when you need access to a new category of tools. The category of tools is described as follows:\n\n';
const SUMMARY_SUFFIX = '\n\nBe sure to call this tool if you need a capability related to the above.';

// categorize all tools except for the 12 default tools
// 12 default tools = semantic_search, grep_search, read_file, create_file, apply_patch, replace_string_in_file,
// insert_edit_into_file, run_in_terminal, list_dir, think, get_terminal_output, manage_todo_list
const BUILT_IN_TOOL_GROUPS = {
'Jupyter Notebook Tools': {
summary: 'Call tools from this group when you need to work with Jupyter notebooks - creating, editing, running cells, and managing notebook operations.',
tools: [
'create_new_jupyter_notebook',
'edit_notebook_file',
'run_notebook_cell',
'copilot_getNotebookSummary',
'read_notebook_cell_output',
'configure_notebook',
'notebook_install_packages',
'notebook_list_packages',
'configure_python_environment',
'get_python_environment_details',
'get_python_executable_details'
]
},
'Web Interaction': {
summary: 'Call tools from this group when you need to interact with web content, browse websites, or access external resources.',
tools: [
'fetch_webpage',
'open_simple_browser',
'github_repo'
]
},
'VS Code Interaction': {
summary: 'Call tools from this group when you need to interact with the VS Code workspace and access VS Code features.',
tools: [
'search_workspace_symbols',
'list_code_usages',
'get_errors',
'get_vscode_api',
'get_changed_files',
'create_new_workspace',
'install_extension',
'get_project_setup_info',
'create_and_run_task',
'run_task',
'get_task_output',
'run_vscode_command',
'install_python_packages',
'get_search_view_results',
'vscode_searchExtensions_internal',
'read_project_structure'
]
},
'Testing': {
summary: 'Call tools from this group when you need to run tests, analyze test failures, and manage test workflows.',
tools: [
'run_tests',
'test_failure',
'test_search',
'runTests'
]
},
'Redundant but Specific': {
summary: 'These tools have overlapping functionalities but are highly specialized for certain tasks. \nTools: file_search, terminal_selection, terminal_last_command, create_directory, get_doc_info, multi_replace_string_in_file, edit_files',
tools: [
'file_search',
'terminal_selection',
'terminal_last_command',
'create_directory',
'get_doc_info',
'edit_files',
'multi_replace_string_in_file'
]
}
} as const;

export class BuiltInToolGroupHandler {
constructor(
private readonly _telemetryService: ITelemetryService,
) { }

/** Creates groups for built-in tools based on the pre-defined enum above*/
createBuiltInToolGroups(tools: LanguageModelToolInformation[]): (VirtualTool | LanguageModelToolInformation)[] {
// If there are too few tools, don't group them
if (tools.length <= Constant.MIN_TOOLSET_SIZE_TO_GROUP) {
return tools;
}

const toolMap = new Map(tools.map(tool => [tool.name, tool]));

const virtualTools: VirtualTool[] = [];
const usedTools = new Set<string>();

// Create virtual tools for each predefined group
for (const [groupName, groupDef] of Object.entries(BUILT_IN_TOOL_GROUPS)) {
const groupTools = groupDef.tools
.map(toolName => toolMap.get(toolName))
.filter((tool): tool is LanguageModelToolInformation => tool !== undefined);

if (groupTools.length > 0) {
// Mark each tool that has already been added to a group
groupTools.forEach(tool => usedTools.add(tool.name));

const virtualTool = new VirtualTool(
VIRTUAL_TOOL_NAME_PREFIX + groupName.toLowerCase().replace(/\s+/g, '_'),
SUMMARY_PREFIX + groupDef.summary + SUMMARY_SUFFIX,
0,
{
toolsetKey: BUILT_IN_GROUP,
groups: [],
possiblePrefix: 'builtin_'
},
groupTools
);
virtualTools.push(virtualTool);
}
}

// Add any remaining uncategorized tools individually
const uncategorizedTools = tools.filter(tool => !usedTools.has(tool.name));

// Send telemetry for built-in tool grouping
this._telemetryService.sendMSFTTelemetryEvent('virtualTools.generate', {
groupKey: BUILT_IN_GROUP,
}, {
uncategorized: uncategorizedTools.length,
toolsBefore: tools.length,
toolsAfter: virtualTools.length,
retries: 0, // No retries for predefined groups
durationMs: 0, // Instant for predefined groups
});

return [...virtualTools, ...uncategorizedTools];
}

static get BUILT_IN_GROUP_KEY(): string {
return BUILT_IN_GROUP;
}
}
21 changes: 14 additions & 7 deletions src/extension/tools/common/virtualTools/toolGrouping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import type { LanguageModelToolInformation } from 'vscode';
import { ConfigKey, HARD_TOOL_LIMIT, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
import { equals as arraysEqual, uniqueFilter } from '../../../../util/vs/base/common/arrays';
Expand Down Expand Up @@ -47,7 +48,13 @@ export class ToolGrouping implements IToolGrouping {
}

public get isEnabled() {
return this._tools.length >= computeToolGroupingMinThreshold(this._experimentationService, this._configurationService).get();
// Match the logic from VirtualToolGrouper.addGroups()
// Enable if we could potentially trigger built-in grouping (when GPT model is used)
const defaultToolGroupingEnabled = this._configurationService.getExperimentBasedConfig(ConfigKey.Internal.DefaultToolsGrouped, this._experimentationService);
const couldTriggerBuiltInGrouping = this._tools.length > Constant.START_BUILTIN_GROUPING_AFTER_TOOL_COUNT && defaultToolGroupingEnabled;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't check everything we need. We also need to confirm that the model is gpt-4.1 or gpt-5, but this is supposed to be a lightweight, quick function to call.
Options:

  • pass the endpoint into this function so we can check to make sure it's the correct model before going to virtual tool grouping
  • let it go to virtual tool grouping every time (since START_BUILTIN_GROUPING_AFTER_TOOL_COUNT is 20) and get stopped from grouping there by the endpoint check

// Or if we meet the standard threshold for all tool types
return couldTriggerBuiltInGrouping || this._tools.length >= Constant.START_GROUPING_AFTER_TOOL_COUNT;
}

constructor(
Expand Down Expand Up @@ -126,19 +133,19 @@ export class ToolGrouping implements IToolGrouping {
this._expandOnNext.add(toolName);
}

async compute(query: string, token: CancellationToken): Promise<LanguageModelToolInformation[]> {
await this._doCompute(query, token);
async compute(query: string, token: CancellationToken, endpoint?: IChatEndpoint): Promise<LanguageModelToolInformation[]> {
await this._doCompute(query, token, endpoint);
return [...this._root.tools()].filter(uniqueFilter(t => t.name));
}

async computeAll(query: string, token: CancellationToken): Promise<(LanguageModelToolInformation | VirtualTool)[]> {
await this._doCompute(query, token);
async computeAll(query: string, token: CancellationToken, endpoint?: IChatEndpoint): Promise<(LanguageModelToolInformation | VirtualTool)[]> {
await this._doCompute(query, token, endpoint);
return this._root.contents;
}

private async _doCompute(query: string, token: CancellationToken) {
private async _doCompute(query: string, token: CancellationToken, endpoint?: IChatEndpoint) {
if (this._didToolsChange) {
await this._grouper.addGroups(query, this._root, this._tools.slice(), token);
await this._grouper.addGroups(query, this._root, this._tools.slice(), token, endpoint);
this._didToolsChange = false;
}

Expand Down
Loading