add openai assistant

This commit is contained in:
Henry
2023-11-07 20:45:25 +00:00
parent 12fb5a3a3b
commit 0f293e5a59
24 changed files with 1443 additions and 21 deletions
+9
View File
@@ -48,6 +48,15 @@ export interface ITool {
createdDate: Date
}
export interface IAssistant {
id: string
details: string
credential: string
iconSrc?: string
updatedDate: Date
createdDate: Date
}
export interface ICredential {
id: string
name: string
@@ -0,0 +1,24 @@
/* eslint-disable */
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { IAssistant } from '../../Interface'
@Entity()
export class Assistant implements IAssistant {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ type: 'text' })
details: string
@Column()
credential: string
@Column({ nullable: true })
iconSrc?: string
@CreateDateColumn()
createdDate: Date
@UpdateDateColumn()
updatedDate: Date
}
@@ -2,10 +2,12 @@ import { ChatFlow } from './ChatFlow'
import { ChatMessage } from './ChatMessage'
import { Credential } from './Credential'
import { Tool } from './Tool'
import { Assistant } from './Assistant'
export const entities = {
ChatFlow,
ChatMessage,
Credential,
Tool
Tool,
Assistant
}
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddAssistantEntity1699325775451 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS \`assistant\` (
\`id\` varchar(36) NOT NULL,
\`credential\` varchar(255) NOT NULL,
\`details\` text NOT NULL,
\`iconSrc\` varchar(255) DEFAULT NULL,
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE assistant`)
}
}
@@ -6,6 +6,7 @@ import { ModifyTool1694001465232 } from './1694001465232-ModifyTool'
import { AddApiConfig1694099200729 } from './1694099200729-AddApiConfig'
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory'
import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity'
export const mysqlMigrations = [
Init1693840429259,
@@ -15,5 +16,6 @@ export const mysqlMigrations = [
ModifyTool1694001465232,
AddApiConfig1694099200729,
AddAnalytic1694432361423,
AddChatHistory1694658767766
AddChatHistory1694658767766,
AddAssistantEntity1699325775451
]
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddAssistantEntity1699325775451 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS assistant (
id uuid NOT NULL DEFAULT uuid_generate_v4(),
"credential" varchar NOT NULL,
"details" text NOT NULL,
"iconSrc" varchar NULL,
"createdDate" timestamp NOT NULL DEFAULT now(),
"updatedDate" timestamp NOT NULL DEFAULT now(),
CONSTRAINT "PK_3c7cea7a044ac4c92764576cdbf" PRIMARY KEY (id)
);`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE assistant`)
}
}
@@ -6,6 +6,7 @@ import { ModifyTool1693997339912 } from './1693997339912-ModifyTool'
import { AddApiConfig1694099183389 } from './1694099183389-AddApiConfig'
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory'
import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity'
export const postgresMigrations = [
Init1693891895163,
@@ -15,5 +16,6 @@ export const postgresMigrations = [
ModifyTool1693997339912,
AddApiConfig1694099183389,
AddAnalytic1694432361423,
AddChatHistory1694658756136
AddChatHistory1694658756136,
AddAssistantEntity1699325775451
]
@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddAssistantEntity1699325775451 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "assistant" ("id" varchar PRIMARY KEY NOT NULL, "details" text NOT NULL, "credential" varchar NOT NULL, "iconSrc" varchar, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')));`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE assistant`)
}
}
@@ -6,6 +6,7 @@ import { ModifyTool1693924207475 } from './1693924207475-ModifyTool'
import { AddApiConfig1694090982460 } from './1694090982460-AddApiConfig'
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory'
import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity'
export const sqliteMigrations = [
Init1693835579790,
@@ -15,5 +16,6 @@ export const sqliteMigrations = [
ModifyTool1693924207475,
AddApiConfig1694090982460,
AddAnalytic1694432361423,
AddChatHistory1694657778173
AddChatHistory1694657778173,
AddAssistantEntity1699325775451
]
+234 -5
View File
@@ -9,6 +9,7 @@ import { Server } from 'socket.io'
import logger from './utils/logger'
import { expressRequestLogger } from './utils/logger'
import { v4 as uuidv4 } from 'uuid'
import OpenAI from 'openai'
import { Between, IsNull, FindOptionsWhere } from 'typeorm'
import {
IChatFlow,
@@ -57,6 +58,7 @@ import { ChatFlow } from './database/entities/ChatFlow'
import { ChatMessage } from './database/entities/ChatMessage'
import { Credential } from './database/entities/Credential'
import { Tool } from './database/entities/Tool'
import { Assistant } from './database/entities/Assistant'
import { ChatflowPool } from './ChatflowPool'
import { CachePool } from './CachePool'
import { ICommonObject, INodeOptionsValue } from 'flowise-components'
@@ -469,8 +471,8 @@ export class App {
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
if (isClearFromViewMessageDialog)
clearSessionMemoryFromViewMessageDialog(
if (isClearFromViewMessageDialog) {
await clearSessionMemoryFromViewMessageDialog(
nodes,
this.nodesPool.componentNodes,
chatId,
@@ -478,7 +480,9 @@ export class App {
sessionId,
memoryType
)
else clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId)
} else {
await clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId)
}
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid, chatId }
if (memoryType) deleteOptions.memoryType = memoryType
@@ -631,6 +635,224 @@ export class App {
return res.json(results)
})
// ----------------------------------------
// Assistant
// ----------------------------------------
// Get all assistants
this.app.get('/api/v1/assistants', async (req: Request, res: Response) => {
const assistants = await this.AppDataSource.getRepository(Assistant).find()
return res.json(assistants)
})
// Get specific assistant
this.app.get('/api/v1/assistants/:id', async (req: Request, res: Response) => {
const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({
id: req.params.id
})
return res.json(assistant)
})
// Get assistant object
this.app.get('/api/v1/openai-assistants/:id', async (req: Request, res: Response) => {
const credentialId = req.query.credential as string
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
id: credentialId
})
if (!credential) return res.status(404).send(`Credential ${credentialId} not found`)
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
const openai = new OpenAI({ apiKey: openAIApiKey })
const retrievedAssistant = await openai.beta.assistants.retrieve(req.params.id)
return res.json(retrievedAssistant)
})
// List available assistants
this.app.get('/api/v1/openai-assistants', async (req: Request, res: Response) => {
const credentialId = req.query.credential as string
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
id: credentialId
})
if (!credential) return res.status(404).send(`Credential ${credentialId} not found`)
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
const openai = new OpenAI({ apiKey: openAIApiKey })
const retrievedAssistants = await openai.beta.assistants.list()
return res.json(retrievedAssistants.data)
})
// Add assistant
this.app.post('/api/v1/assistants', async (req: Request, res: Response) => {
const body = req.body
if (!body.details) return res.status(500).send(`Invalid request body`)
const assistantDetails = JSON.parse(body.details)
if (!assistantDetails.id) {
try {
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
id: body.credential
})
if (!credential) return res.status(404).send(`Credential ${body.credential} not found`)
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
const openai = new OpenAI({ apiKey: openAIApiKey })
let tools = []
if (assistantDetails.tools) {
for (const tool of assistantDetails.tools ?? []) {
tools.push({
type: tool
})
}
}
const newAssistant = await openai.beta.assistants.create({
name: assistantDetails.name,
description: assistantDetails.description,
instructions: assistantDetails.instructions,
model: assistantDetails.model,
tools
})
const newAssistantDetails = {
...assistantDetails,
id: newAssistant.id
}
body.details = JSON.stringify(newAssistantDetails)
} catch (error) {
return res.status(500).send(`Error creating new assistant: ${error}`)
}
}
const newAssistant = new Assistant()
Object.assign(newAssistant, body)
const assistant = this.AppDataSource.getRepository(Assistant).create(newAssistant)
const results = await this.AppDataSource.getRepository(Assistant).save(assistant)
return res.json(results)
})
// Update assistant
this.app.put('/api/v1/assistants/:id', async (req: Request, res: Response) => {
const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({
id: req.params.id
})
if (!assistant) {
res.status(404).send(`Assistant ${req.params.id} not found`)
return
}
try {
const openAIAssistantId = JSON.parse(assistant.details)?.id
const body = req.body
const assistantDetails = JSON.parse(body.details)
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
id: body.credential
})
if (!credential) return res.status(404).send(`Credential ${body.credential} not found`)
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
const openai = new OpenAI({ apiKey: openAIApiKey })
let tools = []
if (assistantDetails.tools) {
for (const tool of assistantDetails.tools ?? []) {
tools.push({
type: tool
})
}
}
await openai.beta.assistants.update(openAIAssistantId, {
name: assistantDetails.name,
description: assistantDetails.description,
instructions: assistantDetails.instructions,
model: assistantDetails.model,
tools
})
const newAssistantDetails = {
...assistantDetails,
id: openAIAssistantId
}
const updateAssistant = new Assistant()
body.details = JSON.stringify(newAssistantDetails)
Object.assign(updateAssistant, body)
this.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
const result = await this.AppDataSource.getRepository(Assistant).save(assistant)
return res.json(result)
} catch (error) {
return res.status(500).send(`Error updating assistant: ${error}`)
}
})
// Delete assistant
this.app.delete('/api/v1/assistants/:id', async (req: Request, res: Response) => {
const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({
id: req.params.id
})
if (!assistant) {
res.status(404).send(`Assistant ${req.params.id} not found`)
return
}
try {
const body = req.body
const assistantDetails = JSON.parse(body.details)
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
id: body.credential
})
if (!credential) return res.status(404).send(`Credential ${body.credential} not found`)
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
const openai = new OpenAI({ apiKey: openAIApiKey })
await openai.beta.assistants.del(assistantDetails.id)
const results = await this.AppDataSource.getRepository(Assistant).delete({ id: req.params.id })
return res.json(results)
} catch (error) {
return res.status(500).send(`Error deleting assistant: ${error}`)
}
})
// ----------------------------------------
// Configuration
// ----------------------------------------
@@ -1121,18 +1343,25 @@ export class App {
logger,
appDataSource: this.AppDataSource,
databaseEntities,
analytic: chatflow.analytic
analytic: chatflow.analytic,
chatId
})
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatHistory: incomingInput.history,
logger,
appDataSource: this.AppDataSource,
databaseEntities,
analytic: chatflow.analytic
analytic: chatflow.analytic,
chatId
})
result = typeof result === 'string' ? { text: result } : result
// Retrieve threadId from assistant if exists
if (typeof result === 'object' && result.assistant) {
sessionId = result.assistant.threadId
}
const userMessage: Omit<IChatMessage, 'id'> = {
role: 'userMessage',
content: incomingInput.question,
+16 -5
View File
@@ -34,6 +34,7 @@ import { ChatFlow } from '../database/entities/ChatFlow'
import { ChatMessage } from '../database/entities/ChatMessage'
import { Credential } from '../database/entities/Credential'
import { Tool } from '../database/entities/Tool'
import { Assistant } from '../database/entities/Assistant'
import { DataSource } from 'typeorm'
import { CachePool } from '../CachePool'
@@ -41,7 +42,13 @@ const QUESTION_VAR_PREFIX = 'question'
const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential }
export const databaseEntities: IDatabaseEntity = {
ChatFlow: ChatFlow,
ChatMessage: ChatMessage,
Tool: Tool,
Credential: Credential,
Assistant: Assistant
}
/**
* Returns the home folder path of the user if
@@ -313,12 +320,14 @@ export const clearAllSessionMemory = async (
sessionId?: string
) => {
for (const node of reactFlowNodes) {
if (node.data.category !== 'Memory') continue
if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass()
if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId
if (sessionId && node.data.inputs) {
node.data.inputs.sessionId = sessionId
}
if (newNodeInstance.clearSessionMemory) {
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
@@ -345,8 +354,8 @@ export const clearSessionMemoryFromViewMessageDialog = async (
) => {
if (!sessionId) return
for (const node of reactFlowNodes) {
if (node.data.category !== 'Memory') continue
if (node.data.label !== memoryType) continue
if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue
if (memoryType && node.data.label !== memoryType) continue
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass()
@@ -912,6 +921,8 @@ export const decryptCredentialData = async (
): Promise<ICredentialDataDecrypted> => {
const encryptKey = await getEncryptionKey()
const decryptedData = AES.decrypt(encryptedData, encryptKey)
const decryptedDataStr = decryptedData.toString(enc.Utf8)
if (!decryptedDataStr) return {}
try {
if (componentCredentialName && componentCredentials) {
const plainDataObj = JSON.parse(decryptedData.toString(enc.Utf8))