Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/inlineChatNotebookCommonPromptElements.tsx
13405 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 { BasePromptElementProps, PromptElement, PromptElementProps, PromptSizing, TextChunk, TokenLimit, UserMessage } from '@vscode/prompt-tsx';
7
import type * as vscode from 'vscode';
8
import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';
9
import { INotebookService, PipPackage, VariablesResult } from '../../../../platform/notebook/common/notebookService';
10
import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';
11
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
12
import { ILanguage } from '../../../../util/common/languages';
13
import { createFencedCodeBlock } from '../../../../util/common/markdown';
14
import { isNotebookCellOrNotebookChatInput } from '../../../../util/common/notebooks';
15
import { illegalArgument } from '../../../../util/vs/base/common/errors';
16
import { Range } from '../../../../vscodeTypes';
17
import { generateNotebookCellContext, getSelectionAndCodeAroundSelection } from '../../../context/node/resolvers/inlineChatSelection';
18
import { CodeContextRegion, CodeContextTracker } from '../../../inlineChat/node/codeContextRegion';
19
import { IDocumentContext } from '../../../prompt/node/documentContext';
20
import { Tag } from '../base/tag';
21
import { NotebookPromptPriority } from './inlineChatNotebookCommon';
22
import { PromptingSummarizedDocument } from './promptingSummarizedDocument';
23
import { ProjectedDocument } from './summarizedDocument/summarizeDocument';
24
25
export interface InlineChatNotebookBasePromptState {
26
summarizedDocument: PromptingSummarizedDocument;
27
isIgnored: boolean;
28
priorities: NotebookPromptPriority;
29
tagBasedDocumentSummary: boolean;
30
}
31
32
export interface InlineChatNotebookSelectionCommonProps extends BasePromptElementProps {
33
documentContext: IDocumentContext;
34
}
35
36
export interface InlineChatNotebookSelectionState {
37
wholeRange: Range;
38
executedCells?: vscode.NotebookCell[];
39
}
40
41
export interface InlineChatCellSelectionProps extends BasePromptElementProps {
42
readonly cellIndex: number;
43
readonly document: TextDocumentSnapshot;
44
readonly projectedDocument: ProjectedDocument;
45
readonly language: ILanguage;
46
readonly diagnostics: vscode.Diagnostic[];
47
readonly selection: vscode.Selection;
48
readonly adjustedSelection: Range;
49
readonly isSummarized: boolean;
50
readonly selectedLinesContent: string;
51
}
52
53
export class NotebookCellList extends PromptElement<{ title: string; cells: CodeContextRegion[]; cellIndexDelta?: number } & BasePromptElementProps> {
54
override render() {
55
return <>
56
{this.props.title}<br />
57
{this.props.cells.map((cell, index) => (<NotebookCellContent index={index + (this.props.cellIndexDelta ?? 0)} cell={cell} />))}
58
</>;
59
}
60
}
61
62
class NotebookCellContent extends PromptElement<{ index: number; cell: CodeContextRegion } & BasePromptElementProps> {
63
override render() {
64
return <>
65
CELL INDEX: {this.props.index}<br />
66
```{this.props.cell.language.languageId}<br />
67
{this.props.cell.lines.join('\n')}<br />
68
```
69
</>;
70
}
71
}
72
73
interface InlineChatJupyterNotebookCellsContextRendererProps extends BasePromptElementProps {
74
documentContext: IDocumentContext;
75
aboveCells: CodeContextRegion[];
76
belowCells: CodeContextRegion[];
77
}
78
79
/**
80
* Notebook cell context renderer. Used by Generate and Edit intents.
81
* It includes the document context of the notebook. It' using legacy prompt technique to include the examples and the cell position and content.
82
*/
83
export class InlineChatJupyterNotebookCellsContextRenderer extends PromptElement<InlineChatJupyterNotebookCellsContextRendererProps> {
84
render(state: void, sizing: PromptSizing) {
85
if (!isNotebookCellOrNotebookChatInput(this.props.documentContext.document.uri)) {
86
throw illegalArgument('InlineChatNotebookSelectionRenderer should be used only with a notebook!');
87
}
88
89
const { aboveCells: aboveCellsInfo, belowCells: belowCellsInfo } = this.props;
90
const lang = this.props.documentContext.language;
91
92
return (
93
<>
94
{
95
(aboveCellsInfo.length > 0 || belowCellsInfo.length > 0) &&
96
<UserMessage>
97
I am working on a Jupyter notebook.<br />
98
This Jupyter Notebook already contains multiple cells.<br />
99
The content of cells are listed below, each cell starts with CELL INDEX and a code block started with ```{lang.languageId}<br />
100
Each cell is a block of code that can be executed independently.<br />
101
Since it is Jupyter Notebook, if a module is already imported in a cell, it can be used in other cells as well.<br />
102
For the same reason, if a variable is defined in a cell, it can be used in other cells as well.<br />
103
We should not repeat the same import or variable definition in multiple cells, unless we want to overwrite the previous definition.<br />
104
Do not generate CELL INDEX in your answer, it is only used to help you understand the context.<br />
105
<br />
106
<>Below you will find a set of examples of what you should respond with. Please follow the exmaples on how to avoid repeating code.<br />
107
## Examples starts here<br />
108
Here are the cells in this Jupyter Notebook:<br />
109
`CELL INDEX: 0<br />
110
```python<br />
111
import pandas as pd<br />
112
<br />
113
# create a dataframe with sample data<br />
114
df = pd.DataFrame(&#123;'Name': ['Alice', 'Bob', 'Charlie'], 'Age': [25, 30, 35], 'Gender': ['F', 'M', 'M']&#125;)<br />
115
print(df)<br />
116
```<br />
117
---------------------------------<br />
118
USER:<br />
119
Now I create a new cell in this Jupyter Notebook document at index 1.<br />
120
In this new cell, I am working with the following code:<br />
121
```python<br />
122
```<br />
123
---------------------------------<br />
124
USER:<br />
125
plot the data frame<br />
126
<br />
127
---------------------------------<br />
128
ChatGPT Answer<br />
129
---------------------------------<br />
130
To plot the dataframe, we can use the `plot()` method of pandas dataframe. Here's the code:<br />
131
<br />
132
```python<br />
133
df.plot(x='Name', y='Age', kind='bar')<br />
134
```<br />
135
## Example ends here<br />
136
</>
137
138
{aboveCellsInfo.length > 0 && <NotebookCellList cells={aboveCellsInfo} title={'Here are the cells in this Jupyter Notebook:\n'} />}
139
{belowCellsInfo.length > 0 && <NotebookCellList cells={belowCellsInfo} cellIndexDelta={aboveCellsInfo.length + 1} title={'Here are the cells below the current cell that I am editing in this Jupyter Notebook:\n'} />}
140
</UserMessage>
141
}
142
</>
143
);
144
}
145
}
146
147
export class InlineChatJupyterNotebookCellsContextTagBasedRenderer extends PromptElement<InlineChatJupyterNotebookCellsContextRendererProps> {
148
render(state: void, sizing: PromptSizing) {
149
if (!isNotebookCellOrNotebookChatInput(this.props.documentContext.document.uri)) {
150
throw illegalArgument('InlineChatNotebookSelectionRenderer should be used only with a notebook!');
151
}
152
153
const { aboveCells: aboveCellsInfo, belowCells: belowCellsInfo } = this.props;
154
const lang = this.props.documentContext.language;
155
156
return (
157
<>
158
{
159
(aboveCellsInfo.length > 0 || belowCellsInfo.length > 0) &&
160
<UserMessage>
161
I am working on a Jupyter notebook.<br />
162
This Jupyter Notebook already contains multiple cells.<br />
163
The content of cells are listed below, source code is contained in ```{lang.languageId} blocks<br />
164
Each cell is a block of code that can be executed independently.<br />
165
Below you will find a set of examples of what you should respond with. Please follow the exmaples on how to avoid repeating code.<br />
166
<Tag name='example'>
167
<Tag name='cellsAbove'>
168
Here are the cells above the current cell that I am editing in this Jupyter Notebook:<br />
169
<IndexedTag name='cell' index={0}>
170
<TextChunk>
171
```python<br />
172
import pandas as pd<br />
173
<br />
174
# create a dataframe with sample data<br />
175
df = pd.DataFrame(&#123;'Name': ['Alice', 'Bob', 'Charlie'], 'Age': [25, 30, 35], 'Gender': ['F', 'M', 'M']&#125;)<br />
176
print(df)<br />
177
```
178
</TextChunk>
179
</IndexedTag>
180
</Tag>
181
<Tag name='UserRequest'>
182
Now I create a new cell in this Jupyter Notebook document at index 1.<br />
183
<TextChunk>
184
```python<br />
185
```<br />
186
</TextChunk>
187
plot the data frame<br />
188
</Tag>
189
<Tag name='Response'>
190
To plot the dataframe, we can use the `plot()` method of pandas dataframe. Here's the code:<br />
191
```python<br />
192
df.plot(x='Name', y='Age', kind='bar')<br />
193
```<br />
194
</Tag>
195
</Tag>
196
{aboveCellsInfo.length > 0 &&
197
<Tag name='cellsAbove'>
198
Here are the cells above the current cell that I am editing in this Jupyter Notebook:<br />
199
{aboveCellsInfo.map((cell, index) => (this._renderCellContent(cell, index)))}
200
</Tag>
201
}
202
{
203
belowCellsInfo.length > 0 &&
204
<Tag name='cellsBelow'>
205
Here are the cells below the current cell that I am editing in this Jupyter Notebook:<br />
206
{belowCellsInfo.map((cell, index) => (this._renderCellContent(cell, index + aboveCellsInfo.length + 1)))}
207
</Tag>
208
}
209
</UserMessage>
210
}
211
</>
212
);
213
}
214
215
private _renderCellContent(cell: CodeContextRegion, index: number) {
216
const code = createFencedCodeBlock(cell.language.languageId, cell.lines.join('\n'));
217
return <IndexedTag name='cell' index={index}>
218
<TextChunk>
219
{code}
220
</TextChunk>
221
</IndexedTag>;
222
}
223
}
224
225
export type IndexedTagProps = PromptElementProps<{
226
name: string;
227
index: number;
228
}>;
229
230
export class IndexedTag extends PromptElement<IndexedTagProps> {
231
232
private static readonly _regex = /^[a-zA-Z_][\w\.\-]*$/;
233
234
render() {
235
const { name, index } = this.props;
236
237
if (!IndexedTag._regex.test(name)) {
238
throw new Error(`Invalid tag name: ${this.props.name}`);
239
}
240
241
return (
242
<>
243
{'<'}{name} index={index}{'>'}<br />
244
<>
245
{this.props.children}<br />
246
</>
247
{'</'}{name}{'>'}
248
</>
249
);
250
}
251
}
252
253
//#region Utility
254
export function generateSelectionContextInNotebook(
255
tokensBudget: number,
256
documentContext: IDocumentContext,
257
range: Range,
258
tabsAndEditorsService: ITabsAndEditorsService,
259
workspaceService: IWorkspaceService
260
) {
261
// 4 chars per token
262
const charLimit = (tokensBudget * 4);
263
const initialTracker = new CodeContextTracker(charLimit);
264
265
const initialContext = getSelectionAndCodeAroundSelection(
266
documentContext.document,
267
documentContext.selection,
268
range,
269
new Range(0, 0, documentContext.document.lineCount, 0),
270
documentContext.language,
271
initialTracker
272
);
273
274
return generateNotebookCellContext(tabsAndEditorsService, workspaceService, documentContext, initialContext, initialTracker);
275
}
276
//#endregion
277
278
//#region Custom Notebook
279
280
export const CustomNotebookExamples = [
281
{
282
viewType: 'polyglot-notebook',
283
exampleCells: [
284
{ lan: 'markdown', source: 'Samples' },
285
{ lan: 'csharp', source: 'using Microsoft.Data.Analysis;' },
286
{ lan: 'csharp', source: 'DateTimeDataFrameColumn dateTimes = new DateTimeDataFrameColumn(\"DateTimes\");\n Int32DataFrameColumn ints = new Int32DataFrameColumn(\"Ints\", 6);\n StringDataFrameColumn strings = new StringDataFrameColumn(\"Strings\", 6);' },
287
{ lan: 'csharp', source: 'dateTimes.Append(DateTime.Parse(\"2019/01/01\"));' }
288
]
289
},
290
{
291
viewType: 'sql-notebook',
292
exampleCells: [
293
{ lan: 'sql', source: 'SELECT * FROM users;' },
294
]
295
},
296
{
297
viewType: 'node-notebook',
298
exampleCells: [
299
{ lan: 'javascript', source: `console.log("Hello World");` },
300
{ lan: 'javascript', source: `const {display} = require('node-kernel');` },
301
{ lan: 'markdown', source: '# Plain text output' },
302
{ lan: 'javascript', source: `display.text('Hello World');` },
303
]
304
},
305
{
306
viewType: 'sas-notebook',
307
exampleCells: [
308
{ lan: 'sas', source: 'proc print data=sashelp.class; run;' },
309
{ lan: 'sas', source: 'data race;\npr = probnorm(-15/sqrt(325));\nrun;\n\nproc print data=race;\nvar pr;\nrun;\n' },
310
]
311
},
312
{
313
viewType: 'http-notebook',
314
exampleCells: [
315
{ lan: 'http', source: 'GET https://httpbin.org/get' },
316
{ lan: 'http', source: 'POST https://httpbin.org/post' },
317
]
318
},
319
{
320
viewType: 'powerbi-notebook',
321
exampleCells: [
322
{ lan: 'markdown', source: '# Get Groups' },
323
{ lan: 'powerbi-api', source: 'GET /groups' },
324
{ lan: 'powerbi-api', source: '%dax /groups/ccce57d1-10af-1234-1234-665f8bbd8458/datasets/51ba6d4b-1234-1234-8635-a7d743a5ea89\nEVALUATE INFO.TABLES()\nThis' },
325
]
326
},
327
{
328
viewType: 'wolfram-language-notebook',
329
exampleCells: [
330
{ lan: 'wolfram', source: 'Plot[Sin[x], {x, 0, 2 Pi}]' },
331
]
332
},
333
{
334
viewType: 'github-issues',
335
exampleCells: [
336
{ lan: 'github-issues', source: '$vscode=repo:microsoft/vscode\n$milestone=milestone:"May 2020"' },
337
{ lan: 'github-issues', source: '$vscode $milestone is:closed author:@me -assignee:@me label:bug -label:verified' },
338
{ lan: 'github-issues', source: '$vscode assignee:@me is:open label:freeze-slow-crash-leak' },
339
]
340
},
341
{
342
viewType: 'rest-book',
343
exampleCells: [
344
{ lan: 'rest-book', source: 'GET google.com' },
345
{ lan: 'rest-book', source: 'GET https://www.google.com\n ?query="fun"\n &page=2\n User-Agent: rest-book\n Content-Type: application/json' },
346
]
347
}
348
];
349
350
export interface CustomNotebookExampleRendererProps extends BasePromptElementProps {
351
viewType: String;
352
}
353
354
export class CustomNotebookExampleRenderer extends PromptElement<CustomNotebookExampleRendererProps> {
355
render() {
356
const viewType = this.props.viewType;
357
const matchedExample = CustomNotebookExamples.find(example => example.viewType === this.props.viewType);
358
if (!matchedExample) {
359
return <></>;
360
}
361
362
const { exampleCells } = matchedExample;
363
364
return (
365
<UserMessage>
366
Below you will find a set of example cells for a {viewType} notebook.<br />
367
{
368
exampleCells.map((cell, index) => (
369
<>
370
CELL INDEX: {index}:<br />
371
```{cell.lan}<br />
372
{cell.source}<br />
373
<br />
374
```
375
</>
376
))
377
}
378
</UserMessage>
379
);
380
}
381
}
382
383
function findNotebookType(
384
workspaceService: IWorkspaceService,
385
uri: vscode.Uri
386
) {
387
const notebook = workspaceService.notebookDocuments.find(
388
doc =>
389
doc.uri.fsPath === uri.fsPath
390
);
391
392
return notebook?.notebookType;
393
}
394
395
export class InlineChatCustomNotebookInfoRenderer extends PromptElement<InlineChatNotebookSelectionCommonProps> {
396
constructor(
397
props: InlineChatNotebookSelectionCommonProps,
398
@IWorkspaceService private readonly workspaceService: IWorkspaceService
399
) {
400
super(props);
401
}
402
render(state: void, sizing: PromptSizing) {
403
if (!isNotebookCellOrNotebookChatInput(this.props.documentContext.document.uri)) {
404
throw illegalArgument('InlineChatCustomNotebookInfoRenderer should be used only with a notebook!');
405
}
406
407
const notebookType = findNotebookType(this.workspaceService, this.props.documentContext.document.uri);
408
const matchedExample = CustomNotebookExamples.find(example => example.viewType === notebookType);
409
const notebookTypeName = matchedExample ? notebookType : 'custom';
410
411
return (
412
<>
413
{
414
<UserMessage>
415
I am working on a {notebookTypeName} notebook in VS Code.<br />
416
{notebookTypeName} notebooks in VS Code are documents that contain a mix of rich Markdown, executable code snippets, <br />
417
and accompanying rich output. These are all separated into distinct cells and can be interleaved in any order <br />
418
A {notebookTypeName} notebook contains multiple cells.<br />
419
</UserMessage>
420
}
421
{
422
matchedExample &&
423
<CustomNotebookExampleRenderer viewType={matchedExample.viewType} />
424
}
425
</>
426
);
427
}
428
}
429
430
export interface InlineChatCustomNotebookCellsContextRendererProps extends InlineChatNotebookSelectionCommonProps {
431
aboveCells?: CodeContextRegion[];
432
belowCells?: CodeContextRegion[];
433
}
434
435
export class InlineChatCustomNotebookCellsContextRenderer extends PromptElement<InlineChatCustomNotebookCellsContextRendererProps> {
436
render(state: void, sizing: PromptSizing) {
437
if (!isNotebookCellOrNotebookChatInput(this.props.documentContext.document.uri)) {
438
throw illegalArgument('InlineChatCustomNotebookCellsContextRenderer should be used only with a notebook!');
439
}
440
441
const { aboveCells, belowCells, documentContext } = this.props;
442
const aboveCellsInfo = aboveCells || [];
443
const belowCellsInfo = belowCells || [];
444
const lang = documentContext.language;
445
return (
446
<>
447
{
448
(aboveCellsInfo.length > 0 || belowCellsInfo.length > 0) &&
449
<UserMessage>
450
The content of cells are listed below, each cell starts with CELL INDEX and a code block started with ```{lang.languageId}<br />
451
Each cell is a block of code that can be executed independently.<br />
452
Do not generate CELL INDEX in your answer, it is only used to help you understand the context.<br />
453
<br />
454
Below you will find a set of examples of what you should respond with. Please follow the exmaples on how to avoid repeating code.<br />
455
{aboveCellsInfo.length > 0 && <NotebookCellList cells={aboveCellsInfo} title={'Here are the cells in this custom notebook:\n'} />}
456
{belowCellsInfo.length > 0 && <NotebookCellList cells={belowCellsInfo} cellIndexDelta={aboveCellsInfo.length + 1} title={'Here are the cells below the current cell that I am editing in this custom notebook:\n'} />}
457
</UserMessage>
458
}
459
</>
460
);
461
}
462
}
463
464
//#endregion
465
466
//#region Variables
467
type InlineChatNotebookVariablesPromptProps = PromptElementProps<{
468
notebookURI: vscode.Uri;
469
query: string;
470
priorities: NotebookPromptPriority;
471
}>;
472
473
interface InlineChatNotebookRuntimeState {
474
variables: VariablesResult[];
475
packages: PipPackage[];
476
}
477
478
export class InlineChatNotebookVariables extends PromptElement<InlineChatNotebookVariablesPromptProps, InlineChatNotebookRuntimeState> {
479
constructor(
480
props: InlineChatNotebookVariablesPromptProps,
481
@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,
482
@INotebookService private readonly notebookService: INotebookService,
483
) {
484
super(props);
485
}
486
487
override async prepare(): Promise<InlineChatNotebookRuntimeState> {
488
if (this.tabsAndEditorsService.activeNotebookEditor?.notebook.uri.path !== this.props.notebookURI.path) {
489
return { variables: [], packages: [] };
490
}
491
492
const notebookEditor = this.tabsAndEditorsService.activeNotebookEditor;
493
const notebook = notebookEditor?.notebook;
494
if (!notebook) {
495
return { variables: [], packages: [] };
496
}
497
498
const fetchVariables = this.notebookService.getVariables(notebook.uri);
499
// disable fetching available packages
500
const fetchPackages = Promise.resolve([]);
501
const [variables, packages] = await Promise.all([fetchVariables, fetchPackages]);
502
return { variables, packages };
503
}
504
505
render(state: InlineChatNotebookRuntimeState) {
506
const { priorities } = this.props;
507
return (
508
<TokenLimit max={16384}>
509
{state.variables.length !== 0 &&
510
<>
511
<UserMessage priority={priorities.runtimeCore}>
512
The following variables are present in this Jupyter Notebook:
513
{
514
state.variables.map((variable) => (
515
<>
516
<TextChunk>
517
Name: {variable.variable.name}<br />
518
{variable.variable.type && <>Type: {variable.variable.type}</>}<br />
519
Value: {variable.variable.value}<br />
520
{variable.indexedChildrenCount > 0 && <>Length: {variable.indexedChildrenCount}</>}<br />
521
{variable.variable.summary && <>Summary: {variable.variable.summary}</>}
522
</TextChunk>
523
</>
524
))
525
526
}
527
</UserMessage>
528
</>}
529
{state.packages.length !== 0 &&
530
<>
531
<UserMessage priority={priorities.other}>
532
The following pip packages are available in this Jupyter Notebook:
533
{
534
state.packages.map((pkg) => (
535
<>
536
<TextChunk>{pkg.name}=={pkg.version}</TextChunk>
537
<br />
538
</>
539
))
540
}
541
</UserMessage>
542
</>
543
}
544
</TokenLimit>
545
);
546
}
547
}
548
549
//#endregion
550
551