UNPKG

@microsoft/teams-ai

Version:

SDK focused on building AI based applications for Microsoft Teams.

232 lines 11.3 kB
"use strict"; /** * @module teams-ai */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ActionPlanner = void 0; const augmentations_1 = require("../augmentations"); const Utilities_1 = require("../Utilities"); const LLMClient_1 = require("./LLMClient"); /** * A planner that uses a Large Language Model (LLM) to generate plans. * @remarks * The ActionPlanner is a powerful planner that uses a LLM to generate plans. The planner can * trigger parameterized actions and send text based responses to the user. The ActionPlanner * supports the following advanced features: * - **Augmentations:** Augmentations virtually eliminate the need for prompt engineering. Prompts * can be configured to use a named augmentation which will be automatically appended to the outgoing * prompt. Augmentations let the developer specify whether they want to support multi-step plans (sequence), * use OpenAI's functions support (functions), or create an AutoGPT style agent (monologue). * - **Validations:** Validators are used to validate the response returned by the LLM and can guarantee * that the parameters passed to an action mach a supplied schema. The validator used is automatically * selected based on the augmentation being used. Validators also prevent hallucinated action names * making it impossible for the LLM to trigger an action that doesn't exist. * - **Repair:** The ActionPlanner will automatically attempt to repair invalid responses returned by the * LLM using a feedback loop. When a validation fails, the ActionPlanner sends the error back to the * model, along with an instruction asking it to fix its mistake. This feedback technique leads to a * dramatic reduction in the number of invalid responses returned by the model. * @template TState Optional. Type of application state. */ class ActionPlanner { _options; _promptFactory; _defaultPrompt; _enableFeedbackLoop; _feedbackLoopType; /** * Creates a new `ActionPlanner` instance. * @param {ActionPlannerOptions<TState>} options Options used to configure the planner. */ constructor(options) { this._options = Object.assign({ max_repair_attempts: 3, logRepairs: false }, options); if (typeof this._options.defaultPrompt == 'function') { this._promptFactory = this._options.defaultPrompt; } else { this._defaultPrompt = this._options.defaultPrompt; this._promptFactory = () => this.prompts.getPrompt(this._defaultPrompt); } } get model() { return this._options.model; } get prompts() { return this._options.prompts; } get defaultPrompt() { return this._defaultPrompt; } /** * Starts a new task. * @remarks * This method is called when the AI system is ready to start a new task. The planner should * generate a plan that the AI system will execute. Returning an empty plan signals that * there is no work to be performed. * * The planner should take the users input from `state.temp.input`. * @param {TurnContext} context Context for the current turn of conversation. * @param {TState} state Application state for the current turn of conversation. * @param {AI<TState>} ai The AI system that is generating the plan. * @returns {Promise<Plan>} The plan that was generated. */ async beginTask(context, state, ai) { return await this.continueTask(context, state, ai); } /** * Continues the current task. * @remarks * This method is called when the AI system has finished executing the previous plan and is * ready to continue the current task. The planner should generate a plan that the AI system * will execute. Returning an empty plan signals that the task is completed and there is no work * to be performed. * * The output from the last plan step that was executed is passed to the planner via `state.temp.input`. * @param {TurnContext} context - Context for the current turn of conversation. * @param {TState} state - Application state for the current turn of conversation. * @param {AI<TState>} ai - The AI system that is generating the plan. * @returns {Promise<Plan>} The plan that was generated. */ async continueTask(context, state, ai) { // Identify the prompt to use const template = await this._promptFactory(context, state, this); // Identify the augmentation to use const augmentation = template.augmentation ?? new augmentations_1.DefaultAugmentation(); if (ai.enableFeedbackLoop != null) { this._enableFeedbackLoop = ai.enableFeedbackLoop; if (ai.feedbackLoopType) { this._feedbackLoopType = ai.feedbackLoopType; } } // Complete prompt const result = await this.completePrompt(context, state, template, augmentation); if (result.status != 'success') { throw result.error; } // Check to see if we have a response // - when a streaming response is used the response message will be undefined. if (result.message) { // Return plan return await augmentation.createPlanFromResponse(context, state, result); } else { // Return an empty plan return { type: 'plan', commands: [] }; } } /** * Completes a prompt using an optional validator. * @remarks * This method allows the developer to manually complete a prompt and access the models * response. If a validator is specified, the response will be validated and repaired if * necessary. If no validator is specified, the response will be returned as-is. * * If a validator like the `JSONResponseValidator` is used, the response returned will be * a message containing a JSON object. If no validator is used, the response will be a * message containing the response text as a string. * @template TContent Optional. Type of message content returned for a 'success' response. The `response.message.content` field will be of type TContent. Defaults to `string`. * @param context Context for the current turn of conversation. * @param {TurnContext} context - Context for the current turn of conversation. * @param {Memory} memory A memory interface used to access state variables (the turn state object implements this interface.) * @param {string | PromptTemplate} prompt - Name of the prompt to use or a prompt template. * @param {PromptResponseValidator<TContent>} validator - Optional. A validator to use to validate the response returned by the model. * @returns {Promise<PromptResponse<TContent>>} The result of the LLM call. */ async completePrompt(context, memory, prompt, validator) { // Cache prompt template if being dynamically assigned let name = ''; if (typeof prompt == 'object') { // Add prompt if it doesn't exist if (!this.prompts.hasPrompt(prompt.name)) { this.prompts.addPrompt(prompt); } name = prompt.name; } else { name = prompt; } // Fetch cached template const template = await this.prompts.getPrompt(name); const model = this.model; // Compute variable names // - The LLM client needs history to work so if the prompt doesn't want history included we'll // just tell the LLM client to use a temp variable instead. // - For prompts that include history we want each prompt to have its own history variable so // we'll use the prompt name in the variable name. const include_history = template.config.completion.include_history; const history_variable = include_history ? `conversation.${name}_history` : `temp.${name}_history`; const input_variable = `temp.input`; // Create LLM client const client = new LLMClient_1.LLMClient({ model, template, history_variable, input_variable, validator, tokenizer: this._options.tokenizer, max_history_messages: this.prompts.options.max_history_messages, max_repair_attempts: this._options.max_repair_attempts, logRepairs: this._options.logRepairs, startStreamingMessage: this._options.startStreamingMessage, endStreamHandler: this._options.endStreamHandler, enableFeedbackLoop: this._enableFeedbackLoop, feedbackLoopType: this._feedbackLoopType }); // Complete prompt return await client.completePrompt(context, memory, this.prompts); } /** * Creates a semantic function that can be registered with the apps prompt manager. * @param {string | PromptTemplate} prompt - The name of the prompt to use. * @param {PromptResponseValidator<any>} validator - Optional. A validator to use to validate the response returned by the model. * @remarks * Semantic functions are functions that make model calls and return their results as template * parameters to other prompts. For example, you could define a semantic function called * 'translator' that first translates the user's input to English before calling your main prompt: * * ```JavaScript * app.ai.prompts.addFunction('translator', app.ai.createSemanticFunction('translator-prompt')); * ``` * * You would then create a prompt called "translator-prompt" that does the translation and then in * your main prompt you can call it using the template expression `{{translator}}`. * @returns {Promise<any>} A promise that resolves to the result of the semantic function. */ addSemanticFunction(prompt, validator) { // Cache prompt template if being dynamically assigned let name = ''; if (typeof prompt == 'object') { this._options.prompts.addPrompt(prompt); name = prompt.name; } else { name = prompt; } // Add semantic function this._options.prompts.addFunction(name, async (context, memory, functions, tokenizer, args) => { // Assign args to input if (Array.isArray(args)) { memory.setValue('temp.input', args.join(' ')); } else if (args != undefined && args != null) { memory.setValue('temp.input', args.toString()); } // Complete prompt const result = await this.completePrompt(context, memory, prompt, validator); if (result.status == 'success') { return Utilities_1.Utilities.toString(tokenizer, result.message.content); } else { throw result.error; } }); return this; } } exports.ActionPlanner = ActionPlanner; //# sourceMappingURL=ActionPlanner.js.map