Skip to content

Commit

Permalink
[JS] chore: #54 Add unit tests for PromptManager.ts (#1246)
Browse files Browse the repository at this point in the history
## Linked issues

closes: #54 

- Update docstrings for PromptManager.ts
- Add unit tests for PromptManager.ts

New JS coverage: 74.63


## Attestation Checklist

- [x] My code follows the style guidelines of this project

- I have checked for/fixed spelling, linting, and other errors
- I have commented my code for clarity
- I have made corresponding changes to the documentation (updating the
doc strings in the code is sufficient)
- My changes generate no new warnings
- I have added tests that validates my changes, and provides sufficient
test coverage. I have tested with:
  - Local testing
  - E2E testing in Teams
- New and existing unit tests pass locally with my changes

Co-authored-by: Corina Gum <>
  • Loading branch information
corinagum authored Feb 6, 2024
1 parent 0bb1e83 commit 1a9191f
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The files in this folder `/test` are used for unit tests in `PromptManager.spec.ts`. The files in this folder are not used in the actual package.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"name": "SendCard",
"description": "Sends an adaptive card to the user",
"parameters": {
"type": "object",
"properties": {
"card": {
"type": "object",
"description": "The adaptive card to send"
}
},
"required": ["card"]
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
You are a friendly assistant for Microsoft Teams.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"name": "SendCard",
"description": "Sends an adaptive card to the user",
"parameters": {
"type": "object",
"properties": {
"card": {
"type": "object",
"description": "The adaptive card to send"
}
},
"required": ["card"]
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schema": 1.1,
"description": "A bot that can manage a list of work items.",
"type": "completion",
"completion": {
"model": "gpt-3.5-turbo",
"completion_type": "chat",
"include_history": true,
"include_input": true,
"max_input_tokens": 2800,
"max_tokens": 1000,
"temperature": 0.2,
"top_p": 0.0,
"presence_penalty": 0.6,
"frequency_penalty": 0.0,
"stop_sequences": []
},
"augmentation": {
"augmentation_type": "monologue"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"name": "SendCard",
"description": "Sends an adaptive card to the user",
"parameters": {
"type": "object",
"properties": {
"card": {
"type": "object",
"description": "The adaptive card to send"
}
},
"required": ["card"]
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schema": 1.1,
"description": "A bot that can manage a list of work items.",
"type": "completion",
"completion": {
"model": "gpt-3.5-turbo",
"completion_type": "chat",
"include_history": true,
"include_input": true,
"max_input_tokens": 2800,
"max_tokens": 1000,
"temperature": 0.2,
"top_p": 0.0,
"presence_penalty": 0.6,
"frequency_penalty": 0.0,
"stop_sequences": []
},
"augmentation": {
"augmentation_type": "monologue"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
You are a friendly assistant for Microsoft Teams.
184 changes: 184 additions & 0 deletions js/packages/teams-ai/src/prompts/PromptManager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { strict as assert } from 'assert';
import path from 'path';

import { DataSource } from '../dataSources';
import { TestPromptManager } from '../internals/testing/TestPromptManager';
import { ConfiguredPromptManagerOptions, PromptManager } from './PromptManager';
import { Message } from './Message';
import { PromptTemplate } from './PromptTemplate';
import { RenderedPromptSection } from './PromptSection';

describe('PromptManager', () => {
describe('constructor', () => {
Expand All @@ -10,6 +17,89 @@ describe('PromptManager', () => {
});
});

it('should return options', () => {
const options: ConfiguredPromptManagerOptions = {
promptsFolder: 'promptFolder',
role: 'system',
max_conversation_history_tokens: -1,
max_history_messages: 10,
max_input_tokens: -1
};
const prompts = new TestPromptManager(options);
const configuredOptions = prompts.options;
assert(configuredOptions !== undefined);
assert(configuredOptions.promptsFolder === 'promptFolder');
assert(configuredOptions.role === 'system');
assert(configuredOptions.max_conversation_history_tokens === -1);
assert(configuredOptions.max_history_messages === 10);
});

describe('addDataSource', () => {
it('should add a data source', () => {
const prompts = new TestPromptManager();
const newDataSource: DataSource = {
name: 'test',
renderData: async (context, memory, tokenizer, maxTokens): Promise<RenderedPromptSection<string>> => {
return Promise.resolve({ text: 'test', output: 'test', length: 1, tokens: 1, tooLong: false });
}
};
prompts.addDataSource(newDataSource);
assert(newDataSource !== undefined);
});

it('should throw an error on adding duplicate data source', () => {
const prompts = new TestPromptManager();
const newDataSource: DataSource = {
name: 'test',
renderData: async (context, memory, tokenizer, maxTokens): Promise<RenderedPromptSection<string>> => {
return Promise.resolve({ text: 'test', output: 'test', length: 1, tokens: 1, tooLong: false });
}
};
prompts.addDataSource(newDataSource);
assert(newDataSource !== undefined);
assert.throws(() => prompts.addDataSource(newDataSource));
});
});

describe('getDataSource', () => {
it('should get a data source', () => {
const prompts = new TestPromptManager();
const newDataSource: DataSource = {
name: 'test',
renderData: async (context, memory, tokenizer, maxTokens): Promise<RenderedPromptSection<string>> => {
return Promise.resolve({ text: 'test', output: 'test', length: 1, tokens: 1, tooLong: false });
}
};
prompts.addDataSource(newDataSource);
const dataSource = prompts.getDataSource('test');
assert(dataSource !== undefined);
});

it('should throw an error on getting a data source that does not exist', () => {
const prompts = new TestPromptManager();
assert.throws(() => prompts.getDataSource('test'));
});
});

describe('hasDataSource', () => {
it('should return true when a data source exists', () => {
const prompts = new TestPromptManager();
const newDataSource: DataSource = {
name: 'test',
renderData: async (context, memory, tokenizer, maxTokens): Promise<RenderedPromptSection<string>> => {
return Promise.resolve({ text: 'test', output: 'test', length: 1, tokens: 1, tooLong: false });
}
};
prompts.addDataSource(newDataSource);
assert(prompts.hasDataSource('test'));
});

it('should return false when a data source does not exist', () => {
const prompts = new TestPromptManager();
assert(!prompts.hasDataSource('test'));
});
});

describe('addFunction', () => {
it('should add a function', () => {
const prompts = new TestPromptManager();
Expand Down Expand Up @@ -50,4 +140,98 @@ describe('PromptManager', () => {
assert.equal(prompts.hasFunction('test'), true);
});
});

describe('Prompts', () => {
const newPrompt: PromptTemplate = {
name: 'test',
prompt: {
required: true,
tokens: -1,
renderAsMessages(context, memory, functions, tokenizer, maxTokens) {
return Promise.resolve({
messages: [],
output: [] as Message<string>[],
length: 1,
tokens: 1,
tooLong: false
});
},
renderAsText(context, memory, functions, tokenizer, maxTokens) {
return Promise.resolve({ text: 'test', output: 'test', length: 1, tokens: 1, tooLong: false });
}
},
config: {
completion: {
frequency_penalty: 0,
include_history: true,
include_input: true,
include_images: false,
max_tokens: 100,
max_input_tokens: 2048,
model: 'test',
presence_penalty: 0,
temperature: 0,
top_p: 1
},
schema: 1.1,
type: 'completion'
}
};
it('addPrompt should add a prompt', async () => {
const prompts = new TestPromptManager();
prompts.addPrompt(newPrompt);
assert.deepEqual(await prompts.getPrompt('test'), newPrompt);
assert.equal(await prompts.hasPrompt('test'), true);
});

it('should throw when adding a prompt that already exists', () => {
const prompts = new TestPromptManager();
prompts.addPrompt(newPrompt);
assert.throws(() => prompts.addPrompt(newPrompt));
});

it(`should resolve the prompt template files if the prompt name doesn't exist`, async () => {
const prompts = new PromptManager({
promptsFolder: path.join(__dirname, '..', 'internals', 'testing', 'assets', 'test')
});
const prompt = await prompts.getPrompt('unknown');
assert.notEqual(prompt, null);
});

it(`should throw an error if config.json is missing when using getPrompt`, async () => {
const prompts = new PromptManager({
promptsFolder: path.join(__dirname, '..', 'internals', 'testing', 'assets', 'test')
});
assert.rejects(async () => {
await prompts.getPrompt('missingConfig');
});
});

it(`should throw an error if skprompt.txt is missing when using getPrompt`, async () => {
const prompts = new PromptManager({
promptsFolder: path.join(__dirname, '..', 'internals', 'testing', 'assets', 'test')
});
assert.rejects(async () => {
await prompts.getPrompt('missingPrompt');
});
});

it(`should assign role 'system' if role is missing`, async () => {
const prompts = new PromptManager({
role: '',
promptsFolder: path.join(__dirname, '..', 'internals', 'testing', 'assets', 'test')
});
assert.rejects(async () => {
await prompts.getPrompt('missingPrompt');
});
});

it(`should resolve the prompt template files if the prompt name doesn't exist`, async () => {
const prompts = new PromptManager({
promptsFolder: path.join(__dirname, '..', 'internals', 'testing', 'assets', 'test')
});
const result = await prompts.hasPrompt('unknown');
assert.equal(result, true);
});
});
});
Loading

0 comments on commit 1a9191f

Please sign in to comment.