Skip to content

Commit

Permalink
Improves AI message generation
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Sep 30, 2024
1 parent b4eaa7c commit d3a9469
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 247 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3657,21 +3657,21 @@
},
"gitlens.experimental.generateCommitMessagePrompt": {
"type": "string",
"default": "Now, please generate a commit message. Ensure that it includes a precise and informative subject line that succinctly summarizes the crux of the changes in under 50 characters. If necessary, follow with an explanatory body providing insight into the nature of the changes, the reasoning behind them, and any significant consequences or considerations arising from them. Conclude with any relevant issue references at the end of the message.",
"default": "Now, based on the provided code diff and any additional context, create a concise but meaningful commit message following the instructions above.",
"markdownDescription": "Specifies the prompt to use to tell the AI provider how to structure or format the generated commit message",
"scope": "window",
"order": 2
},
"gitlens.experimental.generateCloudPatchMessagePrompt": {
"type": "string",
"default": "Now, please generate a title and optional description. Ensure that it includes a precise and informative subject line that succinctly summarizes the crux of the changes in under 50 characters. If necessary, follow with an explanatory body providing insight into the nature of the changes, the reasoning behind them, and any significant consequences or considerations arising from them. Conclude with any relevant issue references at the end of the message.",
"default": "Now, based on the provided code diff and any additional context, create a concise but meaningful title and description following the instructions above.",
"markdownDescription": "Specifies the prompt to use to tell the AI provider how to structure or format the generated title and description",
"scope": "window",
"order": 3
},
"gitlens.experimental.generateCodeSuggestMessagePrompt": {
"type": "string",
"default": "Now, please generate a title and optional description. Ensure that it includes a precise and informative subject line that succinctly summarizes the crux of the changes in under 50 characters. If necessary, follow with an explanatory body providing insight into the nature of the changes, the reasoning behind them, and any significant consequences or considerations arising from them. Conclude with any relevant issue references at the end of the message.",
"default": "Now, based on the provided code diff and any additional context, create a concise but meaningful code review title and description following the instructions above.",
"markdownDescription": "Specifies the prompt to use to tell the AI provider how to structure or format the generated title and description",
"scope": "window",
"order": 3
Expand Down
44 changes: 28 additions & 16 deletions src/ai/aiProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,22 +200,22 @@ export class AIProviderService implements Disposable {
changes: string[],
sourceContext: { source: Sources },
options?: { cancellation?: CancellationToken; context?: string; progress?: ProgressOptions },
): Promise<string | undefined>;
): Promise<{ summary: string; body: string } | undefined>;
async generateCommitMessage(
repoPath: Uri,
sourceContext: { source: Sources },
options?: { cancellation?: CancellationToken; context?: string; progress?: ProgressOptions },
): Promise<string | undefined>;
): Promise<{ summary: string; body: string } | undefined>;
async generateCommitMessage(
repository: Repository,
sourceContext: { source: Sources },
options?: { cancellation?: CancellationToken; context?: string; progress?: ProgressOptions },
): Promise<string | undefined>;
): Promise<{ summary: string; body: string } | undefined>;
async generateCommitMessage(
changesOrRepoOrPath: string[] | Repository | Uri,
sourceContext: { source: Sources },
options?: { cancellation?: CancellationToken; context?: string; progress?: ProgressOptions },
): Promise<string | undefined> {
): Promise<{ summary: string; body: string } | undefined> {
const changes: string | undefined = await this.getChanges(changesOrRepoOrPath);
if (changes == null) return undefined;

Expand Down Expand Up @@ -264,7 +264,8 @@ export class AIProviderService implements Disposable {
payload['output.length'] = result?.length;
this.container.telemetry.sendEvent('ai/generate', { ...payload, duration: Date.now() - start }, source);

return result;
if (result == null) return undefined;
return parseGeneratedMessage(result);
} catch (ex) {
this.container.telemetry.sendEvent(
'ai/generate',
Expand All @@ -291,7 +292,7 @@ export class AIProviderService implements Disposable {
progress?: ProgressOptions;
codeSuggestion?: boolean;
},
): Promise<string | undefined> {
): Promise<{ summary: string; body: string } | undefined> {
const changes: string | undefined = await this.getChanges(changesOrRepoOrPath);
if (changes == null) return undefined;

Expand Down Expand Up @@ -342,7 +343,8 @@ export class AIProviderService implements Disposable {
payload['output.length'] = result?.length;
this.container.telemetry.sendEvent('ai/generate', { ...payload, duration: Date.now() - start }, source);

return result;
if (result == null) return undefined;
return parseGeneratedMessage(result);
} catch (ex) {
this.container.telemetry.sendEvent(
'ai/generate',
Expand Down Expand Up @@ -633,16 +635,26 @@ export async function getApiKey(
return apiKey;
}

export function extractDraftMessage(
message: string,
splitter = '\n\n',
): { title: string; description: string | undefined } {
const firstBreak = message.indexOf(splitter) ?? 0;
const title = firstBreak > -1 ? message.substring(0, firstBreak) : message;
const description = firstBreak > -1 ? message.substring(firstBreak + splitter.length) : undefined;
function parseGeneratedMessage(result: string): { summary: string; body: string } {
let summary = result.match(/<summary>\s?([\s\S]*?)\s?<\/summary>/)?.[1];
let body = result.match(/<body>\s?([\s\S]*?)\s?<\/body>/)?.[1];

result = result.trim();
if ((summary == null || body == null) && result) {
debugger;

const index = result.indexOf('\n');
if (index === -1) {
summary = '';
body = result;
} else {
summary = result.substring(0, index);
body = result.substring(index + 1);
}
}

return {
title: title,
description: description,
summary: summary ?? '',
body: body ?? '',
};
}
108 changes: 46 additions & 62 deletions src/ai/anthropicProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import type { TelemetryEvents } from '../constants.telemetry';
import type { Container } from '../container';
import { CancellationError } from '../errors';
import { sum } from '../system/iterable';
import { interpolate } from '../system/string';
import { configuration } from '../system/vscode/configuration';
import type { Storage } from '../system/vscode/storage';
import type { AIModel, AIProvider } from './aiProviderService';
import { getApiKey as getApiKeyCore, getMaxCharacters } from './aiProviderService';
import { cloudPatchMessageSystemPrompt, codeSuggestMessageSystemPrompt, commitMessageSystemPrompt } from './prompts';
import {
generateCloudPatchMessageSystemPrompt,
generateCloudPatchMessageUserPrompt,
generateCodeSuggestMessageSystemPrompt,
generateCodeSuggestMessageUserPrompt,
generateCommitMessageSystemPrompt,
generateCommitMessageUserPrompt,
} from './prompts';

const provider = { id: 'anthropic', name: 'Anthropic' } as const;
type LegacyModels = Extract<AnthropicModels, 'claude-instant-1' | 'claude-2'>;
Expand Down Expand Up @@ -81,9 +89,10 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {
diff: string,
reporting: TelemetryEvents['ai/generate'],
promptConfig: {
type: 'commit' | 'cloud-patch' | 'code-suggestion';
systemPrompt: string;
customPrompt: string;
contextName: string;
userPrompt: string;
customInstructions?: string;
},
options?: { cancellation?: CancellationToken; context?: string },
): Promise<string | undefined> {
Expand All @@ -99,15 +108,14 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {
model as LegacyModel,
apiKey,
(max, retries) => {
const code = diff.substring(0, max);
let prompt = `\n\nHuman: ${promptConfig.systemPrompt}\n\nHuman: Here is the code diff to use to generate the ${promptConfig.contextName}:\n\n${code}\n`;
if (options?.context) {
prompt += `\nHuman: Here is additional context which should be taken into account when generating the ${promptConfig.contextName}:\n\n${options.context}\n`;
}
if (promptConfig.customPrompt) {
prompt += `\nHuman: ${promptConfig.customPrompt}\n`;
}
prompt += '\nAssistant:';
const prompt = `\n\nHuman: ${promptConfig.systemPrompt}\n\n${interpolate(
promptConfig.userPrompt,
{
diff: diff.substring(0, max),
context: options?.context ?? '',
instructions: promptConfig.customInstructions ?? '',
},
)}\n\nAssistant:`;

reporting['retry.count'] = retries;
reporting['input.length'] = (reporting['input.length'] ?? 0) + prompt.length;
Expand All @@ -123,39 +131,18 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {
apiKey,
promptConfig.systemPrompt,
(max, retries) => {
const code = diff.substring(0, max);
const messages: Message[] = [
{
role: 'user',
content: [
{
type: 'text',
text: `Here is the code diff to use to generate the ${promptConfig.contextName}:`,
},
{
type: 'text',
text: code,
text: interpolate(promptConfig.userPrompt, {
diff: diff.substring(0, max),
context: options?.context ?? '',
instructions: promptConfig.customInstructions ?? '',
}),
},
...(options?.context
? ([
{
type: 'text',
text: `Here is additional context which should be taken into account when generating the ${promptConfig.contextName}:`,
},
{
type: 'text',
text: options.context,
},
] satisfies Message['content'])
: []),
...(promptConfig.customPrompt
? ([
{
type: 'text',
text: promptConfig.customPrompt,
},
] satisfies Message['content'])
: []),
],
},
];
Expand All @@ -180,7 +167,7 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {

return result;
} catch (ex) {
throw new Error(`Unable to generate ${promptConfig.contextName}: ${ex.message}`);
throw new Error(`Unable to generate ${promptConfig.type} message: ${ex.message}`);
}
}

Expand All @@ -190,27 +177,28 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {
reporting: TelemetryEvents['ai/generate'],
options?: { cancellation?: CancellationToken; context?: string; codeSuggestion?: boolean },
): Promise<string | undefined> {
let customPrompt =
options?.codeSuggestion === true
? configuration.get('experimental.generateCodeSuggestionMessagePrompt')
: configuration.get('experimental.generateCloudPatchMessagePrompt');
if (!customPrompt.endsWith('.')) {
customPrompt += '.';
let codeSuggestion;
if (options != null) {
({ codeSuggestion, ...options } = options ?? {});
}

return this.generateMessage(
model,
diff,
reporting,
{
systemPrompt:
options?.codeSuggestion === true ? codeSuggestMessageSystemPrompt : cloudPatchMessageSystemPrompt,
customPrompt: customPrompt,
contextName:
options?.codeSuggestion === true
? 'code suggestion title and description'
: 'cloud patch title and description',
},
codeSuggestion
? {
type: 'code-suggestion',
systemPrompt: generateCodeSuggestMessageSystemPrompt,
userPrompt: generateCodeSuggestMessageUserPrompt,
customInstructions: configuration.get('experimental.generateCodeSuggestionMessagePrompt'),
}
: {
type: 'cloud-patch',
systemPrompt: generateCloudPatchMessageSystemPrompt,
userPrompt: generateCloudPatchMessageUserPrompt,
customInstructions: configuration.get('experimental.generateCloudPatchMessagePrompt'),
},
options,
);
}
Expand All @@ -221,19 +209,15 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {
reporting: TelemetryEvents['ai/generate'],
options?: { cancellation?: CancellationToken; context?: string },
): Promise<string | undefined> {
let customPrompt = configuration.get('experimental.generateCommitMessagePrompt');
if (!customPrompt.endsWith('.')) {
customPrompt += '.';
}

return this.generateMessage(
model,
diff,
reporting,
{
systemPrompt: commitMessageSystemPrompt,
customPrompt: customPrompt,
contextName: 'commit message',
type: 'commit',
systemPrompt: generateCommitMessageSystemPrompt,
userPrompt: generateCommitMessageUserPrompt,
customInstructions: configuration.get('experimental.generateCommitMessagePrompt'),
},
options,
);
Expand Down
Loading

0 comments on commit d3a9469

Please sign in to comment.