Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/embeddings/common/vscodeIndex.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { CancellationToken, CommandInformationResult, RelatedInformationProvider, RelatedInformationResult, SettingInformationResult } from 'vscode';
7
import { createServiceIdentifier } from '../../../util/common/services';
8
import { TelemetryCorrelationId } from '../../../util/common/telemetryCorrelationId';
9
import { sanitizeVSCodeVersion } from '../../../util/common/vscodeVersion';
10
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
11
import { IEnvService } from '../../env/common/envService';
12
import { ILogService } from '../../log/common/logService';
13
import { ITelemetryService } from '../../telemetry/common/telemetry';
14
import { IWorkbenchService } from '../../workbench/common/workbenchService';
15
import { distance, Embedding, EmbeddingType, EmbeddingVector, IEmbeddingsComputer } from './embeddingsComputer';
16
import { BaseEmbeddingsIndex, EmbeddingCacheType, IEmbeddingsCache, LocalEmbeddingsCache, RemoteCacheType, RemoteEmbeddingsExtensionCache } from './embeddingsIndex';
17
18
// A command entry in the embedding index
19
export type CommandListItem = {
20
key: string;
21
embedding?: EmbeddingVector;
22
keybinding: string;
23
label: string;
24
originalLabel: string;
25
};
26
27
// A setting entry in the embedding index
28
export type SettingListItem = {
29
key: string;
30
type: string;
31
default?: unknown;
32
description?: string;
33
deprecationMessage?: string;
34
markdownDeprecationMessage?: string;
35
markdownDescription?: string;
36
enum?: unknown[];
37
enumDescriptions?: string[];
38
source?: { id: string; displayName: string };
39
embedding?: EmbeddingVector;
40
};
41
42
export function settingItemToContext(item: SettingListItem): string {
43
let result = `Setting Id: ${item.key}\n`;
44
result += `Type: ${item.type}\n`;
45
result += `Description: ${item.description ?? item.markdownDescription ?? ''}\n`;
46
if (item.enum) {
47
result += `Possible values:\n`;
48
49
for (let i = 0; i < item.enum.length; i++) {
50
result += ` - ${item.enum[i]} - ${item.enumDescriptions?.[i] ?? ''}\n`;
51
}
52
}
53
54
result += '\n';
55
56
return result;
57
}
58
59
// Lifted from proposed API
60
// TODO @lramos15 where should things like this go?
61
enum RelatedInformationType {
62
SymbolInformation = 1,
63
CommandInformation = 2,
64
SearchInformation = 3,
65
SettingInformation = 4
66
}
67
68
abstract class RelatedInformationProviderEmbeddingsIndex<V extends { key: string; embedding?: EmbeddingVector }> extends BaseEmbeddingsIndex<V> implements RelatedInformationProvider {
69
constructor(
70
loggerContext: string,
71
embeddingType: EmbeddingType,
72
cacheKey: string,
73
embeddingsComputer: IEmbeddingsComputer,
74
embeddingsCache: IEmbeddingsCache,
75
private readonly relatedInformationConfig: { type: RelatedInformationType; threshold: number; maxResults: number },
76
private readonly _logService: ILogService,
77
protected readonly telemetryService: ITelemetryService
78
) {
79
super(
80
loggerContext,
81
embeddingType,
82
cacheKey,
83
embeddingsCache,
84
embeddingsComputer,
85
_logService
86
);
87
this.isIndexLoaded = false;
88
}
89
90
/**
91
* Returns related information for the given query
92
* @param query The base string which will be compared against indexed items
93
* @param types The types of related information to return
94
* @param token A cancellation token to cancel the request
95
* @returns An array of RelatedInformationResult objects
96
*/
97
async provideRelatedInformation(query: string, token: CancellationToken): Promise<RelatedInformationResult[]> {
98
const similarityStart = Date.now();
99
if (!this.isIndexLoaded) {
100
// Queue off the calculation, but don't await as the user doesn't need to wait for it
101
this.calculateEmbeddings();
102
this._logService.debug(`Related Information: Index not loaded yet triggering background calculation, returning ${Date.now() - similarityStart}ms`);
103
return [];
104
}
105
if (token.isCancellationRequested) {
106
// return an array of 0s the same length as comparisons
107
this._logService.debug(`Related Information: Request cancelled, returning ${Date.now() - similarityStart}ms`);
108
return [];
109
}
110
const startOfEmbeddingRequest = Date.now();
111
const embeddingResult = await this.embeddingsComputer.computeEmbeddings(EmbeddingType.text3small_512, [query], {}, new TelemetryCorrelationId('RelatedInformationProviderEmbeddingsIndex::provideRelatedInformation'), token);
112
this._logService.debug(`Related Information: Remote similarly request took ${Date.now() - startOfEmbeddingRequest}ms`);
113
if (token.isCancellationRequested) {
114
// return an array of 0s the same length as comparisons
115
this._logService.debug(`Related Information: Request cancelled or no embeddings computed, returning ${Date.now() - similarityStart}ms`);
116
return [];
117
}
118
119
const results: RelatedInformationResult[] = [];
120
for (const item of this._items.values()) {
121
if (token.isCancellationRequested) {
122
this._logService.debug(`Related Information: Request cancelled, returning ${Date.now() - similarityStart}ms`);
123
break;
124
}
125
if (item.embedding) {
126
const score = distance(embeddingResult.values[0], { value: item.embedding, type: EmbeddingType.text3small_512 }).value;
127
if (score > this.relatedInformationConfig.threshold) {
128
results.push(this.toRelatedInformation(item, score));
129
}
130
}
131
}
132
133
this.logService.debug(`Related Information: Successfully Calculated, returning ${Date.now() - similarityStart}ms`);
134
135
// Only log non-cancelled settings related information queries
136
if (this.relatedInformationConfig.type === RelatedInformationType.SettingInformation) {
137
this.telemetryService.sendInternalMSFTTelemetryEvent('relatedInformationSettings', { query });
138
}
139
140
const returnthis = results
141
.sort((a, b) => b.weight - a.weight)
142
.slice(0, this.relatedInformationConfig.maxResults);
143
144
return returnthis;
145
}
146
147
protected abstract toRelatedInformation(value: V, score: number): RelatedInformationResult;
148
}
149
150
class CommandIdIndex extends RelatedInformationProviderEmbeddingsIndex<CommandListItem> {
151
constructor(
152
embeddingscache: IEmbeddingsCache,
153
@IEmbeddingsComputer embeddingsFetcher: IEmbeddingsComputer,
154
@ILogService logService: ILogService,
155
@ITelemetryService telemetryService: ITelemetryService,
156
@IWorkbenchService private readonly workbenchService: IWorkbenchService
157
) {
158
super(
159
'CommandIdIndex',
160
EmbeddingType.text3small_512,
161
'commandEmbeddings',
162
embeddingsFetcher,
163
embeddingscache,
164
{
165
type: RelatedInformationType.CommandInformation,
166
threshold: /* min threshold of 0 for text-3-small*/ 0,
167
maxResults: 100,
168
},
169
logService,
170
telemetryService
171
);
172
}
173
174
protected override async getLatestItems(): Promise<CommandListItem[]> {
175
const allCommands = await this.workbenchService.getAllCommands();
176
// This isn't in the command palette, but it's a useful command to suggest
177
allCommands.push({
178
label: 'Extensions: Search the marketplace for extensions',
179
command: 'workbench.extensions.search',
180
keybinding: 'Not set',
181
});
182
allCommands.push({
183
label: 'Extensions: Install extension from marketplace',
184
command: 'workbench.extensions.installExtension',
185
keybinding: 'Not set',
186
});
187
return allCommands.map(c => {
188
return {
189
key: c.command,
190
label: c.label.replace('View: Toggle', 'View: Toggle or Show or Hide'),
191
originalLabel: c.label,
192
keybinding: c.keybinding ?? 'Not set',
193
};
194
});
195
}
196
197
protected override getEmbeddingQueryString(value: CommandListItem): string {
198
return `${value.label} - ${value.key}`;
199
}
200
201
protected override toRelatedInformation(value: CommandListItem, score: number): CommandInformationResult {
202
return {
203
type: RelatedInformationType.CommandInformation,
204
weight: score,
205
command: value.key,
206
};
207
}
208
209
}
210
211
class SettingsIndex extends RelatedInformationProviderEmbeddingsIndex<SettingListItem> {
212
constructor(
213
embeddingsCache: IEmbeddingsCache,
214
@IEmbeddingsComputer embeddingsFetcher: IEmbeddingsComputer,
215
@ILogService logService: ILogService,
216
@ITelemetryService telemetryService: ITelemetryService,
217
@IWorkbenchService private readonly workbenchService: IWorkbenchService
218
) {
219
super(
220
'SettingsIndex',
221
EmbeddingType.text3small_512,
222
'settingEmbeddings',
223
embeddingsFetcher,
224
embeddingsCache,
225
{
226
type: RelatedInformationType.SettingInformation,
227
threshold: /* min threshold of 0 for text-3-small*/ 0,
228
maxResults: 100,
229
},
230
logService,
231
telemetryService
232
);
233
}
234
235
protected override async getLatestItems(): Promise<SettingListItem[]> {
236
const settings = await this.workbenchService.getAllSettings();
237
const settingsList: SettingListItem[] = [];
238
for (const settingId of Object.keys(settings)) {
239
const setting = settings[settingId];
240
if (setting.deprecationMessage || setting.markdownDeprecationMessage) {
241
continue;
242
}
243
settingsList.push({ ...setting, key: settingId });
244
}
245
return settingsList;
246
}
247
248
protected override getEmbeddingQueryString(value: SettingListItem): string {
249
return settingItemToContext(value);
250
}
251
252
protected override toRelatedInformation(value: SettingListItem, score: number): SettingInformationResult {
253
return {
254
type: RelatedInformationType.SettingInformation,
255
weight: score,
256
setting: value.key,
257
};
258
}
259
}
260
261
export interface ICombinedEmbeddingIndex {
262
readonly _serviceBrand: undefined;
263
readonly commandIdIndex: CommandIdIndex;
264
readonly settingsIndex: SettingsIndex;
265
loadIndexes(): Promise<void>;
266
nClosestValues(embedding: Embedding, n: number): Promise<{ commands: CommandListItem[]; settings: SettingListItem[] }>;
267
hasSetting(settingId: string): boolean;
268
hasCommand(commandId: string): boolean;
269
getSetting(settingId: string): SettingListItem | undefined;
270
getCommand(commandId: string): CommandListItem | undefined;
271
}
272
273
export const ICombinedEmbeddingIndex = createServiceIdentifier<ICombinedEmbeddingIndex>('ICombinedEmbeddingIndex');
274
275
/**
276
* Combines the settings and command indexes into a single index. This is what is consumed externally
277
* If necessary, the individual indices can be accessed
278
*/
279
export class VSCodeCombinedIndexImpl implements ICombinedEmbeddingIndex {
280
declare readonly _serviceBrand: undefined;
281
public readonly commandIdIndex: CommandIdIndex;
282
public readonly settingsIndex: SettingsIndex;
283
constructor(
284
useRemoteCache: boolean = true,
285
@IInstantiationService instantiationService: IInstantiationService,
286
@IEnvService envService: IEnvService
287
) {
288
// Local embeddings cache version is locked to 1.98
289
const settingsEmbeddingsCache = useRemoteCache ?
290
instantiationService.createInstance(RemoteEmbeddingsExtensionCache, EmbeddingCacheType.GLOBAL, 'settingEmbeddings', sanitizeVSCodeVersion(envService.getEditorInfo().version), EmbeddingType.text3small_512, RemoteCacheType.Settings) :
291
instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'settingEmbeddings', '1.98', EmbeddingType.text3small_512);
292
const commandsEmbeddingsCache = useRemoteCache ?
293
instantiationService.createInstance(RemoteEmbeddingsExtensionCache, EmbeddingCacheType.GLOBAL, 'commandEmbeddings', sanitizeVSCodeVersion(envService.getEditorInfo().version), EmbeddingType.text3small_512, RemoteCacheType.Commands) :
294
instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'commandEmbeddings', '1.98', EmbeddingType.text3small_512);
295
296
this.settingsIndex = instantiationService.createInstance(SettingsIndex, settingsEmbeddingsCache);
297
this.commandIdIndex = instantiationService.createInstance(CommandIdIndex, commandsEmbeddingsCache);
298
}
299
300
public async loadIndexes() {
301
await Promise.all([
302
this.commandIdIndex.isIndexLoaded ? Promise.resolve() : this.commandIdIndex.calculateEmbeddings(),
303
this.settingsIndex.isIndexLoaded ? Promise.resolve() : this.settingsIndex.calculateEmbeddings(),
304
]);
305
}
306
307
public async nClosestValues(embedding: Embedding, n: number) {
308
await this.loadIndexes();
309
return {
310
commands: this.commandIdIndex.nClosestValues(embedding, n),
311
settings: this.settingsIndex.nClosestValues(embedding, n),
312
};
313
}
314
315
public hasSetting(settingId: string): boolean {
316
return this.settingsIndex.hasItem(settingId);
317
}
318
319
public hasCommand(commandId: string): boolean {
320
return this.commandIdIndex.hasItem(commandId);
321
}
322
323
public getSetting(settingId: string): SettingListItem | undefined {
324
return this.settingsIndex.getItem(settingId);
325
}
326
327
public getCommand(commandId: string): CommandListItem | undefined {
328
return this.commandIdIndex.getItem(commandId);
329
}
330
}
331
332