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(participant): route generic prompt by intent and update generic prompt VSCODE-572 #830

Merged
merged 9 commits into from
Sep 26, 2024
91 changes: 89 additions & 2 deletions src/participant/participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from '../telemetry/telemetryService';
import { DocsChatbotAIService } from './docsChatbotAIService';
import type TelemetryService from '../telemetry/telemetryService';
import { IntentPrompt, type PromptIntent } from './prompts/intent';

const log = createLogger('participant');

Expand Down Expand Up @@ -214,8 +215,7 @@ export default class ParticipantController {
}
}

// @MongoDB what is mongodb?
async handleGenericRequest(
async _handleRoutedGenericRequest(
request: vscode.ChatRequest,
context: vscode.ChatContext,
stream: vscode.ChatResponseStream,
Expand All @@ -241,6 +241,93 @@ export default class ParticipantController {
return genericRequestChatResult(context.history);
}

async _routeRequestToHandler({
context,
promptIntent,
request,
stream,
token,
}: {
context: vscode.ChatContext;
promptIntent: Omit<PromptIntent, 'Default'>;
request: vscode.ChatRequest;
stream: vscode.ChatResponseStream;
token: vscode.CancellationToken;
}): Promise<ChatResult> {
switch (promptIntent) {
case 'Query':
return this.handleQueryRequest(request, context, stream, token);
case 'Docs':
return this.handleDocsRequest(request, context, stream, token);
case 'Schema':
return this.handleSchemaRequest(request, context, stream, token);
case 'Code':
return this.handleQueryRequest(request, context, stream, token);
default:
return this._handleRoutedGenericRequest(
request,
context,
stream,
token
);
}
}

async _getIntentFromChatRequest({
context,
request,
token,
}: {
context: vscode.ChatContext;
request: vscode.ChatRequest;
token: vscode.CancellationToken;
}): Promise<PromptIntent> {
const messages = await Prompts.intent.buildMessages({
connectionNames: this._getConnectionNames(),
request,
context,
});

const responseContent = await this.getChatResponseContent({
messages,
token,
});

return IntentPrompt.getIntentFromModelResponse(responseContent);
}

async handleGenericRequest(
request: vscode.ChatRequest,
context: vscode.ChatContext,
stream: vscode.ChatResponseStream,
token: vscode.CancellationToken
): Promise<ChatResult> {
// We "prompt chain" to handle the generic requests.
// First we ask the model to parse for intent.
// If there is an intent, we can route it to one of the handlers (/commands).
// When there is no intention or it's generic we handle it with a generic handler.
const promptIntent = await this._getIntentFromChatRequest({
context,
request,
token,
});

if (token.isCancellationRequested) {
return this._handleCancelledRequest({
context,
stream,
});
}

return this._routeRequestToHandler({
context,
promptIntent,
request,
stream,
token,
});
}

async connectWithParticipant({
id,
command,
Expand Down
18 changes: 11 additions & 7 deletions src/participant/prompts/generic.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import * as vscode from 'vscode';

import type { PromptArgsBase } from './promptBase';
import { PromptBase } from './promptBase';

export class GenericPrompt extends PromptBase<PromptArgsBase> {
protected getAssistantPrompt(): string {
return `You are a MongoDB expert.
Your task is to help the user craft MongoDB queries and aggregation pipelines that perform their task.
Keep your response concise.
You should suggest queries that are performant and correct.
Respond with markdown, suggest code in a Markdown code block that begins with \`\`\`javascript and ends with \`\`\`.
You can imagine the schema, collection, and database name.
Respond in MongoDB shell syntax using the \`\`\`javascript code block syntax.`;
Your task is to help the user with MongoDB related questions.
When applicable, you may suggest MongoDB code, queries, and aggregation pipelines that perform their task.
Rules:
1. Keep your response concise.
2. You should suggest code that is performant and correct.
3. Respond with markdown.
4. When relevant, provide code in a Markdown code block that begins with \`\`\`javascript and ends with \`\`\`.
5. Use MongoDB shell syntax for code unless the user requests a specific language.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a reminder that code blocks should be aligned with codeBlockIdentifier from #835

6. If you require additional information to provide a response, ask the user for it.
7. When specifying a database, use the MongoDB syntax use('databaseName').`;
}

public getEmptyRequestResponse(): string {
// TODO(VSCODE-572): Generic empty response handler
return vscode.l10n.t(
'Ask anything about MongoDB, from writing queries to questions about your cluster.'
);
Expand Down
5 changes: 4 additions & 1 deletion src/participant/prompts/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { GenericPrompt } from './generic';
import type * as vscode from 'vscode';

import { GenericPrompt } from './generic';
import { IntentPrompt } from './intent';
import { NamespacePrompt } from './namespace';
import { QueryPrompt } from './query';
import { SchemaPrompt } from './schema';

export class Prompts {
public static generic = new GenericPrompt();
public static intent = new IntentPrompt();
public static namespace = new NamespacePrompt();
public static query = new QueryPrompt();
public static schema = new SchemaPrompt();
Expand Down
50 changes: 50 additions & 0 deletions src/participant/prompts/intent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { PromptArgsBase } from './promptBase';
import { PromptBase } from './promptBase';

export type PromptIntent = 'Query' | 'Schema' | 'Docs' | 'Default';

export class IntentPrompt extends PromptBase<PromptArgsBase> {
protected getAssistantPrompt(): string {
return `You are a MongoDB expert.
Your task is to help guide a conversation with a user to the correct handler.
You will be provided a conversation and your task is to determine the intent of the user.
The intent handlers are:
- Query
- Schema
- Docs
- Default
Rules:
1. Respond only with the intent handler.
2. Use the "Query" intent handler when the user is asking for code that relates to a specific collection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the user has not specified a namespace? We don't have information about a specific collection, but it still can be a query generation request.

Copy link
Member Author

@Anemy Anemy Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question, and raises a tradeoff we have to take. Some users may ask a question that would result in code for a database, like how can I create a user with specific database permissions or what's the size of X database. In that case the /query handler would be forcing them to choose a collection which doesn't really make sense. I was aiming to make this prompt fairly intentionally selective so we don't restrict the user capabilities. The generic prompt has a lot of the capabilities of the /query resolver.
Are there ways you think we could word this differently or guide the behavior to the query resolver better?

3. Use the "Docs" intent handler when the user is asking a question that involves MongoDB documentation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try this out? I am wondering how the model interprets this instruction. Should users explicitly say find in the documentation? Or they can just ask "How to..." questions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model did successfully route a fairly docs related question to the docs handler in my manual testing. Haven't been that extensive to give a good answer on when it does and when it doesn't. We'd need more accuracy tests.

4. Use the "Schema" intent handler when the user is asking for the schema or shape of documents of a specific collection.
5. Use the "Default" intent handler when a user is asking for code that does NOT relate to a specific collection.
6. Use the "Default" intent handler for everything that may not be handled by another handler.
7. If you are uncertain of the intent, use the "Default" intent handler.

Example:
User: How do I create an index in my pineapples collection?
Response:
Query

Example:
User:
What is $vectorSearch?
Response:
Docs`;
}

static getIntentFromModelResponse(response: string): PromptIntent {
response = response.trim();
switch (response) {
case 'Query':
return 'Query';
case 'Schema':
return 'Schema';
case 'Docs':
return 'Docs';
default:
return 'Default';
}
}
}
Loading
Loading