UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

318 lines 17 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.OpenAIPredictionEngine = void 0; const openai_1 = require("openai"); const ResponseParser_1 = require("./ResponseParser"); const PromptParser_1 = require("./PromptParser"); const ConversationHistory_1 = require("./ConversationHistory"); const AI_1 = require("./AI"); class OpenAIPredictionEngine { constructor(options) { this._options = Object.assign({ oneSayPerTurn: true, logRequests: false }, options); this._configuration = new openai_1.Configuration(options.configuration); this._openai = new openai_1.OpenAIApi(this._configuration, options.basePath, options.axios); // Initialize conversation history this._options.conversationHistory = Object.assign({ addTurnToHistory: true, userPrefix: 'Human: ', botPrefix: 'AI: ', includeDoCommands: true }, this._options.conversationHistory); } get configuration() { return this._configuration; } get openai() { return this._openai; } get options() { return this._options; } expandPromptTemplate(context, state, prompt) { return __awaiter(this, void 0, void 0, function* () { return PromptParser_1.PromptParser.expandPromptTemplate(context, state, {}, prompt, { conversationHistory: this._options.conversationHistory }); }); } prompt(context, state, options, message) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { // Check for chat completion model if (options.promptConfig.model.startsWith('gpt-3.5-turbo')) { // Request base chat completion const chatRequest = yield this.createChatCompletionRequest(context, state, options.prompt, options.promptConfig, message, options.conversationHistory); const result = yield this.createChatCompletion(chatRequest); return ((_a = result === null || result === void 0 ? void 0 : result.data) === null || _a === void 0 ? void 0 : _a.choices) ? (_c = (_b = result.data.choices[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content : undefined; } else { // Request base prompt completion const promptRequest = yield this.createCompletionRequest(context, state, {}, options.prompt, options.promptConfig, options.conversationHistory); const result = yield this.createCompletion(promptRequest); return ((_d = result === null || result === void 0 ? void 0 : result.data) === null || _d === void 0 ? void 0 : _d.choices) ? (_e = result.data.choices[0]) === null || _e === void 0 ? void 0 : _e.text : undefined; } }); } predictCommands(context, state, data, options) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; return __awaiter(this, void 0, void 0, function* () { data = data !== null && data !== void 0 ? data : {}; options = options !== null && options !== void 0 ? options : this._options; if (!options.prompt || !options.promptConfig) { throw new Error(`OpenAIPredictionEngine: "prompt" or "promptConfiguration" not specified.`); } // Check for chat completion model let status; let response; if (options.promptConfig.model.startsWith('gpt-3.5-turbo')) { // Request base chat completion const chatRequest = yield this.createChatCompletionRequest(context, state, options.prompt, options.promptConfig, context.activity.text, options.conversationHistory); const result = yield this.createChatCompletion(chatRequest); status = result === null || result === void 0 ? void 0 : result.status; response = ((_a = result === null || result === void 0 ? void 0 : result.data) === null || _a === void 0 ? void 0 : _a.choices) ? (_c = (_b = result.data.choices[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content : undefined; } else { // Request base prompt completion const promptRequest = yield this.createCompletionRequest(context, state, data, options.prompt, options.promptConfig, options.conversationHistory); const result = yield this.createCompletion(promptRequest); status = result === null || result === void 0 ? void 0 : result.status; response = ((_d = result === null || result === void 0 ? void 0 : result.data) === null || _d === void 0 ? void 0 : _d.choices) ? (_e = result.data.choices[0]) === null || _e === void 0 ? void 0 : _e.text : undefined; } // Ensure we weren't rate limited if (status === 429) { return [ { type: 'DO', action: AI_1.AI.RateLimitedActionName, data: {} } ]; } // Parse returned prompt response if (response) { // Patch the occasional "Then DO" which gets predicted response = response.trim().replace('Then DO ', 'THEN DO ').replace('Then SAY ', 'THEN SAY '); if (response.startsWith('THEN ')) { response = response.substring(5); } // Remove response prefix const historyOptions = (_f = options.conversationHistory) !== null && _f !== void 0 ? _f : {}; if (historyOptions.botPrefix) { // The model sometimes predicts additional text for the human side of things so skip that. const pos = response.toLowerCase().indexOf(historyOptions.botPrefix.toLowerCase()); if (pos >= 0) { response = response.substring(pos + historyOptions.botPrefix.length); } } // Parse response into commands let commands = ResponseParser_1.ResponseParser.parseResponse(response.trim()); // Filter to only a single SAY command if (this._options.oneSayPerTurn) { let spoken = false; commands = commands.filter(cmd => { if (cmd.type == 'SAY') { if (spoken) { return false; } spoken = true; } return true; }); } // Add turn to conversation history if (historyOptions.addTurnToHistory) { if (context.activity.text) { ConversationHistory_1.ConversationHistory.addLine(state, `${(_g = historyOptions.userPrefix) !== null && _g !== void 0 ? _g : ''}${context.activity.text}`, historyOptions.maxLines); } if (historyOptions.includeDoCommands) { if (response) { ConversationHistory_1.ConversationHistory.addLine(state, `${(_h = historyOptions.botPrefix) !== null && _h !== void 0 ? _h : ''}${response}`, historyOptions.maxLines); } } else { const text = commands.filter(v => v.type == 'SAY').map(v => v.response).join('\n'); ConversationHistory_1.ConversationHistory.addLine(state, `${(_j = historyOptions.botPrefix) !== null && _j !== void 0 ? _j : ''}${text}`, historyOptions.maxLines); } } return commands; } return []; }); } createChatCompletionRequest(context, state, prompt, config, userMessage, historyOptions) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { // Clone prompt config const request = Object.assign({ messages: [] }, config); // Expand prompt template // - NOTE: While the local history options and the prompts expected history options are // different types, they're compatible via duck typing. This could impact porting. const systemMsg = yield PromptParser_1.PromptParser.expandPromptTemplate(context, state, {}, prompt, { conversationHistory: historyOptions }); // Populate system message request.messages.push({ role: 'system', content: systemMsg }); // Populate conversation history if (historyOptions) { const userPrefix = ((_a = historyOptions.userPrefix) !== null && _a !== void 0 ? _a : 'Human: ').toLowerCase(); const botPrefix = ((_b = historyOptions.botPrefix) !== null && _b !== void 0 ? _b : 'AI: ').toLowerCase(); const history = ConversationHistory_1.ConversationHistory.toArray(state, historyOptions.maxCharacterLength); for (let i = 0; i < history.length; i++) { let line = history[i]; const lcLine = line.toLowerCase(); if (lcLine.startsWith(userPrefix)) { line = line.substring(userPrefix.length).trim(); request.messages.push({ role: 'user', content: line }); } else if (lcLine.startsWith(botPrefix)) { line = line.substring(botPrefix.length).trim(); request.messages.push({ role: 'assistant', content: line }); } } } // Add user message if (userMessage) { request.messages.push({ role: 'user', content: userMessage }); } return request; }); } createCompletionRequest(context, state, data, prompt, config, historyOptions) { return __awaiter(this, void 0, void 0, function* () { // Clone prompt config const request = Object.assign({}, config); // Expand prompt template // - NOTE: While the local history options and the prompts expected history options are // different types, they're compatible via duck typing. This could impact porting. request.prompt = yield PromptParser_1.PromptParser.expandPromptTemplate(context, state, data, prompt, { conversationHistory: historyOptions }); return request; }); } createChatCompletion(request) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { let response; let error = {}; const startTime = new Date().getTime(); try { response = (yield this._openai.createChatCompletion(request, { validateStatus: (status) => status < 400 || status == 429 })); } catch (err) { error = err; throw err; } finally { if (this._options.logRequests) { const duration = new Date().getTime() - startTime; console.log(`\nCHAT REQUEST:\n\`\`\`\n${printChatMessages(request.messages)}\`\`\``); if (response) { if (response.status != 429) { const choice = Array.isArray((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.choices) && response.data.choices.length > 0 ? response.data.choices[0].message.content : ''; console.log(`CHAT SUCCEEDED: status=${response.status} duration=${duration} prompt=${(_b = response.data.usage) === null || _b === void 0 ? void 0 : _b.prompt_tokens} completion=${(_c = response.data.usage) === null || _c === void 0 ? void 0 : _c.completion_tokens} response=${choice}`); } else { console.error(`CHAT FAILED: status=${response.status} duration=${duration} headers=${JSON.stringify(response.headers)}`); } } else { console.error(`CHAT FAILED: status=${error === null || error === void 0 ? void 0 : error.status} duration=${duration} message=${error === null || error === void 0 ? void 0 : error.toString()}`); } } } return response; }); } createCompletion(request) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { let response; let error = {}; const startTime = new Date().getTime(); try { response = (yield this._openai.createCompletion(request, { validateStatus: (status) => status < 400 || status == 429 })); } catch (err) { error = err; throw err; } finally { if (this._options.logRequests) { const duration = new Date().getTime() - startTime; console.log(`\nPROMPT REQUEST:\n\`\`\`\n${request.prompt}\`\`\``); if (response) { if (response.status != 429) { const choice = Array.isArray((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.choices) && response.data.choices.length > 0 ? response.data.choices[0].text : ''; console.log(`PROMPT SUCCEEDED: status=${response.status} duration=${duration} prompt=${(_b = response.data.usage) === null || _b === void 0 ? void 0 : _b.prompt_tokens} completion=${(_c = response.data.usage) === null || _c === void 0 ? void 0 : _c.completion_tokens} response=${choice}`); } else { console.error(`PROMPT FAILED: status=${response.status} duration=${duration} headers=${JSON.stringify(response.headers)}`); } } else { console.error(`PROMPT FAILED: status=${error === null || error === void 0 ? void 0 : error.status} duration=${duration} message=${error === null || error === void 0 ? void 0 : error.toString()}`); } } } return response; }); } } exports.OpenAIPredictionEngine = OpenAIPredictionEngine; function printChatMessages(messages) { let text = ''; messages.forEach(msg => { switch (msg.role) { case 'system': text += msg.content + '\n'; break; default: text += `\n${msg.role}: ${msg.content}`; break; } }); return text; } //# sourceMappingURL=OpenAIPredictionEngine.js.map