adaptorex
Version:
Connect all your live interactive storytelling devices and software
273 lines (221 loc) • 8.42 kB
JavaScript
/**
* @requires plugin
* @requires logic
*
* @requires file
*
* @module openai/openai
* @copyright Mirko Krejci 2024
* @license MIT
*/
const { OpenAI } = require('openai')
const { Plugin } = require('../plugin.js')
const { PluginItem } = require("../plugin_items")
var schema = require('./schema.json')
/** @typedef {import('../../game.js').Game} Game */
/** @typedef {import('../../session.js').Session} Session */
/** @typedef {import('../../types').AdaptorAction} Action */
/** @typedef {import('../plugin_items').PluginItemsTopic} PluginItemsTopic */
class Openai extends Plugin {
/** @type {PluginItemsTopic} */
assistants
constructor() {
super(schema)
this.openai = null;
this.autoconnect = true
}
/**
* @param {*} config
* @param {Game} game
* @returns {Object}
*/
async setup(config, game) {
super.setup(config, game, { assistants: Assistant })
await game.addDataCollection("threads")
return schema
}
async update(settings) {
if (settings.api && this.settings.api != settings.api) {
this.settings.api = settings.api
await this.connect();
}
}
async connect() {
try {
this.openai = new OpenAI({ apiKey: this.settings.api });
await this.openai.models.list();
this.log.info("Api Key is valid");
this.setConnected(true);
} catch (e) {
throw new adaptor.ConnectionError(e.response ? e.response.data : e.message);
}
}
// Search for Thread in Database.
async getThread(thread) {
if (typeof thread === 'object') {
return thread;
}
else if (typeof thread === 'string') {
let threads = {};
// find threads in DB
if (thread.match("^thread_\\w+$")) {
threads = await this.game.db.threads.find({ id: thread });
}
else {
threads = await this.game.db.threads.find({ name: thread });
}
return threads[0];
}
}
// Use OpenAi Assitants API
async sendThreadPrompt(assistant, prompt, instructions, thread) {
if (prompt) {
await this.openai.beta.threads.messages.create(
thread.id,
{
role: "user",
content: prompt
}
)
}
if (instructions) {
await this.openai.beta.threads.messages.create(
thread.id,
{
role: "assistant",
content: instructions
}
)
}
const run = await this.openai.beta.threads.runs.createAndPoll(
thread.id,
{
assistant_id: assistant.id,
instructions: instructions || "",
})
const message = await this.openai.beta.threads.messages.list(
run.thread_id)
this.log.debug(`Response for prompt to assistant ${assistant.name} in thread ${thread.name}: ${message.data[0].content[0].text.value}`)
return { response: message.data[0].content[0].text.value }
}
/**
*
* @param {prompt:string, system_message:string,model:string}
* Use OpenAI completions API
*/
async sendInstantPrompt(prompt, instructions, model) {
if (!model) {
throw new adaptor.NotFoundError("Model not found. Please specify a model")
}
const completion = await this.openai.chat.completions.create({
model: model,
messages: [{
role: "developer",
content: instructions || "You are a helpful assistant"
},
{
role: "user",
content: prompt
}
],
store: true
});
this.log.debug(`Response for instant prompt: ${completion.choices[0].message.content}`)
return { response: completion.choices[0].message.content }
}
/**
* @type {Action}
* @param {{assistant:string,prompt:string, thread:any, instructions:string, model:string}} data
* @param {Session} session
*/
async sendPrompt(data, session) {
const assistant = data.assistant ? this.assistants.getItem(data.assistant) : null
const thread = await this.getThread(data.thread)
if (data.thread && !thread) {
throw new adaptor.NotFoundError("Thread not found: " + data.thread)
}
let action_data;
if (thread && assistant) {
this.log.debug(`Sending prompt to assistant ${assistant.name} in thread ${thread.name}`)
action_data = await this.sendThreadPrompt(assistant, data.prompt, data.system_message, thread)
} else if (!thread && assistant) {
const composed_system_message = `${assistant.settings.description} ${assistant.settings.instructions} ${data.system_message || ""}`
this.log.debug(`Sending prompt to assistant ${assistant.name} with composed system message: ${composed_system_message}`)
action_data = await this.sendInstantPrompt(data.prompt, composed_system_message, data.model || assistant.settings.model)
} else if (!assistant && thread) {
throw new adaptor.NotFoundError("Threads only work with an assistant")
} else {
this.log.debug(`Sending instant prompt: ${data.prompt}`)
action_data = await this.sendInstantPrompt(data.prompt, data.instructions, data.model)
}
await session.variables.store(action_data, true)
}
/**
* @type {Action}
* @param {{name: string, reference:string}} data
* @param {Session} session
*/
async createThread({ name, reference }, session) {
const existing_thread = await this.getThread(name)
if (existing_thread) {
this.log.warn("Thread already exists, created Reference. To use the Reference set the Thread property to [[thread]] in sendPrompt")
await session.variables.create("threads", existing_thread, "thread")
return
}
const response = await this.openai.beta.threads.create()
if (!response || !response.id) {
throw new adaptor.ConnectionError("Could not create thread: " + response)
}
let thread = {
id: response.id,
name: name
}
await session.variables.create("threads", thread, reference)
}
}// END OF OPENAI
/**
* Base Class for Assistant
* @param {object} config.settings
*/
class Assistant extends PluginItem {
constructor(config, coll, plugin, game) {
super(config, coll, plugin, game)
this.autoconnect = false
}
async setup(data) {
try {
this.openai = new OpenAI({ apiKey: this.plugin.settings.api })
} catch (e) {
throw new adaptor.ConnectionError(e.response ? e.response.data : e.message);
}
await this.update(data)
}
async update(data) {
this.log.info(this.name)
this.log.info(this.settings)
if (!data.id || data.name != this.name || data.settings.description != this.settings.description || data.settings.instructions != this.settings.instructions || data.settings.temperature != this.settings.temperature || data.settings.model != this.settings.model) {
const response = await this.registerAssistant(data.settings);
if (!response || !response.id) {
this.setConnected(false)
throw new adaptor.ConnectionError("Could not register assistant")
}
super.update(data)
this.id = response.id
await this.document.set({ id: response.id })
this.log.info("succsessfully registered with id: " + this.id);
}
this.setConnected(true)
}
async registerAssistant(data) {
const assistant = await this.openai.beta.assistants.create({
name: data.name,
instructions: data.instructions || "",
description: data.description || "",
temperature: data.temperature,
tools: [],
model: data.model
});
return assistant
}
}
module.exports.Plugin = Openai