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

Allows comments in prompt templates #14470

Merged
merged 2 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 16 additions & 0 deletions packages/ai-core/data/prompttemplate.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@
}
}
},
{
"name": "comment.block.prompttemplate",
"begin": "\\A{{!--",
"beginCaptures": {
"0": {
"name": "punctuation.definition.comment.begin"
}
},
"end": "--}}",
"endCaptures": {
"0": {
"name": "punctuation.definition.comment.end"
}
},
"patterns": []
},
{
"name": "variable.other.prompttemplate.double",
"begin": "\\{\\{",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export class AIAgentConfigurationWidget extends ReactWidget {
const promptTemplates = agent.promptTemplates;
const result: ParsedPrompt = { functions: [], globalVariables: [], agentSpecificVariables: [] };
promptTemplates.forEach(template => {
const storedPrompt = this.promptService.getRawPrompt(template.id);
const storedPrompt = this.promptService.getUnresolvedPrompt(template.id);
const prompt = storedPrompt?.template ?? template.template;
const variableMatches = matchVariablesRegEx(prompt);

Expand Down
85 changes: 85 additions & 0 deletions packages/ai-core/src/common/prompt-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,89 @@ describe('PromptService', () => {
const prompt17 = await promptService.getPrompt('17', { name: 'John' });
expect(prompt17?.text).to.equal('Hi, John! John');
});

it('should strip single-line comments at the start of the template', () => {
promptService.storePrompt('comment-basic', '{{!-- Comment --}}Hello, {{name}}!');
const prompt = promptService.getUnresolvedPrompt('comment-basic');
expect(prompt?.template).to.equal('Hello, {{name}}!');
});

it('should remove line break after first-line comment', () => {
promptService.storePrompt('comment-line-break', '{{!-- Comment --}}\nHello, {{name}}!');
const prompt = promptService.getUnresolvedPrompt('comment-line-break');
expect(prompt?.template).to.equal('Hello, {{name}}!');
});

it('should strip multiline comments at the start of the template', () => {
promptService.storePrompt('comment-multiline', '{{!--\nMultiline comment\n--}}\nGoodbye, {{name}}!');
const prompt = promptService.getUnresolvedPrompt('comment-multiline');
expect(prompt?.template).to.equal('Goodbye, {{name}}!');
});

it('should not strip comments not in the first line', () => {
promptService.storePrompt('comment-second-line', 'Hello, {{name}}!\n{{!-- Comment --}}');
const prompt = promptService.getUnresolvedPrompt('comment-second-line');
expect(prompt?.template).to.equal('Hello, {{name}}!\n{{!-- Comment --}}');
});

it('should treat unclosed comments as regular text', () => {
promptService.storePrompt('comment-unclosed', '{{!-- Unclosed comment');
const prompt = promptService.getUnresolvedPrompt('comment-unclosed');
expect(prompt?.template).to.equal('{{!-- Unclosed comment');
});

it('should treat standalone closing delimiters as regular text', () => {
promptService.storePrompt('comment-standalone', '--}} Hello, {{name}}!');
const prompt = promptService.getUnresolvedPrompt('comment-standalone');
expect(prompt?.template).to.equal('--}} Hello, {{name}}!');
});

it('should handle nested comments and stop at the first closing tag', () => {
promptService.storePrompt('nested-comment', '{{!-- {{!-- Nested comment --}} --}}text');
const prompt = promptService.getUnresolvedPrompt('nested-comment');
expect(prompt?.template).to.equal('--}}text');
});

it('should handle templates with only comments', () => {
promptService.storePrompt('comment-only', '{{!-- Only comments --}}');
const prompt = promptService.getUnresolvedPrompt('comment-only');
expect(prompt?.template).to.equal('');
});

it('should handle mixed delimiters on the same line', () => {
promptService.storePrompt('comment-mixed', '{{!-- Unclosed comment --}}');
const prompt = promptService.getUnresolvedPrompt('comment-mixed');
expect(prompt?.template).to.equal('');
});

it('should resolve variables after stripping single-line comments', async () => {
promptService.storePrompt('comment-resolve', '{{!-- Comment --}}Hello, {{name}}!');
const prompt = await promptService.getPrompt('comment-resolve', { name: 'John' });
expect(prompt?.text).to.equal('Hello, John!');
});

it('should resolve variables in multiline templates with comments', async () => {
promptService.storePrompt('comment-multiline-vars', '{{!--\nMultiline comment\n--}}\nHello, {{name}}!');
const prompt = await promptService.getPrompt('comment-multiline-vars', { name: 'John' });
expect(prompt?.text).to.equal('Hello, John!');
});

it('should resolve variables with standalone closing delimiters', async () => {
promptService.storePrompt('comment-standalone-vars', '--}} Hello, {{name}}!');
const prompt = await promptService.getPrompt('comment-standalone-vars', { name: 'John' });
expect(prompt?.text).to.equal('--}} Hello, John!');
});

it('should treat unclosed comments as text and resolve variables', async () => {
promptService.storePrompt('comment-unclosed-vars', '{{!-- Unclosed comment\nHello, {{name}}!');
const prompt = await promptService.getPrompt('comment-unclosed-vars', { name: 'John' });
expect(prompt?.text).to.equal('{{!-- Unclosed comment\nHello, John!');
});

it('should handle templates with mixed comments and variables', async () => {
promptService.storePrompt('comment-mixed-vars', '{{!-- Comment --}}Hi, {{name}}! {{!-- Another comment --}}');
const prompt = await promptService.getPrompt('comment-mixed-vars', { name: 'John' });
expect(prompt?.text).to.equal('Hi, John! {{!-- Another comment --}}');
});

});
25 changes: 23 additions & 2 deletions packages/ai-core/src/common/prompt-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ export interface ResolvedPromptTemplate {
export const PromptService = Symbol('PromptService');
export interface PromptService {
/**
* Retrieve the raw {@link PromptTemplate} object.
* Retrieve the raw {@link PromptTemplate} object (unresolved variables, functions and including comments).
* @param id the id of the {@link PromptTemplate}
*/
getRawPrompt(id: string): PromptTemplate | undefined;
/**
* Retrieve the unresolved {@link PromptTemplate} object (unresolved variables, functions, excluding comments)
* @param id the id of the {@link PromptTemplate}
*/
getUnresolvedPrompt(id: string): PromptTemplate | undefined;
/**
* Retrieve the default raw {@link PromptTemplate} object.
* @param id the id of the {@link PromptTemplate}
Expand Down Expand Up @@ -182,8 +187,24 @@ export class PromptServiceImpl implements PromptService {
return this._prompts[id];
}

getUnresolvedPrompt(id: string): PromptTemplate | undefined {
const rawPrompt = this.getRawPrompt(id);
if (!rawPrompt) {
return undefined;
}
return {
id: rawPrompt.id,
template: this.stripComments(rawPrompt.template)
};
}

protected stripComments(template: string): string {
const commentRegex = /^\s*{{!--[\s\S]*?--}}\s*\n?/;
return commentRegex.test(template) ? template.replace(commentRegex, '').trimStart() : template;
}

async getPrompt(id: string, args?: { [key: string]: unknown }): Promise<ResolvedPromptTemplate | undefined> {
const prompt = this.getRawPrompt(id);
const prompt = this.getUnresolvedPrompt(id);
if (prompt === undefined) {
return undefined;
}
Expand Down
Loading