Skip to content

Commit

Permalink
Support A/B Compiler Arguments Traits to Completions Prompt
Browse files Browse the repository at this point in the history
- User defines are not covered in this PR.
- Depends on cpptools' update.
- A/B Experimental flags
  - copilotcppTraits: boolean flag to enable cpp traits
  - copilotcppExcludeTraits: string array to exclude individual trait, i.e., compilerArguments.
  - copilotcppMsvcCompilerArgumentFilter: regex string to match compiler arguments for GCC.
  - copilotcppClangCompilerArgumentFilter: regex string to match compiler arguments for Clang.
  - copilotcppGccCompilerArgumentFilter: regex string to match compiler arguments for MSVC.
  • Loading branch information
kuchungmsft committed Oct 25, 2024
1 parent 8cb1def commit c03a944
Show file tree
Hide file tree
Showing 6 changed files with 414 additions and 78 deletions.
2 changes: 2 additions & 0 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ export interface ChatContextResult {
compiler: string;
targetPlatform: string;
targetArchitecture: string;
compilerArgs: string[];
compilerUserDefines: string[];
}

// Requests
Expand Down
20 changes: 12 additions & 8 deletions Extension/src/LanguageServer/copilotProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

import * as vscode from 'vscode';
import * as util from '../common';
import { ChatContextResult, GetIncludesResult } from './client';
import { GetIncludesResult } from './client';
import { getActiveClient } from './extension';
import { getCppContext } from './lmTool';

export interface CopilotTrait {
name: string;
Expand Down Expand Up @@ -38,19 +39,22 @@ export async function registerRelatedFilesProvider(): Promise<void> {

const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? [];
const getTraitsHandler = async () => {
const chatContext: ChatContextResult | undefined = await (getActiveClient().getChatContext(token) ?? undefined);
const cppContext = await getCppContext(context, token);

if (!chatContext) {
if (!cppContext) {
return undefined;
}

let traits: CopilotTrait[] = [
{ name: "language", value: chatContext.language, includeInPrompt: true, promptTextOverride: `The language is ${chatContext.language}.` },
{ name: "compiler", value: chatContext.compiler, includeInPrompt: true, promptTextOverride: `This project compiles using ${chatContext.compiler}.` },
{ name: "standardVersion", value: chatContext.standardVersion, includeInPrompt: true, promptTextOverride: `This project uses the ${chatContext.standardVersion} language standard.` },
{ name: "targetPlatform", value: chatContext.targetPlatform, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetPlatform}.` },
{ name: "targetArchitecture", value: chatContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetArchitecture}.` }
{ name: "language", value: cppContext.language, includeInPrompt: true, promptTextOverride: `The language is ${cppContext.language}.` },
{ name: "compiler", value: cppContext.compiler, includeInPrompt: true, promptTextOverride: `This project compiles using ${cppContext.compiler}.` },
{ name: "standardVersion", value: cppContext.standardVersion, includeInPrompt: true, promptTextOverride: `This project uses the ${cppContext.standardVersion} language standard.` },
{ name: "targetPlatform", value: cppContext.targetPlatform, includeInPrompt: true, promptTextOverride: `This build targets ${cppContext.targetPlatform}.` },
{ name: "targetArchitecture", value: cppContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${cppContext.targetArchitecture}.` }
];
if (cppContext.compilerArguments.length > 0) {
traits.push({ name: "compilerArguments", value: cppContext.compilerArguments, includeInPrompt: true, promptTextOverride: `The compiler command line arguments may contain: ${cppContext.compilerArguments}.` });
}

const excludeTraits = context.flags.copilotcppExcludeTraits as string[] ?? [];
traits = traits.filter(trait => !excludeTraits.includes(trait.name));
Expand Down
97 changes: 87 additions & 10 deletions Extension/src/LanguageServer/lmTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ import * as telemetry from '../telemetry';
import { ChatContextResult } from './client';
import { getClients } from './extension';

const MSVC: string = 'MSVC';
const Clang: string = 'Clang';
const GCC: string = 'GCC';
const knownValues: { [Property in keyof ChatContextResult]?: { [id: string]: string } } = {
language: {
'c': 'C',
'cpp': 'C++',
'cuda-cpp': 'CUDA C++'
},
compiler: {
'msvc': 'MSVC',
'clang': 'Clang',
'gcc': 'GCC'
'msvc': MSVC,
'clang': Clang,
'gcc': GCC
},
standardVersion: {
'c++98': 'C++98',
Expand All @@ -44,6 +47,85 @@ const knownValues: { [Property in keyof ChatContextResult]?: { [id: string]: str
}
};

function formatChatContext(chatContext: ChatContextResult): void {
type KnownKeys = 'language' | 'standardVersion' | 'compiler' | 'targetPlatform' | 'targetArchitecture';
for (const key in knownValues) {
const knownKey = key as KnownKeys;
if (knownKey && knownValues[knownKey] && chatContext[knownKey]) {
chatContext[knownKey] = knownValues[knownKey][chatContext[knownKey] as string] || chatContext[knownKey];
}
}
}

export interface CppContext {
language: string;
standardVersion: string;
compiler: string;
targetPlatform: string;
targetArchitecture: string;
compilerArguments: string;
}

// To be updated after A/B experiments.
const defaultCompilerArgumentFilters: { [id: string]: RegExp | undefined } = {
MSVC: undefined,
Clang: undefined,
GCC: undefined
};

function filterComplierArguments(compiler: string, compilerArguments: string[], context: { flags: Record<string, unknown> }): string[] {
let defaultFilter: RegExp | undefined;
let additionalFilter: RegExp | undefined;
switch (compiler) {
case MSVC:
defaultFilter = defaultCompilerArgumentFilters[compiler];
additionalFilter = context.flags.copilotcppMsvcCompilerArgumentFilter ? new RegExp(context.flags.copilotcppMsvcCompilerArgumentFilter as string) : undefined;
break;
case Clang:
defaultFilter = defaultCompilerArgumentFilters[compiler];
additionalFilter = context.flags.copilotcppClangCompilerArgumentFilter ? new RegExp(context.flags.copilotcppClangCompilerArgumentFilter as string) : undefined;
break;
case GCC:
defaultFilter = defaultCompilerArgumentFilters[compiler];
additionalFilter = context.flags.copilotcppGccCompilerArgumentFilter ? new RegExp(context.flags.copilotcppGccCompilerArgumentFilter as string) : undefined;
break;
}

return compilerArguments.filter(arg => defaultFilter?.test(arg) || additionalFilter?.test(arg));
}

export async function getCppContext(context: { flags: Record<string, unknown> }, token: vscode.CancellationToken): Promise<CppContext | undefined> {
const chatContext: ChatContextResult | undefined = await (getClients()?.ActiveClient?.getChatContext(token) ?? undefined);
if (!chatContext) {
return undefined;
}

formatChatContext(chatContext);

const filteredcompilerArguments = filterComplierArguments(chatContext.compiler, chatContext.compilerArgs, context);

telemetry.logLanguageModelToolEvent(
'Completions/tool',
{
"language": chatContext.language,
"compiler": chatContext.compiler,
"standardVersion": chatContext.standardVersion,
"targetPlatform": chatContext.targetPlatform,
"targetArchitecture": chatContext.targetArchitecture,
"compilerArgumentCount": chatContext.compilerArgs.length.toString(),
"filteredCompilerArgumentCount": filteredcompilerArguments.length.toString()
});

return {
language: chatContext.language,
standardVersion: chatContext.standardVersion,
compiler: chatContext.compiler,
targetPlatform: chatContext.targetPlatform,
targetArchitecture: chatContext.targetArchitecture,
compilerArguments: (filteredcompilerArguments.length > 0) ? filteredcompilerArguments.join(' ') : ''
};
}

export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTool<void> {
public async invoke(options: vscode.LanguageModelToolInvocationOptions<void>, token: vscode.CancellationToken): Promise<vscode.LanguageModelToolResult> {
return new vscode.LanguageModelToolResult([
Expand All @@ -63,7 +145,7 @@ export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTo
}

telemetry.logLanguageModelToolEvent(
'cpp',
'Chat/Tool/cpp',
{
"language": chatContext.language,
"compiler": chatContext.compiler,
Expand All @@ -72,12 +154,7 @@ export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTo
"targetArchitecture": chatContext.targetArchitecture
});

for (const key in knownValues) {
const knownKey = key as keyof ChatContextResult;
if (knownValues[knownKey] && chatContext[knownKey]) {
chatContext[knownKey] = knownValues[knownKey][chatContext[knownKey]] || chatContext[knownKey];
}
}
formatChatContext(chatContext);

return `The user is working on a ${chatContext.language} project. The project uses language version ${chatContext.standardVersion}, compiles using the ${chatContext.compiler} compiler, targets the ${chatContext.targetPlatform} platform, and targets the ${chatContext.targetArchitecture} architecture.`;
}
Expand Down
2 changes: 1 addition & 1 deletion Extension/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function logLanguageServerEvent(eventName: string, properties?: Record<st
export function logLanguageModelToolEvent(eventName: string, properties?: Record<string, string>, metrics?: Record<string, number>): void {
const sendTelemetry = () => {
if (experimentationTelemetry) {
const eventNamePrefix: string = "C_Cpp/Copilot/Chat/Tool/";
const eventNamePrefix: string = "C_Cpp/Copilot/";
experimentationTelemetry.sendTelemetryEvent(eventNamePrefix + eventName, properties, metrics);
}
};
Expand Down
Loading

0 comments on commit c03a944

Please sign in to comment.