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