mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
add openai assistant
This commit is contained in:
@@ -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
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user