UNPKG

@microsoft/teams-ai

Version:

SDK focused on building AI based applications for Microsoft Teams.

346 lines 14.7 kB
"use strict"; /** * @module teams-ai */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PromptManager = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const augmentations_1 = require("../augmentations"); const ActionOutputMessage_1 = require("./ActionOutputMessage"); const ConversationHistory_1 = require("./ConversationHistory"); const DataSourceSection_1 = require("./DataSourceSection"); const GroupSection_1 = require("./GroupSection"); const Prompt_1 = require("./Prompt"); const TemplateSection_1 = require("./TemplateSection"); const UserInputMessage_1 = require("./UserInputMessage"); const UserMessage_1 = require("./UserMessage"); /** * A filesystem based prompt manager. * @remarks * The default prompt manager uses the file system to define prompts that are compatible with * Microsoft's Semantic Kernel SDK (see: https://github.com/microsoft/semantic-kernel) * * Each prompt is a separate folder under a root prompts folder. The folder should contain the following files: * * - "config.json": Required. Contains the prompts configuration and is a serialized instance of `PromptTemplateConfig`. * - "skprompt.txt": Required. Contains the text of the prompt and supports Semantic Kernels prompt template syntax. * - "actions.json": Optional. Contains a list of actions that can be called by the prompt. * * Prompts can be loaded and used by name and new dynamically defined prompt templates can be * registered with the prompt manager. * @template TState Optional. Type of the applications turn state. */ class PromptManager { _options; _dataSources = new Map(); _functions = new Map(); _prompts = new Map(); /** * Creates a new 'PromptManager' instance. * @param {PromptManagerOptions} options - Options used to configure the prompt manager. * @returns {PromptManager} A new prompt manager instance. */ constructor(options) { this._options = Object.assign({ role: 'system', max_conversation_history_tokens: 1.0, max_history_messages: 10, max_input_tokens: -1 }, options); } /** * Gets the configured prompt manager options. * @returns {ConfiguredPromptManagerOptions} The configured prompt manager options. */ get options() { return this._options; } /** * Registers a new data source with the prompt manager. * @param {DataSource} dataSource - Data source to add. * @returns {this} The prompt manager for chaining. */ addDataSource(dataSource) { if (this._dataSources.has(dataSource.name)) { throw new Error(`DataSource '${dataSource.name}' already exists.`); } this._dataSources.set(dataSource.name, dataSource); return this; } /** * Looks up a data source by name. * @param {string} name - Name of the data source to lookup. * @returns {DataSource} The data source. */ getDataSource(name) { const dataSource = this._dataSources.get(name); if (!dataSource) { throw new Error(`DataSource '${name}' not found.`); } return dataSource; } /** * Checks for the existence of a named data source. * @param {string} name - Name of the data source to lookup. * @returns {boolean} True if the data source exists. */ hasDataSource(name) { return this._dataSources.has(name); } /** * Registers a new prompt template function with the prompt manager. * @param {string} name - Name of the function to add. * @param {PromptFunction} fn - Function to add. * @returns {this} - The prompt manager for chaining. */ addFunction(name, fn) { if (this._functions.has(name)) { throw new Error(`Function '${name}' already exists.`); } this._functions.set(name, fn); return this; } /** * Looks up a prompt template function by name. * @param {string} name - Name of the function to lookup. * @returns {PromptFunction} The function. */ getFunction(name) { const fn = this._functions.get(name); if (!fn) { throw new Error(`Function '${name}' not found.`); } return fn; } /** * Checks for the existence of a named prompt template function. * @param {string} name Name of the function to lookup. * @returns {boolean} True if the function exists. */ hasFunction(name) { return this._functions.has(name); } /** * Invokes a prompt template function by name. * @param {string} name - Name of the function to invoke. * @param {TurnContext} context - Turn context for the current turn of conversation with the user. * @param {Memory} memory - An interface for accessing state values. * @param {Tokenizer} tokenizer - Tokenizer to use when rendering the prompt. * @param {string[]} args - Arguments to pass to the function. * @returns {Promise<any>} Value returned by the function. */ invokeFunction(name, context, memory, tokenizer, args) { const fn = this.getFunction(name); return fn(context, memory, this, tokenizer, args); } /** * Registers a new prompt template with the prompt manager. * @param {PromptTemplate} prompt - Prompt template to add. * @returns {this} The prompt manager for chaining. */ addPrompt(prompt) { if (this._prompts.has(prompt.name)) { throw new Error(`The PromptManager.addPrompt() method was called with a previously registered prompt named "${prompt.name}".`); } // Clone and cache prompt const clone = Object.assign({}, prompt); this._prompts.set(prompt.name, clone); return this; } /** * Loads a named prompt template from the filesystem. * @remarks * The template will be pre-parsed and cached for use when the template is rendered by name. * * Any augmentations will also be added to the template. * @param {string} name - Name of the prompt to load. * @returns {Promise<PromptTemplate>} The loaded and parsed prompt template. */ async getPrompt(name) { if (!this._prompts.has(name)) { const template = { name }; // Load template from disk const folder = path.join(this._options.promptsFolder, name); const configFile = path.join(folder, 'config.json'); const promptFile = path.join(folder, 'skprompt.txt'); const actionsFile = path.join(folder, 'actions.json'); // Load prompt config try { const config = await fs.readFile(configFile, 'utf-8'); template.config = JSON.parse(config); } catch (err) { throw new Error(`PromptManager.getPrompt(): an error occurred while loading '${configFile}'. The file is either invalid or missing.`); } // Load prompt text let sections = []; try { const role = this._options.role || 'system'; const prompt = await fs.readFile(promptFile, 'utf-8'); sections.push(new TemplateSection_1.TemplateSection(prompt, role)); } catch (err) { throw new Error(`PromptManager.getPrompt(): an error occurred while loading '${promptFile}'. The file is either invalid or missing.`); } // Load optional actions try { const actions = await fs.readFile(actionsFile, 'utf-8'); template.actions = JSON.parse(actions); } catch (err) { // Ignore missing actions file } // Update the templates config file with defaults and migrate as needed this.updateConfig(template); // Add augmentations this.appendAugmentations(template, sections); // Group everything into a system message sections = [new GroupSection_1.GroupSection(sections, 'system')]; // Include conversation history // - The ConversationHistory section will use the remaining tokens from // max_input_tokens. if (template.config.completion.include_history) { sections.push(new ConversationHistory_1.ConversationHistory(`conversation.${template.name}_history`, this.options.max_conversation_history_tokens)); } if (template.config.augmentation && template.config.augmentation.augmentation_type === 'tools') { const includeHistory = template.config.completion.include_history; const historyVariable = includeHistory ? `conversation.${name}_history` : 'temp.${name}_history'; sections.push(new ActionOutputMessage_1.ActionOutputMessage(historyVariable)); } // Include user input if (template.config.completion.include_images) { sections.push(new UserInputMessage_1.UserInputMessage(this.options.max_input_tokens)); } else if (template.config.completion.include_input) { sections.push(new UserMessage_1.UserMessage('{{$temp.input}}', this.options.max_input_tokens)); } // Create prompt template.prompt = new Prompt_1.Prompt(sections); // Cache loaded template this._prompts.set(name, template); } return this._prompts.get(name); } /** * Checks for the existence of a named prompt. * @param {string} name - Name of the prompt to load. * @returns {boolean} True if the prompt exists. */ async hasPrompt(name) { if (!this._prompts.has(name)) { const folder = path.join(this._options.promptsFolder, name); const promptFile = path.join(folder, 'skprompt.txt'); // Check for prompt existence try { await fs.access(promptFile); } catch (err) { return false; } } return true; } /** * @param {PromptTemplate} template - The prompt template to update. * @private */ updateConfig(template) { // Set config defaults template.config.completion = Object.assign({ frequency_penalty: 0.0, include_history: true, include_input: true, include_images: false, max_tokens: 150, max_input_tokens: 2048, presence_penalty: 0.0, temperature: 0.0, top_p: 0.0 }, template.config.completion); // Migrate old schema if (template.config.schema === 1) { template.config.schema = 1.1; if (Array.isArray(template.config.default_backends) && template.config.default_backends.length > 0) { template.config.completion.model = template.config.default_backends[0]; } } } /** * @param {PromptTemplate} template - The prompt template to append augmentations to. * @param {PromptSection[]} sections - The prompt sections to append augmentations to. * @private */ appendAugmentations(template, sections) { // Check for augmentation const augmentation = template.config.augmentation; if (augmentation) { // First append data sources // - We're using a minimum of 2 tokens for each data source to prevent // any sort of prompt rendering conflicts between sources and conversation history. // - If we wanted to let users specify a percentage% for a data source we would need // to track the percentage they gave the data source(s) and give the remaining to // the ConversationHistory section. const data_sources = augmentation.data_sources ?? {}; for (const name in data_sources) { if (!this.hasDataSource(name)) { throw new Error(`DataSource '${name}' not found for prompt '${template.name}'.`); } const dataSource = this.getDataSource(name); const tokens = Math.max(data_sources[name], 2); sections.push(new DataSourceSection_1.DataSourceSection(dataSource, tokens)); } // Next create augmentation switch (augmentation.augmentation_type) { default: case 'none': // No augmentation needed break; case 'monologue': template.augmentation = new augmentations_1.MonologueAugmentation(template.actions ?? []); break; case 'sequence': template.augmentation = new augmentations_1.SequenceAugmentation(template.actions ?? []); break; case 'tools': template.augmentation = new augmentations_1.ToolsAugmentation(); } // Append the augmentations prompt section if (template.augmentation) { const section = template.augmentation.createPromptSection(); if (section) { sections.push(section); } } } } } exports.PromptManager = PromptManager; //# sourceMappingURL=PromptManager.js.map