mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 19:00:59 +03:00
Feature/OpenAI Assistant V2 (#2258)
* add gpt4 turbo to assistant * OpenAI Assistant V2 * update langfuse handler
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import openAIAssistantVectorStoreService from '../../services/openai-assistants-vector-store'
|
||||
|
||||
const getAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.getAssistantVectorStore - id not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.getAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
const apiResponse = await openAIAssistantVectorStoreService.getAssistantVectorStore(req.query.credential as string, req.params.id)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const listAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.listAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
const apiResponse = await openAIAssistantVectorStoreService.listAssistantVectorStore(req.query.credential as string)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const createAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (!req.body) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.createAssistantVectorStore - body not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.createAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
const apiResponse = await openAIAssistantVectorStoreService.createAssistantVectorStore(req.query.credential as string, req.body)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - id not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
if (!req.body) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - body not provided!`
|
||||
)
|
||||
}
|
||||
const apiResponse = await openAIAssistantVectorStoreService.updateAssistantVectorStore(
|
||||
req.query.credential as string,
|
||||
req.params.id,
|
||||
req.body
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.deleteAssistantVectorStore - id not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
const apiResponse = await openAIAssistantVectorStoreService.deleteAssistantVectorStore(
|
||||
req.query.credential as string,
|
||||
req.params.id as string
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFilesToAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (!req.body) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - body not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - id not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
const files = req.files ?? []
|
||||
const uploadFiles: { filePath: string; fileName: string }[] = []
|
||||
|
||||
if (Array.isArray(files)) {
|
||||
for (const file of files) {
|
||||
uploadFiles.push({
|
||||
filePath: file.path,
|
||||
fileName: file.originalname
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const apiResponse = await openAIAssistantVectorStoreService.uploadFilesToAssistantVectorStore(
|
||||
req.query.credential as string,
|
||||
req.params.id as string,
|
||||
uploadFiles
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteFilesFromAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (!req.body) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - body not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - id not provided!`
|
||||
)
|
||||
}
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
|
||||
const apiResponse = await openAIAssistantVectorStoreService.deleteFilesFromAssistantVectorStore(
|
||||
req.query.credential as string,
|
||||
req.params.id as string,
|
||||
req.body.file_ids
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAssistantVectorStore,
|
||||
listAssistantVectorStore,
|
||||
createAssistantVectorStore,
|
||||
updateAssistantVectorStore,
|
||||
deleteAssistantVectorStore,
|
||||
uploadFilesToAssistantVectorStore,
|
||||
deleteFilesFromAssistantVectorStore
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import openaiAssistantsService from '../../services/openai-assistants'
|
||||
import { getUserHome } from '../../utils'
|
||||
import contentDisposition from 'content-disposition'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { streamStorageFile } from 'flowise-components'
|
||||
|
||||
// List available assistants
|
||||
const getAllOpenaiAssistants = async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -48,27 +47,57 @@ const getSingleOpenaiAssistant = async (req: Request, res: Response, next: NextF
|
||||
// Download file from assistant
|
||||
const getFileFromAssistant = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', req.body.fileName)
|
||||
//raise error if file path is not absolute
|
||||
if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`)
|
||||
//raise error if file path contains '..'
|
||||
if (filePath.includes('..')) return res.status(500).send(`Invalid file path`)
|
||||
//only return from the .flowise openai-assistant folder
|
||||
if (!(filePath.includes('.flowise') && filePath.includes('openai-assistant'))) return res.status(500).send(`Invalid file path`)
|
||||
if (fs.existsSync(filePath)) {
|
||||
res.setHeader('Content-Disposition', contentDisposition(path.basename(filePath)))
|
||||
const fileStream = fs.createReadStream(filePath)
|
||||
if (!req.body.chatflowId || !req.body.chatId || !req.body.fileName) {
|
||||
return res.status(500).send(`Invalid file path`)
|
||||
}
|
||||
const chatflowId = req.body.chatflowId as string
|
||||
const chatId = req.body.chatId as string
|
||||
const fileName = req.body.fileName as string
|
||||
res.setHeader('Content-Disposition', contentDisposition(fileName))
|
||||
const fileStream = await streamStorageFile(chatflowId, chatId, fileName)
|
||||
|
||||
if (!fileStream) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: getFileFromAssistant`)
|
||||
|
||||
if (fileStream instanceof fs.ReadStream && fileStream?.pipe) {
|
||||
fileStream.pipe(res)
|
||||
} else {
|
||||
return res.status(404).send(`File ${req.body.fileName} not found`)
|
||||
res.send(fileStream)
|
||||
}
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAssistantFiles = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.PRECONDITION_FAILED,
|
||||
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - credential not provided!`
|
||||
)
|
||||
}
|
||||
const files = req.files ?? []
|
||||
const uploadFiles: { filePath: string; fileName: string }[] = []
|
||||
|
||||
if (Array.isArray(files)) {
|
||||
for (const file of files) {
|
||||
uploadFiles.push({
|
||||
filePath: file.path,
|
||||
fileName: file.originalname
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const apiResponse = await openaiAssistantsService.uploadFilesToAssistant(req.query.credential as string, uploadFiles)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAllOpenaiAssistants,
|
||||
getSingleOpenaiAssistant,
|
||||
getFileFromAssistant
|
||||
getFileFromAssistant,
|
||||
uploadAssistantFiles
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export class App {
|
||||
'/api/v1/components-credentials-icon/',
|
||||
'/api/v1/chatflows-streaming',
|
||||
'/api/v1/chatflows-uploads',
|
||||
'/api/v1/openai-assistants-file',
|
||||
'/api/v1/openai-assistants-file/download',
|
||||
'/api/v1/feedback',
|
||||
'/api/v1/get-upload-file',
|
||||
'/api/v1/ip'
|
||||
|
||||
@@ -25,6 +25,7 @@ import nodeLoadMethodRouter from './node-load-methods'
|
||||
import nodesRouter from './nodes'
|
||||
import openaiAssistantsRouter from './openai-assistants'
|
||||
import openaiAssistantsFileRouter from './openai-assistants-files'
|
||||
import openaiAssistantsVectorStoreRouter from './openai-assistants-vector-store'
|
||||
import predictionRouter from './predictions'
|
||||
import promptListsRouter from './prompts-lists'
|
||||
import publicChatbotRouter from './public-chatbots'
|
||||
@@ -65,6 +66,7 @@ router.use('/node-load-method', nodeLoadMethodRouter)
|
||||
router.use('/nodes', nodesRouter)
|
||||
router.use('/openai-assistants', openaiAssistantsRouter)
|
||||
router.use('/openai-assistants-file', openaiAssistantsFileRouter)
|
||||
router.use('/openai-assistants-vector-store', openaiAssistantsVectorStoreRouter)
|
||||
router.use('/prediction', predictionRouter)
|
||||
router.use('/prompts-list', promptListsRouter)
|
||||
router.use('/public-chatbotConfig', publicChatbotRouter)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import path from 'path'
|
||||
import openaiAssistantsController from '../../controllers/openai-assistants'
|
||||
const router = express.Router()
|
||||
|
||||
// CREATE
|
||||
router.post('/', openaiAssistantsController.getFileFromAssistant)
|
||||
const router = express.Router()
|
||||
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
|
||||
|
||||
router.post('/download/', openaiAssistantsController.getFileFromAssistant)
|
||||
router.post('/upload/', upload.array('files'), openaiAssistantsController.uploadAssistantFiles)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import path from 'path'
|
||||
import openaiAssistantsVectorStoreController from '../../controllers/openai-assistants-vector-store'
|
||||
|
||||
const router = express.Router()
|
||||
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
|
||||
|
||||
// CREATE
|
||||
router.post('/', openaiAssistantsVectorStoreController.createAssistantVectorStore)
|
||||
|
||||
// READ
|
||||
router.get('/:id', openaiAssistantsVectorStoreController.getAssistantVectorStore)
|
||||
|
||||
// LIST
|
||||
router.get('/', openaiAssistantsVectorStoreController.listAssistantVectorStore)
|
||||
|
||||
// UPDATE
|
||||
router.put(['/', '/:id'], openaiAssistantsVectorStoreController.updateAssistantVectorStore)
|
||||
|
||||
// DELETE
|
||||
router.delete(['/', '/:id'], openaiAssistantsVectorStoreController.deleteAssistantVectorStore)
|
||||
|
||||
// POST
|
||||
router.post('/:id', upload.array('files'), openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore)
|
||||
|
||||
// DELETE
|
||||
router.patch(['/', '/:id'], openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore)
|
||||
|
||||
export default router
|
||||
@@ -1,12 +1,10 @@
|
||||
import OpenAI from 'openai'
|
||||
import path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { uniqWith, isEqual } from 'lodash'
|
||||
import { uniqWith, isEqual, cloneDeep } from 'lodash'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { Assistant } from '../../database/entities/Assistant'
|
||||
import { Credential } from '../../database/entities/Credential'
|
||||
import { getUserHome, decryptCredentialData, getAppVersion } from '../../utils'
|
||||
import { decryptCredentialData, getAppVersion } from '../../utils'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
|
||||
@@ -34,6 +32,7 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
}
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
|
||||
// Prepare tools
|
||||
let tools = []
|
||||
if (assistantDetails.tools) {
|
||||
for (const tool of assistantDetails.tools ?? []) {
|
||||
@@ -43,40 +42,25 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
}
|
||||
}
|
||||
|
||||
if (assistantDetails.uploadFiles) {
|
||||
// Base64 strings
|
||||
let files: string[] = []
|
||||
const fileBase64 = assistantDetails.uploadFiles
|
||||
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
|
||||
files = JSON.parse(fileBase64)
|
||||
} else {
|
||||
files = [fileBase64]
|
||||
}
|
||||
// Save tool_resources to be stored later into database
|
||||
const savedToolResources = cloneDeep(assistantDetails.tool_resources)
|
||||
|
||||
const uploadedFiles = []
|
||||
for (const file of files) {
|
||||
const splitDataURI = file.split(',')
|
||||
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
|
||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', filename)
|
||||
if (!fs.existsSync(path.join(getUserHome(), '.flowise', 'openai-assistant'))) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
||||
// Cleanup tool_resources for creating assistant
|
||||
if (assistantDetails.tool_resources) {
|
||||
for (const toolResource in assistantDetails.tool_resources) {
|
||||
if (toolResource === 'file_search') {
|
||||
assistantDetails.tool_resources['file_search'] = {
|
||||
vector_store_ids: assistantDetails.tool_resources['file_search'].vector_store_ids
|
||||
}
|
||||
} else if (toolResource === 'code_interpreter') {
|
||||
assistantDetails.tool_resources['code_interpreter'] = {
|
||||
file_ids: assistantDetails.tool_resources['code_interpreter'].file_ids
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(filePath)) {
|
||||
fs.writeFileSync(filePath, bf)
|
||||
}
|
||||
|
||||
const createdFile = await openai.files.create({
|
||||
file: fs.createReadStream(filePath),
|
||||
purpose: 'assistants'
|
||||
})
|
||||
uploadedFiles.push(createdFile)
|
||||
|
||||
fs.unlinkSync(filePath)
|
||||
}
|
||||
assistantDetails.files = [...assistantDetails.files, ...uploadedFiles]
|
||||
}
|
||||
|
||||
// If the assistant doesn't exist, create a new one
|
||||
if (!assistantDetails.id) {
|
||||
const newAssistant = await openai.beta.assistants.create({
|
||||
name: assistantDetails.name,
|
||||
@@ -84,12 +68,15 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
instructions: assistantDetails.instructions,
|
||||
model: assistantDetails.model,
|
||||
tools,
|
||||
file_ids: (assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)
|
||||
tool_resources: assistantDetails.tool_resources,
|
||||
temperature: assistantDetails.temperature,
|
||||
top_p: assistantDetails.top_p
|
||||
})
|
||||
assistantDetails.id = newAssistant.id
|
||||
} else {
|
||||
const retrievedAssistant = await openai.beta.assistants.retrieve(assistantDetails.id)
|
||||
let filteredTools = uniqWith([...retrievedAssistant.tools, ...tools], isEqual)
|
||||
let filteredTools = uniqWith([...retrievedAssistant.tools.filter((tool) => tool.type === 'function'), ...tools], isEqual)
|
||||
// Remove empty functions
|
||||
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
|
||||
|
||||
await openai.beta.assistants.update(assistantDetails.id, {
|
||||
@@ -98,17 +85,16 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
instructions: assistantDetails.instructions ?? '',
|
||||
model: assistantDetails.model,
|
||||
tools: filteredTools,
|
||||
file_ids: uniqWith(
|
||||
[...retrievedAssistant.file_ids, ...(assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)],
|
||||
isEqual
|
||||
)
|
||||
tool_resources: assistantDetails.tool_resources,
|
||||
temperature: assistantDetails.temperature,
|
||||
top_p: assistantDetails.top_p
|
||||
})
|
||||
}
|
||||
|
||||
const newAssistantDetails = {
|
||||
...assistantDetails
|
||||
}
|
||||
if (newAssistantDetails.uploadFiles) delete newAssistantDetails.uploadFiles
|
||||
if (savedToolResources) newAssistantDetails.tool_resources = savedToolResources
|
||||
|
||||
requestBody.details = JSON.stringify(newAssistantDetails)
|
||||
} catch (error) {
|
||||
@@ -117,7 +103,7 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||
const newAssistant = new Assistant()
|
||||
Object.assign(newAssistant, requestBody)
|
||||
|
||||
const assistant = await appServer.AppDataSource.getRepository(Assistant).create(newAssistant)
|
||||
const assistant = appServer.AppDataSource.getRepository(Assistant).create(newAssistant)
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
|
||||
|
||||
await appServer.telemetry.sendTelemetry('assistant_created', {
|
||||
@@ -249,42 +235,26 @@ const updateAssistant = async (assistantId: string, requestBody: any): Promise<a
|
||||
}
|
||||
}
|
||||
|
||||
if (assistantDetails.uploadFiles) {
|
||||
// Base64 strings
|
||||
let files: string[] = []
|
||||
const fileBase64 = assistantDetails.uploadFiles
|
||||
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
|
||||
files = JSON.parse(fileBase64)
|
||||
} else {
|
||||
files = [fileBase64]
|
||||
}
|
||||
// Save tool_resources to be stored later into database
|
||||
const savedToolResources = cloneDeep(assistantDetails.tool_resources)
|
||||
|
||||
const uploadedFiles = []
|
||||
for (const file of files) {
|
||||
const splitDataURI = file.split(',')
|
||||
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
|
||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', filename)
|
||||
if (!fs.existsSync(path.join(getUserHome(), '.flowise', 'openai-assistant'))) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
||||
// Cleanup tool_resources before updating
|
||||
if (assistantDetails.tool_resources) {
|
||||
for (const toolResource in assistantDetails.tool_resources) {
|
||||
if (toolResource === 'file_search') {
|
||||
assistantDetails.tool_resources['file_search'] = {
|
||||
vector_store_ids: assistantDetails.tool_resources['file_search'].vector_store_ids
|
||||
}
|
||||
} else if (toolResource === 'code_interpreter') {
|
||||
assistantDetails.tool_resources['code_interpreter'] = {
|
||||
file_ids: assistantDetails.tool_resources['code_interpreter'].file_ids
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(filePath)) {
|
||||
fs.writeFileSync(filePath, bf)
|
||||
}
|
||||
|
||||
const createdFile = await openai.files.create({
|
||||
file: fs.createReadStream(filePath),
|
||||
purpose: 'assistants'
|
||||
})
|
||||
uploadedFiles.push(createdFile)
|
||||
|
||||
fs.unlinkSync(filePath)
|
||||
}
|
||||
assistantDetails.files = [...assistantDetails.files, ...uploadedFiles]
|
||||
}
|
||||
|
||||
const retrievedAssistant = await openai.beta.assistants.retrieve(openAIAssistantId)
|
||||
let filteredTools = uniqWith([...retrievedAssistant.tools, ...tools], isEqual)
|
||||
let filteredTools = uniqWith([...retrievedAssistant.tools.filter((tool) => tool.type === 'function'), ...tools], isEqual)
|
||||
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
|
||||
|
||||
await openai.beta.assistants.update(openAIAssistantId, {
|
||||
@@ -293,23 +263,22 @@ const updateAssistant = async (assistantId: string, requestBody: any): Promise<a
|
||||
instructions: assistantDetails.instructions,
|
||||
model: assistantDetails.model,
|
||||
tools: filteredTools,
|
||||
file_ids: uniqWith(
|
||||
[...retrievedAssistant.file_ids, ...(assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)],
|
||||
isEqual
|
||||
)
|
||||
tool_resources: assistantDetails.tool_resources,
|
||||
temperature: assistantDetails.temperature,
|
||||
top_p: assistantDetails.top_p
|
||||
})
|
||||
|
||||
const newAssistantDetails = {
|
||||
...assistantDetails,
|
||||
id: openAIAssistantId
|
||||
}
|
||||
if (newAssistantDetails.uploadFiles) delete newAssistantDetails.uploadFiles
|
||||
if (savedToolResources) newAssistantDetails.tool_resources = savedToolResources
|
||||
|
||||
const updateAssistant = new Assistant()
|
||||
body.details = JSON.stringify(newAssistantDetails)
|
||||
Object.assign(updateAssistant, body)
|
||||
|
||||
await appServer.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
|
||||
appServer.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
import OpenAI from 'openai'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import fs from 'fs'
|
||||
import { Credential } from '../../database/entities/Credential'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { decryptCredentialData } from '../../utils'
|
||||
|
||||
const getAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const dbResponse = await openai.beta.vectorStores.retrieve(vectorStoreId)
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.getAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const listAssistantVectorStore = async (credentialId: string) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const dbResponse = await openai.beta.vectorStores.list()
|
||||
return dbResponse.data
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.listAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const createAssistantVectorStore = async (credentialId: string, obj: OpenAI.Beta.VectorStores.VectorStoreCreateParams) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const dbResponse = await openai.beta.vectorStores.create(obj)
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.createAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssistantVectorStore = async (
|
||||
credentialId: string,
|
||||
vectorStoreId: string,
|
||||
obj: OpenAI.Beta.VectorStores.VectorStoreUpdateParams
|
||||
) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const dbResponse = await openai.beta.vectorStores.update(vectorStoreId, obj)
|
||||
const vectorStoreFiles = await openai.beta.vectorStores.files.list(vectorStoreId)
|
||||
if (vectorStoreFiles.data?.length) {
|
||||
const files = []
|
||||
for (const file of vectorStoreFiles.data) {
|
||||
const fileData = await openai.files.retrieve(file.id)
|
||||
files.push(fileData)
|
||||
}
|
||||
;(dbResponse as any).files = files
|
||||
}
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.updateAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const dbResponse = await openai.beta.vectorStores.del(vectorStoreId)
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.deleteAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFilesToAssistantVectorStore = async (
|
||||
credentialId: string,
|
||||
vectorStoreId: string,
|
||||
files: { filePath: string; fileName: string }[]
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const uploadedFiles = []
|
||||
for (const file of files) {
|
||||
const createdFile = await openai.files.create({
|
||||
file: new File([new Blob([fs.readFileSync(file.filePath)])], file.fileName),
|
||||
purpose: 'assistants'
|
||||
})
|
||||
uploadedFiles.push(createdFile)
|
||||
fs.unlinkSync(file.filePath)
|
||||
}
|
||||
|
||||
const file_ids = [...uploadedFiles.map((file) => file.id)]
|
||||
|
||||
const res = await openai.beta.vectorStores.fileBatches.createAndPoll(vectorStoreId, {
|
||||
file_ids
|
||||
})
|
||||
if (res.status === 'completed' && res.file_counts.completed === uploadedFiles.length) return uploadedFiles
|
||||
else if (res.status === 'failed')
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
'Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - Upload failed!'
|
||||
)
|
||||
else
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
'Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - Upload cancelled!'
|
||||
)
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteFilesFromAssistantVectorStore = async (credentialId: string, vectorStoreId: string, file_ids: string[]) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const deletedFileIds = []
|
||||
let count = 0
|
||||
for (const file of file_ids) {
|
||||
const res = await openai.beta.vectorStores.files.del(vectorStoreId, file)
|
||||
if (res.deleted) {
|
||||
deletedFileIds.push(file)
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
return { deletedFileIds, count }
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAssistantVectorStore,
|
||||
listAssistantVectorStore,
|
||||
createAssistantVectorStore,
|
||||
updateAssistantVectorStore,
|
||||
deleteAssistantVectorStore,
|
||||
uploadFilesToAssistantVectorStore,
|
||||
deleteFilesFromAssistantVectorStore
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import OpenAI from 'openai'
|
||||
import fs from 'fs'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { decryptCredentialData } from '../../utils'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
@@ -59,8 +60,18 @@ const getSingleOpenaiAssistant = async (credentialId: string, assistantId: strin
|
||||
const dbResponse = await openai.beta.assistants.retrieve(assistantId)
|
||||
const resp = await openai.files.list()
|
||||
const existingFiles = resp.data ?? []
|
||||
if (dbResponse.file_ids && dbResponse.file_ids.length) {
|
||||
;(dbResponse as any).files = existingFiles.filter((file) => dbResponse.file_ids.includes(file.id))
|
||||
if (dbResponse.tool_resources?.code_interpreter?.file_ids?.length) {
|
||||
;(dbResponse.tool_resources.code_interpreter as any).files = [
|
||||
...existingFiles.filter((file) => dbResponse.tool_resources?.code_interpreter?.file_ids?.includes(file.id))
|
||||
]
|
||||
}
|
||||
if (dbResponse.tool_resources?.file_search?.vector_store_ids?.length) {
|
||||
// Since there can only be 1 vector store per assistant
|
||||
const vectorStoreId = dbResponse.tool_resources.file_search.vector_store_ids[0]
|
||||
const vectorStoreFiles = await openai.beta.vectorStores.files.list(vectorStoreId)
|
||||
const fileIds = vectorStoreFiles.data?.map((file) => file.id) ?? []
|
||||
;(dbResponse.tool_resources.file_search as any).files = [...existingFiles.filter((file) => fileIds.includes(file.id))]
|
||||
;(dbResponse.tool_resources.file_search as any).vector_store_object = await openai.beta.vectorStores.retrieve(vectorStoreId)
|
||||
}
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
@@ -71,7 +82,38 @@ const getSingleOpenaiAssistant = async (credentialId: string, assistantId: strin
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFilesToAssistant = async (credentialId: string, files: { filePath: string; fileName: string }[]) => {
|
||||
const appServer = getRunningExpressApp()
|
||||
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
if (!credential) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||
}
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const uploadedFiles = []
|
||||
|
||||
for (const file of files) {
|
||||
const createdFile = await openai.files.create({
|
||||
file: new File([new Blob([fs.readFileSync(file.filePath)])], file.fileName),
|
||||
purpose: 'assistants'
|
||||
})
|
||||
uploadedFiles.push(createdFile)
|
||||
fs.unlinkSync(file.filePath)
|
||||
}
|
||||
|
||||
return uploadedFiles
|
||||
}
|
||||
|
||||
export default {
|
||||
getAllOpenaiAssistants,
|
||||
getSingleOpenaiAssistant
|
||||
getSingleOpenaiAssistant,
|
||||
uploadFilesToAssistant
|
||||
}
|
||||
|
||||
@@ -1066,6 +1066,9 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If agent is openAIAssistant, streaming is enabled
|
||||
if (endingNodeData.name === 'openAIAssistant') return true
|
||||
} else if (endingNodeData.category === 'Engine') {
|
||||
// Engines that are available to stream
|
||||
const whitelistEngine = ['contextChatEngine', 'simpleChatEngine', 'queryEngine', 'subQuestionQueryEngine']
|
||||
|
||||
Reference in New Issue
Block a user