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