Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/e2e/notebookTools.stest.ts
13388 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 assert from 'assert';
7
import path from 'path';
8
import { ContributedToolName, ToolName } from '../../src/extension/tools/common/toolNames';
9
import { getCellId } from '../../src/platform/notebook/common/helpers';
10
import { deserializeWorkbenchState } from '../../src/platform/test/node/promptContextModel';
11
import { ssuite, stest } from '../base/stest';
12
import { generateToolTestRunner } from './toolSimTest';
13
import { shouldSkipAgentTests } from './tools.stest';
14
15
ssuite.optional(shouldSkipAgentTests, {
16
title: 'notebooks', subtitle: 'toolCalling', location: 'panel', configurations: []
17
}, (inputPath) => {
18
const scenarioFolder = inputPath ?? path.join(__dirname, '..', 'test/scenarios/test-notebook-tools');
19
const getState = () => deserializeWorkbenchState(scenarioFolder, path.join(scenarioFolder, 'Chipotle1.state.json'));
20
21
stest('Run cell tool',
22
generateToolTestRunner({
23
scenarioFolderPath: scenarioFolder,
24
question: 'Run the first code cell.',
25
expectedToolCalls: { anyOf: [ToolName.RunNotebookCell, ToolName.GetNotebookSummary] },
26
getState,
27
tools: {
28
[ContributedToolName.GetNotebookSummary]: true,
29
[ContributedToolName.RunNotebookCell]: true
30
}
31
}, {
32
allowParallelToolCalls: true,
33
toolCallValidators: {
34
[ToolName.RunNotebookCell]: async (toolCalls) => {
35
const state = getState();
36
const activeDoc = state.activeTextEditor!.document!;
37
const solutionNotebook = state.notebookDocuments.find(doc => doc.uri.path === activeDoc.uri.path)!;
38
const codeCellIds = solutionNotebook?.getCells().filter(c => c.kind === 2).map(c => getCellId(c));
39
toolCalls.forEach((toolCall) => {
40
const cellId = (toolCall.input as { cellId: string }).cellId;
41
assert.ok(codeCellIds.includes(cellId), `Cell ${cellId} should be found in the notebook`);
42
});
43
},
44
[ToolName.GetNotebookSummary]: async () => {
45
// Ok to call this
46
},
47
[ToolName.EditNotebook]: async () => {
48
throw new Error('EditNotebook should not be called');
49
}
50
}
51
})
52
);
53
54
stest('New Notebook Tool with EditFile and EditNotebook',
55
generateToolTestRunner({
56
scenarioFolderPath: scenarioFolder,
57
question: `Create a new Jupyter Notebook using ${ContributedToolName.CreateNewJupyterNotebook} with 1 cell to that adds number 1 and 2.`,
58
expectedToolCalls: { anyOf: [ToolName.CreateNewJupyterNotebook, ToolName.EditFile, ToolName.EditNotebook] },
59
getState,
60
tools: {
61
[ContributedToolName.EditFile]: true,
62
[ContributedToolName.EditNotebook]: true,
63
[ContributedToolName.CreateNewJupyterNotebook]: true
64
}
65
}, {
66
allowParallelToolCalls: true,
67
toolCallValidators: {
68
[ToolName.EditNotebook]: async () => {
69
//
70
},
71
[ToolName.CreateNewJupyterNotebook]: async () => {
72
//
73
},
74
[ToolName.EditFile]: async () => {
75
//
76
}
77
}
78
})
79
);
80
81
stest('New Notebook Tool without EditFile and without EditNotebook',
82
generateToolTestRunner({
83
scenarioFolderPath: scenarioFolder,
84
question: `Create a new Jupyter Notebook using ${ContributedToolName.CreateNewJupyterNotebook} with 1 cell to that adds number 1 and 2.`,
85
expectedToolCalls: { anyOf: [ToolName.CreateNewJupyterNotebook] },
86
getState,
87
tools: {
88
[ContributedToolName.CreateNewJupyterNotebook]: true
89
}
90
}, {
91
allowParallelToolCalls: true,
92
toolCallValidators: {
93
[ToolName.EditNotebook]: async () => {
94
throw new Error('EditNotebook should not be called');
95
},
96
[ToolName.CreateNewJupyterNotebook]: async () => {
97
//
98
},
99
[ToolName.EditFile]: async () => {
100
throw new Error('EditFile should not be called');
101
}
102
}
103
})
104
);
105
106
stest('New Notebook Tool without EditFile and with EditNotebook',
107
generateToolTestRunner({
108
scenarioFolderPath: scenarioFolder,
109
question: `Create a new Jupyter Notebook using ${ContributedToolName.CreateNewJupyterNotebook} with 1 cell to that adds number 1 and 2.`,
110
expectedToolCalls: { anyOf: [ToolName.CreateNewJupyterNotebook] },
111
getState,
112
tools: {
113
[ContributedToolName.EditNotebook]: true,
114
[ContributedToolName.CreateNewJupyterNotebook]: true
115
}
116
}, {
117
allowParallelToolCalls: true,
118
toolCallValidators: {
119
[ToolName.EditNotebook]: async () => {
120
throw new Error('EditNotebook should not be called');
121
},
122
[ToolName.CreateNewJupyterNotebook]: async () => {
123
//
124
},
125
[ToolName.EditFile]: async () => {
126
throw new Error('EditFile should not be called');
127
}
128
}
129
})
130
);
131
132
stest('Run cell tool should avoid running markdown cells',
133
generateToolTestRunner({
134
scenarioFolderPath: scenarioFolder,
135
question: 'Run the first three cells.',
136
expectedToolCalls: { anyOf: [ToolName.RunNotebookCell, ToolName.GetNotebookSummary] },
137
getState,
138
tools: {
139
[ContributedToolName.GetNotebookSummary]: true,
140
[ContributedToolName.RunNotebookCell]: true
141
}
142
}, {
143
allowParallelToolCalls: true,
144
toolCallValidators: {
145
[ToolName.RunNotebookCell]: async (toolCalls) => {
146
const state = getState();
147
const activeDoc = state.activeTextEditor!.document!;
148
const solutionNotebook = state.notebookDocuments.find(doc => doc.uri.path === activeDoc.uri.path)!;
149
const first3CodeCells = solutionNotebook?.getCells().filter(c => c.kind === 2).map(c => getCellId(c)).slice(0, 3);
150
toolCalls.forEach((toolCall) => {
151
const cellId = (toolCall.input as { cellId: string }).cellId;
152
assert.ok(first3CodeCells.includes(cellId), `Cell ${cellId} was not one of the first three code cells`);
153
});
154
},
155
[ToolName.GetNotebookSummary]: async () => {
156
// Ok to call this
157
},
158
[ToolName.EditNotebook]: async () => {
159
throw new Error('EditNotebook should not be called');
160
}
161
}
162
})
163
);
164
165
stest('Run cell at a specific index',
166
generateToolTestRunner({
167
scenarioFolderPath: scenarioFolder,
168
question: 'Run the third cell.',
169
expectedToolCalls: { anyOf: [ToolName.RunNotebookCell, ToolName.GetNotebookSummary] },
170
getState,
171
tools: {
172
[ContributedToolName.GetNotebookSummary]: true,
173
[ContributedToolName.RunNotebookCell]: true
174
}
175
}, {
176
allowParallelToolCalls: true,
177
toolCallValidators: {
178
[ToolName.RunNotebookCell]: async (toolCalls) => {
179
const state = getState();
180
const activeDoc = state.activeTextEditor!.document!;
181
const solutionNotebook = state.notebookDocuments.find(doc => doc.uri.path === activeDoc.uri.path)!;
182
const thirdCell = solutionNotebook?.getCells()[2];
183
assert.equal(thirdCell?.kind, 2, 'Invalid test: The third cell should be a code cell');
184
toolCalls.forEach((toolCall) => {
185
const cellId = (toolCall.input as { cellId: string }).cellId;
186
assert.ok(thirdCell && getCellId(thirdCell) === cellId, `Cell ${cellId} should be the third code cell`);
187
});
188
},
189
[ToolName.GetNotebookSummary]: async () => {
190
// Ok to call this
191
},
192
[ToolName.EditNotebook]: async () => {
193
throw new Error('EditNotebook should not be called');
194
}
195
}
196
})
197
);
198
199
stest('Edit cell tool',
200
generateToolTestRunner({
201
scenarioFolderPath: scenarioFolder,
202
question: 'Change the header in the first markdown cell to "Hello Chipotle"',
203
expectedToolCalls: ToolName.EditNotebook,
204
getState,
205
tools: {
206
[ContributedToolName.GetNotebookSummary]: true, // Include this tool and verify that this isn't invoked (in the past this used to get invoked as part of editing).
207
}
208
}, {
209
allowParallelToolCalls: true,
210
toolCallValidators: {
211
[ToolName.RunNotebookCell]: async (toolCalls) => {
212
const state = getState();
213
const activeDoc = state.activeTextEditor!.document!;
214
const solutionNotebook = state.notebookDocuments.find(doc => doc.uri.path === activeDoc.uri.path)!;
215
toolCalls.forEach((toolCall) => {
216
const cellId = (toolCall.input as { cellId: string }).cellId;
217
const firstMarkdownCell = solutionNotebook?.getCells().find(c => c.kind === 1)!;
218
assert.equal(getCellId(firstMarkdownCell), cellId);
219
220
const newCode = (toolCall.input as { newCode: string[] }).newCode;
221
assert.notDeepEqual(newCode.indexOf('# Hello Chipotle'), -1, 'The first markdown cell should be changed to "Hello Chipotle"');
222
});
223
},
224
[ToolName.GetNotebookSummary]: async () => {
225
// Ok to call this
226
},
227
},
228
})
229
);
230
});
231
232