Path: blob/main/extensions/copilot/src/platform/embeddings/common/vscodeIndex.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { CancellationToken, CommandInformationResult, RelatedInformationProvider, RelatedInformationResult, SettingInformationResult } from 'vscode';6import { createServiceIdentifier } from '../../../util/common/services';7import { TelemetryCorrelationId } from '../../../util/common/telemetryCorrelationId';8import { sanitizeVSCodeVersion } from '../../../util/common/vscodeVersion';9import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';10import { IEnvService } from '../../env/common/envService';11import { ILogService } from '../../log/common/logService';12import { ITelemetryService } from '../../telemetry/common/telemetry';13import { IWorkbenchService } from '../../workbench/common/workbenchService';14import { distance, Embedding, EmbeddingType, EmbeddingVector, IEmbeddingsComputer } from './embeddingsComputer';15import { BaseEmbeddingsIndex, EmbeddingCacheType, IEmbeddingsCache, LocalEmbeddingsCache, RemoteCacheType, RemoteEmbeddingsExtensionCache } from './embeddingsIndex';1617// A command entry in the embedding index18export type CommandListItem = {19key: string;20embedding?: EmbeddingVector;21keybinding: string;22label: string;23originalLabel: string;24};2526// A setting entry in the embedding index27export type SettingListItem = {28key: string;29type: string;30default?: unknown;31description?: string;32deprecationMessage?: string;33markdownDeprecationMessage?: string;34markdownDescription?: string;35enum?: unknown[];36enumDescriptions?: string[];37source?: { id: string; displayName: string };38embedding?: EmbeddingVector;39};4041export function settingItemToContext(item: SettingListItem): string {42let result = `Setting Id: ${item.key}\n`;43result += `Type: ${item.type}\n`;44result += `Description: ${item.description ?? item.markdownDescription ?? ''}\n`;45if (item.enum) {46result += `Possible values:\n`;4748for (let i = 0; i < item.enum.length; i++) {49result += ` - ${item.enum[i]} - ${item.enumDescriptions?.[i] ?? ''}\n`;50}51}5253result += '\n';5455return result;56}5758// Lifted from proposed API59// TODO @lramos15 where should things like this go?60enum RelatedInformationType {61SymbolInformation = 1,62CommandInformation = 2,63SearchInformation = 3,64SettingInformation = 465}6667abstract class RelatedInformationProviderEmbeddingsIndex<V extends { key: string; embedding?: EmbeddingVector }> extends BaseEmbeddingsIndex<V> implements RelatedInformationProvider {68constructor(69loggerContext: string,70embeddingType: EmbeddingType,71cacheKey: string,72embeddingsComputer: IEmbeddingsComputer,73embeddingsCache: IEmbeddingsCache,74private readonly relatedInformationConfig: { type: RelatedInformationType; threshold: number; maxResults: number },75private readonly _logService: ILogService,76protected readonly telemetryService: ITelemetryService77) {78super(79loggerContext,80embeddingType,81cacheKey,82embeddingsCache,83embeddingsComputer,84_logService85);86this.isIndexLoaded = false;87}8889/**90* Returns related information for the given query91* @param query The base string which will be compared against indexed items92* @param types The types of related information to return93* @param token A cancellation token to cancel the request94* @returns An array of RelatedInformationResult objects95*/96async provideRelatedInformation(query: string, token: CancellationToken): Promise<RelatedInformationResult[]> {97const similarityStart = Date.now();98if (!this.isIndexLoaded) {99// Queue off the calculation, but don't await as the user doesn't need to wait for it100this.calculateEmbeddings();101this._logService.debug(`Related Information: Index not loaded yet triggering background calculation, returning ${Date.now() - similarityStart}ms`);102return [];103}104if (token.isCancellationRequested) {105// return an array of 0s the same length as comparisons106this._logService.debug(`Related Information: Request cancelled, returning ${Date.now() - similarityStart}ms`);107return [];108}109const startOfEmbeddingRequest = Date.now();110const embeddingResult = await this.embeddingsComputer.computeEmbeddings(EmbeddingType.text3small_512, [query], {}, new TelemetryCorrelationId('RelatedInformationProviderEmbeddingsIndex::provideRelatedInformation'), token);111this._logService.debug(`Related Information: Remote similarly request took ${Date.now() - startOfEmbeddingRequest}ms`);112if (token.isCancellationRequested) {113// return an array of 0s the same length as comparisons114this._logService.debug(`Related Information: Request cancelled or no embeddings computed, returning ${Date.now() - similarityStart}ms`);115return [];116}117118const results: RelatedInformationResult[] = [];119for (const item of this._items.values()) {120if (token.isCancellationRequested) {121this._logService.debug(`Related Information: Request cancelled, returning ${Date.now() - similarityStart}ms`);122break;123}124if (item.embedding) {125const score = distance(embeddingResult.values[0], { value: item.embedding, type: EmbeddingType.text3small_512 }).value;126if (score > this.relatedInformationConfig.threshold) {127results.push(this.toRelatedInformation(item, score));128}129}130}131132this.logService.debug(`Related Information: Successfully Calculated, returning ${Date.now() - similarityStart}ms`);133134// Only log non-cancelled settings related information queries135if (this.relatedInformationConfig.type === RelatedInformationType.SettingInformation) {136this.telemetryService.sendInternalMSFTTelemetryEvent('relatedInformationSettings', { query });137}138139const returnthis = results140.sort((a, b) => b.weight - a.weight)141.slice(0, this.relatedInformationConfig.maxResults);142143return returnthis;144}145146protected abstract toRelatedInformation(value: V, score: number): RelatedInformationResult;147}148149class CommandIdIndex extends RelatedInformationProviderEmbeddingsIndex<CommandListItem> {150constructor(151embeddingscache: IEmbeddingsCache,152@IEmbeddingsComputer embeddingsFetcher: IEmbeddingsComputer,153@ILogService logService: ILogService,154@ITelemetryService telemetryService: ITelemetryService,155@IWorkbenchService private readonly workbenchService: IWorkbenchService156) {157super(158'CommandIdIndex',159EmbeddingType.text3small_512,160'commandEmbeddings',161embeddingsFetcher,162embeddingscache,163{164type: RelatedInformationType.CommandInformation,165threshold: /* min threshold of 0 for text-3-small*/ 0,166maxResults: 100,167},168logService,169telemetryService170);171}172173protected override async getLatestItems(): Promise<CommandListItem[]> {174const allCommands = await this.workbenchService.getAllCommands();175// This isn't in the command palette, but it's a useful command to suggest176allCommands.push({177label: 'Extensions: Search the marketplace for extensions',178command: 'workbench.extensions.search',179keybinding: 'Not set',180});181allCommands.push({182label: 'Extensions: Install extension from marketplace',183command: 'workbench.extensions.installExtension',184keybinding: 'Not set',185});186return allCommands.map(c => {187return {188key: c.command,189label: c.label.replace('View: Toggle', 'View: Toggle or Show or Hide'),190originalLabel: c.label,191keybinding: c.keybinding ?? 'Not set',192};193});194}195196protected override getEmbeddingQueryString(value: CommandListItem): string {197return `${value.label} - ${value.key}`;198}199200protected override toRelatedInformation(value: CommandListItem, score: number): CommandInformationResult {201return {202type: RelatedInformationType.CommandInformation,203weight: score,204command: value.key,205};206}207208}209210class SettingsIndex extends RelatedInformationProviderEmbeddingsIndex<SettingListItem> {211constructor(212embeddingsCache: IEmbeddingsCache,213@IEmbeddingsComputer embeddingsFetcher: IEmbeddingsComputer,214@ILogService logService: ILogService,215@ITelemetryService telemetryService: ITelemetryService,216@IWorkbenchService private readonly workbenchService: IWorkbenchService217) {218super(219'SettingsIndex',220EmbeddingType.text3small_512,221'settingEmbeddings',222embeddingsFetcher,223embeddingsCache,224{225type: RelatedInformationType.SettingInformation,226threshold: /* min threshold of 0 for text-3-small*/ 0,227maxResults: 100,228},229logService,230telemetryService231);232}233234protected override async getLatestItems(): Promise<SettingListItem[]> {235const settings = await this.workbenchService.getAllSettings();236const settingsList: SettingListItem[] = [];237for (const settingId of Object.keys(settings)) {238const setting = settings[settingId];239if (setting.deprecationMessage || setting.markdownDeprecationMessage) {240continue;241}242settingsList.push({ ...setting, key: settingId });243}244return settingsList;245}246247protected override getEmbeddingQueryString(value: SettingListItem): string {248return settingItemToContext(value);249}250251protected override toRelatedInformation(value: SettingListItem, score: number): SettingInformationResult {252return {253type: RelatedInformationType.SettingInformation,254weight: score,255setting: value.key,256};257}258}259260export interface ICombinedEmbeddingIndex {261readonly _serviceBrand: undefined;262readonly commandIdIndex: CommandIdIndex;263readonly settingsIndex: SettingsIndex;264loadIndexes(): Promise<void>;265nClosestValues(embedding: Embedding, n: number): Promise<{ commands: CommandListItem[]; settings: SettingListItem[] }>;266hasSetting(settingId: string): boolean;267hasCommand(commandId: string): boolean;268getSetting(settingId: string): SettingListItem | undefined;269getCommand(commandId: string): CommandListItem | undefined;270}271272export const ICombinedEmbeddingIndex = createServiceIdentifier<ICombinedEmbeddingIndex>('ICombinedEmbeddingIndex');273274/**275* Combines the settings and command indexes into a single index. This is what is consumed externally276* If necessary, the individual indices can be accessed277*/278export class VSCodeCombinedIndexImpl implements ICombinedEmbeddingIndex {279declare readonly _serviceBrand: undefined;280public readonly commandIdIndex: CommandIdIndex;281public readonly settingsIndex: SettingsIndex;282constructor(283useRemoteCache: boolean = true,284@IInstantiationService instantiationService: IInstantiationService,285@IEnvService envService: IEnvService286) {287// Local embeddings cache version is locked to 1.98288const settingsEmbeddingsCache = useRemoteCache ?289instantiationService.createInstance(RemoteEmbeddingsExtensionCache, EmbeddingCacheType.GLOBAL, 'settingEmbeddings', sanitizeVSCodeVersion(envService.getEditorInfo().version), EmbeddingType.text3small_512, RemoteCacheType.Settings) :290instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'settingEmbeddings', '1.98', EmbeddingType.text3small_512);291const commandsEmbeddingsCache = useRemoteCache ?292instantiationService.createInstance(RemoteEmbeddingsExtensionCache, EmbeddingCacheType.GLOBAL, 'commandEmbeddings', sanitizeVSCodeVersion(envService.getEditorInfo().version), EmbeddingType.text3small_512, RemoteCacheType.Commands) :293instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'commandEmbeddings', '1.98', EmbeddingType.text3small_512);294295this.settingsIndex = instantiationService.createInstance(SettingsIndex, settingsEmbeddingsCache);296this.commandIdIndex = instantiationService.createInstance(CommandIdIndex, commandsEmbeddingsCache);297}298299public async loadIndexes() {300await Promise.all([301this.commandIdIndex.isIndexLoaded ? Promise.resolve() : this.commandIdIndex.calculateEmbeddings(),302this.settingsIndex.isIndexLoaded ? Promise.resolve() : this.settingsIndex.calculateEmbeddings(),303]);304}305306public async nClosestValues(embedding: Embedding, n: number) {307await this.loadIndexes();308return {309commands: this.commandIdIndex.nClosestValues(embedding, n),310settings: this.settingsIndex.nClosestValues(embedding, n),311};312}313314public hasSetting(settingId: string): boolean {315return this.settingsIndex.hasItem(settingId);316}317318public hasCommand(commandId: string): boolean {319return this.commandIdIndex.hasItem(commandId);320}321322public getSetting(settingId: string): SettingListItem | undefined {323return this.settingsIndex.getItem(settingId);324}325326public getCommand(commandId: string): CommandListItem | undefined {327return this.commandIdIndex.getItem(commandId);328}329}330331332