Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chat): update schema assistant prompt to handle empty and short prompts better VSCODE-648 #874

Merged
merged 5 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions src/participant/participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1187,17 +1187,6 @@ export default class ParticipantController {
});
}

if (
Prompts.isPromptEmpty(request) &&
this._doesLastMessageAskForNamespace(context.history)
) {
return this.handleEmptyNamespaceMessage({
command: '/schema',
context,
stream,
});
}

const namespace = await this._getNamespaceFromChat({
request,
context,
Expand All @@ -1221,6 +1210,19 @@ export default class ParticipantController {
});
}

if (Prompts.isPromptEmpty(request)) {
if (this._doesLastMessageAskForNamespace(context.history)) {
return this.handleEmptyNamespaceMessage({
command: '/schema',
context,
stream,
});
}

stream.markdown(Prompts.schema.emptyRequestResponse);
return emptyRequestChatResult(context.history);
}

if (token.isCancellationRequested) {
return this._handleCancelledRequest({
context,
Expand Down Expand Up @@ -1314,19 +1316,6 @@ export default class ParticipantController {
});
}

if (Prompts.isPromptEmpty(request)) {
if (this._doesLastMessageAskForNamespace(context.history)) {
return this.handleEmptyNamespaceMessage({
command: '/query',
context,
stream,
});
}

stream.markdown(Prompts.query.emptyRequestResponse);
return emptyRequestChatResult(context.history);
}

// We "prompt chain" to handle the query requests.
// First we ask the model to parse for the database and collection name.
// If they exist, we can then use them in our final completion.
Expand Down Expand Up @@ -1361,6 +1350,19 @@ export default class ParticipantController {
});
}

if (Prompts.isPromptEmpty(request)) {
if (this._doesLastMessageAskForNamespace(context.history)) {
return this.handleEmptyNamespaceMessage({
command: '/query',
context,
stream,
});
}

stream.markdown(Prompts.query.emptyRequestResponse);
return emptyRequestChatResult(context.history);
}

let schema: string | undefined;
let sampleDocuments: Document[] | undefined;
try {
Expand Down
14 changes: 11 additions & 3 deletions src/participant/prompts/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UserPromptResponse } from './promptBase';
import { PromptBase, type PromptArgsBase } from './promptBase';
import * as vscode from 'vscode';

export const DOCUMENTS_TO_SAMPLE_FOR_SCHEMA_PROMPT = 100;

Expand All @@ -21,9 +22,10 @@ export class SchemaPrompt extends PromptBase<SchemaPromptArgs> {
return `You are a senior engineer who describes the schema of documents in a MongoDB database.
The schema is generated from a sample of documents in the user's collection.
You must follow these rules.
Rule 1: Try to be as concise as possible.
Rule 2: Pay attention to the JSON schema.
Rule 3: Mention the amount of documents sampled in your response.
Rule 1: Your answer should always describe the schema of documents in the collection.
gagik marked this conversation as resolved.
Show resolved Hide resolved
Rule 2: Try to be as concise as possible.
Rule 3: Pay attention to the JSON schema.
Rule 4: Mention the amount of documents sampled in your response.
Amount of documents sampled: ${amountOfDocumentsSampled}.`;
}

Expand All @@ -44,4 +46,10 @@ ${schema}`,
hasSampleDocs: false,
});
}

get emptyRequestResponse(): string {
return vscode.l10n.t(
'Please specify a question when using this command. Usage: @MongoDB /schema what is the schema for the sample_mflix.users collection?'
);
}
}
89 changes: 48 additions & 41 deletions src/test/suite/participant/participant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,30 @@ suite('Participant Controller Test Suite', function () {
});
});

test('returns a special response with an empty prompt', async function () {
// Put the namespace in metadata as otherwise the user is meant to
// be prompted for a namespace with an empty prompt
sinon.replace(
testParticipantController._chatMetadataStore,
'getChatMetadata',
() => ({
databaseName: 'dbOne',
collectionName: 'collectionOne',
})
);

await invokeChatHandler({
prompt: '',
command: 'query',
references: [],
});

expect(chatStreamStub.markdown.calledOnce).is.true;
expect(chatStreamStub.markdown.getCall(0).firstArg).equals(
Prompts.query.emptyRequestResponse
);
});

test('generates a query', async function () {
const chatRequestMock = {
prompt: 'find all docs by a name example',
Expand Down Expand Up @@ -1335,47 +1359,6 @@ suite('Participant Controller Test Suite', function () {
'Which database would you like to use? Select one by either clicking on an item in the list or typing the name manually in the chat.\n\n'
);
});

test('with history, and a blank prompt, it sets a message so it does not cause model error (VSCODE-626)', async function () {
gagik marked this conversation as resolved.
Show resolved Hide resolved
const chatRequestMock = {
prompt: '',
command: 'schema',
references: [],
};
chatContextStub = {
history: [
createChatRequestTurn(
'/query',
'how do I make a find request vs favorite_fruits.pineapple?'
),
createChatResponseTurn('/query', {
response: [
{
value: { value: 'some code' } as vscode.MarkdownString,
},
],
result: {
metadata: {
intent: 'query',
chatId: 'abc',
},
},
}),
],
};
await invokeChatHandler(chatRequestMock);

expect(sendRequestStub.calledOnce).to.be.true;

const messages = sendRequestStub.firstCall
.args[0] as vscode.LanguageModelChatMessage[];
expect(getMessageContent(messages[0])).to.include(
'Parse all user messages to find a database name and a collection name.'
);
expect(getMessageContent(messages[3])).to.include(
'see previous messages'
);
});
});

suite(
Expand All @@ -1387,6 +1370,30 @@ suite('Participant Controller Test Suite', function () {
});
});

test('returns a special response with an empty prompt', async function () {
// Put the namespace in metadata as otherwise the user is meant to
// be prompted for a namespace with an empty prompt
sinon.replace(
testParticipantController._chatMetadataStore,
'getChatMetadata',
() => ({
databaseName: 'dbOne',
collectionName: 'collectionOne',
})
);

await invokeChatHandler({
prompt: '',
command: 'schema',
references: [],
});

expect(chatStreamStub.markdown.calledOnce).is.true;
expect(chatStreamStub.markdown.getCall(0).firstArg).equals(
Prompts.schema.emptyRequestResponse
);
});

test('shows a button to view the json output', async function () {
const chatRequestMock = {
prompt: 'what is my schema',
Expand Down
Loading