UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

299 lines 14.8 kB
"use strict"; /* eslint-disable security/detect-object-injection */ /** * @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.OpenAIPlanner = void 0; const OpenAIClients_1 = require("./OpenAIClients"); const ResponseParser_1 = require("./ResponseParser"); const ConversationHistory_1 = require("./ConversationHistory"); const AI_1 = require("./AI"); class OpenAIPlanner { constructor(options) { this._options = Object.assign({ oneSayPerTurn: false, useSystemMessage: false, logRequests: false }, options); this._client = this.createClient(this._options); } get options() { return this._options; } completePrompt(context, state, prompt, options) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; return __awaiter(this, void 0, void 0, function* () { // Check for chat completion model const model = this.getModel(prompt); if (model.startsWith('gpt-')) { // Request base chat completion const temp = ((_b = (_a = state['temp']) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : {}); const chatRequest = this.createChatCompletionRequest(state, prompt, temp.input, options); const result = yield this.createChatCompletion(chatRequest); return (_f = (_e = (_d = (_c = result.data) === null || _c === void 0 ? void 0 : _c.choices[0]) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.content) !== null && _f !== void 0 ? _f : ''; } else { // Request base prompt completion const promptRequest = this.createCompletionRequest(prompt); const result = yield this.createCompletion(promptRequest); return (_j = (_h = (_g = result.data) === null || _g === void 0 ? void 0 : _g.choices[0]) === null || _h === void 0 ? void 0 : _h.text) !== null && _j !== void 0 ? _j : ''; } }); } generatePlan(context, state, prompt, options) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; return __awaiter(this, void 0, void 0, function* () { // Check for chat completion model let status; let response; const model = this.getModel(prompt); if (model.startsWith('gpt-')) { // Request base chat completion const temp = ((_b = (_a = state['temp']) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : {}); const chatRequest = yield this.createChatCompletionRequest(state, prompt, temp.input, options); const result = yield this.createChatCompletion(chatRequest); status = result === null || result === void 0 ? void 0 : result.status; response = (_f = (_e = (_d = (_c = result.data) === null || _c === void 0 ? void 0 : _c.choices[0]) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.content) !== null && _f !== void 0 ? _f : ''; } else { // Request base prompt completion const promptRequest = this.createCompletionRequest(prompt); const result = yield this.createCompletion(promptRequest); status = result === null || result === void 0 ? void 0 : result.status; response = (_j = (_h = (_g = result.data) === null || _g === void 0 ? void 0 : _g.choices[0]) === null || _h === void 0 ? void 0 : _h.text) !== null && _j !== void 0 ? _j : ''; } // Ensure we weren't rate limited if (status === 429) { return { type: 'plan', commands: [ { type: 'DO', action: AI_1.AI.RateLimitedActionName, entities: {} } ] }; } // 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 if (options.history.assistantPrefix) { // The model sometimes predicts additional text for the human side of things so skip that. const pos = response.toLowerCase().indexOf(options.history.assistantPrefix.toLowerCase()); if (pos >= 0) { response = response.substring(pos + options.history.assistantPrefix.length); } } // Parse response into commands const plan = ResponseParser_1.ResponseParser.parseResponse(response.trim()); // Filter to only a single SAY command if (this._options.oneSayPerTurn) { let spoken = false; plan.commands = plan.commands.filter((cmd) => { if (cmd.type == 'SAY') { if (spoken) { return false; } spoken = true; } return true; }); } return plan; } // Return an empty plan by default return { type: 'plan', commands: [] }; }); } createClient(options) { return new OpenAIClients_1.OpenAIClient({ apiKey: options.apiKey, organization: options.organization, endpoint: options.endpoint }); } getModel(prompt) { if (Array.isArray(prompt.config.default_backends) && prompt.config.default_backends.length > 0) { return prompt.config.default_backends[0]; } else { return this._options.defaultModel; } } createChatCompletionRequest(state, prompt, userMessage, options) { // Clone prompt config const request = Object.assign({ model: this.getModel(prompt), messages: [] }, prompt.config.completion); this.patchStopSequences(request); // Populate system message request.messages.push({ role: this._options.useSystemMessage ? 'system' : 'user', content: prompt.text }); // Populate conversation history if (options.history.trackHistory) { const userPrefix = options.history.userPrefix.trim().toLowerCase(); const assistantPrefix = options.history.assistantPrefix.trim().toLowerCase(); const history = ConversationHistory_1.ConversationHistory.toArray(state, options.history.maxTokens); 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(assistantPrefix)) { line = line.substring(assistantPrefix.length).trim(); request.messages.push({ role: 'assistant', content: line }); } } } // Add user message if (userMessage) { request.messages.push({ role: 'user', content: userMessage }); } return request; } createCompletionRequest(prompt) { // Clone prompt config const request = Object.assign({}, prompt.config.completion); this.patchStopSequences(request); request.model = this.getModel(prompt); request.prompt = prompt.text; return request; } patchStopSequences(request) { if (request['stop_sequences']) { request.stop = request['stop_sequences']; delete request['stop_sequences']; } } createChatCompletion(request) { var _a, _b, _c, _d, _e, _f; return __awaiter(this, void 0, void 0, function* () { let response; let error = {}; const startTime = new Date().getTime(); try { response = yield this._client.createChatCompletion(request); } 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 && response.data.choices.length > 0 && ((_b = response.data.choices[0].message) === null || _b === void 0 ? void 0 : _b.content); // TODO: if we add telemetry, we should log this console.log(`CHAT SUCCEEDED: status=${response.status} duration=${duration} prompt=${(_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.usage) === null || _d === void 0 ? void 0 : _d.prompt_tokens} completion=${(_f = (_e = response === null || response === void 0 ? void 0 : response.data) === null || _e === void 0 ? void 0 : _e.usage) === null || _f === void 0 ? void 0 : _f.completion_tokens} response=${choice}`); } else { console.error(`CHAT FAILED due to rate limiting: 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._client.createCompletion(request); } 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 && response.data.choices.length > 0 ? response.data.choices[0].text : ''; // TODO: telemetry 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.OpenAIPlanner = OpenAIPlanner; /** * Format text chat message. * * @param {ChatCompletionRequestMessage[]} messages The list of messages to be sent by the bot. * @returns {string} The text string to be sent to the user. */ 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=OpenAIPlanner.js.map