@microsoft/botbuilder-m365
Version:
M365 extensions for Microsoft BotBuilder, Alpha release.
274 lines • 13.4 kB
JavaScript
;
/**
* @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