Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/test/node/notebookPromptRendering.spec.ts
13399 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 { beforeAll, beforeEach, describe, expect, test } from 'vitest';
7
import type * as vscode from 'vscode';
8
import { getTextPart } from '../../../platform/chat/common/globalStringUtils';
9
import { CHAT_MODEL } from '../../../platform/configuration/common/configurationService';
10
import { DiffServiceImpl } from '../../../platform/diff/node/diffServiceImpl';
11
import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';
12
import { MockEndpoint } from '../../../platform/endpoint/test/node/mockEndpoint';
13
import { ILogger, ILogService } from '../../../platform/log/common/logService';
14
import { IChatEndpoint } from '../../../platform/networking/common/networking';
15
import { IAlternativeNotebookContentService } from '../../../platform/notebook/common/alternativeContent';
16
import { AlternativeNotebookContentEditGenerator, IAlternativeNotebookContentEditGenerator } from '../../../platform/notebook/common/alternativeContentEditGenerator';
17
import { INotebookService, PipPackage, VariablesResult } from '../../../platform/notebook/common/notebookService';
18
import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEditorsService';
19
import { IExperimentationService, NullExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
20
import { NullTelemetryService } from '../../../platform/telemetry/common/nullTelemetryService';
21
import { ITestingServicesAccessor } from '../../../platform/test/node/services';
22
import { SimulationAlternativeNotebookContentService, TestingTabsAndEditorsService } from '../../../platform/test/node/simulationWorkspaceServices';
23
import { ITokenizerProvider } from '../../../platform/tokenizer/node/tokenizer';
24
import { AbstractWorkspaceService, IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
25
import { ExtHostNotebookDocumentData } from '../../../util/common/test/shims/notebookDocument';
26
import { TokenizerType } from '../../../util/common/tokenizer';
27
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
28
import { Event } from '../../../util/vs/base/common/event';
29
import { URI } from '../../../util/vs/base/common/uri';
30
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
31
import { NotebookCellData, NotebookCellKind, NotebookData, Range, Selection, Uri } from '../../../vscodeTypes';
32
import { ChatVariablesCollection } from '../../prompt/common/chatVariablesCollection';
33
import { IDocumentContext } from '../../prompt/node/documentContext';
34
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
35
import { InlineChatNotebookGeneratePrompt } from '../../prompts/node/inline/inlineChatNotebookGeneratePrompt';
36
import { createExtensionUnitTestingServices } from './services';
37
38
function getFakeDocumentContext(notebook: vscode.NotebookDocument, index: number = 0) {
39
const cell = notebook.getCells()[index];
40
const docSnapshot = TextDocumentSnapshot.create(cell.document);
41
42
const context: IDocumentContext = {
43
document: docSnapshot,
44
language: { languageId: docSnapshot.languageId, lineComment: { start: '//' } },
45
fileIndentInfo: undefined,
46
wholeRange: new Range(0, 0, 1, 0),
47
selection: new Selection(0, 0, 0, 0),
48
};
49
50
return context;
51
}
52
53
function getFakeNotebookEditor(): vscode.NotebookEditor {
54
const cells = [
55
new NotebookCellData(NotebookCellKind.Code, 'print("hello")', 'python'),
56
new NotebookCellData(NotebookCellKind.Code, 'print("world")', 'python'),
57
];
58
const uri = URI.from({ scheme: 'file', path: '/path/file.ipynb' });
59
const notebook = ExtHostNotebookDocumentData.fromNotebookData(uri, new NotebookData(cells), 'jupyter-notebook').document;
60
const selection = {
61
start: 1,
62
end: 2,
63
isEmpty: false,
64
with() {
65
return selection;
66
}
67
};
68
69
return {
70
notebook,
71
revealRange() { },
72
selections: [selection],
73
selection: selection,
74
visibleRanges: [],
75
viewColumn: 1
76
};
77
}
78
79
describe('Notebook Prompt Rendering', function () {
80
let accessor: ITestingServicesAccessor;
81
const contexts: IDocumentContext[] = [];
82
const treatmeants = {
83
'copilotchat.notebookPackages': false,
84
'copilotchat.notebookPriorities': false
85
};
86
87
beforeAll(() => {
88
const notebookEditor = getFakeNotebookEditor();
89
contexts.length = 0;
90
contexts.push(getFakeDocumentContext(notebookEditor.notebook, 0));
91
contexts.push(getFakeDocumentContext(notebookEditor.notebook, 1));
92
93
const testingServiceCollection = createExtensionUnitTestingServices();
94
testingServiceCollection.define(ITabsAndEditorsService, new TestingTabsAndEditorsService({
95
getActiveTextEditor: () => undefined,
96
getVisibleTextEditors: () => [],
97
getActiveNotebookEditor: () => notebookEditor
98
}));
99
testingServiceCollection.define(IWorkspaceService, new class extends AbstractWorkspaceService {
100
override fs!: vscode.FileSystem;
101
override textDocuments: readonly vscode.TextDocument[] = [];
102
override notebookDocuments: readonly vscode.NotebookDocument[] = [notebookEditor.notebook];
103
override onDidOpenTextDocument = Event.None;
104
override onDidCloseTextDocument = Event.None;
105
override onDidOpenNotebookDocument = Event.None;
106
override onDidCloseNotebookDocument = Event.None;
107
override onDidChangeTextDocument = Event.None;
108
override onDidChangeWorkspaceFolders = Event.None;
109
override onDidChangeNotebookDocument = Event.None;
110
override onDidChangeTextEditorSelection = Event.None;
111
override openTextDocument(uri: vscode.Uri): Promise<vscode.TextDocument> {
112
throw new Error('Method not implemented.');
113
}
114
override showTextDocument(document: vscode.TextDocument): Promise<void> {
115
throw new Error('Method not implemented.');
116
}
117
override async openNotebookDocument(uri: Uri): Promise<vscode.NotebookDocument>;
118
override async openNotebookDocument(notebookType: string, content?: vscode.NotebookData): Promise<vscode.NotebookDocument>;
119
override async openNotebookDocument(arg1: Uri | string, arg2?: vscode.NotebookData): Promise<vscode.NotebookDocument> {
120
throw new Error('Method not implemented.');
121
}
122
123
override getWorkspaceFolders(): URI[] {
124
return [];
125
}
126
override getWorkspaceFolderName(workspaceFolderUri: URI): string {
127
return '';
128
}
129
override ensureWorkspaceIsFullyLoaded(): Promise<void> {
130
throw new Error('Method not implemented.');
131
}
132
override async showWorkspaceFolderPicker(): Promise<vscode.WorkspaceFolder | undefined> {
133
return;
134
}
135
override applyEdit(edit: vscode.WorkspaceEdit): Thenable<boolean> {
136
throw new Error('Method not implemented.');
137
}
138
override isResourceTrusted(_resource: vscode.Uri): Thenable<boolean> {
139
return Promise.resolve(true);
140
}
141
override requestResourceTrust(_options: vscode.ResourceTrustRequestOptions): Thenable<boolean | undefined> {
142
return Promise.resolve(true);
143
}
144
override requestWorkspaceTrust(_options?: vscode.WorkspaceTrustRequestOptions): Thenable<boolean | undefined> {
145
return Promise.resolve(true);
146
}
147
148
});
149
testingServiceCollection.define(IExperimentationService, new class extends NullExperimentationService {
150
override getTreatmentVariable<T extends string | number | boolean>(_name: string): T | undefined {
151
if (_name === 'copilotchat.notebookPackages' || _name === 'copilotchat.notebookPriorities') {
152
return treatmeants[_name] as T;
153
}
154
155
return undefined;
156
}
157
});
158
testingServiceCollection.define(INotebookService, new class implements INotebookService {
159
_serviceBrand: undefined;
160
async getVariables(notebook: Uri): Promise<VariablesResult[]> {
161
return [
162
{
163
variable: {
164
name: 'x',
165
value: '1',
166
type: 'int',
167
summary: 'int'
168
},
169
hasNamedChildren: false,
170
indexedChildrenCount: 0
171
}
172
];
173
}
174
async getPipPackages(notebook: Uri): Promise<PipPackage[]> {
175
return [
176
{ name: 'numpy', version: '1.0.0' }
177
];
178
}
179
setVariables(notebook: Uri, variables: VariablesResult[]): void {
180
}
181
getCellExecutions(notebook: vscode.Uri): vscode.NotebookCell[] {
182
return [];
183
}
184
runCells(notebook: Uri, range: { start: number; end: number }, autoreveal: boolean): Promise<void> {
185
return Promise.resolve();
186
}
187
ensureKernelSelected(notebook: Uri): Promise<void> {
188
return Promise.resolve();
189
}
190
populateNotebookProviders(): void {
191
return;
192
}
193
hasSupportedNotebooks(uri: Uri): boolean {
194
return false;
195
}
196
trackAgentUsage() { }
197
setFollowState(state: boolean): void { }
198
getFollowState(): boolean {
199
return false;
200
}
201
});
202
const mockLogger: ILogger = {
203
error: () => { /* no-op */ },
204
warn: () => { /* no-op */ },
205
info: () => { /* no-op */ },
206
debug: () => { /* no-op */ },
207
trace: () => { /* no-op */ },
208
show: () => { /* no-op */ },
209
createSubLogger(): ILogger { return mockLogger; },
210
withExtraTarget(): ILogger { return mockLogger; }
211
};
212
testingServiceCollection.define(IAlternativeNotebookContentService, new SimulationAlternativeNotebookContentService('json'));
213
testingServiceCollection.define(IAlternativeNotebookContentEditGenerator, new AlternativeNotebookContentEditGenerator(new SimulationAlternativeNotebookContentService('json'), new DiffServiceImpl(), new class implements ILogService {
214
_serviceBrand: undefined;
215
internal = mockLogger;
216
logger = mockLogger;
217
trace = mockLogger.trace;
218
debug = mockLogger.debug;
219
info = mockLogger.info;
220
warn = mockLogger.warn;
221
error = mockLogger.error;
222
show(preserveFocus?: boolean): void {
223
//
224
}
225
createSubLogger(): ILogger {
226
return this;
227
}
228
withExtraTarget(): ILogger {
229
return this;
230
}
231
}(), new NullTelemetryService()));
232
accessor = testingServiceCollection.createTestingAccessor();
233
});
234
235
beforeEach(() => {
236
treatmeants['copilotchat.notebookPackages'] = false;
237
treatmeants['copilotchat.notebookPriorities'] = false;
238
});
239
240
test('Notebook prompt structure is rendered correctly', async function () {
241
const endpoint = accessor.get(IInstantiationService).createInstance(MockEndpoint, undefined);
242
const progressReporter = { report() { } };
243
const renderer = PromptRenderer.create(accessor.get(IInstantiationService), endpoint, InlineChatNotebookGeneratePrompt, {
244
documentContext: contexts[1],
245
promptContext: {
246
query: 'print hello world',
247
chatVariables: new ChatVariablesCollection([]),
248
history: [],
249
}
250
});
251
const promptResult = await renderer.render(progressReporter, CancellationToken.None);
252
expect(promptResult.messages.length).toBe(5);
253
expect(getTextPart(promptResult.messages[0].content)).contains('AI programming'); // System message
254
expect(getTextPart(promptResult.messages[1].content)).contains('I am working on a Jupyter notebook'); // Notebook Document Context
255
expect(getTextPart(promptResult.messages[2].content)).contains('Now I edit a cell'); // Current Cell
256
expect(getTextPart(promptResult.messages[3].content)).contains('The following variables'); // Variables
257
expect(getTextPart(promptResult.messages[4].content)).contains('print hello world'); // User Query
258
});
259
260
test('Disable package should not render packages', async function () {
261
treatmeants['copilotchat.notebookPackages'] = true;
262
const endpoint = accessor.get(IInstantiationService).createInstance(MockEndpoint, undefined);
263
const progressReporter = { report() { } };
264
const renderer = PromptRenderer.create(accessor.get(IInstantiationService), endpoint, InlineChatNotebookGeneratePrompt, {
265
documentContext: contexts[1],
266
promptContext: {
267
query: 'print hello world',
268
chatVariables: new ChatVariablesCollection([]),
269
history: [],
270
}
271
});
272
const promptResult = await renderer.render(progressReporter, CancellationToken.None);
273
/**
274
* System+Instructions
275
* Notebook Document Context
276
* Current Cell
277
* Variables
278
* User Query
279
*/
280
expect(promptResult.messages.length).toBe(5);
281
});
282
283
test('Priorities: Package should be dropped first', async function () {
284
treatmeants['copilotchat.notebookPriorities'] = true;
285
const endpoint: IChatEndpoint = {
286
modelMaxPromptTokens: 880,
287
supportsToolCalls: false,
288
supportsVision: false,
289
supportsPrediction: false,
290
isPremium: false,
291
multiplier: 0,
292
maxOutputTokens: 4096,
293
tokenizer: TokenizerType.O200K,
294
modelProvider: 'Test',
295
name: 'Test',
296
family: 'Test',
297
version: 'Test',
298
showInModelPicker: false,
299
isFallback: false,
300
urlOrRequestMetadata: '',
301
model: CHAT_MODEL.GPT41,
302
acquireTokenizer() {
303
return accessor.get(ITokenizerProvider).acquireTokenizer({ tokenizer: TokenizerType.O200K });
304
},
305
processResponseFromChatEndpoint: async () => { throw new Error('Method not implemented.'); },
306
cloneWithTokenOverride: () => endpoint,
307
createRequestBody: () => { return {}; },
308
makeChatRequest2: () => { throw new Error('Method not implemented.'); },
309
makeChatRequest: async () => { throw new Error('Method not implemented.'); },
310
};
311
const progressReporter = { report() { } };
312
const renderer = PromptRenderer.create(accessor.get(IInstantiationService), endpoint, InlineChatNotebookGeneratePrompt, {
313
documentContext: contexts[1],
314
promptContext: {
315
query: 'print hello world',
316
chatVariables: new ChatVariablesCollection([]),
317
history: [],
318
}
319
});
320
const promptResult = await renderer.render(progressReporter, CancellationToken.None);
321
expect(promptResult.messages.length).toBe(5);
322
expect(getTextPart(promptResult.messages[3].content)).contains('The following variables'); // Variables
323
expect(getTextPart(promptResult.messages[4].content)).contains('print hello world'); // User Query
324
});
325
});
326
327