adaptorex
Version:
Connect all your live interactive storytelling devices and software
285 lines (247 loc) • 7.69 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