UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

274 lines 13.4 kB
"use strict"; /** * @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.AI = void 0; const botbuilder_1 = require("botbuilder"); const ConversationHistory_1 = require("./ConversationHistory"); const DefaultModerator_1 = require("./DefaultModerator"); const ResponseParser_1 = require("./ResponseParser"); class AI { constructor(options) { this._actions = new Map(); this._options = Object.assign({}, options); // Create moderator if needed if (!this._options.moderator) { this._options.moderator = new DefaultModerator_1.DefaultModerator(); } // Initialize history options this._options.history = Object.assign({ trackHistory: true, maxTurns: 3, maxTokens: 1000, lineSeparator: '\n', userPrefix: 'User:', assistantPrefix: 'Assistant:', assistantHistoryType: 'planObject' }, this._options.history); // Register default UnknownAction handler this.action(AI.UnknownActionName, (context, state, data, action) => { console.error(`An AI action named "${action}" was predicted but no handler was registered.`); return Promise.resolve(true); }, true); // Register default FlaggedInputAction handler this.action(AI.FlaggedInputActionName, (context, state, data, action) => { console.error(`The users input has been moderated but no handler was registered for 'AI.FlaggedInputActionName'.`); return Promise.resolve(true); }, true); // Register default FlaggedOutputAction handler this.action(AI.FlaggedOutputActionName, (context, state, data, action) => { console.error(`The bots output has been moderated but no handler was registered for 'AI.FlaggedOutputActionName'.`); return Promise.resolve(true); }, true); // Register default RateLimitedActionName this.action(AI.RateLimitedActionName, (context, state, data, action) => { throw new Error(`An AI request failed because it was rate limited`); }, true); // Register default PlanReadyActionName this.action(AI.PlanReadyActionName, (context, state, plan) => __awaiter(this, void 0, void 0, function* () { return Array.isArray(plan.commands) && plan.commands.length > 0; }), true); // Register default DoCommandActionName this.action(AI.DoCommandActionName, (context, state, data, action) => __awaiter(this, void 0, void 0, function* () { const { entities, handler } = data; return yield handler(context, state, entities, action); }), true); // Register default SayCommandActionName this.action(AI.SayCommandActionName, (context, state, data, action) => __awaiter(this, void 0, void 0, function* () { const response = data.response; const card = ResponseParser_1.ResponseParser.parseAdaptiveCard(response); if (card) { const attachment = botbuilder_1.CardFactory.adaptiveCard(card); const activity = botbuilder_1.MessageFactory.attachment(attachment); yield context.sendActivity(activity); } else if (context.activity.channelId == botbuilder_1.Channels.Msteams) { yield context.sendActivity(response.split('\n').join('<br>')); } else { yield context.sendActivity(response); } return true; }), true); } get moderator() { return this._options.moderator; } get options() { return this._options; } get planner() { return this._options.planner; } get prompts() { return this._options.promptManager; } /** * Registers a handler for a named action. * * * Actions can be triggered by a planner returning a DO command. * * @param {string | string[]} name Unique name of the action. * @param {Promise<boolean>} handler Function to call when the action is triggered. * @param {boolean} allowOverrides Optional. If true, default and/or existing handlers can be overridden. * @returns {this} The application instance for chaining purposes. */ action(name, handler, allowOverrides = false) { (Array.isArray(name) ? name : [name]).forEach((n) => { if (!this._actions.has(n) || allowOverrides) { this._actions.set(n, { handler, allowOverrides }); } else { const entry = this._actions.get(n); if (entry.allowOverrides) { entry.handler = handler; } else { throw new Error(`The AI.action() method was called with a previously registered action named "${n}".`); } } }); return this; } chain(context, state, prompt, options) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { // Configure options const opts = this.configureOptions(options); // Select prompt if (!prompt) { if (opts.prompt == undefined) { throw new Error(`AI.chain() was called without a prompt and no default prompt was configured.`); } else if (typeof opts.prompt == 'function') { prompt = yield opts.prompt(context, state); } else { prompt = opts.prompt; } } // Populate {{$temp.input}} const temp = (_c = (_b = (_a = state) === null || _a === void 0 ? void 0 : _a.temp) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : {}; if (typeof temp.input != 'string') { // Use the received activity text temp.input = context.activity.text; } // Populate {{$temp.history}} if (typeof temp.history != 'string' && opts.history.trackHistory) { temp.history = ConversationHistory_1.ConversationHistory.toString(state, opts.history.maxTokens, opts.history.lineSeparator); } // Render the prompt const renderedPrompt = yield opts.promptManager.renderPrompt(context, state, prompt); // Generate plan let plan = yield opts.moderator.reviewPrompt(context, state, renderedPrompt, opts); if (!plan) { plan = yield opts.planner.generatePlan(context, state, renderedPrompt, opts); plan = yield opts.moderator.reviewPlan(context, state, plan); } // Process generated plan let continueChain = yield this._actions.get(AI.PlanReadyActionName).handler(context, state, plan, ''); if (continueChain) { // Update conversation history if (opts.history.trackHistory) { ConversationHistory_1.ConversationHistory.addLine(state, `${opts.history.userPrefix.trim()} ${temp.input.trim()}`, opts.history.maxTurns * 2); switch (opts.history.assistantHistoryType) { case 'text': { // Extract only the things the assistant has said const text = plan.commands .filter((v) => v.type == 'SAY') .map((v) => v.response) .join('\n'); ConversationHistory_1.ConversationHistory.addLine(state, `${opts.history.assistantPrefix.trim()} ${text}`, opts.history.maxTurns * 2); break; } case 'planObject': default: // Embed the plan object to re-enforce the model // - TODO: add support for XML as well ConversationHistory_1.ConversationHistory.addLine(state, `${opts.history.assistantPrefix.trim()} ${JSON.stringify(plan)}`, opts.history.maxTurns * 2); break; } } // Run predicted commands for (let i = 0; i < plan.commands.length && continueChain; i++) { // TODO // eslint-disable-next-line security/detect-object-injection const cmd = plan.commands[i]; switch (cmd.type) { case 'DO': { const { action } = cmd; if (this._actions.has(action)) { // Call action handler const handler = this._actions.get(action).handler; continueChain = yield this._actions .get(AI.DoCommandActionName) .handler(context, state, Object.assign({ handler }, cmd), action); } else { // Redirect to UnknownAction handler continueChain = yield this._actions .get(AI.UnknownActionName) .handler(context, state, plan, action); } break; } case 'SAY': continueChain = yield this._actions .get(AI.SayCommandActionName) .handler(context, state, cmd, AI.SayCommandActionName); break; default: throw new Error(`Application.run(): unknown command of '${cmd.type}' predicted.`); } } } return continueChain; }); } completePrompt(context, state, prompt, options) { return __awaiter(this, void 0, void 0, function* () { // Configure options const opts = this.configureOptions(options); // Render the prompt const renderedPrompt = yield opts.promptManager.renderPrompt(context, state, prompt); // Complete the prompt return yield opts.planner.completePrompt(context, state, renderedPrompt, opts); }); } createSemanticFunction(name, template, options) { // Cache prompt template if being dynamically assigned if (template) { this._options.promptManager.addPromptTemplate(name, template); } return (context, state) => this.completePrompt(context, state, name, options); } doAction(context, state, action, data) { if (!this._actions.has(action)) { throw new Error(`Can't find an action named '${action}'.`); } const handler = this._actions.get(action).handler; return handler(context, state, data, action); } configureOptions(options) { let configuredOptions; if (options) { configuredOptions = Object.assign({}, this._options, options); if (options.history) { // Just inherit any missing history settings options.history = Object.assign({}, this._options.history, options.history); } else { // Disable history tracking by default options.history = Object.assign({}, this._options.history, { trackHistory: false }); } } else { configuredOptions = this._options; } return configuredOptions; } } exports.AI = AI; AI.UnknownActionName = '___UnknownAction___'; AI.FlaggedInputActionName = '___FlaggedInput___'; AI.FlaggedOutputActionName = '___FlaggedOutput___'; AI.RateLimitedActionName = '___RateLimited___'; AI.PlanReadyActionName = '___PlanReady___'; AI.DoCommandActionName = '___DO___'; AI.SayCommandActionName = '___SAY___'; //# sourceMappingURL=AI.js.map