@indra.ai/opendeva
Version:
The Open Deva handles #ChatGPT and #DALLE in deva.world.
788 lines (714 loc) • 24.3 kB
JavaScript
// Copyright (c)2023 Quinn Michaels
// OpenAI Deva
// used for connecting into open ai services for cht and images.
const { OpenAI } = require("openai");
const package = require('./package.json');
const info = {
id: package.id,
name: package.name,
describe: package.description,
version: package.version,
url: package.homepage,
dir: __dirname,
git: package.repository.url,
bugs: package.bugs.url,
author: package.author,
license: package.license,
copyright: package.copyright,
};
const {agent,vars} = require('./data.json').DATA;
const Deva = require('@indra.ai/deva');
const OPEN = new Deva({
info,
agent,
vars,
utils: {
translate(input) {return input.trim();},
parse(input) {
return input.split('\n\n').map(p => {
const beginNum = /^\d/.test(p);
const valid = p.length && p !== '\n' && !beginNum ? true : false;
return valid ? `p: ${p}` : p;
}).join('\n\n');
},
process(input) {
return input.replace(/If there .+ share them!/g, '')
.replace(/Let me know .+ you with!/g, '')
.replace(/If you'd like .+ let me know!/g, '')
.replace(/If you have .+ share them!/g, '')
.replace(/If you have .+ do so!/g, '')
.replace(/If you have .+ let me know!/g, '')
.replace(/If you have .+ free to ask!/g, '')
.replace(/If you have .+ your thoughts!/g, '')
.replace(/If you have .+ for further discussion./g, '')
.replace(/If you have .+ analysis or discussion./g, '');
},
},
listeners: {
'open:location'(packet) {
this.vars.location = packet.data;
},
'open:topic'(packet) {
this.vars.topic = packet.data;
},
},
modules: {
openai: false,
},
func: {
/**************
func: chat
params: opts
describe: Call the OpenAI API with the proper data and parameters.
***************/
async chat(content, opts) {
this.action('func', 'chat');
this.state('set', 'history');
const self = this;
const _hist = {
role: this.vars.chat.role,
content,
};
if (opts.history) opts.history.push(_hist);
else this.vars.history.push(_hist);
const messages = opts.history || this.vars.history.slice(-10);
if (opts.corpus) {
this.state('set', 'corpus');
messages.unshift({role: 'system', content: opts.corpus,});
}
if (opts.profile) {
this.state('set', 'profile');
messages.unshift({role: 'system', content: opts.profile,});
}
if (opts.user) {
this.state('set', 'user');
messages.unshift({role: 'system', content: opts.user,});
}
if (opts.header) {
this.state('set', 'header');
messages.unshift({role: 'system', content: opts.header,});
}
this.state('set', 'model');
const _model = opts.model || this.vars.chat.model;
const model = this.func.getModel(_model);
this.state('set', 'params');
const params = {
model,
n: this.vars.chat.n,
messages,
temperature: this.vars.chat.temperature,
top_p: this.vars.chat.top_p,
frequency_penalty: this.vars.chat.frequency_penalty,
presence_penalty: this.vars.chat.presence_penalty,
tools: this.copy(this.vars.chat.tools),
};
if (opts.max_tokens) {
this.action('set', 'max tokens');
params.max_tokens = opts.max_tokens;
}
// if (opts.askAgent) {
// this.state('set', 'askAgent');
// const newTools = this.copy(this.vars.chat.tools);
// const toolkey = opts.memory || this.agent().key;
// const agents = newTools[0].function.parameters.properties.agent.enum.filter(k => k !== toolkey);
// newTools[0].function.parameters.properties.agent.enum = agents;
// params.tools = newTools;
// }
// else {
// // remove the first element from tools so agents can't call other agents.
// const askitem = params.tools.shift();
// }
// async function ask_entity(args) {
// const theAgent = await self.question(`${self.askChr}${args.agent} ask:agent ${args.text}`);
// return [
// `from: ${theAgent.a.agent.name}`,
// `message: ${theAgent.a.data.chat.text}`,
// `date: ${self.formatDate(Date.now(), 'long', true)}`,
// ].join('\n');
// }
const memkey = opts.memory || this.agent().key;
async function search_memory(args) {
const theMem = await self.question(`${self.askChr}data memory:${memkey}:3 ${args.text}`);
return theMem.a.text;
}
async function search_knowledge(args) {
const theKnowledge = await self.question(`${self.askChr}data knowledge:3 ${args.text}`);
return theKnowledge.a.text;
}
async function get_hymn(args) {
const theHymn = await self.question(`${self.askChr}veda hymn ${args.hymn}`);
return theHymn.a.text;
}
const funcs = {
search_knowledge,
search_memory,
// search_archive,
// get_hymn,
}
this.state('get', 'chat');
const chat = await this.modules.openai.chat.completions.create(params)
const {tool_calls} = chat.choices[0].message;
// this is where we want to trap the function.
if (tool_calls) {
this.state('set', 'tool calls');
messages.push(chat.choices[0].message);
for (const tool of tool_calls) {
const func = tool.function.name;
const funcArgs = JSON.parse(tool.function.arguments);
const funcResponse = await funcs[func](funcArgs);
messages.push({
tool_call_id: tool.id,
role: "tool",
name: func,
content: funcResponse || 'no-data',
}); // extend conversation with function response
}
this.state('set', 'second params');
const second_params = {
model,
n: this.vars.chat.n,
messages,
temperature: this.vars.chat.temperature,
top_p: this.vars.chat.top_p,
frequency_penalty: this.vars.chat.frequency_penalty,
presence_penalty: this.vars.chat.presence_penalty,
};
this.context('second_chat');
const second_chat = await this.modules.openai.chat.completions.create(second_params);
const second_data = {
id: second_chat.id,
model: second_chat.model,
usage: second_chat.usage,
role: second_chat.choices[0].message.role,
text: second_chat.choices[0].message.content,
created: second_chat.created,
}
this.state('set', 'response'); // set response state
this.vars.response = this.copy(second_data);
if (!opts.history) this.vars.history.push({
role: second_data.role,
content: second_data.text,
});
this.state('return', 'second chat');
return second_data;
}
else {
this.state('set', 'first chat');
const data = {
id: chat.id,
model: chat.model,
usage: chat.usage,
role: chat.choices[0].message.role,
text: this.utils.process(chat.choices[0].message.content),
created: chat.created,
}
this.state('set', 'response'); // set response state
this.vars.response = this.copy(data);
// push local history of no history in options.
if (!opts.history) this.vars.history.push({
role: data.role,
content: data.text,
});
this.state('return', 'first chat');
return data;
}
},
/**************
func: speech
params: opts
describe: call the openAI api for the text to speech service.
***************/
async speech(opts) {
this.action('func', 'speech');
const file = `${Date.now()}.mp3`;
const speechFile = this.path.join(this.config.dir, 'public', 'devas', opts.agent.key, 'audio', file);
const speechUrl = `/public/devas/${opts.agent.key}/audio/${file}`;
this.state('create', 'speech');
const mp3 = await this.modules.openai.audio.speech.create({
model: this.vars.speech.model,
voice: opts.meta.params[1] || this.vars.speech.voice,
input: opts.text,
});
const buffer = Buffer.from(await mp3.arrayBuffer());
this.state('write', 'speech file');
await this.fs.promises.writeFile(speechFile, buffer);
this.state('return', 'speech');
return {
path: speechFile,
url: speechUrl,
};
},
/**************
func: image
params: opts
describe: image function to generate a new image.
***************/
image(opts) {
this.action('func', 'image')
this.vars.image.prompt = opts.text;
const {key} = this.agent();
return new Promise((resolve, reject) => {
if (!opts.text) return resolve(this._messages.notext);
if (opts.meta.params[1] && this.vars.image.sizes[opts.meta.params[1]]) {
this.vars.image.size = this.vars.image.sizes[opts.meta.params[1]];
}
this.state('create', 'image');
this.modules.openai.images.generate({
model: this.vars.image.model,
prompt: this.vars.image.prompt,
n: this.vars.image.n,
size: this.vars.image.size,
response_format: this.vars.image.response_format,
}).then(image => {
// here we need to save the return data to a file
const imageName = `${Date.now()}.png`;
const imagePath = this.path.join(this.config.dir, 'public', 'devas', opts.agent.key, 'gallery', imageName);
const imageUrl = `/public/devas/${opts.agent.key}/gallery/${imageName}`;
this.state('write', 'image');
this.fs.writeFile(imagePath, Buffer.from(image.data[0].b64_json, 'base64'), 'base64', err => {
if (err) console.log('file write err', err);
});
const data = {
name: imageName,
path: imagePath,
url: imageUrl,
prompt: this.utils.parse(image.data[0].revised_prompt),
};
this.state('resolve', 'image')
return resolve(data);
}).catch(reject);
});
},
async fileGet(file) {
this.action('func', 'fileGet');
const data = await this.modules.openai.files.retrieve(file);
const text = [
`::begin:file:${data.id}`,
`### File Details`,
`id: ${data.id}`,
`file: ${data.filename}`,
`status: ${data.status}`,
`created: ${this.formatDate(data.created_at * 1000, 'long', true)}`,
`::end:file`
].join('\n');
return {
text,
data,
};
},
async fileUpload(file) {
this.action('func', 'fileUpload');
const data = await this.modules.openai.files.create({
file: this.fs.createReadStream(file),
purpose: this.vars.file.purpose
});
const text = [
`::begin:file:${data.id}`,
`### File Upload`,
`id: ${data.id}`,
`file: ${data.filename}`,
`purpose: ${data.purpose}`,
`status: ${data.status}`,
`created: ${this.formatDate(data.created_at * 1000, 'long', true)}`,
`::end:file`
].join('\n');
return {
text,
data,
};
},
async fileList(file) {
this.context('func', 'fileList');
const files = await this.modules.openai.files.list();
const text = files.data.map(file => {
return [
`::begin:file:${file.id}`,
`#### ${file.id}`,
`id: ${file.id}`,
`status: ${file.status}`,
`purpose: ${file.purpose}`,
`filename: ${file.filename}`,
`created: ${this.formatDate(file.created_at * 1000, 'long', true)}`,
'::end:file',
].join('\n');
});
text.unshift('### Files');
return {
text: text.join('\n\n'),
data: files.data,
};
},
async tuneCreate(file) {
this.action('func', 'tuneCreate');
const data = await this.modules.openai.fineTuning.jobs.create({
training_file: file,
model: this.func.getModel(this.vars.tune.model, 'tune'),
});
const text = [
`id: ${data.id}`,
`model: ${data.model}`,
`created: ${this.formatDate(data.created_at * 1000, 'long', true)}`,
`status: ${data.status}`,
`file: ${data.file}`,
`error: ${data.error}`,
].join('\n');
return {
text,
data,
}
},
async tuneList() {
this.action('func', 'tuneList');
const jobs = await this.modules.openai.fineTuning.jobs.list();
const text = jobs.data.map(job => {
return [
`::begin:job`,
`#### ${job.id}`,
`id: ${job.id}`,
`status: ${job.status}`,
`model: ${job.model}`,
`created: ${this.formatDate(job.created * 1000, 'long', true)}`,
`file: ${job.training_file}`,
job.error ? `${job.error.message}` : '',
`::end:job`,
].join('\n');
}).join('\n\n');
return {
text,
data: jobs.data,
};
},
async tuneGet(job) {
this.action('func', 'tuneGet');
const data = await this.modules.openai.fineTuning.jobs.retrieve(job);
const text = [
`::begin:job:${data.id}`,
'### Fine Tune Job',
`id: ${data.id}`,
`status: ${data.status}`,
`file: ${data.training_file}`,
`base: ${data.model}`,
`model: ${data.fine_tuned_model}`,
`tokens: ${data.trained_tokens}`,
`created: ${this.formatDate(data.created_at * 1000, 'long', true)}`,
`finished: ${this.formatDate(data.finished_at * 1000, 'long', true)}`,
`::end:job`,
].join('\n');
return {
text,
data,
}
},
async tuneCancel(job) {
this.action('func', 'tuneCancel');
const data = await this.modules.openai.fineTuning.jobs.cancel(job);
return {
text: data.id,
data,
}
},
/**************
func: modelList
params: none
describe: Get the listing of models from the api.
***************/
async modelList() {
this.action('func', 'modelList');
const models = await this.modules.openai.models.list();
const text = models.data.map(item => {
return [
`::begin:model`,
`#### ${item.id}`,
`id: ${item.id}`,
`owner: ${item.owned_by}`,
`::end:model`,
].join('\n');
});
text.unshift('## Models');
return {
text: text.join('\n'),
data: models.data,
};
},
/**************
func: modelGet
params: model
describe: Get a specfic model details from the api.
***************/
async modelGet(model) {
this.action('func', 'modelGet');
const data = await this.modules.openai.models.retrieve(model);
const text = [
`::begin:model:${data.id}`,
'### Model Details',
`id: ${data.id}`,
`parent: ${data.parent}`,
`root: ${data.root}`,
`created: ${this.formatDate(data.created * 1000, 'long', true)}`,
'::end:model',
].join('\n');
return {
text,
data,
}
},
processor(packet, funcMap) {
const data = {};
const func = funcMap[packet.q.meta.params[1]];
return new Promise((resolve, reject) => {
this.func[func](packet.q.text).then(file => {
data.file = file.data;
return this.question(`${this.askChr}feecting parse ${file.text}`);
}).then(feecting => {
data.feecting = feecting.a.data;
return resolve({
text: feecting.a.text,
html: feecting.a.html,
data,
})
}).catch(err => {
return this.error(err, packet, reject);
});
});
},
/**************
func: setModel
params: model
describe: Set the current model that the AI is suppose to use.
***************/
setModel(model=false, type='chat') {
if (!model) return model;
const models = this.services().personal[type].models;
if (!models || !models[model]) return false;
this.vars[type].model = model;
},
/**************
func: getModel
params: model
describe: Get a model value from services.
***************/
getModel(model=false,type='chat') {
if (!model) return model;
const models = this.services().personal[type].models;
if (!models) return false;
if (!models[model]) return models[this.vars[type].model];
return models[model];
}
},
methods: {
/**************
method: chat
params: packet
describe: send a chat to oepnai
***************/
chat(packet) {
return new Promise((resolve, reject) => {
if (!packet) return (`chat: ${this._messages.nopacket}`);
this.context('chat', packet.q.agent.profile.name);
this.action('method', 'chat');
const agent = this.agent();
const data = {};
if (packet.q.meta.params[1]) this.func.setModel(packet.q.meta.params[1]);
this.func.chat(packet.q.text, packet.q.data).then(chat => {
data.chat = chat;
const response = [
`::begin:${chat.role}:${packet.id}`,
this.utils.parse(chat.text),
`::end:${chat.role}:${this.hash(chat.text)}`,
`date: ${this.formatDate(Date.now(), 'long', true)}`,
].join('\n');
this.state('parse', 'chat');
return this.question(`${this.askChr}feecting parse ${response}`);
}).then(feecting => {
data.feecting = feecting.a.data;
this.action('resolve', 'chat');
return resolve({
text:feecting.a.text,
html: feecting.a.html,
data,
});
}).catch(err => {
this.state('reject', 'chat');
return this.error(err, packet, reject);
})
});
},
/**************
method: relay
params: packet
describe: send a relay to oepnai without a formatted return.
***************/
relay(packet) {
const { meta, text } = packet.q;
const role = meta.params[1] || false;
const data = {};
return new Promise((resolve, reject) => {
if (!packet) return resolve(`relay: ${this._messages.nopacket}`);
if (!text) return resolve(this._messages.notext);
this.context('relay', packet.q.agent.profile.name);
this.action('method', 'relay');
this.func.chat(text, packet.q.data).then(chat => {
data.parsed = this.utils.parse(chat.text);
data.chat = chat;
this.state('resolve', `relay:${packet.q.agent.profile.name}`);
return resolve({
text: chat.text,
html: false,
data,
});
}).catch(err => {
this.state('reject', `relay:${packet.q.agent.profile.name}`);
console.log('PACKET DATA', packet.q.data);
return this.error(err, packet, reject);
})
});
},
/**************
func: response
params: packet
describe: return the last response to the caller.
***************/
response(packet) {
this.context('response');
return Promise.resolve({text:this.vars.response.text});
},
/**************
func: speech
params: packet
describe: transcribe text to speech
***************/
speech(packet) {
const agent = this.agent();
const data = {};
return new Promise((resolve, reject) => {
if (!packet) return resolve(`speech: ${this._messages.nopacket}`);
if (!packet.q.text) return resolve(`speech: ${this._messages.notext}`);
this.context('speech', packet.q.agent.name);
this.action('method', 'speech');
this.func.speech(packet.q).then(speech => {
data.speech = speech;
const text = [
`::begin:audio:${packet.id}`,
`audio[tts]:${speech.url}`,
`url: ${speech.url}`,
`::end:audio:${this.hash(speech)}`
].join('\n');
this.state('parse', 'speech');
return this.question(`${this.askChr}feecting parse ${text}`);
}).then(feecting => {
data.feecting = feecting.a.data;
this.state('resolve', 'speech');
return resolve({
text: feecting.a.text,
html: feecting.a.html,
data,
})
}).catch(err => {
this.context('error', this.vars.messages.error_speech)
return this.error(err, packet, reject);
});
});
},
/**************
method: images
params: packet
describe: get an image from oepn ai
***************/
image(packet) {
this.context('image');
const data = {};
return new Promise((resolve, reject) => {
this.func.image(packet.q).then(image => {
data.image = image;
const text = [
`::begin:image:${packet.id}`,
`image:${image.url}`,
`url: ${image.url}`,
``,
`${image.prompt}`,
`::end:image:${this.hash(image)}`,
].join('\n');
return this.question(`${this.askChr}feecting parse ${text}`);
}).then(feecting => {
data.feecting = feecting.a.data;
return resolve({
text: feecting.a.text,
html: feecting.a.html,
data,
})
}).catch(err => {
this.context('error', this.vars.messages.error_image)
return this.error(err, packet, reject);
})
});
},
file(packet) {
const data = {};
return this.func.processor(packet, this.vars.funcMap.file);
},
tune(packet) {
return this.func.processor(packet, this.vars.funcMap.tune);
},
model(packet) {
return this.func.processor(packet, this.vars.funcMap.model);
},
/**************
method: topic
params: packet
describe: set the global topic for the conversation.
***************/
topic(packet) {
this.context('topic', this.trimWords(packet.q.text, 3));
return new Promise((resolve, reject) => {
if (!packet.q.text) return resolve({text:this.vars.topic});
this.vars.topic = packet.q.text;
const topic = `topic: ${this.vars.topic}`;
this.question(`${this.askChr}feecting parse ${topic}`).then(parsed => {
return resolve({
text: parsed.a.text,
html: parsed.a.html,
data: parsed.a.data
})
}).catch(err => {
return this.error(packet, err, reject);
});
});
},
/**************
method: location
params: packet
describe: set the global location for the conversation
***************/
location(packet) {
this.context('location', this.trimWords(packet.q.text, 3));
return new Promise((resolve, reject) => {
if (!packet.q.text) return resolve({text:this.vars.location});
this.vars.location = packet.q.text;
const location = `location: ${this.vars.location}`;
this.question(`${this.askChr}feecting parse ${location}`).then(parsed => {
return resolve({
text: parsed.a.text,
html: parsed.a.html,
data: parsed.a.data
})
}).catch(err => {
return this.error(packet, err, reject);
});
});
}
},
async onInit(data) {
const {personal} = this.security();
const {chat} = this.services().personal;
this.vars.chat.tools = chat.tools;
// console.log('THIS VARS', this.vars.chat.role);
this.modules.openai = new OpenAI({
apiKey: personal.key,
});
return this.start(data);
},
onError(err) {
console.log('open error', err);
}
});
module.exports = OPEN