UNPKG

adaptorex

Version:

Connect all your live interactive storytelling devices and software

273 lines (221 loc) 8.42 kB
/** * @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