Skip to content

Commit b439fc4

Browse files
committed
nes: /models: prioritize exp model config over preferred model
1 parent 6f7c3b0 commit b439fc4

File tree

1 file changed

+106
-22
lines changed

1 file changed

+106
-22
lines changed

src/platform/inlineEdits/node/inlineEditsModelService.ts

Lines changed: 106 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { derived, IObservable, observableFromEvent } from '../../../util/vs/base
1515
import { CopilotToken } from '../../authentication/common/copilotToken';
1616
import { ICopilotTokenStore } from '../../authentication/common/copilotTokenStore';
1717
import { ConfigKey, ExperimentBasedConfig, IConfigurationService } from '../../configuration/common/configurationService';
18+
import { IVSCodeExtensionContext } from '../../extContext/common/extensionContext';
1819
import { ILogService } from '../../log/common/logService';
1920
import { IProxyModelsService } from '../../proxyModels/common/proxyModelsService';
2021
import { IExperimentationService } from '../../telemetry/common/nullExperimentationService';
@@ -71,17 +72,20 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
7172
private _expBasedModelConfigObs = this._configService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsXtabProviderModelConfigurationString, this._expService);
7273
private _defaultModelConfigObs = this._configService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsXtabProviderDefaultModelConfigurationString, this._expService);
7374

74-
private _models: IObservable<Model[]>;
75-
private _currentModelIdObs: IObservable<Model>;
76-
private _modelInfo: IObservable<ModelInfo>;
75+
private _modelsObs: IObservable<Model[]>;
76+
private _currentModelObs: IObservable<Model>;
77+
private _modelInfoObs: IObservable<ModelInfo>;
7778

7879
public readonly onModelListUpdated: Event<void>;
7980

8081
private _tracer = createTracer(['NES', 'ModelsService'], (msg) => this._logService.trace(msg));
8182

83+
private _undesiredModelsManager: UndesiredModels.Manager;
84+
8285
constructor(
8386
@ICopilotTokenStore private readonly _tokenStore: ICopilotTokenStore,
8487
@IProxyModelsService private readonly _proxyModelsService: IProxyModelsService,
88+
@IVSCodeExtensionContext private readonly _vscodeExtensionContext: IVSCodeExtensionContext,
8589
@IConfigurationService private readonly _configService: IConfigurationService,
8690
@IExperimentationService private readonly _expService: IExperimentationService,
8791
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@@ -91,7 +95,7 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
9195

9296
const tracer = this._tracer.sub('constructor');
9397

94-
this._models = derived((reader) => {
98+
this._modelsObs = derived((reader) => {
9599
tracer.trace('computing models');
96100
return this.computeModelInfo({
97101
copilotToken: this._copilotTokenObs.read(reader),
@@ -102,32 +106,34 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
102106
});
103107
}).recomputeInitiallyAndOnChange(this._store);
104108

105-
this._currentModelIdObs = derived<Model, void>((reader) => {
109+
this._currentModelObs = derived<Model, void>((reader) => {
106110
tracer.trace('computing current model');
107111
return this._pickModel({
108112
preferredModelName: this._preferredModelNameObs.read(reader),
109-
models: this._models.read(reader),
113+
models: this._modelsObs.read(reader),
110114
});
111115
}).recomputeInitiallyAndOnChange(this._store);
112116

113-
this._modelInfo = derived((reader) => {
117+
this._modelInfoObs = derived((reader) => {
114118
tracer.trace('computing model info');
115119
return {
116-
models: this._models.read(reader),
117-
currentModelId: this._currentModelIdObs.read(reader).modelName,
120+
models: this._modelsObs.read(reader),
121+
currentModelId: this._currentModelObs.read(reader).modelName,
118122
};
119123
}).recomputeInitiallyAndOnChange(this._store);
120124

121-
this.onModelListUpdated = Event.fromObservableLight(this._modelInfo);
125+
this.onModelListUpdated = Event.fromObservableLight(this._modelInfoObs);
126+
127+
this._undesiredModelsManager = new UndesiredModels.Manager(this._vscodeExtensionContext);
122128
}
123129

124130
get modelInfo(): vscode.InlineCompletionModelInfo | undefined {
125-
const models: vscode.InlineCompletionModel[] = this._models.get().map(m => ({
131+
const models: vscode.InlineCompletionModel[] = this._modelsObs.get().map(m => ({
126132
id: m.modelName,
127133
name: m.modelName,
128134
}));
129135

130-
const currentModel = this._currentModelIdObs.get();
136+
const currentModel = this._currentModelObs.get();
131137

132138
return {
133139
models,
@@ -136,23 +142,44 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
136142
}
137143

138144

139-
async setCurrentModelId(modelId: string): Promise<void> {
140-
const preferredModel = this._configService.getExperimentBasedConfig(ConfigKey.Advanced.InlineEditsPreferredModel, this._expService);
145+
// FIXME@ulugbekna: don't do async risking race condition; use a TaskQueue to serialize updates?
146+
async setCurrentModelId(newPreferredModelId: string): Promise<void> {
147+
const currentPreferredModelId = this._configService.getExperimentBasedConfig(ConfigKey.Advanced.InlineEditsPreferredModel, this._expService);
141148

142-
const isSameModel = preferredModel === modelId;
149+
const isSameModel = currentPreferredModelId === newPreferredModelId;
143150
if (isSameModel) {
144151
return;
145152
}
146153

147-
if (!this._models.get().some(m => m.modelName === modelId)) {
148-
this._logService.warn(`Trying to set unknown model id: ${modelId}`);
154+
// snapshot before async calls
155+
const currentPreferredModel = this._currentModelObs.get();
156+
157+
const models = this._modelsObs.get();
158+
const newPreferredModel = models.find(m => m.modelName === newPreferredModelId);
159+
160+
if (newPreferredModel === undefined) {
161+
this._logService.error(`New preferred model id ${newPreferredModelId} not found in model list.`);
149162
return;
150163
}
151164

152165
// if user picks same as the default model, we should reset the user setting
153166
// otherwise, update the model
167+
if (newPreferredModelId === models.at(0)?.modelName) {
168+
this._tracer.trace(`New preferred model id ${newPreferredModelId} is the same as the default model, resetting user setting.`);
169+
await this._configService.setConfig(ConfigKey.Advanced.InlineEditsPreferredModel, 'none');
170+
} else {
171+
this._tracer.trace(`New preferred model id ${newPreferredModelId} is different from the default model, updating user setting to ${newPreferredModelId}.`);
172+
await this._configService.setConfig(ConfigKey.Advanced.InlineEditsPreferredModel, newPreferredModelId);
173+
}
174+
175+
// if currently selected model is from exp config, then mark that model as undesired
176+
if (currentPreferredModel.source === ModelSource.ExpConfig) {
177+
await this._undesiredModelsManager.addUndesiredModelId(currentPreferredModel.modelName);
178+
}
154179

155-
await this._configService.setConfig(ConfigKey.Advanced.InlineEditsPreferredModel, modelId);
180+
if (this._undesiredModelsManager.isUndesiredModelId(newPreferredModelId)) {
181+
await this._undesiredModelsManager.removeUndesiredModelId(newPreferredModelId);
182+
}
156183
}
157184

158185
private computeModelInfo(
@@ -235,9 +262,9 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
235262

236263
public selectedModelConfiguration(): ModelConfiguration {
237264
const tracer = this._tracer.sub('selectedModelConfiguration');
238-
const currentModel = this._currentModelIdObs.get();
265+
const currentModel = this._currentModelObs.get();
239266
tracer.trace(`Current model id: ${currentModel.modelName}`);
240-
const model = this._models.get().find(m => m.modelName === currentModel.modelName);
267+
const model = this._modelsObs.get().find(m => m.modelName === currentModel.modelName);
241268
if (model) {
242269
tracer.trace(`Selected model found: ${model.modelName}`);
243270
return {
@@ -274,9 +301,22 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
274301
preferredModelName: string;
275302
models: Model[];
276303
}): Model {
277-
const userHasPreferredModel = preferredModelName !== 'none';
304+
// priority of picking a model:
305+
// 0. model from modelConfigurationString setting from ExP, unless marked as undesired
306+
// 1. user preferred model
307+
// 2. first model in the list
308+
309+
const expConfiguredModel = models.find(m => m.source === ModelSource.ExpConfig);
310+
if (expConfiguredModel) {
311+
const isUndesiredModelId = this._undesiredModelsManager.isUndesiredModelId(expConfiguredModel.modelName);
312+
if (isUndesiredModelId) {
313+
this._tracer.trace(`Exp-configured model ${expConfiguredModel.modelName} is marked as undesired by the user. Skipping.`);
314+
} else {
315+
return expConfiguredModel;
316+
}
317+
}
278318

279-
// FIXME@ulugbekna: respect exp-set model name
319+
const userHasPreferredModel = preferredModelName !== 'none';
280320

281321
if (userHasPreferredModel) {
282322
const preferredModel = models.find(m => m.modelName === preferredModelName);
@@ -321,3 +361,47 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
321361
return parsedConfig;
322362
}
323363
}
364+
365+
namespace UndesiredModels {
366+
367+
const UNDESIRED_MODELS_KEY = 'copilot.chat.nextEdits.undesiredModelIds';
368+
type UndesiredModelsValue = string[];
369+
370+
export class Manager {
371+
372+
constructor(
373+
private readonly _vscodeExtensionContext: IVSCodeExtensionContext,
374+
) {
375+
}
376+
377+
isUndesiredModelId(modelId: string) {
378+
const models = this._getModels();
379+
return models.includes(modelId);
380+
}
381+
382+
async addUndesiredModelId(modelId: string): Promise<void> {
383+
const models = this._getModels();
384+
if (!models.includes(modelId)) {
385+
models.push(modelId);
386+
return this._setModels(models);
387+
}
388+
}
389+
390+
async removeUndesiredModelId(modelId: string): Promise<void> {
391+
const models = this._getModels();
392+
const index = models.indexOf(modelId);
393+
if (index !== -1) {
394+
models.splice(index, 1);
395+
return this._setModels(models);
396+
}
397+
}
398+
399+
private _getModels(): string[] {
400+
return this._vscodeExtensionContext.globalState.get<UndesiredModelsValue>(UNDESIRED_MODELS_KEY) ?? [];
401+
}
402+
403+
private _setModels(models: string[]): Thenable<void> {
404+
return this._vscodeExtensionContext.globalState.update(UNDESIRED_MODELS_KEY, models);
405+
}
406+
}
407+
}

0 commit comments

Comments
 (0)