@microsoft/teams-ai
Version:
SDK focused on building AI based applications for Microsoft Teams.
230 lines • 9.59 kB
JavaScript
;
/**
* @module teams-ai
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LayoutEngine = void 0;
/**
* Base layout engine that renders a set of `auto`, `fixed`, or `proportional` length sections.
* @remarks
* This class is used internally by the `Prompt` and `GroupSection` classes to render their sections.
*/
class LayoutEngine {
sections;
required;
tokens;
separator;
/**
*
* @param {PromptSection[]} sections List of sections to layout.
* @param {number} tokens Sizing strategy for this section.
* @param {boolean} required Indicates if this section is required.
* @param {string} separator Separator to use between sections when rendering as text.
*/
constructor(sections, tokens, required, separator) {
this.sections = sections;
this.required = required;
this.tokens = tokens;
this.separator = separator;
}
/**
* @private
* @param {TurnContext} context Context for the current turn of conversation with the user.
* @param {Memory} memory The current memory.
* @param {PromptFunctions} functions The functions available to use in the prompt.
* @param {Tokenizer} tokenizer Tokenizer to use when rendering as text.
* @param {number} maxTokens Maximum number of tokens allowed.
* @returns {Promise<RenderedPromptSection<string>>} Rendered section.
*/
async renderAsText(context, memory, functions, tokenizer, maxTokens) {
// Start a new layout
// - Adds all sections from the current LayoutEngine hierarchy to a flat array
const layout = [];
this.addSectionsToLayout(this.sections, layout);
// Layout sections
const remaining = await this.layoutSections(layout, maxTokens, (section) => section.renderAsText(context, memory, functions, tokenizer, maxTokens), (section, remaining) => section.renderAsText(context, memory, functions, tokenizer, remaining), true, tokenizer);
// Build output
const output = [];
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (section.layout) {
output.push(section.layout.output);
}
}
const text = output.join(this.separator);
return { output: text, length: tokenizer.encode(text).length, tooLong: remaining < 0 };
}
/**
* @private
* @param {TurnContext} context Context for the current turn of conversation with the user.
* @param {Memory} memory The current memory.
* @param {PromptFunctions} functions The functions available to use in the prompt.
* @param {Tokenizer} tokenizer Tokenizer to use when rendering as text.
* @param {number} maxTokens Maximum number of tokens allowed.
* @returns {Promise<RenderedPromptSection<Message[]>>} Rendered section.
*/
async renderAsMessages(context, memory, functions, tokenizer, maxTokens) {
// Start a new layout
// - Adds all sections from the current LayoutEngine hierarchy to a flat array
const layout = [];
this.addSectionsToLayout(this.sections, layout);
// Layout sections
const remaining = await this.layoutSections(layout, maxTokens, (section) => section.renderAsMessages(context, memory, functions, tokenizer, maxTokens), (section, remaining) => section.renderAsMessages(context, memory, functions, tokenizer, remaining));
// Build output
const output = [];
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (section.layout) {
output.push(...section.layout.output);
}
}
return { output: output, length: this.getLayoutLength(layout), tooLong: remaining < 0 };
}
/**
* @private
* @template T
* @param {PromptSection[]} sections Sections to add to the layout.
* @param {PromptSectionLayout<T>[]} layout Layout to add the sections to.
*/
addSectionsToLayout(sections, layout) {
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
if (section instanceof LayoutEngine) {
this.addSectionsToLayout(section.sections, layout);
}
else {
layout.push({ section: section });
}
}
}
/**
* @private
* @template T
* @param {PromptSectionLayout<T>[]} layout Layout to render.
* @param {number} maxTokens Maximum number of tokens allowed.
* @param {Function} cbFixed Callback to render a fixed section.
* @param {Function} cbProportional Callback to render a proportional section.
* @param {boolean} textLayout Indicates if the layout is being rendered as text.
* @param {Tokenizer} tokenizer Tokenizer to use when rendering as text.
* @returns {Promise<number>} Number of tokens remaining.
*/
async layoutSections(layout, maxTokens, cbFixed, cbProportional, textLayout = false, tokenizer) {
// Layout fixed sections
await this.layoutFixedSections(layout, cbFixed);
// Get tokens remaining and drop optional sections if too long
let remaining = maxTokens - this.getLayoutLength(layout, textLayout, tokenizer);
while (remaining < 0 && this.dropLastOptionalSection(layout)) {
remaining = maxTokens - this.getLayoutLength(layout, textLayout, tokenizer);
}
// Layout proportional sections
if (this.needsMoreLayout(layout) && remaining > 0) {
// Layout proportional sections
await this.layoutProportionalSections(layout, (section) => cbProportional(section, remaining));
// Get tokens remaining and drop optional sections if too long
remaining = maxTokens - this.getLayoutLength(layout, textLayout, tokenizer);
while (remaining < 0 && this.dropLastOptionalSection(layout)) {
remaining = maxTokens - this.getLayoutLength(layout, textLayout, tokenizer);
}
}
return remaining;
}
/**
* @private
* @template T
* @param {PromptSectionLayout<T>[]} layout Layout to render.
* @param {Function} callback Callback to render a fixed section of the layout.
*/
async layoutFixedSections(layout, callback) {
const promises = [];
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (section.section.tokens < 0 || section.section.tokens > 1.0) {
promises.push(callback(section.section).then((output) => (section.layout = output)));
}
}
await Promise.all(promises);
}
/**
* @private
* @template T
* @param {PromptSectionLayout<T>[]} layout Layout to render.
* @param {Function} callback Callback to render a proportional section of the layout.
*/
async layoutProportionalSections(layout, callback) {
const promises = [];
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (section.section.tokens >= 0.0 && section.section.tokens <= 1.0) {
promises.push(callback(section.section).then((output) => (section.layout = output)));
}
}
await Promise.all(promises);
}
/**
* @private
* @template T
* @param {PromptSectionLayout<T>[]} layout Layout to get the length of.
* @param {boolean} textLayout Indicates if the layout is being rendered as text.
* @param {Tokenizer} tokenizer Tokenizer to use when rendering as text.
* @returns {number} Length of the layout.
*/
getLayoutLength(layout, textLayout = false, tokenizer) {
if (textLayout && tokenizer) {
const output = [];
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (section.layout) {
output.push(section.layout.output);
}
}
return tokenizer.encode(output.join(this.separator)).length;
}
else {
let length = 0;
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (section.layout) {
length += section.layout.length;
}
}
return length;
}
}
/**
* @private
* @template T
* @param {PromptSectionLayout<T>[]} layout Layout to drop the last optional section from.
* @returns {boolean} True if an optional section was dropped, false otherwise.
*/
dropLastOptionalSection(layout) {
for (let i = layout.length - 1; i >= 0; i--) {
const section = layout[i];
if (!section.section.required) {
layout.splice(i, 1);
return true;
}
}
return false;
}
/**
* @private
* @template T
* @param {PromptSectionLayout<T>[]} layout Layout to check.
* @returns {boolean} True if the layout needs more rendering, false otherwise.
*/
needsMoreLayout(layout) {
for (let i = 0; i < layout.length; i++) {
const section = layout[i];
if (!section.layout) {
return true;
}
}
return false;
}
}
exports.LayoutEngine = LayoutEngine;
//# sourceMappingURL=LayoutEngine.js.map