Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/node/intentDetector.tsx
13399 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, PromptMetadata, Raw, SystemMessage, UserMessage } from '@vscode/prompt-tsx';
7
import type { CancellationToken, ChatContext, ChatParticipantDetectionProvider, ChatParticipantDetectionResult, ChatParticipantMetadata, ChatRequest, Uri, ChatLocation as VscodeChatLocation } from 'vscode';
8
import { CHAT_PARTICIPANT_ID_PREFIX, getChatParticipantIdFromName } from '../../../platform/chat/common/chatAgents';
9
import { ChatFetchResponseType, ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes';
10
import { getTextPart, roleToString } from '../../../platform/chat/common/globalStringUtils';
11
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
12
import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';
13
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
14
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
15
import { ILogService } from '../../../platform/log/common/logService';
16
import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEditorsService';
17
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
18
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
19
import { isNotebookCellOrNotebookChatInput } from '../../../util/common/notebooks';
20
import { isFalsyOrEmpty } from '../../../util/vs/base/common/arrays';
21
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
22
import { Position, Range } from '../../../vscodeTypes';
23
import { getAgentForIntent, GITHUB_PLATFORM_AGENT, Intent } from '../../common/constants';
24
import { IIntentService } from '../../intents/node/intentService';
25
import { UnknownIntent } from '../../intents/node/unknownIntent';
26
import { InstructionMessage } from '../../prompts/node/base/instructionMessage';
27
import { PromptRenderer, renderPromptElement } from '../../prompts/node/base/promptRenderer';
28
import { ChatVariablesAndQuery } from '../../prompts/node/panel/chatVariables';
29
import { ConversationHistory, HistoryWithInstructions } from '../../prompts/node/panel/conversationHistory';
30
import { CurrentSelection } from '../../prompts/node/panel/currentSelection';
31
import { CodeBlock } from '../../prompts/node/panel/safeElements';
32
import { getToolName } from '../../tools/common/toolNames';
33
import { CodebaseTool } from '../../tools/node/codebaseTool';
34
import { ChatVariablesCollection } from '../common/chatVariablesCollection';
35
import { Turn } from '../common/conversation';
36
import { addHistoryToConversation } from './chatParticipantRequestHandler';
37
import { IDocumentContext } from './documentContext';
38
import { IIntent } from './intents';
39
import { ConversationalBaseTelemetryData } from './telemetry';
40
41
export class IntentDetector implements ChatParticipantDetectionProvider {
42
43
constructor(
44
@ILogService private readonly logService: ILogService,
45
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
46
@ITelemetryService private readonly telemetryService: ITelemetryService,
47
@IConfigurationService private readonly configurationService: IConfigurationService,
48
@IIntentService private readonly intentService: IIntentService,
49
@IInstantiationService private readonly instantiationService: IInstantiationService,
50
@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,
51
@IExperimentationService private readonly experimentationService: IExperimentationService,
52
) { }
53
54
async provideParticipantDetection(chatRequest: ChatRequest, context: ChatContext, options: { participants?: ChatParticipantMetadata[]; location: VscodeChatLocation }, token: CancellationToken): Promise<ChatParticipantDetectionResult | null | undefined> {
55
if ((options.location !== ChatLocation.Panel && options.location !== ChatLocation.Editor) || this.configurationService.getNonExtensionConfig('chat.detectParticipant.enabled') === false) {
56
return;
57
}
58
59
const selectedEndpoint = await this.endpointProvider.getChatEndpoint(chatRequest);
60
// Disable intent detection if the user is requesting their request be completed with o1 since o1 has such a low RPS the cost of an incorrect intent is high
61
if (selectedEndpoint.family.startsWith('o1')) {
62
return;
63
}
64
65
const chatVariables = new ChatVariablesCollection(chatRequest.references);
66
const { turns } = this.instantiationService.invokeFunction(accessor => addHistoryToConversation(accessor, context.history));
67
let detectedIntentId: string | ChatParticipantDetectionResult | undefined;
68
const shouldIncludeGitHub = (chatRequest.toolReferences.length === 0);
69
const builtinParticipants = options.participants?.filter(p => ((p.participant === GITHUB_PLATFORM_AGENT && shouldIncludeGitHub) || p.participant.startsWith(CHAT_PARTICIPANT_ID_PREFIX)) && p.disambiguation.length) ?? [];
70
const thirdPartyParticipants = options.participants?.filter(p => p.participant !== GITHUB_PLATFORM_AGENT && !p.participant.startsWith(CHAT_PARTICIPANT_ID_PREFIX) && p.disambiguation.length) ?? [];
71
72
try {
73
74
const detectedIntent = await this.detectIntent(
75
options.location,
76
IDocumentContext.inferDocumentContext(chatRequest, this.tabsAndEditorsService.activeTextEditor, turns),
77
chatRequest.prompt,
78
token,
79
undefined,
80
chatVariables,
81
builtinParticipants,
82
undefined,
83
turns,
84
);
85
86
87
if (detectedIntent && 'participant' in detectedIntent) {
88
if (detectedIntent.participant === getChatParticipantIdFromName('workspace')) {
89
if (chatRequest.toolReferences.find((ref) => getToolName(ref.name) === CodebaseTool.toolName)) {
90
return undefined;
91
}
92
93
if (this.configurationService.getExperimentBasedConfig<boolean>(ConfigKey.TeamInternal.AskAgent, this.experimentationService)
94
&& chatRequest.model.capabilities.supportsToolCalling) {
95
return undefined;
96
}
97
}
98
99
detectedIntentId = detectedIntent;
100
return detectedIntent;
101
} else if (detectedIntent) {
102
detectedIntentId = detectedIntent.id;
103
const agent = getAgentForIntent(detectedIntent.id as Intent, options.location);
104
105
if (agent) {
106
const overrideCommand = agent.agent === Intent.Editor && (agent.command === Intent.Edit || agent.command === Intent.Generate) ? undefined : agent.command;
107
108
return {
109
participant: getChatParticipantIdFromName(agent.agent),
110
command: overrideCommand,
111
};
112
}
113
} else if (thirdPartyParticipants.length && options.location === ChatLocation.Panel) {
114
// If the detected intent is `unknown` and we have 3P participants, try picking from them instead
115
const detectedIntent = await this.detectIntent(
116
options.location,
117
undefined,
118
chatRequest.prompt,
119
token,
120
undefined,
121
new ChatVariablesCollection(chatRequest.references),
122
builtinParticipants,
123
thirdPartyParticipants,
124
turns,
125
);
126
127
if (detectedIntent && 'participant' in detectedIntent) {
128
detectedIntentId = detectedIntent;
129
return detectedIntent;
130
}
131
}
132
} finally {
133
if (detectedIntentId) {
134
// Collect telemetry based on the full unfiltered history, rather than when the request handler is invoked (at which point the conversation history is already scoped)
135
const doc = this.tabsAndEditorsService.activeTextEditor?.document;
136
const docSnapshot = doc ? TextDocumentSnapshot.create(doc) : undefined;
137
this.collectIntentDetectionContextInternal(
138
chatRequest.prompt,
139
detectedIntentId,
140
chatVariables,
141
options.location,
142
turns.slice(0, -1),
143
docSnapshot
144
);
145
}
146
}
147
}
148
149
private async getPreferredIntent(location: ChatLocation, documentContext: IDocumentContext | undefined, history?: readonly Turn[], messageText?: string) {
150
let preferredIntent: Intent | undefined;
151
if (location === ChatLocation.Editor && documentContext && !history?.length) {
152
if (documentContext.selection.isEmpty && documentContext.document.lineAt(documentContext.selection.start.line).text.trim() === '') {
153
preferredIntent = Intent.Generate;
154
} else if (!documentContext.selection.isEmpty && documentContext.selection.start.line !== documentContext.selection.end.line) {
155
preferredIntent = Intent.Edit;
156
}
157
}
158
// /fixTestFailure was removed, delegate to /fix if there are historical usages of it.
159
if (messageText?.trimStart().startsWith('/fixTestFailure')) {
160
preferredIntent = Intent.Fix;
161
}
162
163
return preferredIntent;
164
}
165
166
/**
167
* @param preferredIntent tells the model that this intent is the most likely the developer wants.
168
* @param currentFilePath file path relative to the workspace root and will be mentioned in the prompt if present
169
* @param isRerunWithoutIntentDetection for telemetry purposes -- if `undefined`, then intent detection is invoked either from inline chat of an older vscode or from panel chat
170
*/
171
async detectIntent(
172
location: ChatLocation,
173
documentContext: IDocumentContext | undefined,
174
messageText: string,
175
token: CancellationToken,
176
baseUserTelemetry: ConversationalBaseTelemetryData | undefined,
177
chatVariables: ChatVariablesCollection,
178
builtinParticipants: ChatParticipantMetadata[],
179
thirdPartyParticipants?: ChatParticipantMetadata[],
180
history?: readonly Turn[],
181
): Promise<IIntent | ChatParticipantDetectionResult | undefined> {
182
183
this.logService.trace('Building intent detector');
184
185
if (builtinParticipants.length === 0 && (isFalsyOrEmpty(thirdPartyParticipants))) {
186
this.logService.trace('No participants available for intent detection');
187
return undefined;
188
}
189
190
const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');
191
192
const preferredIntent = await this.getPreferredIntent(location, documentContext, history, messageText);
193
194
const promptRenderer = PromptRenderer.create(
195
this.instantiationService,
196
endpoint,
197
(location === ChatLocation.Editor
198
? IntentDetectionPrompt
199
: GPT4OIntentDetectionPrompt),
200
{
201
preferredIntent,
202
location,
203
userQuestion: messageText,
204
documentContext,
205
history,
206
chatVariables,
207
builtinParticipants: builtinParticipants,
208
thirdPartyParticipants: thirdPartyParticipants,
209
}
210
);
211
212
const { messages, metadata } = await promptRenderer.render(undefined, token);
213
this.logService.trace('Built intent detector');
214
215
const fetchResult = await endpoint.makeChatRequest(
216
'intentDetection',
217
messages,
218
undefined,
219
token,
220
location,
221
undefined,
222
{
223
stop: [';'],
224
max_tokens: 20
225
}
226
);
227
const intent = this.validateResult(fetchResult, baseUserTelemetry, messageText, location, preferredIntent, thirdPartyParticipants ? thirdPartyParticipants : builtinParticipants, documentContext);
228
const chosenIntent = intent && 'id' in intent ? intent?.id : intent?.participant;
229
230
this.sendTelemetry(
231
preferredIntent,
232
chosenIntent,
233
documentContext?.language.languageId,
234
undefined,
235
location
236
);
237
238
const fileMetadata = metadata.get(DocumentExcerptInfo);
239
240
this.sendInternalTelemetry(
241
messageText,
242
preferredIntent,
243
fileMetadata?.filePath,
244
fileMetadata?.fileExcerpt,
245
chosenIntent,
246
documentContext?.language.languageId,
247
undefined,
248
messages.slice(0, -1),
249
location
250
);
251
252
return intent;
253
}
254
255
async collectIntentDetectionContextInternal(
256
userQuery: string,
257
assignedIntent: string | ChatParticipantDetectionResult,
258
chatVariables: ChatVariablesCollection,
259
location: ChatLocation,
260
history: Turn[] = [],
261
document?: TextDocumentSnapshot
262
) {
263
const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');
264
265
const { messages: currentSelection } = await renderPromptElement(this.instantiationService, endpoint, CurrentSelection, { document });
266
const { messages: conversationHistory } = await renderPromptElement(this.instantiationService, endpoint, ConversationHistory, { history, priority: 1000 }, undefined, undefined).catch(() => ({ messages: [] }));
267
268
const { history: historyMessages, fileExcerpt, attachedContext, fileExcerptExceedsBudget } = this.prepareInternalTelemetryContext(getTextPart(currentSelection?.[0]?.content), conversationHistory, chatVariables);
269
270
this.telemetryService.sendInternalMSFTTelemetryEvent(
271
'participantDetectionContext',
272
{
273
chatLocation: ChatLocation.toString(location),
274
userQuery,
275
history: historyMessages.join(''),
276
assignedIntent: typeof assignedIntent === 'string' ? assignedIntent : undefined,
277
assignedThirdPartyChatParticipant: typeof assignedIntent !== 'string' ? assignedIntent.participant : undefined,
278
assignedThirdPartyChatCommand: typeof assignedIntent !== 'string' ? assignedIntent.command : undefined,
279
fileExcerpt: fileExcerpt ?? (fileExcerptExceedsBudget ? '<truncated>' : '<none>'),
280
attachedContext: attachedContext.join(';')
281
},
282
{}
283
);
284
}
285
286
private validateResult(
287
fetchResult: ChatResponse,
288
baseUserTelemetry: ConversationalBaseTelemetryData | undefined,
289
messageText: string,
290
location: ChatLocation,
291
preferredIntent?: Intent,
292
participants?: ChatParticipantMetadata[],
293
documentContext?: IDocumentContext | undefined,
294
): IIntent | ChatParticipantDetectionResult | undefined {
295
296
if (fetchResult.type !== ChatFetchResponseType.Success) {
297
if (baseUserTelemetry) {
298
this.sendPromptIntentErrorTelemetry(baseUserTelemetry, fetchResult);
299
}
300
return undefined;
301
}
302
303
let cleanedIntentResponses = [fetchResult.value].map(intentResponse =>
304
intentResponse
305
.trimStart()
306
.split('\n')[0]
307
.replaceAll('```', '')
308
.replace(/function id:|response:/i, '')
309
.trim());
310
311
cleanedIntentResponses = cleanedIntentResponses.filter(i => i !== UnknownIntent.ID);
312
if (!cleanedIntentResponses.length && preferredIntent) {
313
cleanedIntentResponses = [preferredIntent];
314
}
315
316
// Dynamic chat participants
317
if ((cleanedIntentResponses[0] === 'github_questions') && participants?.find(p => p.participant === GITHUB_PLATFORM_AGENT)) {
318
return { participant: GITHUB_PLATFORM_AGENT };
319
}
320
321
const categoryNamesToParticipants = participants?.reduce<{ [categoryName: string]: { participant: string; command?: string } }>((acc, participant) => {
322
participant.disambiguation.forEach((alias) => {
323
acc[alias.category] = { participant: participant.participant, command: participant.command };
324
});
325
return acc;
326
}, {});
327
328
let intent = cleanedIntentResponses
329
.map(r => this.intentService.getIntent(r, location) ?? categoryNamesToParticipants?.[r])
330
.filter((s): s is (IIntent | { participant: string; command?: string }) => s !== undefined)?.[0];
331
332
const chosenIntent = intent && 'id' in intent ? intent?.id : intent?.participant;
333
334
this.logService.debug(`picked intent "${chosenIntent}" from ${JSON.stringify(fetchResult.value, null, '\t')}`);
335
336
// override /edit in inline chat based on the document context
337
if (location === ChatLocation.Editor
338
&& chosenIntent === Intent.Edit
339
&& documentContext
340
&& documentContext.selection.isEmpty
341
&& documentContext.document.lineAt(documentContext.selection.start.line).text.trim() === ''
342
) {
343
// the selection is empty and sits on a whitespace only line, we will always detect generate instead of edit
344
const editIntent = this.intentService.getIntent(Intent.Generate, ChatLocation.Editor);
345
if (editIntent) {
346
intent = editIntent;
347
}
348
}
349
350
351
if (baseUserTelemetry) {
352
const promptTelemetryData = baseUserTelemetry.extendedBy({
353
messageText,
354
promptContext: cleanedIntentResponses.join(),
355
intent: chosenIntent || 'unknown',
356
});
357
this.telemetryService.sendEnhancedGHTelemetryEvent('conversation.promptIntent', promptTelemetryData.raw.properties, promptTelemetryData.raw.measurements);
358
}
359
return intent;
360
}
361
362
private sendPromptIntentErrorTelemetry(
363
baseUserTelemetry: ConversationalBaseTelemetryData,
364
fetchResult: { type: string; reason: string; requestId: string }
365
) {
366
const telemetryErrorData = baseUserTelemetry.extendedBy({
367
resultType: fetchResult.type,
368
reason: fetchResult.reason,
369
});
370
this.telemetryService.sendEnhancedGHTelemetryErrorEvent('conversation.promptIntentError', telemetryErrorData.raw.properties, telemetryErrorData.raw.measurements);
371
}
372
373
private sendTelemetry(
374
preferredIntent: Intent | undefined,
375
detectedIntent: string | undefined,
376
languageId: string | undefined,
377
isRerunWithoutIntentDetection: boolean | undefined,
378
location: ChatLocation
379
) {
380
/* __GDPR__
381
"intentDetection" : {
382
"owner": "ulugbekna",
383
"comment": "Intent detection telemetry.",
384
"chatLocation": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Which chat (panel or inline) intent detection is used for." },
385
"preferredIntent": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Which intent was initially provided as preferred." },
386
"detectedIntent": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Intent that was detected by Copilot" },
387
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language ID of the document for which intent detection happened." },
388
"isRerunWithoutIntentDetection": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Whether the user disliked the detected intent and tried to rerun without it." }
389
}
390
*/
391
this.telemetryService.sendMSFTTelemetryEvent(
392
'intentDetection',
393
{
394
chatLocation: ChatLocation.toString(location),
395
preferredIntent: preferredIntent ?? '<none>',
396
detectedIntent: detectedIntent ?? '<none>',
397
languageId: languageId ?? '<none>',
398
isRerunWithoutIntentDetection: String(isRerunWithoutIntentDetection) ?? '<none>',
399
}
400
);
401
}
402
403
private prepareInternalTelemetryContext(fileExcerpt: string | undefined, historyMessages: Raw.ChatMessage[], attachedContext?: ChatVariablesCollection) {
404
405
// Single internal telemetry size must be less than 8KB
406
// Be conservative and set the budget to 5KB to account for other properties
407
let telemetryBudget = 5000;
408
409
const names: string[] = [];
410
if (attachedContext) {
411
for (const attachment of attachedContext) {
412
const nameLength = Buffer.byteLength(attachment.uniqueName, 'utf8');
413
if (telemetryBudget - nameLength < 0) {
414
break;
415
}
416
telemetryBudget -= nameLength;
417
names.push(attachment.uniqueName);
418
}
419
}
420
421
let fileExcerptExceedsBudget = false;
422
if (fileExcerpt) {
423
const fileExcerptSize = Buffer.byteLength(fileExcerpt, 'utf8');
424
if (fileExcerptSize > telemetryBudget) {
425
fileExcerptExceedsBudget = true;
426
fileExcerpt = undefined;
427
} else {
428
telemetryBudget -= fileExcerptSize;
429
}
430
} else {
431
fileExcerpt = undefined;
432
}
433
434
const history: string[] = [];
435
for (let i = historyMessages.length - 1; i >= 0; i -= 1) {
436
const message = historyMessages[i];
437
const text = `${roleToString(message.role).toUpperCase()}: ${message.content}\n\n`;
438
const textLength = Buffer.byteLength(text, 'utf8');
439
if (telemetryBudget - textLength < 0) {
440
break;
441
}
442
history.push(text);
443
telemetryBudget -= textLength;
444
}
445
446
return { fileExcerpt, fileExcerptExceedsBudget, history: history.reverse(), attachedContext: names };
447
}
448
449
private sendInternalTelemetry(
450
request: string,
451
preferredIntent: Intent | undefined,
452
currentFilePath: string | undefined,
453
fileExerpt: string | undefined,
454
detectedIntent: string | undefined,
455
languageId: string | undefined,
456
isRerunWithoutIntentDetection: boolean | undefined,
457
historyMessages: Raw.ChatMessage[],
458
location: ChatLocation
459
) {
460
const { fileExcerpt, history } = this.prepareInternalTelemetryContext(fileExerpt, historyMessages);
461
462
this.telemetryService.sendInternalMSFTTelemetryEvent(
463
'intentDetection',
464
{
465
chatLocation: ChatLocation.toString(location),
466
request,
467
preferredIntent: preferredIntent ?? '<none>',
468
filePath: currentFilePath ?? '<none>',
469
fileExerpt: fileExcerpt ?? '<none>',
470
detectedIntent: detectedIntent ?? '<none>',
471
languageId: languageId ?? '<none>',
472
isRerunWithoutIntentDetection: String(isRerunWithoutIntentDetection) ?? '<none>',
473
history: history.join('')
474
},
475
{}
476
);
477
}
478
}
479
480
class DocumentExcerptInfo extends PromptMetadata {
481
constructor(
482
readonly fileExcerpt: string | undefined,
483
readonly filePath: string | undefined,
484
) {
485
super();
486
}
487
}
488
489
type IntentDetectionPromptProps = PromptElementProps<{
490
history?: readonly Turn[];
491
preferredIntent: Intent | undefined;
492
location: ChatLocation;
493
userQuestion: string;
494
documentContext: IDocumentContext | undefined;
495
chatVariables: ChatVariablesCollection;
496
builtinParticipants: ChatParticipantMetadata[];
497
thirdPartyParticipants?: ChatParticipantMetadata[];
498
}>;
499
500
class IntentDetectionPrompt extends PromptElement<IntentDetectionPromptProps> {
501
502
constructor(
503
props: IntentDetectionPromptProps,
504
@IIgnoreService protected readonly _ignoreService: IIgnoreService,
505
@IConfigurationService protected readonly _configurationService: IConfigurationService,
506
@IIntentService protected readonly _intentService: IIntentService,
507
) {
508
super(props);
509
}
510
511
async render() {
512
let {
513
builtinParticipants,
514
preferredIntent,
515
userQuestion,
516
documentContext,
517
} = this.props;
518
519
let currentFileUri: Uri | undefined;
520
let currentFileContext: string | undefined;
521
let fileExcerptCodeBlock: CodeBlock | undefined;
522
try {
523
if (documentContext !== undefined && !(await this._ignoreService.isCopilotIgnored(documentContext.document.uri))) {
524
525
const { document, selection } = documentContext;
526
527
currentFileUri = document.uri;
528
const range = new Range(
529
new Position(Math.max(selection.start.line - 5, 0), 0),
530
new Position(Math.min(selection.end.line + 5, document.lineCount), document.lineAt(selection.end.line).text.length),
531
);
532
currentFileContext = document.getText(range);
533
fileExcerptCodeBlock = currentFileContext.trim().length > 0 ? <CodeBlock uri={currentFileUri} languageId={document.languageId} code={currentFileContext} shouldTrim={false} /> : undefined;
534
}
535
} catch (_e) { }
536
537
const fileMetadata = new DocumentExcerptInfo(currentFileContext, currentFileUri?.path);
538
539
if (documentContext !== undefined && isNotebookCellOrNotebookChatInput(documentContext.document.uri)) {
540
builtinParticipants = builtinParticipants.filter((participant) => participant.command !== 'tests');
541
}
542
543
544
function commands(participant: ChatParticipantMetadata[]) {
545
const seen = new Set<string>();
546
547
const a = participant.flatMap((p) => p.disambiguation);
548
549
return a.filter(value => {
550
if (seen.has(value.category)) {
551
return false;
552
}
553
seen.add(value.category);
554
return true;
555
});
556
}
557
558
return (
559
<>
560
<meta value={fileMetadata} />
561
<SystemMessage>
562
When asked for your name, you must respond with "GitHub Copilot".<br />
563
Follow the user's requirements carefully & to the letter.<br />
564
</SystemMessage>
565
<UserMessage>
566
A software developer is using an AI chatbot in a code editor{currentFileUri && ` in file ${currentFileUri.path}`}.<br />
567
{fileExcerptCodeBlock === undefined
568
? <br />
569
: <>
570
Current active file contains following excerpt:<br />
571
{fileExcerptCodeBlock}<br />
572
</>}
573
The developer added the following request to the chat and your goal is to select a function to perform the request.<br />
574
{preferredIntent && `The developer probably wants Function Id '${preferredIntent}', pick different only if you're certain.`}<br />
575
Request: {userQuestion}<br />
576
<br />
577
Available functions:<br />
578
{commands(builtinParticipants).map((alias) =>
579
<>
580
Function Id: {alias.category}<br />
581
Function Description: {alias.description}<br />
582
<br />
583
</>
584
)}
585
<br />
586
Here are some examples to make the instructions clearer:<br />
587
{commands(builtinParticipants).map((alias) =>
588
<>
589
Request: {alias.examples[0]}<br />
590
Response: {alias.category}<br />
591
<br />
592
</>)
593
}
594
Request: {userQuestion}<br />
595
Response:
596
</UserMessage>
597
</>
598
);
599
}
600
601
602
}
603
604
interface BuiltinParticipantDescriptionsProps extends BasePromptElementProps {
605
includeDynamicParticipants: boolean;
606
participants: ChatParticipantMetadata[];
607
}
608
609
class ParticipantDescriptions extends PromptElement<BuiltinParticipantDescriptionsProps> {
610
override render() {
611
return (<>
612
{this.props.participants.flatMap((p) => {
613
return p.disambiguation.map((alias) => {
614
return (
615
<>
616
| {alias.category ?? (alias as any).categoryName} | {alias.description} | {alias.examples.length ? alias.examples.map(example => `"${example}"`).join(', ') : '--'} |<br />
617
</>
618
);
619
});
620
})}
621
{this.props.includeDynamicParticipants && <>| github_questions | The user is asking about an issue, pull request, branch, commit hash, diff, discussion, repository, or published release on GitHub.com. This category does not include performing local Git operations using the CLI. | "What has been changed in the pull request 1361 in browserify/browserify repo?" |<br /></>}
622
{this.props.includeDynamicParticipants && <>| web_questions | The user is asking a question that requires current knowledge from a web search engine. Such questions often reference time periods that exceed your knowledge cutoff. | "What is the latest LTS version of Node.js?" |<br /></>}
623
| unknown | The user's question does not fit exactly one of the categories above, is about a product other than Visual Studio Code or GitHub, or is a general question about code, code errors, or software engineering. | "How do I center a div in CSS?" |<br /></>);
624
}
625
}
626
627
export class GPT4OIntentDetectionPrompt extends IntentDetectionPrompt {
628
629
override render() {
630
const { history, chatVariables, userQuestion } = this.props;
631
632
return (<>
633
<HistoryWithInstructions history={history || []} passPriority historyPriority={800}>
634
<InstructionMessage>
635
You are a helpful AI programming assistant to a user who is a software engineer, acting on behalf of the Visual Studio Code editor. Your task is to choose one category from the Markdown table of categories below that matches the user's question. Carefully review the user's question, any previous messages, and any provided context such as code snippets. Respond with just the category name. Your chosen category will help Visual Studio Code provide the user with a higher-quality response, and choosing incorrectly will degrade the user's experience of using Visual Studio Code, so you must choose wisely. If you cannot choose just one category, or if none of the categories seem like they would provide the user with a better result, you must always respond with "unknown".<br />
636
<br />
637
| Category name | Category description | Example of matching question |<br />
638
| -- | -- | -- |<br />
639
<ParticipantDescriptions participants={this.props.thirdPartyParticipants ? this.props.thirdPartyParticipants : this.props.builtinParticipants} includeDynamicParticipants={!this.props.thirdPartyParticipants} />
640
</InstructionMessage>
641
</HistoryWithInstructions>
642
{<ChatVariablesAndQuery query={userQuestion} chatVariables={chatVariables} priority={900} embeddedInsideUserMessage={false} />}
643
</>
644
);
645
}
646
}
647
648