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