Skip to content

Commit fcc64c0

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

File tree

1 file changed

+114
-25
lines changed

1 file changed

+114
-25
lines changed

src/platform/inlineEdits/node/inlineEditsModelService.ts

Lines changed: 114 additions & 25 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,9 +95,9 @@ 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');
96-
return this.computeModelInfo({
100+
return this.aggregateModels({
97101
copilotToken: this._copilotTokenObs.read(reader),
98102
fetchedNesModels: this._fetchedModelsObs.read(reader),
99103
localModelConfig: this._localModelConfigObs.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,26 +142,48 @@ 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+
const expectedDefaultModel = this._pickModel({ preferredModelName: 'none', models });
168+
if (newPreferredModelId === expectedDefaultModel.modelName) {
169+
this._tracer.trace(`New preferred model id ${newPreferredModelId} is the same as the default model, resetting user setting.`);
170+
await this._configService.setConfig(ConfigKey.Advanced.InlineEditsPreferredModel, 'none');
171+
} else {
172+
this._tracer.trace(`New preferred model id ${newPreferredModelId} is different from the default model, updating user setting to ${newPreferredModelId}.`);
173+
await this._configService.setConfig(ConfigKey.Advanced.InlineEditsPreferredModel, newPreferredModelId);
174+
}
175+
176+
// if currently selected model is from exp config, then mark that model as undesired
177+
if (currentPreferredModel.source === ModelSource.ExpConfig) {
178+
await this._undesiredModelsManager.addUndesiredModelId(currentPreferredModel.modelName);
179+
}
154180

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

158-
private computeModelInfo(
186+
private aggregateModels(
159187
{
160188
copilotToken,
161189
fetchedNesModels,
@@ -170,7 +198,7 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
170198
defaultModelConfigString: string | undefined;
171199
},
172200
): Model[] {
173-
const tracer = this._tracer.sub('computeModelInfo');
201+
const tracer = this._tracer.sub('aggregateModels');
174202

175203
const models: Model[] = [];
176204

@@ -235,9 +263,9 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
235263

236264
public selectedModelConfiguration(): ModelConfiguration {
237265
const tracer = this._tracer.sub('selectedModelConfiguration');
238-
const currentModel = this._currentModelIdObs.get();
266+
const currentModel = this._currentModelObs.get();
239267
tracer.trace(`Current model id: ${currentModel.modelName}`);
240-
const model = this._models.get().find(m => m.modelName === currentModel.modelName);
268+
const model = this._modelsObs.get().find(m => m.modelName === currentModel.modelName);
241269
if (model) {
242270
tracer.trace(`Selected model found: ${model.modelName}`);
243271
return {
@@ -274,9 +302,22 @@ export class InlineEditsModelService extends Disposable implements IInlineEditsM
274302
preferredModelName: string;
275303
models: Model[];
276304
}): Model {
277-
const userHasPreferredModel = preferredModelName !== 'none';
305+
// priority of picking a model:
306+
// 0. model from modelConfigurationString setting from ExP, unless marked as undesired
307+
// 1. user preferred model
308+
// 2. first model in the list
309+
310+
const expConfiguredModel = models.find(m => m.source === ModelSource.ExpConfig);
311+
if (expConfiguredModel) {
312+
const isUndesiredModelId = this._undesiredModelsManager.isUndesiredModelId(expConfiguredModel.modelName);
313+
if (isUndesiredModelId) {
314+
this._tracer.trace(`Exp-configured model ${expConfiguredModel.modelName} is marked as undesired by the user. Skipping.`);
315+
} else {
316+
return expConfiguredModel;
317+
}
318+
}
278319

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

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

0 commit comments

Comments
 (0)