UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

174 lines 8.22 kB
"use strict"; /* eslint-disable security/detect-object-injection */ /** * @module botbuilder-m365 */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PromptTemplateEngine = void 0; const Block_1 = require("./Block"); const CodeBlock_1 = require("./CodeBlock"); const TextBlock_1 = require("./TextBlock"); const VarBlock_1 = require("./VarBlock"); /** * Given a prompt, that might contain references to variables and functions: * - Get the list of references * - Resolve each reference * - Variable references are resolved using the context variables * - Function references are resolved invoking those functions * - Functions can be invoked passing in variables * - Functions do not receive the context variables, unless specified using a special variable * - Functions can be invoked in order and in parallel so the context variables must be immutable when invoked within the template */ class PromptTemplateEngine { constructor(promptManager) { // Blocks delimitation this.starter = '{'; this.ender = '}'; this._promptManager = promptManager; } extractBlocks(templateText, validate = true) { const blocks = this.tokenizeInternal(templateText); if (validate) { this.validateBlocksSyntax(blocks); } return blocks; } render(context, state, textOrBlocks) { return __awaiter(this, void 0, void 0, function* () { if (typeof textOrBlocks == 'string') { const blocks = this.extractBlocks(textOrBlocks); return yield this.render(context, state, blocks); } else { let result = ''; for (const block of textOrBlocks) { switch (block.type) { case Block_1.BlockTypes.Text: result += block.content; break; case Block_1.BlockTypes.Variable: result += block.render(context, state); break; case Block_1.BlockTypes.Code: result += yield block.renderCode(context, state, this._promptManager); break; case Block_1.BlockTypes.Undefined: default: throw new Error(`Invalid bock of type "${block.type}" encountered.`); } } // TODO: good time to count tokens, though that depends on the model used return result; } }); } renderVariables(context, state, blocks) { return blocks.map((block) => { return block.type != Block_1.BlockTypes.Variable ? block : new TextBlock_1.TextBlock(block.render(context, state)); }); } renderCode(context, state, blocks) { return __awaiter(this, void 0, void 0, function* () { const updatedBlocks = []; for (const block of blocks) { if (block.type != Block_1.BlockTypes.Code) { updatedBlocks.push(block); } else { const codeResult = yield block.renderCode(context, state, this._promptManager); updatedBlocks.push(new TextBlock_1.TextBlock(codeResult)); } } return updatedBlocks; }); } tokenizeInternal(template) { // An empty block consists of 4 chars: "{{}}" const EMPTY_CODE_BLOCK_LENGTH = 4; // A block shorter than 5 chars is either empty or invalid, e.g. "{{ }}" and "{{$}}" const MIN_CODE_BLOCK_LENGTH = EMPTY_CODE_BLOCK_LENGTH + 1; // Render NULL to "" // Since the template is a string, all `template` instances can have non-null assertion if (template === null) { return [new TextBlock_1.TextBlock('')]; } // If the template is "empty" return the content as a text block if (template.length < MIN_CODE_BLOCK_LENGTH) { return [new TextBlock_1.TextBlock(template)]; } const blocks = []; let cursor = 0; let endOfLastBlock = 0; let startPos = 0; let startFound = false; while (cursor < template.length - 1) { // When "{{" is found if (template[cursor] === this.starter && template[cursor + 1] === this.starter) { startPos = cursor; startFound = true; } // When "}}" is found else if (startFound && template[cursor] === this.ender && template[cursor + 1] === this.ender) { // If there is plain text between the current var/code block and the previous one, capture that as a TextBlock if (startPos > endOfLastBlock) { blocks.push(new TextBlock_1.TextBlock(template, endOfLastBlock, startPos)); } // Skip ahead to the second "}" of "}}" cursor++; // Extract raw block const contentWithDelimiters = template.substring(startPos, cursor + 1); // Remove "{{" and "}}" delimiters and trim empty chars const contentWithoutDelimiters = contentWithDelimiters .substring(2, contentWithDelimiters.length - 2) .trim(); if (contentWithoutDelimiters.length === 0) { // If what is left is empty, consider the raw block a Text Block blocks.push(new TextBlock_1.TextBlock(contentWithDelimiters)); } else { // If the block starts with "$" it's a variable if (VarBlock_1.VarBlock.hasVarPrefix(contentWithoutDelimiters)) { // Note: validation is delayed to the time VarBlock is rendered blocks.push(new VarBlock_1.VarBlock(contentWithoutDelimiters)); } else { // Note: validation is delayed to the time CodeBlock is rendered blocks.push(new CodeBlock_1.CodeBlock(contentWithoutDelimiters)); } } endOfLastBlock = cursor + 1; startFound = false; } cursor++; } // If there is something left after the last block, capture it as a TextBlock if (endOfLastBlock < template.length) { blocks.push(new TextBlock_1.TextBlock(template, endOfLastBlock, template.length)); } return blocks; } validateBlocksSyntax(blocks) { blocks.forEach((block) => { var _a; const { valid, errorMessage: error } = block.isValid(); if (!valid || error) { throw new Error(`Prompt template syntax error: ${(_a = error === null || error === void 0 ? void 0 : error.toString()) !== null && _a !== void 0 ? _a : 'unknown'}`); } }); } } exports.PromptTemplateEngine = PromptTemplateEngine; //# sourceMappingURL=PromptTemplateEngine.js.map