@microsoft/botbuilder-m365
Version:
M365 extensions for Microsoft BotBuilder, Alpha release.
318 lines • 17 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.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