From 0f464fddb8c787334385320db768f991fbc6bb2d Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 17 Nov 2023 07:22:08 +0530 Subject: [PATCH 001/268] Conversation Starters: Initial commit --- .../cards/StarterConversationCard.js | 49 ++++++++++++++++ .../ui/src/views/chatflows/Configuration.js | 57 ++++++++++++++----- .../ui/src/views/chatmessage/ChatMessage.js | 15 ++++- 3 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 packages/ui/src/ui-component/cards/StarterConversationCard.js diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.js b/packages/ui/src/ui-component/cards/StarterConversationCard.js new file mode 100644 index 00000000..946c4c11 --- /dev/null +++ b/packages/ui/src/ui-component/cards/StarterConversationCard.js @@ -0,0 +1,49 @@ +import Chip from '@mui/material/Chip' +import Box from '@mui/material/Box' +import PropTypes from 'prop-types' +import { MenuItem, Select } from '@mui/material' + +const StarterConversationCard = ({ isGrid, chipsData, onChipClick }) => { + if (isGrid) { + const chipStyle = { + margin: '5px', + width: 'calc(50% - 10px)' + } + + return ( + + {chipsData.map((chipLabel, index) => ( + onChipClick(chipLabel)} /> + ))} + + ) + } else { + return ( + + ) + } +} + +StarterConversationCard.propTypes = { + isGrid: PropTypes.bool, + chipsData: PropTypes.arrayOf(PropTypes.string), + onChipClick: PropTypes.func +} + +export default StarterConversationCard diff --git a/packages/ui/src/views/chatflows/Configuration.js b/packages/ui/src/views/chatflows/Configuration.js index d569020b..9dc74090 100644 --- a/packages/ui/src/views/chatflows/Configuration.js +++ b/packages/ui/src/views/chatflows/Configuration.js @@ -4,6 +4,10 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba import PropTypes from 'prop-types' import { Box, Typography, Button, OutlinedInput } from '@mui/material' +import Accordion from '@mui/material/Accordion' +import AccordionSummary from '@mui/material/AccordionSummary' +import AccordionDetails from '@mui/material/AccordionDetails' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' // Project import import { StyledButton } from 'ui-component/button/StyledButton' @@ -32,6 +36,10 @@ const Configuration = () => { const [limitMax, setLimitMax] = useState(apiConfig?.rateLimit?.limitMax ?? '') const [limitDuration, setLimitDuration] = useState(apiConfig?.rateLimit?.limitDuration ?? '') const [limitMsg, setLimitMsg] = useState(apiConfig?.rateLimit?.limitMsg ?? '') + const [prompt1, setPrompt1] = useState(apiConfig?.prompt?.prompt1 ?? '') + const [prompt2, setPrompt2] = useState(apiConfig?.prompt?.prompt2 ?? '') + const [prompt3, setPrompt3] = useState(apiConfig?.prompt?.prompt3 ?? '') + const [prompt4, setPrompt4] = useState(apiConfig?.prompt?.prompt4 ?? '') const formatObj = () => { const obj = { @@ -111,7 +119,7 @@ const Configuration = () => { return (
- {fieldLabel} + {fieldLabel && {fieldLabel}} { return ( <> - {/*Rate Limit*/} - - Rate Limit{' '} - Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' - } - /> - - {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} - {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} - {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} + + } aria-controls='panel1a-content' id='panel1a-header'> + + Rate Limit{' '} + Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' + } + /> + + + + + {/*Rate Limit*/} + {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} + {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} + {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} + + + + + } aria-controls='panel2a-content' id='panel2a-header'> + Conversation Starters + + + + {textField(prompt1, 'prompt1', 'Starter Prompts', 'string')} + {textField(prompt2, 'prompt2', '', 'string')} + {textField(prompt3, 'prompt3', '', 'string')} + {textField(prompt4, 'prompt4', '', 'string')} + + + onSave()}> Save Changes diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 0cf5695b..d047a3aa 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -32,6 +32,7 @@ import { baseURL, maxScroll } from 'store/constant' import robotPNG from 'assets/images/robot.png' import userPNG from 'assets/images/account.png' import { isValidURL, removeDuplicateURL } from 'utils/genericHelper' +import StarterConversationCard from '../../ui-component/cards/StarterConversationCard' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -103,9 +104,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }, 100) } + const handlePromptClick = async (prompt) => { + setUserInput(prompt) + await handleSubmit() + } + // Handle form submission const handleSubmit = async (e) => { - e.preventDefault() + if (e) e.preventDefault() if (userInput.trim() === '') { return @@ -369,6 +375,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
+ {messages && messages.length === 1 && ( + + )} Date: Tue, 21 Nov 2023 17:15:39 +0530 Subject: [PATCH 002/268] Conversation Starters: Initial Implementation --- .../server/src/database/entities/ChatFlow.ts | 3 + ...00565042576-AddStarterPromptsToChatFlow.ts | 12 ++ .../src/database/migrations/mysql/index.ts | 4 +- ...00565042576-AddStarterPromptsToChatFlow.ts | 11 + .../src/database/migrations/postgres/index.ts | 4 +- ...00565042576-AddStarterPromptsToChatFlow.ts | 11 + .../src/database/migrations/sqlite/index.ts | 4 +- packages/ui/src/menu-items/settings.js | 11 +- .../cards/StarterConversationCard.css | 16 ++ .../cards/StarterConversationCard.js | 53 ++--- .../dialog/ConversationStarterDialog.js | 188 ++++++++++++++++++ packages/ui/src/views/canvas/CanvasHeader.js | 14 ++ .../ui/src/views/chatflows/Configuration.js | 57 ++---- .../ui/src/views/chatmessage/ChatMessage.js | 24 ++- 14 files changed, 319 insertions(+), 93 deletions(-) create mode 100644 packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts create mode 100644 packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts create mode 100644 packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts create mode 100644 packages/ui/src/ui-component/cards/StarterConversationCard.css create mode 100644 packages/ui/src/ui-component/dialog/ConversationStarterDialog.js diff --git a/packages/server/src/database/entities/ChatFlow.ts b/packages/server/src/database/entities/ChatFlow.ts index 376a100b..626f743a 100644 --- a/packages/server/src/database/entities/ChatFlow.ts +++ b/packages/server/src/database/entities/ChatFlow.ts @@ -28,6 +28,9 @@ export class ChatFlow implements IChatFlow { @Column({ nullable: true, type: 'text' }) apiConfig?: string + @Column({ nullable: true, type: 'text' }) + starterPrompt?: string + @Column({ nullable: true, type: 'text' }) analytic?: string diff --git a/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts new file mode 100644 index 00000000..1f7a3c54 --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts @@ -0,0 +1,12 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddStarterPrompt1700565042576 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const columnExists = await queryRunner.hasColumn('chat_flow', 'starterPrompt') + if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`starterPrompt\` TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`starterPrompt\`;`) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index 4b7b8a95..f38a9cfe 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const mysqlMigrations = [ Init1693840429259, @@ -19,5 +20,6 @@ export const mysqlMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658767766, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341 + AddUsedToolsToChatMessage1699481607341, + AddStarterPrompt1700565042576 ] diff --git a/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts new file mode 100644 index 00000000..ac2694b9 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddStarterPrompt1700565042576 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "starterPrompt" TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index 75562c0b..e9018265 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const postgresMigrations = [ Init1693891895163, @@ -19,5 +20,6 @@ export const postgresMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658756136, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341 + AddUsedToolsToChatMessage1699481607341, + AddStarterPrompt1700565042576 ] diff --git a/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts new file mode 100644 index 00000000..3d180797 --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddStarterPrompt1700565042576 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "starterPrompt" TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index 4a14fc40..414d755e 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const sqliteMigrations = [ Init1693835579790, @@ -19,5 +20,6 @@ export const sqliteMigrations = [ AddAnalytic1694432361423, AddChatHistory1694657778173, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341 + AddUsedToolsToChatMessage1699481607341, + AddStarterPrompt1700565042576 ] diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 307bd0bd..9224b78f 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -1,8 +1,8 @@ // assets -import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } from '@tabler/icons' +import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } from '@tabler/icons' // constant -const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } +const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } // ==============================|| SETTINGS MENU ITEMS ||============================== // @@ -11,6 +11,13 @@ const settings = { title: '', type: 'group', children: [ + { + id: 'conversationStarters', + title: 'Set Starter Prompts', + type: 'item', + url: '', + icon: icons.IconPictureInPictureOff + }, { id: 'viewMessages', title: 'View Messages', diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.css b/packages/ui/src/ui-component/cards/StarterConversationCard.css new file mode 100644 index 00000000..8eb3b0ad --- /dev/null +++ b/packages/ui/src/ui-component/cards/StarterConversationCard.css @@ -0,0 +1,16 @@ +.button-container { + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */ + scrollbar-width: none; /* For Firefox */ +} + +/*.button-container::-webkit-scrollbar {*/ +/* display: none; !* For Chrome, Safari, and Opera *!*/ +/*}*/ + +.button { + flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */ + margin: 5px; /* Adjust as needed for spacing between buttons */ + /* Add additional button styling here */ +} diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.js b/packages/ui/src/ui-component/cards/StarterConversationCard.js index 946c4c11..aa50f266 100644 --- a/packages/ui/src/ui-component/cards/StarterConversationCard.js +++ b/packages/ui/src/ui-component/cards/StarterConversationCard.js @@ -1,49 +1,24 @@ -import Chip from '@mui/material/Chip' import Box from '@mui/material/Box' import PropTypes from 'prop-types' -import { MenuItem, Select } from '@mui/material' +import { Button } from '@mui/material' +import './StarterConversationCard.css' -const StarterConversationCard = ({ isGrid, chipsData, onChipClick }) => { - if (isGrid) { - const chipStyle = { - margin: '5px', - width: 'calc(50% - 10px)' - } - - return ( - - {chipsData.map((chipLabel, index) => ( - onChipClick(chipLabel)} /> - ))} - - ) - } else { - return ( - - ) - } +const StarterConversationCard = ({ isGrid, starterPrompts, onPromptClick }) => { + return ( + + {starterPrompts.map((sp, index) => ( + + ))} + + ) } StarterConversationCard.propTypes = { isGrid: PropTypes.bool, - chipsData: PropTypes.arrayOf(PropTypes.string), - onChipClick: PropTypes.func + starterPrompts: PropTypes.arrayOf(PropTypes.string), + onPromptClick: PropTypes.func } export default StarterConversationCard diff --git a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js new file mode 100644 index 00000000..1139f21e --- /dev/null +++ b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js @@ -0,0 +1,188 @@ +import { createPortal } from 'react-dom' +import { useDispatch } from 'react-redux' +import { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' + +// material-ui +import { Button, IconButton, Dialog, DialogContent, OutlinedInput, DialogTitle, DialogActions, Box, List, Divider } from '@mui/material' +import { IconX, IconTrash, IconPlus } from '@tabler/icons' + +// Project import +import { StyledButton } from 'ui-component/button/StyledButton' + +// store +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' +import useNotifier from 'utils/useNotifier' + +// API +import chatflowsApi from 'api/chatflows' + +const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { + const portalElement = document.getElementById('portal') + const dispatch = useDispatch() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [inputFields, setInputFields] = useState([ + { + prompt: '' + } + ]) + + const addInputField = () => { + setInputFields([ + ...inputFields, + { + prompt: '' + } + ]) + } + const removeInputFields = (index) => { + const rows = [...inputFields] + rows.splice(index, 1) + setInputFields(rows) + } + + const handleChange = (index, evnt) => { + const { name, value } = evnt.target + const list = [...inputFields] + list[index][name] = value + setInputFields(list) + } + + const onSave = async () => { + try { + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + starterPrompt: JSON.stringify(inputFields) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Conversation Starter Prompts Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + onCancel() + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Conversation Starter Prompts: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + useEffect(() => { + if (dialogProps.chatflow && dialogProps.chatflow.starterPrompt) { + try { + setInputFields(JSON.parse(dialogProps.chatflow.starterPrompt)) + } catch (e) { + setInputFields([ + { + prompt: '' + } + ]) + console.error(e) + } + } + + return () => {} + }, [dialogProps]) + + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + + const component = show ? ( + + + Set Conversation Starter Prompts + + + + + {inputFields.map((data, index) => { + return ( +
+ + handleChange(index, e)} + value={data.prompt} + name='prompt' + /> + + + {inputFields.length !== 1 && ( + + + + )} + + + {index === inputFields.length - 1 && ( + + + + )} + +
+ ) + })} +
+
+
+ + + + Cancel + + + Save + + +
+ ) : null + + return createPortal(component, portalElement) +} + +ConversationStarterDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func +} + +export default ConversationStarterDialog diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 56365ba8..d931ee26 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -16,6 +16,7 @@ import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' import APICodeDialog from 'views/chatflows/APICodeDialog' import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog' import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog' +import ConversationStarterDialog from 'ui-component/dialog/ConversationStarterDialog' // API import chatflowsApi from 'api/chatflows' @@ -45,6 +46,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl const [apiDialogProps, setAPIDialogProps] = useState({}) const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false) const [analyseDialogProps, setAnalyseDialogProps] = useState({}) + const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false) + const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({}) const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false) const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({}) @@ -56,6 +59,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl if (setting === 'deleteChatflow') { handleDeleteFlow() + } else if (setting === 'conversationStarters') { + setConversationStartersDialogProps({ + title: 'Set Conversation Starters - ' + chatflow.name, + chatflow: chatflow + }) + setConversationStartersDialogOpen(true) } else if (setting === 'analyseChatflow') { setAnalyseDialogProps({ title: 'Analyse Chatflow', @@ -376,6 +385,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl /> setAPIDialogOpen(false)} /> setAnalyseDialogOpen(false)} /> + setConversationStartersDialogOpen(false)} + /> { const [limitMax, setLimitMax] = useState(apiConfig?.rateLimit?.limitMax ?? '') const [limitDuration, setLimitDuration] = useState(apiConfig?.rateLimit?.limitDuration ?? '') const [limitMsg, setLimitMsg] = useState(apiConfig?.rateLimit?.limitMsg ?? '') - const [prompt1, setPrompt1] = useState(apiConfig?.prompt?.prompt1 ?? '') - const [prompt2, setPrompt2] = useState(apiConfig?.prompt?.prompt2 ?? '') - const [prompt3, setPrompt3] = useState(apiConfig?.prompt?.prompt3 ?? '') - const [prompt4, setPrompt4] = useState(apiConfig?.prompt?.prompt4 ?? '') const formatObj = () => { const obj = { @@ -119,7 +111,7 @@ const Configuration = () => { return (
- {fieldLabel && {fieldLabel}} + {fieldLabel} { return ( <> - - } aria-controls='panel1a-content' id='panel1a-header'> - - Rate Limit{' '} - Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' - } - /> - - - - - {/*Rate Limit*/} - {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} - {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} - {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} - - - + {/*Rate Limit*/} + + Rate Limit{' '} + Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' + } + /> + + {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} + {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} + {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} - - } aria-controls='panel2a-content' id='panel2a-header'> - Conversation Starters - - - - {textField(prompt1, 'prompt1', 'Starter Prompts', 'string')} - {textField(prompt2, 'prompt2', '', 'string')} - {textField(prompt3, 'prompt3', '', 'string')} - {textField(prompt4, 'prompt4', '', 'string')} - - - onSave()}> Save Changes diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index d047a3aa..8e9b753b 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -57,6 +57,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const inputRef = useRef(null) const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow) const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) + const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow) + + const [starterPrompts, setStarterPrompts] = useState([]) const onSourceDialogClick = (data, title) => { setSourceDialogProps({ data, title }) @@ -104,14 +107,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }, 100) } - const handlePromptClick = async (prompt) => { + const handlePromptClick = async (prompt, e) => { setUserInput(prompt) - await handleSubmit() + await handleSubmit(e) } // Handle form submission const handleSubmit = async (e) => { - if (e) e.preventDefault() + e.preventDefault() if (userInput.trim() === '') { return @@ -202,10 +205,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { if (getIsChatflowStreamingApi.data) { setIsChatFlowAvailableToStream(getIsChatflowStreamingApi.data?.isStreaming ?? false) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [getIsChatflowStreamingApi.data]) + useEffect(() => { + if (getChatflowConfig.data) { + if (getChatflowConfig.data?.starterPrompt && JSON.parse(getChatflowConfig.data?.starterPrompt)) { + setStarterPrompts(JSON.parse(getChatflowConfig.data?.starterPrompt)) + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getChatflowConfig.data]) + // Auto scroll chat to bottom useEffect(() => { scrollToBottom() @@ -224,6 +235,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { if (open && chatflowid) { getChatmessageApi.request(chatflowid) getIsChatflowStreamingApi.request(chatflowid) + getChatflowConfig.request(chatflowid) scrollToBottom() socket = socketIOClient(baseURL) @@ -377,8 +389,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { {messages && messages.length === 1 && ( )} From a27da2375ba8519287c4a11f8b0a3b522ca420a0 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Thu, 23 Nov 2023 10:30:18 +0530 Subject: [PATCH 003/268] Conversation Starters: - Updates to use the chatbot config for persistence. --- packages/server/src/database/entities/ChatFlow.ts | 3 --- .../1700565042576-AddStarterPromptsToChatFlow.ts | 12 ------------ .../server/src/database/migrations/mysql/index.ts | 4 +--- .../1700565042576-AddStarterPromptsToChatFlow.ts | 11 ----------- .../server/src/database/migrations/postgres/index.ts | 4 +--- .../1700565042576-AddStarterPromptsToChatFlow.ts | 11 ----------- .../server/src/database/migrations/sqlite/index.ts | 4 +--- packages/ui/src/menu-items/settings.js | 2 +- .../ui-component/dialog/ConversationStarterDialog.js | 10 ++++++---- packages/ui/src/views/canvas/CanvasHeader.js | 2 +- packages/ui/src/views/chatmessage/ChatMessage.js | 7 +++++-- 11 files changed, 16 insertions(+), 54 deletions(-) delete mode 100644 packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts delete mode 100644 packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts delete mode 100644 packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts diff --git a/packages/server/src/database/entities/ChatFlow.ts b/packages/server/src/database/entities/ChatFlow.ts index 626f743a..376a100b 100644 --- a/packages/server/src/database/entities/ChatFlow.ts +++ b/packages/server/src/database/entities/ChatFlow.ts @@ -28,9 +28,6 @@ export class ChatFlow implements IChatFlow { @Column({ nullable: true, type: 'text' }) apiConfig?: string - @Column({ nullable: true, type: 'text' }) - starterPrompt?: string - @Column({ nullable: true, type: 'text' }) analytic?: string diff --git a/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts deleted file mode 100644 index 1f7a3c54..00000000 --- a/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddStarterPrompt1700565042576 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - const columnExists = await queryRunner.hasColumn('chat_flow', 'starterPrompt') - if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`starterPrompt\` TEXT;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`starterPrompt\`;`) - } -} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index f38a9cfe..4b7b8a95 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -8,7 +8,6 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' -import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const mysqlMigrations = [ Init1693840429259, @@ -20,6 +19,5 @@ export const mysqlMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658767766, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341, - AddStarterPrompt1700565042576 + AddUsedToolsToChatMessage1699481607341 ] diff --git a/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts deleted file mode 100644 index ac2694b9..00000000 --- a/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddStarterPrompt1700565042576 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "starterPrompt" TEXT;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) - } -} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index e9018265..75562c0b 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -8,7 +8,6 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' -import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const postgresMigrations = [ Init1693891895163, @@ -20,6 +19,5 @@ export const postgresMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658756136, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341, - AddStarterPrompt1700565042576 + AddUsedToolsToChatMessage1699481607341 ] diff --git a/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts deleted file mode 100644 index 3d180797..00000000 --- a/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddStarterPrompt1700565042576 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "starterPrompt" TEXT;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) - } -} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index 414d755e..4a14fc40 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -8,7 +8,6 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' -import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const sqliteMigrations = [ Init1693835579790, @@ -20,6 +19,5 @@ export const sqliteMigrations = [ AddAnalytic1694432361423, AddChatHistory1694657778173, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341, - AddStarterPrompt1700565042576 + AddUsedToolsToChatMessage1699481607341 ] diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 9224b78f..1e0f58dd 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -13,7 +13,7 @@ const settings = { children: [ { id: 'conversationStarters', - title: 'Set Starter Prompts', + title: 'Starter Prompts', type: 'item', url: '', icon: icons.IconPictureInPictureOff diff --git a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js index 1139f21e..979a4526 100644 --- a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js +++ b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js @@ -57,7 +57,9 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { const onSave = async () => { try { const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - starterPrompt: JSON.stringify(inputFields) + chatbotConfig: { + starterPrompts: JSON.stringify(inputFields) + } }) if (saveResp.data) { enqueueSnackbar({ @@ -94,9 +96,9 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { } useEffect(() => { - if (dialogProps.chatflow && dialogProps.chatflow.starterPrompt) { + if (dialogProps.chatflow && dialogProps.chatbotConfig.starterPrompts) { try { - setInputFields(JSON.parse(dialogProps.chatflow.starterPrompt)) + setInputFields(JSON.parse(dialogProps.chatbotConfig.starterPrompts)) } catch (e) { setInputFields([ { @@ -126,7 +128,7 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { aria-describedby='alert-dialog-description' > - Set Conversation Starter Prompts + {dialogProps.title || 'Conversation Starter Prompts'} diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index d931ee26..b08eb6ab 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -61,7 +61,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl handleDeleteFlow() } else if (setting === 'conversationStarters') { setConversationStartersDialogProps({ - title: 'Set Conversation Starters - ' + chatflow.name, + title: 'Starter Prompts - ' + chatflow.name, chatflow: chatflow }) setConversationStartersDialogOpen(true) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 8e9b753b..e006ba49 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -210,8 +210,11 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { useEffect(() => { if (getChatflowConfig.data) { - if (getChatflowConfig.data?.starterPrompt && JSON.parse(getChatflowConfig.data?.starterPrompt)) { - setStarterPrompts(JSON.parse(getChatflowConfig.data?.starterPrompt)) + if ( + getChatflowConfig.data?.chatbotConfig?.starterPrompts && + JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts) + ) { + setStarterPrompts(JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts)) } } // eslint-disable-next-line react-hooks/exhaustive-deps From 6881231ea40c73018199b4450e7566b333e81fb5 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Thu, 23 Nov 2023 22:38:45 +0530 Subject: [PATCH 004/268] Conversation Starter: Setup of prompts from the chatflow dashboard and other refactorings. --- .../src/ui-component/button/FlowListMenu.js | 22 +++++ ...rsationCard.css => StarterPromptsCard.css} | 0 ...versationCard.js => StarterPromptsCard.js} | 8 +- ...arterDialog.js => StarterPromptsDialog.js} | 86 +++++++++++++------ packages/ui/src/views/canvas/CanvasHeader.js | 4 +- .../ui/src/views/chatmessage/ChatMessage.css | 2 +- .../ui/src/views/chatmessage/ChatMessage.js | 24 +++--- 7 files changed, 104 insertions(+), 42 deletions(-) rename packages/ui/src/ui-component/cards/{StarterConversationCard.css => StarterPromptsCard.css} (100%) rename packages/ui/src/ui-component/cards/{StarterConversationCard.js => StarterPromptsCard.js} (75%) rename packages/ui/src/ui-component/dialog/{ConversationStarterDialog.js => StarterPromptsDialog.js} (66%) diff --git a/packages/ui/src/ui-component/button/FlowListMenu.js b/packages/ui/src/ui-component/button/FlowListMenu.js index b242d2cb..3f94c400 100644 --- a/packages/ui/src/ui-component/button/FlowListMenu.js +++ b/packages/ui/src/ui-component/button/FlowListMenu.js @@ -11,6 +11,7 @@ import FileCopyIcon from '@mui/icons-material/FileCopy' import FileDownloadIcon from '@mui/icons-material/Downloading' import FileDeleteIcon from '@mui/icons-material/Delete' import FileCategoryIcon from '@mui/icons-material/Category' +import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt' import Button from '@mui/material/Button' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import { IconX } from '@tabler/icons' @@ -28,6 +29,7 @@ import TagDialog from '../dialog/TagDialog' import { generateExportFlowData } from '../../utils/genericHelper' import useNotifier from '../../utils/useNotifier' +import StarterPromptsDialog from '../dialog/StarterPromptsDialog' const StyledMenu = styled((props) => ( { setAnchorEl(event.currentTarget) @@ -93,6 +97,15 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { setFlowDialogOpen(true) } + const handleFlowStarterPrompts = () => { + setAnchorEl(null) + setConversationStartersDialogProps({ + title: 'Starter Prompts - ' + chatflow.name, + chatflow: chatflow + }) + setConversationStartersDialogOpen(true) + } + const saveFlowRename = async (chatflowName) => { const updateBody = { name: chatflowName, @@ -254,6 +267,10 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { Export + + + Starter Prompts + Update Category @@ -281,6 +298,11 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { onClose={() => setCategoryDialogOpen(false)} onSubmit={saveFlowCategory} /> + setConversationStartersDialogOpen(false)} + />
) } diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.css b/packages/ui/src/ui-component/cards/StarterPromptsCard.css similarity index 100% rename from packages/ui/src/ui-component/cards/StarterConversationCard.css rename to packages/ui/src/ui-component/cards/StarterPromptsCard.css diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.js b/packages/ui/src/ui-component/cards/StarterPromptsCard.js similarity index 75% rename from packages/ui/src/ui-component/cards/StarterConversationCard.js rename to packages/ui/src/ui-component/cards/StarterPromptsCard.js index aa50f266..caf8a219 100644 --- a/packages/ui/src/ui-component/cards/StarterConversationCard.js +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.js @@ -1,9 +1,9 @@ import Box from '@mui/material/Box' import PropTypes from 'prop-types' import { Button } from '@mui/material' -import './StarterConversationCard.css' +import './StarterPromptsCard.css' -const StarterConversationCard = ({ isGrid, starterPrompts, onPromptClick }) => { +const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => { return ( {starterPrompts.map((sp, index) => ( @@ -15,10 +15,10 @@ const StarterConversationCard = ({ isGrid, starterPrompts, onPromptClick }) => { ) } -StarterConversationCard.propTypes = { +StarterPromptsCard.propTypes = { isGrid: PropTypes.bool, starterPrompts: PropTypes.arrayOf(PropTypes.string), onPromptClick: PropTypes.func } -export default StarterConversationCard +export default StarterPromptsCard diff --git a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js similarity index 66% rename from packages/ui/src/ui-component/dialog/ConversationStarterDialog.js rename to packages/ui/src/ui-component/dialog/StarterPromptsDialog.js index 979a4526..7752e32f 100644 --- a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js @@ -5,7 +5,18 @@ import PropTypes from 'prop-types' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' // material-ui -import { Button, IconButton, Dialog, DialogContent, OutlinedInput, DialogTitle, DialogActions, Box, List, Divider } from '@mui/material' +import { + Button, + IconButton, + Dialog, + DialogContent, + OutlinedInput, + DialogTitle, + DialogActions, + Box, + List, + InputAdornment +} from '@mui/material' import { IconX, IconTrash, IconPlus } from '@tabler/icons' // Project import @@ -18,7 +29,7 @@ import useNotifier from 'utils/useNotifier' // API import chatflowsApi from 'api/chatflows' -const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { +const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -56,10 +67,13 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { const onSave = async () => { try { - const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - chatbotConfig: { - starterPrompts: JSON.stringify(inputFields) + let value = { + starterPrompts: { + ...inputFields } + } + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + chatbotConfig: JSON.stringify(value) }) if (saveResp.data) { enqueueSnackbar({ @@ -96,16 +110,30 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { } useEffect(() => { - if (dialogProps.chatflow && dialogProps.chatbotConfig.starterPrompts) { + if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) { try { - setInputFields(JSON.parse(dialogProps.chatbotConfig.starterPrompts)) + let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig) + if (chatbotConfig.starterPrompts) { + let inputFields = [] + Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => { + if (chatbotConfig.starterPrompts[key]) { + inputFields.push(chatbotConfig.starterPrompts[key]) + } + }) + setInputFields(inputFields) + } else { + setInputFields([ + { + prompt: '' + } + ]) + } } catch (e) { setInputFields([ { prompt: '' } ]) - console.error(e) } } @@ -131,28 +159,34 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { {dialogProps.title || 'Conversation Starter Prompts'} - + :not(style)': { m: 1 }, pt: 2 }}> {inputFields.map((data, index) => { return ( -
- +
+ handleChange(index, e)} + size='small' value={data.prompt} name='prompt' + endAdornment={ + + + + + + } /> - - {inputFields.length !== 1 && ( - - - - )} - {index === inputFields.length - 1 && ( @@ -160,6 +194,13 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { )} + {/**/} + {/* {inputFields.length !== 1 && (*/} + {/* */} + {/* */} + {/* */} + {/* )}*/} + {/**/}
) })} @@ -167,10 +208,7 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => {
- - - Cancel - + Save @@ -181,10 +219,10 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { return createPortal(component, portalElement) } -ConversationStarterDialog.propTypes = { +StarterPromptsDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func } -export default ConversationStarterDialog +export default StarterPromptsDialog diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index b08eb6ab..2c3bdfe4 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -16,7 +16,7 @@ import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' import APICodeDialog from 'views/chatflows/APICodeDialog' import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog' import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog' -import ConversationStarterDialog from 'ui-component/dialog/ConversationStarterDialog' +import StarterPromptsDialog from 'ui-component/dialog/StarterPromptsDialog' // API import chatflowsApi from 'api/chatflows' @@ -385,7 +385,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl /> setAPIDialogOpen(false)} /> setAnalyseDialogOpen(false)} /> - setConversationStartersDialogOpen(false)} diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index 2298fee6..2c627988 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -118,7 +118,7 @@ .cloud { width: 400px; - height: calc(100vh - 260px); + height: calc(90vh - 260px); border-radius: 0.5rem; display: flex; justify-content: center; diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 81ba9512..962ecb36 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -33,7 +33,7 @@ import { baseURL, maxScroll } from 'store/constant' import robotPNG from 'assets/images/robot.png' import userPNG from 'assets/images/account.png' import { isValidURL, removeDuplicateURL } from 'utils/genericHelper' -import StarterConversationCard from '../../ui-component/cards/StarterConversationCard' +import StarterPromptsCard from '../../ui-component/cards/StarterPromptsCard' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -238,11 +238,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { useEffect(() => { if (getChatflowConfig.data) { - if ( - getChatflowConfig.data?.chatbotConfig?.starterPrompts && - JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts) - ) { - setStarterPrompts(JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts)) + if (getChatflowConfig.data?.chatbotConfig && JSON.parse(getChatflowConfig.data?.chatbotConfig)) { + let config = JSON.parse(getChatflowConfig.data?.chatbotConfig) + if (config.starterPrompts) { + let inputFields = [] + Object.getOwnPropertyNames(config.starterPrompts).forEach((key) => { + if (config.starterPrompts[key]) { + inputFields.push(config.starterPrompts[key]) + } + }) + setStarterPrompts(inputFields) + } } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -439,11 +445,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
{messages && messages.length === 1 && ( - + )} Date: Fri, 24 Nov 2023 11:20:35 +0530 Subject: [PATCH 005/268] Conversation Starter: Changes to ensure that the chatbotConfig is not overwritten between the starter prompts and share chatbot dialogs --- .../ui/src/ui-component/button/FlowListMenu.js | 6 ++++++ .../ui-component/dialog/StarterPromptsDialog.js | 15 +++++++++++---- packages/ui/src/views/chatflows/ShareChatbot.js | 2 ++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/ui-component/button/FlowListMenu.js b/packages/ui/src/ui-component/button/FlowListMenu.js index 3f94c400..2f5bdd5d 100644 --- a/packages/ui/src/ui-component/button/FlowListMenu.js +++ b/packages/ui/src/ui-component/button/FlowListMenu.js @@ -106,6 +106,11 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { setConversationStartersDialogOpen(true) } + const saveFlowStarterPrompts = async () => { + setConversationStartersDialogOpen(false) + await updateFlowsApi.request() + } + const saveFlowRename = async (chatflowName) => { const updateBody = { name: chatflowName, @@ -301,6 +306,7 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { setConversationStartersDialogOpen(false)} />
diff --git a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js index 7752e32f..286399a6 100644 --- a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js @@ -29,7 +29,7 @@ import useNotifier from 'utils/useNotifier' // API import chatflowsApi from 'api/chatflows' -const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { +const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm = undefined }) => { const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -44,6 +44,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { } ]) + const [chatbotConfig, setChatbotConfig] = useState({}) + const addInputField = () => { setInputFields([ ...inputFields, @@ -72,8 +74,9 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { ...inputFields } } + chatbotConfig.starterPrompts = value.starterPrompts const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - chatbotConfig: JSON.stringify(value) + chatbotConfig: JSON.stringify(chatbotConfig) }) if (saveResp.data) { enqueueSnackbar({ @@ -90,7 +93,9 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { }) dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) } - onCancel() + if (onConfirm) { + onConfirm() + } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ @@ -113,6 +118,7 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) { try { let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig) + setChatbotConfig(chatbotConfig || {}) if (chatbotConfig.starterPrompts) { let inputFields = [] Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => { @@ -222,7 +228,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { StarterPromptsDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, - onCancel: PropTypes.func + onCancel: PropTypes.func, + onConfirm: PropTypes.func } export default StarterPromptsDialog diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index dc6c0621..0bf5fc39 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -135,6 +135,8 @@ const ShareChatbot = ({ isSessionMemory }) => { if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession + if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts + return obj } From 4a3e1784d8f8b4532587bfb5ddce61eb29c35977 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Mon, 27 Nov 2023 06:06:18 +0530 Subject: [PATCH 006/268] LS Prompt Hub: Initial Commit --- .../dialog/PromptLangsmithHubDialog.js | 368 ++++++++++++++++++ .../ui/src/views/canvas/NodeInputHandler.js | 29 ++ 2 files changed, 397 insertions(+) create mode 100644 packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js new file mode 100644 index 00000000..16d5c30f --- /dev/null +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js @@ -0,0 +1,368 @@ +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { + Box, + Button, + Card, + CardContent, + Chip, + Dialog, + DialogContent, + DialogTitle, + Divider, + Grid, + InputLabel, + List, + ListItemButton, + ListItemText, + OutlinedInput, + Select, + Typography +} from '@mui/material' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '../../store/actions' +import { useDispatch } from 'react-redux' +import FormControl from '@mui/material/FormControl' +import Checkbox from '@mui/material/Checkbox' +import MenuItem from '@mui/material/MenuItem' +import axios from 'axios' +import ReactMarkdown from 'react-markdown' +import CredentialInputHandler from '../../views/canvas/CredentialInputHandler' + +const PromptLangsmithHubDialog = ({ promptType, show, onCancel }) => { + const portalElement = document.getElementById('portal') + const dispatch = useDispatch() + + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [show, dispatch]) + + const ITEM_HEIGHT = 48 + const ITEM_PADDING_TOP = 8 + const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250 + } + } + } + + const models = [ + { id: 101, name: 'anthropic:claude-instant-1' }, + { id: 102, name: 'anthropic:claude-instant-1.2' }, + { id: 103, name: 'anthropic:claude-2' }, + { id: 104, name: 'google:palm-2-chat-bison' }, + { id: 105, name: 'google:palm-2-codechat-bison' }, + { id: 106, name: 'google:palm-2-text-bison' }, + { id: 107, name: 'meta:llama-2-13b-chat' }, + { id: 108, name: 'meta:llama-2-70b-chat' }, + { id: 109, name: 'openai:gpt-3.5-turbo' }, + { id: 110, name: 'openai:gpt-4' }, + { id: 111, name: 'openai:text-davinci-003' } + ] + const [modelName, setModelName] = useState([]) + + const usecases = [ + { id: 201, name: 'Agents' }, + { id: 202, name: 'Agent Stimulation' }, + { id: 203, name: 'Autonomous agents' }, + { id: 204, name: 'Classification' }, + { id: 205, name: 'Chatbots' }, + { id: 206, name: 'Code understanding' }, + { id: 207, name: 'Code writing' }, + { id: 208, name: 'Evaluation' }, + { id: 209, name: 'Extraction' }, + { id: 210, name: 'Interacting with APIs' }, + { id: 211, name: 'Multi-modal' }, + { id: 212, name: 'QA over documents' }, + { id: 213, name: 'Self-checking' }, + { id: 214, name: 'SQL' }, + { id: 215, name: 'Summarization' }, + { id: 216, name: 'Tagging' } + ] + const [usecase, setUsecase] = useState([]) + + const languages = [ + { id: 301, name: 'Chinese' }, + { id: 302, name: 'English' }, + { id: 303, name: 'French' }, + { id: 304, name: 'German' }, + { id: 305, name: 'Russian' }, + { id: 306, name: 'Spanish' } + ] + const [language, setLanguage] = useState([]) + const [prompts, setPrompts] = useState([]) + const [selectedPrompt, setSelectedPrompt] = useState({}) + + const [credentialId, setCredentialId] = useState('') + + const handleListItemClick = (event, index) => { + setSelectedPrompt(prompts[index]) + } + + const fetchPrompts = async () => { + let tags = promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' + modelName.forEach((item) => { + tags += `tags=${item.name}&` + }) + usecase.forEach((item) => { + tags += `tags=${item.name}&` + }) + language.forEach((item) => { + tags += `tags=${item.name}&` + }) + const url = `https://web.hub.langchain.com/repos/?${tags}offset=0&limit=20&has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` + axios.get(url).then((response) => { + if (response.data.repos) { + setPrompts(response.data.repos) + // response.data.repos.forEach((item) => { + // console.log(item) + // }) + } + }) + // latestReleaseReq.then() + } + const removeDuplicates = (value) => { + let duplicateRemoved = [] + + value.forEach((item) => { + if (value.filter((o) => o.id === item.id).length === 1) { + duplicateRemoved.push(item) + } + }) + return duplicateRemoved + } + + const handleModelChange = (event) => { + const { + target: { value } + } = event + + setModelName(removeDuplicates(value)) + } + + const handleUsecaseChange = (event) => { + const { + target: { value } + } = event + + setUsecase(removeDuplicates(value)) + } + const handleLanguageChange = (event) => { + const { + target: { value } + } = event + + setLanguage(removeDuplicates(value)) + } + + const component = show ? ( + + + Load Prompts from Langsmith Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'}) + + + + + Langsmith Credential + * + + { + setCredentialId(newValue) + }} + /> + + + + + Model + + + + + + Usecase + + + + + + Language + + + + + + + + + + + + + + + Available Prompts + + + {prompts.map((item, index) => ( + handleListItemClick(event, index)} + > + {item.full_name} + + ))} + + + + + + + + + + + Description + + {selectedPrompt?.description} + + + + + + Tags + + {selectedPrompt?.tags?.map((item) => ( + + ))} + + + + + + Readme + + + {selectedPrompt?.readme} + + + + + + + + + ) : null + + return createPortal(component, portalElement) +} + +PromptLangsmithHubDialog.propTypes = { + promptType: PropTypes.string, + show: PropTypes.bool, + onCancel: PropTypes.func +} + +export default PromptLangsmithHubDialog diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 7eb31bdb..162455df 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -6,6 +6,7 @@ import { useSelector } from 'react-redux' // material-ui import { useTheme, styled } from '@mui/material/styles' import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material' +import IconAutoFixHigh from '@mui/icons-material/AutoFixHigh' import { tooltipClasses } from '@mui/material/Tooltip' import { IconArrowsMaximize, IconEdit, IconAlertTriangle } from '@tabler/icons' @@ -31,6 +32,7 @@ import { getInputVariables } from 'utils/genericHelper' // const import { FLOWISE_CREDENTIAL_ID } from 'store/constant' +import PromptLangsmithHubDialog from '../../ui-component/dialog/PromptLangsmithHubDialog' const EDITABLE_OPTIONS = ['selectedTool', 'selectedAssistant'] @@ -56,6 +58,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false) const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({}) + const [showPromptHubDialog, setShowPromptHubDialog] = useState(false) const onExpandDialogClicked = (value, inputParam) => { const dialogProp = { @@ -69,6 +72,9 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA setShowExpandDialog(true) } + const onShowPromptHubButtonClicked = () => { + setShowPromptHubDialog(true) + } const onFormatPromptValuesClicked = (value, inputParam) => { // Preset values if the field is format prompt values let inputValue = value @@ -209,6 +215,28 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA )} + {(inputParam.name === 'template' || inputParam.name === 'systemMessagePrompt') && ( + <> + + setShowPromptHubDialog(false)} + > + + )}
{inputParam.label} @@ -260,6 +288,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA }} /> )} + {inputParam.type === 'file' && ( Date: Mon, 27 Nov 2023 22:42:04 +0530 Subject: [PATCH 007/268] LS Prompt Hub: Moving calls to server side and adding functionality to show the detailed prompt --- packages/server/src/index.ts | 40 +++++++ packages/server/src/utils/hub.ts | 27 +++++ packages/ui/src/api/prompt.js | 9 ++ .../dialog/PromptLangsmithHubDialog.js | 111 +++++++++++------- 4 files changed, 144 insertions(+), 43 deletions(-) create mode 100644 packages/server/src/utils/hub.ts create mode 100644 packages/ui/src/api/prompt.js diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 91de4f4c..92b32b59 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -64,6 +64,9 @@ import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' +import axios from 'axios' +import { Client } from 'langchainhub' +import { parsePrompt } from './utils/hub' export class App { app: express.Application @@ -1093,6 +1096,43 @@ export class App { await this.buildChatflow(req, res, undefined, true, true) }) + // ---------------------------------------- + // Prompt from Hub + // ---------------------------------------- + this.app.post('/api/v1/load-prompt', async (req: Request, res: Response) => { + try { + const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ + id: req.body.credential + }) + + if (!credential) return res.status(404).json({ error: `Credential ${req.body.credential} not found` }) + + // Decrypt credentialData + const decryptedCredentialData = await decryptCredentialData(credential.encryptedData, credential.credentialName, undefined) + let hub = new Client({ apiKey: decryptedCredentialData.langsmithApiKey, apiUrl: decryptedCredentialData.langsmithEndpoint }) + const prompt = await hub.pull(req.body.promptName) + const templates = parsePrompt(prompt) + + return res.json({ status: 'OK', prompt: req.body.promptName, templates: templates }) + } catch (e: any) { + return res.json({ status: 'ERROR', prompt: req.body.promptName, error: e?.message }) + } + }) + + this.app.post('/api/v1/prompts-list', async (req: Request, res: Response) => { + try { + const tags = req.body.tags ? `tags=${req.body.tags}` : '' + const url = `https://web.hub.langchain.com/repos/?${tags}offset=0&limit=20&has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` + axios.get(url).then((response) => { + if (response.data.repos) { + return res.json({ status: 'OK', repos: response.data.repos }) + } + }) + } catch (e: any) { + return res.json({ status: 'ERROR', repos: [] }) + } + }) + // ---------------------------------------- // Prediction // ---------------------------------------- diff --git a/packages/server/src/utils/hub.ts b/packages/server/src/utils/hub.ts new file mode 100644 index 00000000..79e7136f --- /dev/null +++ b/packages/server/src/utils/hub.ts @@ -0,0 +1,27 @@ +export function parsePrompt(prompt: string): any[] { + const promptObj = JSON.parse(prompt) + let response = [] + if (promptObj.kwargs.messages) { + promptObj.kwargs.messages.forEach((message: any) => { + let messageType = message.id.includes('SystemMessagePromptTemplate') + ? 'systemMessagePrompt' + : message.id.includes('HumanMessagePromptTemplate') + ? 'humanMessagePrompt' + : message.id.includes('AIMessagePromptTemplate') + ? 'aiMessagePrompt' + : 'template' + let template = message.kwargs.prompt.kwargs.template + response.push({ + type: messageType, + template: template + }) + }) + } else if (promptObj.kwargs.template) { + let template = promptObj.kwargs.template + response.push({ + type: 'template', + template: template + }) + } + return response +} diff --git a/packages/ui/src/api/prompt.js b/packages/ui/src/api/prompt.js new file mode 100644 index 00000000..42b1bdbc --- /dev/null +++ b/packages/ui/src/api/prompt.js @@ -0,0 +1,9 @@ +import client from './client' + +const getAvailablePrompts = (body) => client.post(`/prompts-list`, body) +const getPrompt = (body) => client.post(`/load-prompt`, body) + +export default { + getAvailablePrompts, + getPrompt +} diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js index 16d5c30f..4db61633 100644 --- a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js @@ -25,9 +25,9 @@ import { useDispatch } from 'react-redux' import FormControl from '@mui/material/FormControl' import Checkbox from '@mui/material/Checkbox' import MenuItem from '@mui/material/MenuItem' -import axios from 'axios' import ReactMarkdown from 'react-markdown' import CredentialInputHandler from '../../views/canvas/CredentialInputHandler' +import promptApi from '../../api/prompt' const PromptLangsmithHubDialog = ({ promptType, show, onCancel }) => { const portalElement = document.getElementById('portal') @@ -96,13 +96,24 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel }) => { { id: 306, name: 'Spanish' } ] const [language, setLanguage] = useState([]) - const [prompts, setPrompts] = useState([]) + const [availablePrompNameList, setAvailablePrompNameList] = useState([]) const [selectedPrompt, setSelectedPrompt] = useState({}) const [credentialId, setCredentialId] = useState('') - const handleListItemClick = (event, index) => { - setSelectedPrompt(prompts[index]) + const handleListItemClick = async (event, index) => { + const prompt = availablePrompNameList[index] + + if (!prompt.detailed) { + const createResp = await promptApi.getPrompt({ + credential: credentialId, + promptName: selectedPrompt.full_name + }) + if (createResp.data) { + prompt.detailed = createResp.data.templates + } + } + setSelectedPrompt(prompt) } const fetchPrompts = async () => { @@ -116,17 +127,15 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel }) => { language.forEach((item) => { tags += `tags=${item.name}&` }) - const url = `https://web.hub.langchain.com/repos/?${tags}offset=0&limit=20&has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` - axios.get(url).then((response) => { - if (response.data.repos) { - setPrompts(response.data.repos) - // response.data.repos.forEach((item) => { - // console.log(item) - // }) - } + const createResp = await promptApi.getAvailablePrompts({ + credential: credentialId, + tags: tags }) - // latestReleaseReq.then() + if (createResp.data) { + setAvailablePrompNameList(createResp.data.repos) + } } + const removeDuplicates = (value) => { let duplicateRemoved = [] @@ -173,26 +182,29 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel }) => { Load Prompts from Langsmith Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'}) - - + + Langsmith Credential * - { - setCredentialId(newValue) - }} - /> + + { + setCredentialId(newValue) + }} + /> + { Model { } - renderValue={(selected) => selected.map((x) => x.name).join(', ')} - MenuProps={MenuProps} - > - {models.map((variant) => ( - - item.id === variant.id) >= 0} /> - - - ))} - - - - - Usecase - - - - - - Language - - - - - - - - - - - - - - - Available Prompts - - - {availablePrompNameList.map((item, index) => ( - handleListItemClick(event, index)} - > - {item.full_name} - - ))} - - - + {credentialId && ( + + + + Model + + + + + + Usecase + + + + + + Language + + + + + + + + )} + {availablePrompNameList && availablePrompNameList.length == 0 && ( + + + promptEmptySVG - - - - - - handleAccordionChange('panel1')}> - } - id='panel1d-header' - > - Description - - - - {selectedPrompt?.description} - - - - handleAccordionChange('panel2')}> - } - id='panel2d-header' - > - Prompt - - - - {selectedPrompt?.detailed?.map((item) => ( - <> - - {item.typeDisplay.toUpperCase()} +
No Available Prompts
+ + )} + {availablePrompNameList && availablePrompNameList.length > 0 && ( + + + + + + + + + Available Prompts + + + {availablePrompNameList.map((item, index) => ( + handleListItemClick(index)} + > +
+ + {item.full_name} + +
+ {item.tags.map((tag, index) => ( + + ))} +
+
+
+ ))} +
+
+
+
+
+ + + + + + } + id='panel2d-header' + > + Prompt + + + + {selectedPrompt?.detailed?.map((item) => ( + <> + + {item.typeDisplay.toUpperCase()} + + +

+ {item.template} +

+
+ + ))}
- -

+ + + } + id='panel1d-header' + > + Description + + + + {selectedPrompt?.description} + + + + + } + aria-controls='panel3d-content' + id='panel3d-header' + > + Readme + + +

+ + ) : ( + + {children} + + ) + } }} > - {item.template} -

- - - ))} - - - - handleAccordionChange('panel3')}> - } - aria-controls='panel3d-content' - id='panel3d-header' - > - Readme - - - - {selectedPrompt?.readme} - - - - - + {selectedPrompt?.readme} +
+
+
+
+
+
+
+
+
-
-
+ + )}
- - - onSubmit(selectedPrompt.detailed)} variant='contained'> - Submit - - + {availablePrompNameList && availablePrompNameList.length > 0 && ( + + + onSubmit(selectedPrompt.detailed)} + variant='contained' + > + Load + + + )} ) : null diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 24ba3c92..7920af6a 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -231,7 +231,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA flexDirection: 'row', width: '100%' }} - sx={{ borderRadius: 25, width: '100%', mb: 2, mt: 2 }} + sx={{ borderRadius: 25, width: '100%', mb: 2, mt: 0 }} variant='outlined' onClick={() => onShowPromptHubButtonClicked()} endIcon={} From bfeebcd229477b95bf67b404a234e1d9416704fa Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 5 Dec 2023 19:02:33 +0000 Subject: [PATCH 021/268] update Zep version --- .../nodes/memory/ZepMemory/ZepMemory.ts | 104 ++++++++---------- packages/components/package.json | 2 +- .../chatflows/Long Term Memory.json | 20 +--- 3 files changed, 51 insertions(+), 75 deletions(-) diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index ced871a1..e72a6704 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,9 +1,8 @@ -import { SystemMessage } from 'langchain/schema' +import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' +import { getBufferString, InputValues, MemoryVariables, OutputValues } from 'langchain/memory' import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' -import { getBufferString } from 'langchain/memory' class ZepMemory_Memory implements INode { label: string @@ -20,7 +19,7 @@ class ZepMemory_Memory implements INode { constructor() { this.label = 'Zep Memory' this.name = 'ZepMemory' - this.version = 1.0 + this.version = 2.0 this.type = 'ZepMemory' this.icon = 'zep.png' this.category = 'Memory' @@ -41,17 +40,12 @@ class ZepMemory_Memory implements INode { type: 'string', default: 'http://127.0.0.1:8000' }, - { - label: 'Auto Summary', - name: 'autoSummary', - type: 'boolean', - default: true - }, { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -60,15 +54,10 @@ class ZepMemory_Memory implements INode { label: 'Size', name: 'k', type: 'number', - default: '10', - description: 'Window of size k to surface the last k back-and-forth to use as memory.' - }, - { - label: 'Auto Summary Template', - name: 'autoSummaryTemplate', - type: 'string', - default: 'This is the summary of the following conversation:\n{summary}', - additionalParams: true + placeholder: '10', + description: 'Window of size k to surface the last k back-and-forth to use as memory.', + additionalParams: true, + optional: true }, { label: 'AI Prefix', @@ -109,36 +98,7 @@ class ZepMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string - const autoSummary = nodeData.inputs?.autoSummary as boolean - - const k = nodeData.inputs?.k as string - - let zep = await initalizeZep(nodeData, options) - - // hack to support summary - let tmpFunc = zep.loadMemoryVariables - zep.loadMemoryVariables = async (values) => { - let data = await tmpFunc.bind(zep, values)() - if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) { - const zepClient = await zep.zepClientPromise - const memory = await zepClient.memory.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) - if (memory?.summary) { - let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) - // eslint-disable-next-line no-console - console.log('[ZepMemory] auto summary:', summary) - data[zep.memoryKey].unshift(new SystemMessage(summary)) - } - } - // for langchain zep memory compatibility, or we will get "Missing value for input variable chat_history" - if (data instanceof Array) { - data = { - [zep.memoryKey]: data - } - } - return data - } - return zep + return await initalizeZep(nodeData, options) } //@ts-ignore @@ -169,40 +129,72 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis const humanPrefix = nodeData.inputs?.humanPrefix as string const memoryKey = nodeData.inputs?.memoryKey as string const inputKey = nodeData.inputs?.inputKey as string - const sessionId = nodeData.inputs?.sessionId as string + const k = nodeData.inputs?.k as string const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false - if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + let sessionId = '' + + if (!nodeData.inputs?.sessionId && chatId) { + isSessionIdUsingChatMessageId = true + sessionId = chatId + } else { + sessionId = nodeData.inputs?.sessionId + } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) - const obj: ZepMemoryInput & Partial = { + const obj: ZepMemoryInput & ZepMemoryExtendedInput = { baseURL, sessionId: sessionId ? sessionId : chatId, aiPrefix, humanPrefix, returnMessages: true, memoryKey, - inputKey + inputKey, + isSessionIdUsingChatMessageId, + k: k ? parseInt(k, 10) : undefined } if (apiKey) obj.apiKey = apiKey - if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true return new ZepMemoryExtended(obj) } interface ZepMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean + k?: number } class ZepMemoryExtended extends ZepMemory { isSessionIdUsingChatMessageId? = false + lastN?: number - constructor(fields: ZepMemoryInput & Partial) { + constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) { super(fields) this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + this.lastN = fields.k + } + + async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise { + if (overrideSessionId) { + super.sessionId = overrideSessionId + } + return super.loadMemoryVariables({ ...values, lastN: this.lastN }) + } + + async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise { + if (overrideSessionId) { + super.sessionId = overrideSessionId + } + return super.saveContext(inputValues, outputValues) + } + + async clear(overrideSessionId = ''): Promise { + if (overrideSessionId) { + super.sessionId = overrideSessionId + } + return super.clear() } } diff --git a/packages/components/package.json b/packages/components/package.json index bea9a7a0..dd87754d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -21,7 +21,7 @@ "@aws-sdk/client-s3": "^3.427.0", "@dqbd/tiktoken": "^1.0.7", "@elastic/elasticsearch": "^8.9.0", - "@getzep/zep-js": "^0.6.3", + "@getzep/zep-js": "^0.9.0", "@gomomento/sdk": "^1.51.1", "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", diff --git a/packages/server/marketplaces/chatflows/Long Term Memory.json b/packages/server/marketplaces/chatflows/Long Term Memory.json index c508b480..c39f746a 100644 --- a/packages/server/marketplaces/chatflows/Long Term Memory.json +++ b/packages/server/marketplaces/chatflows/Long Term Memory.json @@ -205,7 +205,7 @@ "data": { "id": "ZepMemory_0", "label": "Zep Memory", - "version": 1, + "version": 2, "name": "ZepMemory", "type": "ZepMemory", "baseClasses": ["ZepMemory", "BaseChatMemory", "BaseMemory"], @@ -228,13 +228,6 @@ "default": "http://127.0.0.1:8000", "id": "ZepMemory_0-input-baseURL-string" }, - { - "label": "Auto Summary", - "name": "autoSummary", - "type": "boolean", - "default": true, - "id": "ZepMemory_0-input-autoSummary-boolean" - }, { "label": "Session Id", "name": "sessionId", @@ -251,17 +244,10 @@ "type": "number", "default": "10", "step": 1, + "additionalParams": true, "description": "Window of size k to surface the last k back-and-forths to use as memory.", "id": "ZepMemory_0-input-k-number" }, - { - "label": "Auto Summary Template", - "name": "autoSummaryTemplate", - "type": "string", - "default": "This is the summary of the following conversation:\n{summary}", - "additionalParams": true, - "id": "ZepMemory_0-input-autoSummaryTemplate-string" - }, { "label": "AI Prefix", "name": "aiPrefix", @@ -306,10 +292,8 @@ "inputAnchors": [], "inputs": { "baseURL": "http://127.0.0.1:8000", - "autoSummary": true, "sessionId": "", "k": "10", - "autoSummaryTemplate": "This is the summary of the following conversation:\n{summary}", "aiPrefix": "ai", "humanPrefix": "human", "memoryKey": "chat_history", From 59308665c2b8c83b2b77ecd2a0753a86847ea037 Mon Sep 17 00:00:00 2001 From: takuabonn Date: Wed, 6 Dec 2023 07:32:45 +0900 Subject: [PATCH 022/268] remove_props --- packages/ui/src/views/chatflows/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/views/chatflows/index.js b/packages/ui/src/views/chatflows/index.js index 0ace4033..c87ad306 100644 --- a/packages/ui/src/views/chatflows/index.js +++ b/packages/ui/src/views/chatflows/index.js @@ -161,7 +161,6 @@ const Chatflows = () => { variant='contained' value='card' title='Card View' - selectedcolor='#00abc0' > From d397adb47a1c363e44b1ddcd3b2965ad8466cf09 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 6 Dec 2023 00:30:51 +0000 Subject: [PATCH 023/268] avoid button showing up for other systemprompt like conversation chain --- .../ui/src/views/canvas/NodeInputHandler.js | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 7920af6a..fc2e7ac8 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -223,29 +223,30 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA )} - {(inputParam.name === 'template' || inputParam.name === 'systemMessagePrompt') && ( - <> - - setShowPromptHubDialog(false)} - onSubmit={onShowPromptHubButtonSubmit} - > - - )} + {(data.name === 'promptTemplate' || data.name === 'chatPromptTemplate') && + (inputParam.name === 'template' || inputParam.name === 'systemMessagePrompt') && ( + <> + + setShowPromptHubDialog(false)} + onSubmit={onShowPromptHubButtonSubmit} + > + + )}
{inputParam.label} From 2da6f598340593556bc40860babbf474a52fae62 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 6 Dec 2023 01:51:14 +0000 Subject: [PATCH 024/268] fix ttl parseInt error --- packages/components/nodes/cache/RedisCache/RedisCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts index 3b68cf12..8128b6e3 100644 --- a/packages/components/nodes/cache/RedisCache/RedisCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -89,7 +89,7 @@ class RedisCache implements INode { redisClient.update = async (prompt: string, llmKey: string, value: Generation[]) => { for (let i = 0; i < value.length; i += 1) { const key = getCacheKey(prompt, llmKey, String(i)) - if (ttl !== undefined) { + if (ttl) { await client.set(key, JSON.stringify(serializeGeneration(value[i])), 'EX', parseInt(ttl, 10)) } else { await client.set(key, JSON.stringify(serializeGeneration(value[i]))) From 8122377bbb794564d89fa79096fcff6a209a2f9e Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Wed, 6 Dec 2023 12:53:43 +0530 Subject: [PATCH 025/268] LS Prompt Hub: Minor fixes --- packages/server/src/index.ts | 2 +- .../ui/src/ui-component/dialog/PromptLangsmithHubDialog.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 42fb326d..9df016d0 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1077,7 +1077,7 @@ export class App { headers['x-api-key'] = decryptedCredentialData.langsmithApiKey const tags = req.body.tags ? `tags=${req.body.tags}` : '' - const url = `https://web.hub.langchain.com/repos/?${tags}offset=0&limit=20&has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` + const url = `https://web.hub.langchain.com/repos/?${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` axios.get(url, headers).then((response) => { if (response.data.repos) { return res.json({ status: 'OK', repos: response.data.repos }) diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js index e1cfaaa9..e6f06e20 100644 --- a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js @@ -181,7 +181,6 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { } } setSelectedPrompt(prompt) - await new Promise((resolve) => setTimeout(resolve, 500)) } const fetchPrompts = async () => { @@ -201,7 +200,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { }) if (createResp.data) { setAvailablePrompNameList(createResp.data.repos) - if (createResp.data.repos?.length) handleListItemClick(0, createResp.data.repos) + if (createResp.data.repos?.length) await handleListItemClick(0, createResp.data.repos) } } From cc1a3101e26d22642ac4d74d63528f474f0bd43f Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Wed, 6 Dec 2023 15:01:30 +0530 Subject: [PATCH 026/268] S3 File Loader: Region missing fix --- packages/components/nodes/documentloaders/S3File/S3File.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/S3File/S3File.ts b/packages/components/nodes/documentloaders/S3File/S3File.ts index 07295aba..58ffd8af 100644 --- a/packages/components/nodes/documentloaders/S3File/S3File.ts +++ b/packages/components/nodes/documentloaders/S3File/S3File.ts @@ -162,8 +162,11 @@ class S3_DocumentLoaders implements INode { accessKeyId?: string secretAccessKey?: string } = { - accessKeyId, - secretAccessKey + region, + credentials: { + accessKeyId, + secretAccessKey + } } loader.load = async () => { From 275540d1830575c56679666e759ba35f74699f91 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 6 Dec 2023 17:39:18 +0000 Subject: [PATCH 027/268] add default limit to 100 --- packages/server/src/index.ts | 3 ++- packages/ui/src/views/canvas/NodeInputHandler.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 9df016d0..3d8208f9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1077,7 +1077,8 @@ export class App { headers['x-api-key'] = decryptedCredentialData.langsmithApiKey const tags = req.body.tags ? `tags=${req.body.tags}` : '' - const url = `https://web.hub.langchain.com/repos/?${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` + // Default to 100, TODO: add pagination and use offset & limit + const url = `https://web.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` axios.get(url, headers).then((response) => { if (response.data.repos) { return res.json({ status: 'OK', repos: response.data.repos }) diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index fc2e7ac8..103af6b4 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -232,6 +232,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA flexDirection: 'row', width: '100%' }} + disabled={disabled} sx={{ borderRadius: 25, width: '100%', mb: 2, mt: 0 }} variant='outlined' onClick={() => onShowPromptHubButtonClicked()} From e67c43157a53cc208776431c1fad829f5170d9fd Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Thu, 7 Dec 2023 16:06:32 +0530 Subject: [PATCH 028/268] XSS: Simplified by adding XSS middleware --- packages/server/package.json | 2 +- packages/server/src/index.ts | 421 ++++++++++--------------------- packages/server/src/utils/XSS.ts | 11 + 3 files changed, 142 insertions(+), 292 deletions(-) create mode 100644 packages/server/src/utils/XSS.ts diff --git a/packages/server/package.json b/packages/server/package.json index 38c20389..97a95d43 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -54,7 +54,6 @@ "express": "^4.17.3", "express-basic-auth": "^1.2.1", "express-rate-limit": "^6.9.0", - "express-validator": "^7.0.1", "flowise-components": "*", "flowise-ui": "*", "moment-timezone": "^0.5.34", @@ -64,6 +63,7 @@ "reflect-metadata": "^0.1.13", "socket.io": "^4.6.1", "sqlite3": "^5.1.6", + "strip-js": "^1.2.0", "typeorm": "^0.3.6", "uuid": "^9.0.1", "winston": "^3.9.0" diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 61f34e92..d40b42bf 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -58,7 +58,7 @@ import { CachePool } from './CachePool' import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' -import { body, param, query, validationResult } from 'express-validator' +import { sanitizeMiddleware } from './utils/XSS' export class App { app: express.Application @@ -122,6 +122,9 @@ export class App { // Add the expressRequestLogger middleware to log all requests this.app.use(expressRequestLogger) + // Add the sanitizeMiddleware to guard against XSS + this.app.use(sanitizeMiddleware) + if (process.env.FLOWISE_USERNAME && process.env.FLOWISE_PASSWORD) { const username = process.env.FLOWISE_USERNAME const password = process.env.FLOWISE_PASSWORD @@ -184,27 +187,17 @@ export class App { }) // Get specific component node via name - this.app.get('/api/v1/nodes/:name', param('name').notEmpty().escape(), (req: Request, res: Response) => { - const name = req.params.name - const result = validationResult(req) - if (!result.isEmpty()) { - throw new Error(`Node ${name} not found`) - } - if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, name)) { + this.app.get('/api/v1/nodes/:name', (req: Request, res: Response) => { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { return res.json(this.nodesPool.componentNodes[req.params.name]) } else { - throw new Error(`Node ${name} not found`) + throw new Error(`Node ${req.params.name} not found`) } }) // Get component credential via name - this.app.get('/api/v1/components-credentials/:name', param('name').notEmpty().escape(), (req: Request, res: Response) => { - const name = req.params.name - const result = validationResult(req) - if (!result.isEmpty()) { - throw new Error(`Credential ${name} not found`) - } - if (!req.params.name.includes('&')) { + this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => { + if (!req.params.name.includes('&')) { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { return res.json(this.nodesPool.componentCredentials[req.params.name]) } else { @@ -212,7 +205,7 @@ export class App { } } else { const returnResponse = [] - for (const name of req.params.name.split('&')) { + for (const name of req.params.name.split('&')) { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) { returnResponse.push(this.nodesPool.componentCredentials[name]) } else { @@ -224,14 +217,9 @@ export class App { }) // Returns specific component node icon via name - this.app.get('/api/v1/node-icon/:name', param('name').notEmpty().escape(), (req: Request, res: Response) => { - const name = req.params.name - const result = validationResult(req) - if (!result.isEmpty()) { - throw new Error(`Node ${name} not found`) - } - if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, name)) { - const nodeInstance = this.nodesPool.componentNodes[name] + this.app.get('/api/v1/node-icon/:name', (req: Request, res: Response) => { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { + const nodeInstance = this.nodesPool.componentNodes[req.params.name] if (nodeInstance.icon === undefined) { throw new Error(`Node ${req.params.name} icon not found`) } @@ -240,48 +228,38 @@ export class App { const filepath = nodeInstance.icon res.sendFile(filepath) } else { - throw new Error(`Node ${name} icon is missing icon`) + throw new Error(`Node ${req.params.name} icon is missing icon`) } } else { - throw new Error(`Node ${name} not found`) + throw new Error(`Node ${req.params.name} not found`) } }) // Returns specific component credential icon via name - this.app.get('/api/v1/components-credentials-icon/:name', param('name').notEmpty().escape(), (req: Request, res: Response) => { - const name = req.params.name - const result = validationResult(req) - if (!result.isEmpty()) { - throw new Error(`Credential ${name} not found`) - } - if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) { - const credInstance = this.nodesPool.componentCredentials[name] + this.app.get('/api/v1/components-credentials-icon/:name', (req: Request, res: Response) => { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { + const credInstance = this.nodesPool.componentCredentials[req.params.name] if (credInstance.icon === undefined) { - throw new Error(`Credential ${name} icon not found`) + throw new Error(`Credential ${req.params.name} icon not found`) } if (credInstance.icon.endsWith('.svg') || credInstance.icon.endsWith('.png') || credInstance.icon.endsWith('.jpg')) { const filepath = credInstance.icon res.sendFile(filepath) } else { - throw new Error(`Credential ${name} is missing icon`) + throw new Error(`Credential ${req.params.name} icon is missing icon`) } } else { - throw new Error(`Credential ${name} not found`) + throw new Error(`Credential ${req.params.name} not found`) } }) // load async options - this.app.post('/api/v1/node-load-method/:name', param('name').notEmpty().escape(), async (req: Request, res: Response) => { - const name = req.params.name - const result = validationResult(req) - if (!result.isEmpty()) { - throw new Error(`Node ${name} not found`) - } + this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => { const nodeData: INodeData = req.body - if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, name)) { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { try { - const nodeInstance = this.nodesPool.componentNodes[name] + const nodeInstance = this.nodesPool.componentNodes[req.params.name] const methodName = nodeData.loadMethod || '' const returnOptions: INodeOptionsValue[] = await nodeInstance.loadMethods![methodName]!.call(nodeInstance, nodeData, { @@ -294,7 +272,7 @@ export class App { return res.json([]) } } else { - res.status(404).send(`Node ${name} not found`) + res.status(404).send(`Node ${req.params.name} not found`) return } }) @@ -310,11 +288,7 @@ export class App { }) // Get specific chatflow via api key - this.app.get('/api/v1/chatflows/apikey/:apiKey', param('apiKey').notEmpty().escape(), async (req: Request, res: Response) => { - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(401).send('Unauthorized') - } + this.app.get('/api/v1/chatflows/apikey/:apiKey', async (req: Request, res: Response) => { try { const apiKey = await getApiKey(req.params.apiKey) if (!apiKey) return res.status(401).send('Unauthorized') @@ -326,19 +300,14 @@ export class App { .orderBy('cf.name', 'ASC') .getMany() if (chatflows.length >= 1) return res.status(200).send(chatflows) - return res.status(404).send('APIKey not found') + return res.status(404).send('Chatflow not found') } catch (err: any) { return res.status(500).send(err?.message) } }) // Get specific chatflow via id - this.app.get('/api/v1/chatflows/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const chatflowId = req.params.id - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatflow ${chatflowId} not found`) - } + this.app.get('/api/v1/chatflows/:id', async (req: Request, res: Response) => { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ id: req.params.id }) @@ -347,12 +316,7 @@ export class App { }) // Get specific chatflow via id (PUBLIC endpoint, used when sharing chatbot link) - this.app.get('/api/v1/public-chatflows/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const chatflowId = req.params.id - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatflow ${chatflowId} not found`) - } + this.app.get('/api/v1/public-chatflows/:id', async (req: Request, res: Response) => { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ id: req.params.id }) @@ -374,69 +338,48 @@ export class App { }) // Update chatflow - this.app.put( - '/api/v1/chatflows/:id', - body('chatflow.id').notEmpty(), - param('id').notEmpty().escape(), - async (req: Request, res: Response) => { - const chatflowId = req.params.id - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Chatflow ${chatflowId} not found`) - } - const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: chatflowId - }) + this.app.put('/api/v1/chatflows/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) - if (!chatflow) { - res.status(404).send(`Chatflow ${chatflowId} not found`) - return - } - - const body = req.body - const updateChatFlow = new ChatFlow() - Object.assign(updateChatFlow, body) - - updateChatFlow.id = chatflow.id - createRateLimiter(updateChatFlow) - - this.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow) - const result = await this.AppDataSource.getRepository(ChatFlow).save(chatflow) - - // chatFlowPool is initialized only when a flow is opened - // if the user attempts to rename/update category without opening any flow, chatFlowPool will be undefined - if (this.chatflowPool) { - // Update chatflowpool inSync to false, to build Langchain again because data has been changed - this.chatflowPool.updateInSync(chatflow.id, false) - } - - return res.json(result) + if (!chatflow) { + res.status(404).send(`Chatflow ${req.params.id} not found`) + return } - ) + + const body = req.body + const updateChatFlow = new ChatFlow() + Object.assign(updateChatFlow, body) + + updateChatFlow.id = chatflow.id + createRateLimiter(updateChatFlow) + + this.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow) + const result = await this.AppDataSource.getRepository(ChatFlow).save(chatflow) + + // chatFlowPool is initialized only when a flow is opened + // if the user attempts to rename/update category without opening any flow, chatFlowPool will be undefined + if (this.chatflowPool) { + // Update chatflowpool inSync to false, to build Langchain again because data has been changed + this.chatflowPool.updateInSync(chatflow.id, false) + } + + return res.json(result) + }) // Delete chatflow via id - this.app.delete('/api/v1/chatflows/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const chatflowId = req.params.id - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatflow ${chatflowId} not found`) - } + this.app.delete('/api/v1/chatflows/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getRepository(ChatFlow).delete({ id: req.params.id }) return res.json(results) }) // Check if chatflow valid for streaming - this.app.get('/api/v1/chatflows-streaming/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const chatflowId = req.params.id - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatflow ${chatflowId} not found`) - } - + this.app.get('/api/v1/chatflows-streaming/:id', async (req: Request, res: Response) => { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: chatflowId + id: req.params.id }) - if (!chatflow) return res.status(404).send(`Chatflow ${chatflowId} not found`) + if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`) /*** Get Ending Node with Directed Graph ***/ const flowData = chatflow.flowData @@ -466,84 +409,58 @@ export class App { // ---------------------------------------- // Get all chatmessages from chatflowid - this.app.get( - '/api/v1/chatmessage/:id', - query('chatId').notEmpty().escape(), - query('sortOrder').notEmpty().escape(), - query('memoryType').notEmpty().escape(), - query('sessionId').notEmpty().escape(), - query('startDate').notEmpty().escape(), - query('endDate').notEmpty().escape(), - query('chatTypeFilter').notEmpty().escape(), - async (req: Request, res: Response) => { - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatmessage not found`) - } - const sortOrder = req.query?.order as string | undefined - const chatId = req.query?.chatId as string | undefined - const memoryType = req.query?.memoryType as string | undefined - const sessionId = req.query?.sessionId as string | undefined - const startDate = req.query?.startDate as string | undefined - const endDate = req.query?.endDate as string | undefined - let chatTypeFilter = req.query?.chatType as chatType | undefined + this.app.get('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { + const sortOrder = req.query?.order as string | undefined + const chatId = req.query?.chatId as string | undefined + const memoryType = req.query?.memoryType as string | undefined + const sessionId = req.query?.sessionId as string | undefined + const startDate = req.query?.startDate as string | undefined + const endDate = req.query?.endDate as string | undefined + let chatTypeFilter = req.query?.chatType as chatType | undefined - if (chatTypeFilter) { - try { - const chatTypeFilterArray = JSON.parse(chatTypeFilter) - if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) { - chatTypeFilter = undefined - } else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) { - chatTypeFilter = chatType.EXTERNAL - } else if (chatTypeFilterArray.includes(chatType.INTERNAL)) { - chatTypeFilter = chatType.INTERNAL - } - } catch (e) { - return res.status(500).send(e) + if (chatTypeFilter) { + try { + const chatTypeFilterArray = JSON.parse(chatTypeFilter) + if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) { + chatTypeFilter = undefined + } else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) { + chatTypeFilter = chatType.EXTERNAL + } else if (chatTypeFilterArray.includes(chatType.INTERNAL)) { + chatTypeFilter = chatType.INTERNAL } + } catch (e) { + return res.status(500).send(e) } - - const chatmessages = await this.getChatMessage( - req.params.id, - chatTypeFilter, - sortOrder, - chatId, - memoryType, - sessionId, - startDate, - endDate - ) - return res.json(chatmessages) } - ) + + const chatmessages = await this.getChatMessage( + req.params.id, + chatTypeFilter, + sortOrder, + chatId, + memoryType, + sessionId, + startDate, + endDate + ) + return res.json(chatmessages) + }) // Get internal chatmessages from chatflowid - this.app.get('/api/v1/internal-chatmessage/:id', param('chatId').notEmpty().escape(), async (req: Request, res: Response) => { - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatmessage not found`) - } + this.app.get('/api/v1/internal-chatmessage/:id', async (req: Request, res: Response) => { const chatmessages = await this.getChatMessage(req.params.id, chatType.INTERNAL) return res.json(chatmessages) }) // Add chatmessages for chatflowid - this.app.post('/api/v1/chatmessage/:id', param('chatId').notEmpty().escape(), async (req: Request, res: Response) => { - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatmessage not found`) - } + this.app.post('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { const body = req.body const results = await this.addChatMessage(body) return res.json(results) }) // Delete all chatmessages from chatId - this.app.delete('/api/v1/chatmessage/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Chatmessage not found`) - } + this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { const chatflowid = req.params.id const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ id: chatflowid @@ -627,11 +544,7 @@ export class App { }) // Get specific credential - this.app.get('/api/v1/credentials/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const result = validationResult(req) - if (!result.isEmpty()) { - return res.status(404).send(`Credential ${req.params.id} not found`) - } + this.app.get('/api/v1/credentials/:id', async (req: Request, res: Response) => { const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ id: req.params.id }) @@ -652,11 +565,7 @@ export class App { }) // Update credential - this.app.put('/api/v1/credentials/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Credential ${req.params.id} not found`) - } + this.app.put('/api/v1/credentials/:id', async (req: Request, res: Response) => { const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ id: req.params.id }) @@ -672,11 +581,7 @@ export class App { }) // Delete all chatmessages from chatflowid - this.app.delete('/api/v1/credentials/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Credential ${req.params.id} not found`) - } + this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id }) return res.json(results) }) @@ -692,11 +597,7 @@ export class App { }) // Get specific tool - this.app.get('/api/v1/tools/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Tool ${req.params.id} not found`) - } + this.app.get('/api/v1/tools/:id', async (req: Request, res: Response) => { const tool = await this.AppDataSource.getRepository(Tool).findOneBy({ id: req.params.id }) @@ -716,11 +617,7 @@ export class App { }) // Update tool - this.app.put('/api/v1/tools/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Tool ${req.params.id} not found`) - } + this.app.put('/api/v1/tools/:id', async (req: Request, res: Response) => { const tool = await this.AppDataSource.getRepository(Tool).findOneBy({ id: req.params.id }) @@ -741,11 +638,7 @@ export class App { }) // Delete tool - this.app.delete('/api/v1/tools/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Tool ${req.params.id} not found`) - } + this.app.delete('/api/v1/tools/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getRepository(Tool).delete({ id: req.params.id }) return res.json(results) }) @@ -761,11 +654,7 @@ export class App { }) // Get specific assistant - this.app.get('/api/v1/assistants/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Assistant ${req.params.id} not found`) - } + this.app.get('/api/v1/assistants/:id', async (req: Request, res: Response) => { const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({ id: req.params.id }) @@ -773,46 +662,33 @@ export class App { }) // Get assistant object - this.app.get( - '/api/v1/openai-assistants/:id', - param('id').notEmpty().escape(), - query('credential').notEmpty().escape(), - async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Assistant or Credential not found`) - } - const credentialId = req.query.credential as string - const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ - id: credentialId - }) + 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`) + 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`) + // 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) - const resp = await openai.files.list() - const existingFiles = resp.data ?? [] + const openai = new OpenAI({ apiKey: openAIApiKey }) + const retrievedAssistant = await openai.beta.assistants.retrieve(req.params.id) + const resp = await openai.files.list() + const existingFiles = resp.data ?? [] - if (retrievedAssistant.file_ids && retrievedAssistant.file_ids.length) { - ;(retrievedAssistant as any).files = existingFiles.filter((file) => retrievedAssistant.file_ids.includes(file.id)) - } - - return res.json(retrievedAssistant) + if (retrievedAssistant.file_ids && retrievedAssistant.file_ids.length) { + ;(retrievedAssistant as any).files = existingFiles.filter((file) => retrievedAssistant.file_ids.includes(file.id)) } - ) + + return res.json(retrievedAssistant) + }) // List available assistants - this.app.get('/api/v1/openai-assistants', query('credential').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Assistant or Credential not found`) - } + 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 @@ -947,11 +823,7 @@ export class App { }) // Update assistant - this.app.put('/api/v1/assistants/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Assistant ${req.params.id} not found`) - } + this.app.put('/api/v1/assistants/:id', async (req: Request, res: Response) => { const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({ id: req.params.id }) @@ -1059,11 +931,7 @@ export class App { }) // Delete assistant - this.app.delete('/api/v1/assistants/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Assistant ${req.params.id} not found`) - } + this.app.delete('/api/v1/assistants/:id', async (req: Request, res: Response) => { const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({ id: req.params.id }) @@ -1118,11 +986,7 @@ export class App { // Configuration // ---------------------------------------- - this.app.get('/api/v1/flow-config/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Chatflow ${req.params.id} not found`) - } + this.app.get('/api/v1/flow-config/:id', async (req: Request, res: Response) => { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ id: req.params.id }) @@ -1181,11 +1045,7 @@ export class App { } ) - this.app.post('/api/v1/vector/internal-upsert/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Upsert ${req.params.id} not found`) - } + this.app.post('/api/v1/vector/internal-upsert/:id', async (req: Request, res: Response) => { await this.buildChatflow(req, res, undefined, true, true) }) @@ -1196,24 +1056,15 @@ export class App { // Send input message and get prediction result (External) this.app.post( '/api/v1/prediction/:id', - param('id').notEmpty().escape(), upload.array('files'), (req: Request, res: Response, next: NextFunction) => getRateLimiter(req, res, next), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Error Processing Prediction`) - } await this.buildChatflow(req, res, socketIO) } ) // Send input message and get prediction result (Internal) - this.app.post('/api/v1/internal-prediction/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Error Processing Prediction`) - } + this.app.post('/api/v1/internal-prediction/:id', async (req: Request, res: Response) => { await this.buildChatflow(req, res, socketIO, true) }) @@ -1308,31 +1159,19 @@ export class App { }) // Update api key - this.app.put('/api/v1/apikey/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Error Processing Update API Key`) - } + this.app.put('/api/v1/apikey/:id', async (req: Request, res: Response) => { const keys = await updateAPIKey(req.params.id, req.body.keyName) return addChatflowsCount(keys, res) }) // Delete new api key - this.app.delete('/api/v1/apikey/:id', param('id').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Error Processing Update API Key`) - } + this.app.delete('/api/v1/apikey/:id', async (req: Request, res: Response) => { const keys = await deleteAPIKey(req.params.id) return addChatflowsCount(keys, res) }) // Verify api key - this.app.get('/api/v1/verify/apikey/:apiKey', param('apikey').notEmpty().escape(), async (req: Request, res: Response) => { - const valResult = validationResult(req) - if (!valResult.isEmpty()) { - return res.status(404).send(`Error Processing API Key`) - } + this.app.get('/api/v1/verify/apikey/:apiKey', async (req: Request, res: Response) => { try { const apiKey = await getApiKey(req.params.apiKey) if (!apiKey) return res.status(401).send('Unauthorized') diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts new file mode 100644 index 00000000..a69cde21 --- /dev/null +++ b/packages/server/src/utils/XSS.ts @@ -0,0 +1,11 @@ +import { Request, Response, NextFunction } from 'express' +let stripJs = require('strip-js') + +export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void { + req.url = stripJs(req.url) + for (let p in req.query) { + req.query[p] = stripJs(req.query[p]) + } + + next() +} From 7578183ac25e74518e07f06a61b3eec8609ac2ed Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 7 Dec 2023 18:46:03 +0000 Subject: [PATCH 029/268] add custom analytics --- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 32 +- packages/components/package.json | 3 +- packages/components/src/handler.ts | 489 ++++++++++++++++++ packages/server/src/index.ts | 2 + 4 files changed, 522 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 7f2377bd..d4426394 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -8,6 +8,7 @@ import * as path from 'node:path' import fetch from 'node-fetch' import { flatten, uniqWith, isEqual } from 'lodash' import { zodToJsonSchema } from 'zod-to-json-schema' +import { AnalyticHandler } from '../../../src/handler' class OpenAIAssistant_Agents implements INode { label: string @@ -149,6 +150,11 @@ class OpenAIAssistant_Agents implements INode { const openai = new OpenAI({ apiKey: openAIApiKey }) + // Start analytics + const analyticHandlers = new AnalyticHandler(nodeData, options) + await analyticHandlers.init() + const parentIds = await analyticHandlers.onChainStart('OpenAIAssistant', input) + try { const assistantDetails = JSON.parse(assistant.details) const openAIAssistantId = assistantDetails.id @@ -171,7 +177,8 @@ class OpenAIAssistant_Agents implements INode { } const chatmessage = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ - chatId: options.chatId + chatId: options.chatId, + chatflowid: options.chatflowid }) let threadId = '' @@ -185,7 +192,7 @@ class OpenAIAssistant_Agents implements INode { threadId = thread.id } - // List all runs + // List all runs, in case existing thread is still running if (!isNewThread) { const promise = (threadId: string) => { return new Promise((resolve) => { @@ -221,6 +228,7 @@ class OpenAIAssistant_Agents implements INode { }) // Run assistant thread + const llmIds = await analyticHandlers.onLLMStart('ChatOpenAI', input, parentIds) const runThread = await openai.beta.threads.runs.create(threadId, { assistant_id: retrievedAssistant.id }) @@ -253,7 +261,15 @@ class OpenAIAssistant_Agents implements INode { for (let i = 0; i < actions.length; i += 1) { const tool = tools.find((tool: any) => tool.name === actions[i].tool) if (!tool) continue + + // Start tool analytics + const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds) + const toolOutput = await tool.call(actions[i].toolInput) + + // End tool analytics + await analyticHandlers.onToolEnd(toolIds, toolOutput) + submitToolOutputs.push({ tool_call_id: actions[i].toolCallId, output: toolOutput @@ -302,7 +318,9 @@ class OpenAIAssistant_Agents implements INode { runThreadId = newRunThread.id state = await promise(threadId, newRunThread.id) } else { - throw new Error(`Error processing thread: ${state}, Thread ID: ${threadId}`) + const errMsg = `Error processing thread: ${state}, Thread ID: ${threadId}` + await analyticHandlers.onChainError(parentIds, errMsg) + throw new Error(errMsg) } } @@ -387,11 +405,18 @@ class OpenAIAssistant_Agents implements INode { const bitmap = fsDefault.readFileSync(filePath) const base64String = Buffer.from(bitmap).toString('base64') + // TODO: Use a file path and retrieve image on the fly. Storing as base64 to localStorage and database will easily hit limits const imgHTML = `${fileObj.filename}
` returnVal += imgHTML } } + const imageRegex = /]*\/>/g + let llmOutput = returnVal.replace(imageRegex, '') + llmOutput = llmOutput.replace('
', '') + await analyticHandlers.onLLMEnd(llmIds, llmOutput) + await analyticHandlers.onChainEnd(parentIds, messageData, true) + return { text: returnVal, usedTools, @@ -399,6 +424,7 @@ class OpenAIAssistant_Agents implements INode { assistant: { assistantId: openAIAssistantId, threadId, runId: runThreadId, messages: messageData } } } catch (error) { + await analyticHandlers.onChainError(parentIds, error, true) throw new Error(error) } } diff --git a/packages/components/package.json b/packages/components/package.json index dd87754d..a775e630 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -51,8 +51,9 @@ "husky": "^8.0.3", "ioredis": "^5.3.2", "langchain": "^0.0.196", + "langfuse": "^1.2.0", "langfuse-langchain": "^1.0.31", - "langsmith": "^0.0.32", + "langsmith": "^0.0.49", "linkifyjs": "^4.1.1", "llmonitor": "^0.5.5", "mammoth": "^1.5.1", diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index 456cf39c..ae5a9de0 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -8,6 +8,10 @@ import { LLMonitorHandler } from 'langchain/callbacks/handlers/llmonitor' import { getCredentialData, getCredentialParam } from './utils' import { ICommonObject, INodeData } from './Interface' import CallbackHandler from 'langfuse-langchain' +import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith' +import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse' // or "langfuse-node" +import monitor from 'llmonitor' +import { v4 as uuidv4 } from 'uuid' interface AgentRun extends Run { actions: AgentAction[] @@ -273,3 +277,488 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO throw new Error(e) } } + +export class AnalyticHandler { + nodeData: INodeData + options: ICommonObject = {} + handlers: ICommonObject = {} + + constructor(nodeData: INodeData, options: ICommonObject) { + this.options = options + this.nodeData = nodeData + this.init() + } + + async init() { + try { + if (!this.options.analytic) return + + const analytic = JSON.parse(this.options.analytic) + + for (const provider in analytic) { + const providerStatus = analytic[provider].status as boolean + + if (providerStatus) { + const credentialId = analytic[provider].credentialId as string + const credentialData = await getCredentialData(credentialId ?? '', this.options) + if (provider === 'langSmith') { + const langSmithProject = analytic[provider].projectName as string + const langSmithApiKey = getCredentialParam('langSmithApiKey', credentialData, this.nodeData) + const langSmithEndpoint = getCredentialParam('langSmithEndpoint', credentialData, this.nodeData) + + const client = new LangsmithClient({ + apiUrl: langSmithEndpoint ?? 'https://api.smith.langchain.com', + apiKey: langSmithApiKey + }) + + this.handlers['langSmith'] = { client, langSmithProject } + } else if (provider === 'langFuse') { + const release = analytic[provider].release as string + const langFuseSecretKey = getCredentialParam('langFuseSecretKey', credentialData, this.nodeData) + const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, this.nodeData) + const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, this.nodeData) + + const langfuse = new Langfuse({ + secretKey: langFuseSecretKey, + publicKey: langFusePublicKey, + baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com', + release + }) + this.handlers['langFuse'] = { client: langfuse } + } else if (provider === 'llmonitor') { + const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, this.nodeData) + const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, this.nodeData) + + monitor.init({ + appId: llmonitorAppId, + apiUrl: llmonitorEndpoint + }) + + this.handlers['llmonitor'] = { client: monitor } + } + } + } + } catch (e) { + throw new Error(e) + } + } + + async onChainStart(name: string, input: string, parentIds?: ICommonObject) { + const returnIds: ICommonObject = { + langSmith: {}, + langFuse: {}, + llmonitor: {} + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + if (!parentIds || !Object.keys(parentIds).length) { + const parentRunConfig: RunTreeConfig = { + name, + run_type: 'chain', + inputs: { + text: input + }, + serialized: {}, + project_name: this.handlers['langSmith'].langSmithProject, + client: this.handlers['langSmith'].client + } + const parentRun = new RunTree(parentRunConfig) + await parentRun.postRun() + this.handlers['langSmith'].chainRun = { [parentRun.id]: parentRun } + returnIds['langSmith'].chainRun = parentRun.id + } else { + const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun] + if (parentRun) { + const childChainRun = await parentRun.createChild({ + name, + run_type: 'chain', + inputs: { + text: input + } + }) + await childChainRun.postRun() + this.handlers['langSmith'].chainRun = { [childChainRun.id]: childChainRun } + returnIds['langSmith'].chainRun = childChainRun.id + } + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + let langfuseTraceClient: LangfuseTraceClient + + if (!parentIds || !Object.keys(parentIds).length) { + const langfuse: Langfuse = this.handlers['langFuse'].client + langfuseTraceClient = langfuse.trace({ + name, + userId: this.options.chatId, + metadata: { tags: ['openai-assistant'] } + }) + } else { + langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']] + } + + if (langfuseTraceClient) { + const span = langfuseTraceClient.span({ + name, + input: { + text: input + } + }) + this.handlers['langFuse'].trace = { [langfuseTraceClient.id]: langfuseTraceClient } + this.handlers['langFuse'].span = { [span.id]: span } + returnIds['langFuse'].trace = langfuseTraceClient.id + returnIds['langFuse'].span = span.id + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const monitor = this.handlers['llmonitor'].client + + if (monitor) { + const runId = uuidv4() + await monitor.trackEvent('chain', 'start', { + runId, + name, + userId: this.options.chatId, + input + }) + this.handlers['llmonitor'].chainEvent = { [runId]: runId } + returnIds['llmonitor'].chainEvent = runId + } + } + + return returnIds + } + + async onChainEnd(returnIds: ICommonObject, output: string | object, shutdown = false) { + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const chainRun: RunTree | undefined = this.handlers['langSmith'].chainRun[returnIds['langSmith'].chainRun] + if (chainRun) { + await chainRun.end({ + outputs: { + output + } + }) + await chainRun.patchRun() + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const span: LangfuseSpanClient | undefined = this.handlers['langFuse'].span[returnIds['langFuse'].span] + if (span) { + span.end({ + output + }) + if (shutdown) { + const langfuse: Langfuse = this.handlers['langFuse'].client + await langfuse.shutdownAsync() + } + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const chainEventId = returnIds['llmonitor'].chainEvent + const monitor = this.handlers['llmonitor'].client + + if (monitor && chainEventId) { + await monitor.trackEvent('chain', 'end', { + runId: chainEventId, + output + }) + } + } + } + + async onChainError(returnIds: ICommonObject, error: string | object, shutdown = false) { + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const chainRun: RunTree | undefined = this.handlers['langSmith'].chainRun[returnIds['langSmith'].chainRun] + if (chainRun) { + await chainRun.end({ + error: { + error + } + }) + await chainRun.patchRun() + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const span: LangfuseSpanClient | undefined = this.handlers['langFuse'].span[returnIds['langFuse'].span] + if (span) { + span.end({ + output: { + error + } + }) + if (shutdown) { + const langfuse: Langfuse = this.handlers['langFuse'].client + await langfuse.shutdownAsync() + } + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const chainEventId = returnIds['llmonitor'].chainEvent + const monitor = this.handlers['llmonitor'].client + + if (monitor && chainEventId) { + await monitor.trackEvent('chain', 'end', { + runId: chainEventId, + output: error + }) + } + } + } + + async onLLMStart(name: string, input: string, parentIds: ICommonObject) { + const returnIds: ICommonObject = { + langSmith: {}, + langFuse: {}, + llmonitor: {} + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun] + if (parentRun) { + const childLLMRun = await parentRun.createChild({ + name, + run_type: 'llm', + inputs: { + prompts: [input] + } + }) + await childLLMRun.postRun() + this.handlers['langSmith'].llmRun = { [childLLMRun.id]: childLLMRun } + returnIds['langSmith'].llmRun = childLLMRun.id + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace] + if (trace) { + const generation = trace.generation({ + name, + prompt: input + }) + this.handlers['langFuse'].generation = { [generation.id]: generation } + returnIds['langFuse'].generation = generation.id + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const monitor = this.handlers['llmonitor'].client + const chainEventId: string = this.handlers['llmonitor'].chainEvent[parentIds['llmonitor'].chainEvent] + + if (monitor && chainEventId) { + const runId = uuidv4() + await monitor.trackEvent('llm', 'start', { + runId, + parentRunId: chainEventId, + name, + userId: this.options.chatId, + input + }) + this.handlers['llmonitor'].llmEvent = { [runId]: runId } + returnIds['llmonitor'].llmEvent = runId + } + } + + return returnIds + } + + async onLLMEnd(returnIds: ICommonObject, output: string) { + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const llmRun: RunTree | undefined = this.handlers['langSmith'].llmRun[returnIds['langSmith'].llmRun] + if (llmRun) { + await llmRun.end({ + outputs: { + generations: [output] + } + }) + await llmRun.patchRun() + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation] + if (generation) { + generation.end({ + completion: output + }) + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const llmEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].llmEvent] + const monitor = this.handlers['llmonitor'].client + + if (monitor && llmEventId) { + await monitor.trackEvent('llm', 'end', { + runId: llmEventId, + output + }) + } + } + } + + async onLLMError(returnIds: ICommonObject, error: string | object) { + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const llmRun: RunTree | undefined = this.handlers['langSmith'].llmRun[returnIds['langSmith'].llmRun] + if (llmRun) { + await llmRun.end({ + error: { + error + } + }) + await llmRun.patchRun() + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation] + if (generation) { + generation.end({ + completion: error + }) + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const llmEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].llmEvent] + const monitor = this.handlers['llmonitor'].client + + if (monitor && llmEventId) { + await monitor.trackEvent('llm', 'end', { + runId: llmEventId, + output: error + }) + } + } + } + + async onToolStart(name: string, input: string | object, parentIds: ICommonObject) { + const returnIds: ICommonObject = { + langSmith: {}, + langFuse: {}, + llmonitor: {} + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun] + if (parentRun) { + const childToolRun = await parentRun.createChild({ + name, + run_type: 'tool', + inputs: { + input + } + }) + await childToolRun.postRun() + this.handlers['langSmith'].toolRun = { [childToolRun.id]: childToolRun } + returnIds['langSmith'].toolRun = childToolRun.id + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace] + if (trace) { + const toolSpan = trace.span({ + name, + input + }) + this.handlers['langFuse'].toolSpan = { [toolSpan.id]: toolSpan } + returnIds['langFuse'].toolSpan = toolSpan.id + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const monitor = this.handlers['llmonitor'].client + const chainEventId: string = this.handlers['llmonitor'].chainEvent[parentIds['llmonitor'].chainEvent] + + if (monitor && chainEventId) { + const runId = uuidv4() + await monitor.trackEvent('tool', 'start', { + runId, + parentRunId: chainEventId, + name, + userId: this.options.chatId, + input + }) + this.handlers['llmonitor'].toolEvent = { [runId]: runId } + returnIds['llmonitor'].toolEvent = runId + } + } + + return returnIds + } + + async onToolEnd(returnIds: ICommonObject, output: string | object) { + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const toolRun: RunTree | undefined = this.handlers['langSmith'].toolRun[returnIds['langSmith'].toolRun] + if (toolRun) { + await toolRun.end({ + outputs: { + output + } + }) + await toolRun.patchRun() + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const toolSpan: LangfuseSpanClient | undefined = this.handlers['langFuse'].toolSpan[returnIds['langFuse'].toolSpan] + if (toolSpan) { + toolSpan.end({ + output + }) + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const toolEventId: string = this.handlers['llmonitor'].toolEvent[returnIds['llmonitor'].toolEvent] + const monitor = this.handlers['llmonitor'].client + + if (monitor && toolEventId) { + await monitor.trackEvent('tool', 'end', { + runId: toolEventId, + output + }) + } + } + } + + async onToolError(returnIds: ICommonObject, error: string | object) { + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) { + const toolRun: RunTree | undefined = this.handlers['langSmith'].toolRun[returnIds['langSmith'].toolRun] + if (toolRun) { + await toolRun.end({ + error: { + error + } + }) + await toolRun.patchRun() + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { + const toolSpan: LangfuseSpanClient | undefined = this.handlers['langFuse'].toolSpan[returnIds['langFuse'].toolSpan] + if (toolSpan) { + toolSpan.end({ + output: error + }) + } + } + + if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) { + const toolEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].toolEvent] + const monitor = this.handlers['llmonitor'].client + + if (monitor && toolEventId) { + await monitor.trackEvent('tool', 'end', { + runId: toolEventId, + output: error + }) + } + } + } +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index d87d2c0a..61e55159 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1470,6 +1470,7 @@ export class App { let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatflowid, chatHistory, socketIO, socketIOClientId: incomingInput.socketIOClientId, @@ -1480,6 +1481,7 @@ export class App { chatId }) : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatflowid, chatHistory, logger, appDataSource: this.AppDataSource, From c9a7ee2ad4c1d23471b926babf848e18c22d7f1b Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 7 Dec 2023 23:17:27 +0000 Subject: [PATCH 030/268] update hyde retriever --- .../retrievers/HydeRetriever/HydeRetriever.ts | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts b/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts index 9ec7ada0..10d9a6e7 100644 --- a/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts +++ b/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts @@ -18,7 +18,7 @@ class HydeRetriever_Retrievers implements INode { constructor() { this.label = 'Hyde Retriever' this.name = 'HydeRetriever' - this.version = 1.0 + this.version = 2.0 this.type = 'HydeRetriever' this.icon = 'hyderetriever.svg' this.category = 'Retrievers' @@ -36,41 +36,66 @@ class HydeRetriever_Retrievers implements INode { type: 'VectorStore' }, { - label: 'Prompt Key', + label: 'Select Defined Prompt', name: 'promptKey', + description: 'Select a pre-defined prompt', type: 'options', options: [ { label: 'websearch', - name: 'websearch' + name: 'websearch', + description: `Please write a passage to answer the question +Question: {question} +Passage:` }, { label: 'scifact', - name: 'scifact' + name: 'scifact', + description: `Please write a scientific paper passage to support/refute the claim +Claim: {question} +Passage:` }, { label: 'arguana', - name: 'arguana' + name: 'arguana', + description: `Please write a counter argument for the passage +Passage: {question} +Counter Argument:` }, { label: 'trec-covid', - name: 'trec-covid' + name: 'trec-covid', + description: `Please write a scientific paper passage to answer the question +Question: {question} +Passage:` }, { label: 'fiqa', - name: 'fiqa' + name: 'fiqa', + description: `Please write a financial article passage to answer the question +Question: {question} +Passage:` }, { label: 'dbpedia-entity', - name: 'dbpedia-entity' + name: 'dbpedia-entity', + description: `Please write a passage to answer the question. +Question: {question} +Passage:` }, { label: 'trec-news', - name: 'trec-news' + name: 'trec-news', + description: `Please write a news passage about the topic. +Topic: {question} +Passage:` }, { label: 'mr-tydi', - name: 'mr-tydi' + name: 'mr-tydi', + description: `Please write a passage in Swahili/Korean/Japanese/Bengali to answer the question in detail. +Question: {question} +Passage:` } ], default: 'websearch' @@ -78,7 +103,7 @@ class HydeRetriever_Retrievers implements INode { { label: 'Custom Prompt', name: 'customPrompt', - description: 'If custom prompt is used, this will override Prompt Key', + description: 'If custom prompt is used, this will override Defined Prompt', placeholder: 'Please write a passage to answer the question\nQuestion: {question}\nPassage:', type: 'string', rows: 4, From da2fe78e4491a3d611266325ba9b2b2e06c1b732 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 8 Dec 2023 12:09:29 +0000 Subject: [PATCH 031/268] Update handler.ts --- packages/components/src/handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index ae5a9de0..29aff3e2 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -9,7 +9,7 @@ import { getCredentialData, getCredentialParam } from './utils' import { ICommonObject, INodeData } from './Interface' import CallbackHandler from 'langfuse-langchain' import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith' -import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse' // or "langfuse-node" +import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse' import monitor from 'llmonitor' import { v4 as uuidv4 } from 'uuid' From 99bc9d64fbd79aacfd2d488ace0044dcb61fb391 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 8 Dec 2023 18:50:58 +0530 Subject: [PATCH 032/268] XSS: replacing deprecated sanitize-js with sanitize-html --- packages/server/package.json | 2 +- packages/server/src/utils/XSS.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 97a95d43..013e6007 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -61,9 +61,9 @@ "mysql": "^2.18.1", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", + "sanitize-html": "^2.11.0", "socket.io": "^4.6.1", "sqlite3": "^5.1.6", - "strip-js": "^1.2.0", "typeorm": "^0.3.6", "uuid": "^9.0.1", "winston": "^3.9.0" diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index a69cde21..329c2ed2 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -1,10 +1,12 @@ import { Request, Response, NextFunction } from 'express' -let stripJs = require('strip-js') +const sanitizeHtml = require('sanitize-html') export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void { - req.url = stripJs(req.url) + // decoding is necessary as the url is encoded by the browser + const decodedURI = decodeURI(req.url) + req.url = sanitizeHtml(decodedURI) for (let p in req.query) { - req.query[p] = stripJs(req.query[p]) + req.query[p] = sanitizeHtml(req.query[p]) } next() From b91cf551288016855407c5f22ac9cc1027e0f855 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 8 Dec 2023 14:14:22 +0000 Subject: [PATCH 033/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.4.6?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index a775e630..66e6d6d9 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.5", + "version": "1.4.6", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 1e5a37ad0f673d1ee8e2ed2d13b3b5711b86578f Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 8 Dec 2023 14:16:04 +0000 Subject: [PATCH 034/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.4.2=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 76369ab2..2b1f49e9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.4.1", + "version": "1.4.2", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From a0c2b8b26a3721fff503aa17a210cb298ffee1fb Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 8 Dec 2023 14:19:57 +0000 Subject: [PATCH 035/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.4.4=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 649a9a47..1993c7e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.3", + "version": "1.4.4", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 6f4ccaf4..f2ec5c09 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.3", + "version": "1.4.4", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From d2d21c45fe36128a0d299f2eb349c93520902c45 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 8 Dec 2023 18:51:40 +0000 Subject: [PATCH 036/268] fix upser vector API --- packages/server/src/ChatflowPool.ts | 2 +- packages/server/src/Interface.ts | 2 +- packages/server/src/index.ts | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/server/src/ChatflowPool.ts b/packages/server/src/ChatflowPool.ts index d296dcfe..325fac56 100644 --- a/packages/server/src/ChatflowPool.ts +++ b/packages/server/src/ChatflowPool.ts @@ -16,7 +16,7 @@ export class ChatflowPool { * @param {IReactFlowNode[]} startingNodes * @param {ICommonObject} overrideConfig */ - add(chatflowid: string, endingNodeData: INodeData, startingNodes: IReactFlowNode[], overrideConfig?: ICommonObject) { + add(chatflowid: string, endingNodeData: INodeData | undefined, startingNodes: IReactFlowNode[], overrideConfig?: ICommonObject) { this.activeChatflows[chatflowid] = { startingNodes, endingNodeData, diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index d5890ab6..f82c6690 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -172,7 +172,7 @@ export interface IncomingInput { export interface IActiveChatflows { [key: string]: { startingNodes: IReactFlowNode[] - endingNodeData: INodeData + endingNodeData?: INodeData inSync: boolean overrideConfig?: ICommonObject } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0262bff4..95b8aa01 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1394,16 +1394,19 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges - /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation) when all these conditions met: + /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met: * - Node Data already exists in pool * - Still in sync (i.e the flow has not been modified since) * - Existing overrideConfig and new overrideConfig are the same * - Flow doesn't start with/contain nodes that depend on incomingInput.question + * - Its not an Upsert request + * TODO: convert overrideConfig to hash when we no longer store base64 string but filepath ***/ const isFlowReusable = () => { return ( Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) && this.chatflowPool.activeChatflows[chatflowid].inSync && + this.chatflowPool.activeChatflows[chatflowid].endingNodeData && isSameOverrideConfig( isInternal, this.chatflowPool.activeChatflows[chatflowid].overrideConfig, @@ -1415,7 +1418,7 @@ export class App { } if (isFlowReusable()) { - nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData + nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData as INodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) logger.debug( `[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` @@ -1466,6 +1469,7 @@ export class App { const constructedObj = constructGraphs(nodes, edges, true) const nonDirectedGraph = constructedObj.graph const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) logger.debug(`[server]: Start building chatflow ${chatflowid}`) /*** BFS to traverse from Starting Nodes to Ending Node ***/ @@ -1485,13 +1489,18 @@ export class App { isUpsert, incomingInput.stopNodeId ) - if (isUpsert) return res.status(201).send('Successfully Upserted') + if (isUpsert) { + this.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig) + return res.status(201).send('Successfully Upserted') + } const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`) - if (incomingInput.overrideConfig) + if (incomingInput.overrideConfig) { nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) + } + const reactFlowNodeData: INodeData = resolveVariables( nodeToExecute.data, reactFlowNodes, @@ -1500,7 +1509,6 @@ export class App { ) nodeToExecuteData = reactFlowNodeData - const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig) } From 9a5d5720f9fa67f6f90d0cb5b482967a61d1d16f Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 9 Dec 2023 13:49:53 +0000 Subject: [PATCH 037/268] get rid of credential for langchain hub --- packages/server/src/index.ts | 27 +- .../dialog/PromptLangsmithHubDialog.js | 301 ++++++++---------- .../ui/src/views/canvas/NodeInputHandler.js | 2 +- 3 files changed, 140 insertions(+), 190 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 95b8aa01..805a0fec 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1044,18 +1044,9 @@ export class App { // ---------------------------------------- this.app.post('/api/v1/load-prompt', async (req: Request, res: Response) => { try { - const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ - id: req.body.credential - }) - - if (!credential) return res.status(404).json({ error: `Credential ${req.body.credential} not found` }) - - // Decrypt credentialData - const decryptedCredentialData = await decryptCredentialData(credential.encryptedData, credential.credentialName, undefined) - let hub = new Client({ apiKey: decryptedCredentialData.langsmithApiKey, apiUrl: decryptedCredentialData.langsmithEndpoint }) + let hub = new Client() const prompt = await hub.pull(req.body.promptName) const templates = parsePrompt(prompt) - return res.json({ status: 'OK', prompt: req.body.promptName, templates: templates }) } catch (e: any) { return res.json({ status: 'ERROR', prompt: req.body.promptName, error: e?.message }) @@ -1064,22 +1055,10 @@ export class App { this.app.post('/api/v1/prompts-list', async (req: Request, res: Response) => { try { - const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ - id: req.body.credential - }) - - if (!credential) return res.status(404).json({ error: `Credential ${req.body.credential} not found` }) - // Decrypt credentialData - const decryptedCredentialData = await decryptCredentialData(credential.encryptedData, credential.credentialName, undefined) - - const headers = {} - // @ts-ignore - headers['x-api-key'] = decryptedCredentialData.langsmithApiKey - const tags = req.body.tags ? `tags=${req.body.tags}` : '' // Default to 100, TODO: add pagination and use offset & limit - const url = `https://web.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` - axios.get(url, headers).then((response) => { + const url = `https://api.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false` + axios.get(url).then((response) => { if (response.data.repos) { return res.json({ status: 'OK', repos: response.data.repos }) } diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js index e6f06e20..35b4ead7 100644 --- a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js @@ -42,12 +42,12 @@ import ClearIcon from '@mui/icons-material/Clear' import { styled } from '@mui/material/styles' //Project Import -import CredentialInputHandler from 'views/canvas/CredentialInputHandler' import { StyledButton } from 'ui-component/button/StyledButton' import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown' import { CodeBlock } from 'ui-component/markdown/CodeBlock' import promptEmptySVG from 'assets/images/prompt_empty.svg' +import useApi from 'hooks/useApi' import promptApi from 'api/prompt' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' @@ -89,6 +89,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const portalElement = document.getElementById('portal') const dispatch = useDispatch() const customization = useSelector((state) => state.customization) + const getAvailablePromptsApi = useApi(promptApi.getAvailablePrompts) useEffect(() => { if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) @@ -98,6 +99,22 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [show, dispatch]) + useEffect(() => { + if (promptType) { + getAvailablePromptsApi.request({ tags: promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [promptType]) + + useEffect(() => { + if (getAvailablePromptsApi.data && getAvailablePromptsApi.data.repos) { + setAvailablePrompNameList(getAvailablePromptsApi.data.repos) + if (getAvailablePromptsApi.data.repos?.length) handleListItemClick(0, getAvailablePromptsApi.data.repos) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getAvailablePromptsApi.data]) + const ITEM_HEIGHT = 48 const ITEM_PADDING_TOP = 8 const MenuProps = { @@ -156,7 +173,6 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const [availablePrompNameList, setAvailablePrompNameList] = useState([]) const [selectedPrompt, setSelectedPrompt] = useState({}) - const [credentialId, setCredentialId] = useState('') const [accordionExpanded, setAccordionExpanded] = useState(['prompt']) const handleAccordionChange = (accordionName) => (event, isExpanded) => { @@ -173,7 +189,6 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { if (!prompt.detailed) { const createResp = await promptApi.getPrompt({ - credential: credentialId, promptName: prompt.full_name }) if (createResp.data) { @@ -194,14 +209,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { language.forEach((item) => { tags += `tags=${item.name}&` }) - const createResp = await promptApi.getAvailablePrompts({ - credential: credentialId, - tags: tags - }) - if (createResp.data) { - setAvailablePrompNameList(createResp.data.repos) - if (createResp.data.repos?.length) await handleListItemClick(0, createResp.data.repos) - } + getAvailablePromptsApi.request({ tags: tags }) } const removeDuplicates = (value) => { @@ -238,176 +246,139 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { setLanguage(removeDuplicates(value)) } - const clear = () => { - setModelName([]) - setUsecase([]) - setLanguage([]) - setSelectedPrompt({}) - setAvailablePrompNameList([]) - setAccordionExpanded(['prompt']) - } - const component = show ? ( - Load Prompts from Langsmith Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'}) + Langchain Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'}) - - - Langsmith Credential   - * - - - + + + Model + + + + + + Usecase + + + + + + Language + + + + + - {credentialId && ( - - - - Model - - - - - - Usecase - - - - - - Language - - - - - - - - )} + {availablePrompNameList && availablePrompNameList.length == 0 && ( diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 103af6b4..892a6273 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -238,7 +238,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA onClick={() => onShowPromptHubButtonClicked()} endIcon={} > - Langsmith Prompt Hub + Langchain Hub Date: Sat, 9 Dec 2023 14:12:30 +0000 Subject: [PATCH 038/268] add sanitize html types --- packages/server/package.json | 1 + packages/server/src/utils/XSS.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 013e6007..1eeb43f1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -72,6 +72,7 @@ "@types/cors": "^2.8.12", "@types/crypto-js": "^4.1.1", "@types/multer": "^1.4.7", + "@types/sanitize-html": "^2.9.5", "concurrently": "^7.1.0", "nodemon": "^2.0.15", "oclif": "^3", diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index 329c2ed2..3e96e6c8 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -1,12 +1,12 @@ import { Request, Response, NextFunction } from 'express' -const sanitizeHtml = require('sanitize-html') +import sanitizeHtml from 'sanitize-html' export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void { // decoding is necessary as the url is encoded by the browser const decodedURI = decodeURI(req.url) req.url = sanitizeHtml(decodedURI) for (let p in req.query) { - req.query[p] = sanitizeHtml(req.query[p]) + req.query[p] = sanitizeHtml(req.query[p] as string) } next() From c26e37a923b39a07ac81b2da23409b8efbc8c85c Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 9 Dec 2023 14:44:01 +0000 Subject: [PATCH 039/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.4.3=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 2b1f49e9..7a739978 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.4.2", + "version": "1.4.3", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From bac91eed00bd6a97567c245798818f93b681a152 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 9 Dec 2023 14:44:53 +0000 Subject: [PATCH 040/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.4.5=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1993c7e5..804c3c96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.4", + "version": "1.4.5", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index a7315999..ab1f6149 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.4", + "version": "1.4.5", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 1d1bd4f5562a34f91ca0bb390ee08f4337e420cb Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sun, 10 Dec 2023 22:38:18 +0530 Subject: [PATCH 041/268] Environment Variables: Dashboard along with CRUD Operations on variables. --- packages/server/src/Interface.ts | 9 + .../server/src/database/entities/Variable.ts | 25 ++ .../server/src/database/entities/index.ts | 4 +- .../mysql/1702200925471-AddVariableEntity.ts | 21 ++ .../src/database/migrations/mysql/index.ts | 4 +- .../1702200925471-AddVariableEntity.ts | 21 ++ .../src/database/migrations/postgres/index.ts | 4 +- .../sqlite/1702200925471-AddVariableEntity.ts | 13 + .../src/database/migrations/sqlite/index.ts | 4 +- packages/server/src/index.ts | 42 +++ packages/ui/src/api/variables.js | 16 + packages/ui/src/menu-items/dashboard.js | 12 +- packages/ui/src/routes/MainRoutes.js | 7 + .../views/variables/AddEditVariableDialog.js | 265 +++++++++++++++ packages/ui/src/views/variables/index.js | 301 ++++++++++++++++++ 15 files changed, 742 insertions(+), 6 deletions(-) create mode 100644 packages/server/src/database/entities/Variable.ts create mode 100644 packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts create mode 100644 packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts create mode 100644 packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts create mode 100644 packages/ui/src/api/variables.js create mode 100644 packages/ui/src/views/variables/AddEditVariableDialog.js create mode 100644 packages/ui/src/views/variables/index.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index f82c6690..126aac38 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -68,6 +68,15 @@ export interface ICredential { createdDate: Date } +export interface IVariable { + id: string + name: string + value: string + type: string + updatedDate: Date + createdDate: Date +} + export interface IComponentNodes { [key: string]: INode } diff --git a/packages/server/src/database/entities/Variable.ts b/packages/server/src/database/entities/Variable.ts new file mode 100644 index 00000000..88e0587d --- /dev/null +++ b/packages/server/src/database/entities/Variable.ts @@ -0,0 +1,25 @@ +/* eslint-disable */ +import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' +import { IVariable } from "../../Interface"; + +@Entity() +export class Variable implements IVariable{ + @PrimaryGeneratedColumn('uuid') + id: string + + @Column() + name: string + + @Column({ nullable: true, type: 'text' }) + value: string + + @Column({default: 'string', type: 'text'}) + type: string + + + @CreateDateColumn() + createdDate: Date + + @UpdateDateColumn() + updatedDate: Date +} diff --git a/packages/server/src/database/entities/index.ts b/packages/server/src/database/entities/index.ts index 58447a1f..af5c559f 100644 --- a/packages/server/src/database/entities/index.ts +++ b/packages/server/src/database/entities/index.ts @@ -3,11 +3,13 @@ import { ChatMessage } from './ChatMessage' import { Credential } from './Credential' import { Tool } from './Tool' import { Assistant } from './Assistant' +import { Variable } from './Variable' export const entities = { ChatFlow, ChatMessage, Credential, Tool, - Assistant + Assistant, + Variable } diff --git a/packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts new file mode 100644 index 00000000..a6c81887 --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddVariableEntity1699325775451 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS \`variable\` ( + \`id\` varchar(36) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`value\` text NOT NULL, + \`type\` 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 { + await queryRunner.query(`DROP TABLE variable`) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index 8f9824a8..a5220ad8 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -10,6 +10,7 @@ import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEnt import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' +import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' export const mysqlMigrations = [ Init1693840429259, @@ -23,5 +24,6 @@ export const mysqlMigrations = [ AddAssistantEntity1699325775451, AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, - AddFileAnnotationsToChatMessage1700271021237 + AddFileAnnotationsToChatMessage1700271021237, + AddVariableEntity1699325775451 ] diff --git a/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts new file mode 100644 index 00000000..d4a1d7be --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddVariableEntity1699325775451 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS variable ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" varchar NOT NULL, + "value" text NOT NULL, + "type" text 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 { + await queryRunner.query(`DROP TABLE variable`) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index d196fbc1..3c3fa396 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -10,6 +10,7 @@ import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEnt import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' +import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' export const postgresMigrations = [ Init1693891895163, @@ -23,5 +24,6 @@ export const postgresMigrations = [ AddAssistantEntity1699325775451, AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, - AddFileAnnotationsToChatMessage1700271021237 + AddFileAnnotationsToChatMessage1700271021237, + AddVariableEntity1699325775451 ] diff --git a/packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts new file mode 100644 index 00000000..63ec709f --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddVariableEntity1699325775451 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS "variable" ("id" varchar PRIMARY KEY NOT NULL, "name" text NOT NULL, "value" text NOT NULL, "type" varchar, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')));` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE variable`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index fdd83064..c0ade080 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -10,6 +10,7 @@ import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEnt import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' +import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' export const sqliteMigrations = [ Init1693835579790, @@ -23,5 +24,6 @@ export const sqliteMigrations = [ AddAssistantEntity1699325775451, AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, - AddFileAnnotationsToChatMessage1700271021237 + AddFileAnnotationsToChatMessage1700271021237, + AddVariableEntity1699325775451 ] diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2ab454ad..2b4e6436 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -62,6 +62,7 @@ import { sanitizeMiddleware } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' +import { Variable } from "./database/entities/Variable"; export class App { app: express.Application @@ -1150,6 +1151,47 @@ export class App { return res.json(templates) }) + // ---------------------------------------- + // Variables + // ---------------------------------------- + this.app.get('/api/v1/variables', async (req: Request, res: Response) => { + const variables = await getDataSource().getRepository(Variable).find() + return res.json(variables) + }) + + // Create new variable + this.app.post('/api/v1/variables', async (req: Request, res: Response) => { + const body = req.body + const newVariable = new Variable() + Object.assign(newVariable, body) + const variable = this.AppDataSource.getRepository(Variable).create(newVariable) + const results = await this.AppDataSource.getRepository(Variable).save(variable) + return res.json(results) + }) + + // Update variable + this.app.put('/api/v1/variables/:id', async (req: Request, res: Response) => { + const variable = await this.AppDataSource.getRepository(Variable).findOneBy({ + id: req.params.id + }) + + if (!variable) return res.status(404).send(`Variable ${req.params.id} not found`) + + const body = req.body + const updateVariable = new Variable() + Object.assign(updateVariable, body) + this.AppDataSource.getRepository(Variable).merge(variable, updateVariable) + const result = await this.AppDataSource.getRepository(Variable).save(variable) + + return res.json(result) + }) + + // Delete variable via id + this.app.delete('/api/v1/variables/:id', async (req: Request, res: Response) => { + const results = await this.AppDataSource.getRepository(Variable).delete({ id: req.params.id }) + return res.json(results) + }) + // ---------------------------------------- // API Keys // ---------------------------------------- diff --git a/packages/ui/src/api/variables.js b/packages/ui/src/api/variables.js new file mode 100644 index 00000000..944b8319 --- /dev/null +++ b/packages/ui/src/api/variables.js @@ -0,0 +1,16 @@ +import client from './client' + +const getAllVariables = () => client.get('/variables') + +const createVariable = (body) => client.post(`/variables`, body) + +const updateVariable = (id, body) => client.put(`/variables/${id}`, body) + +const deleteVariable = (id) => client.delete(`/variables/${id}`) + +export default { + getAllVariables, + createVariable, + updateVariable, + deleteVariable +} diff --git a/packages/ui/src/menu-items/dashboard.js b/packages/ui/src/menu-items/dashboard.js index 8bf5b392..b0e5d66e 100644 --- a/packages/ui/src/menu-items/dashboard.js +++ b/packages/ui/src/menu-items/dashboard.js @@ -1,8 +1,8 @@ // assets -import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } from '@tabler/icons' +import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable } from '@tabler/icons' // constant -const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } +const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable } // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -51,6 +51,14 @@ const dashboard = { icon: icons.IconLock, breadcrumbs: true }, + { + id: 'variables', + title: 'Environment Variables', + type: 'item', + url: '/variables', + icon: icons.IconVariable, + breadcrumbs: true + }, { id: 'apikey', title: 'API Keys', diff --git a/packages/ui/src/routes/MainRoutes.js b/packages/ui/src/routes/MainRoutes.js index bce0de13..08dd721d 100644 --- a/packages/ui/src/routes/MainRoutes.js +++ b/packages/ui/src/routes/MainRoutes.js @@ -22,6 +22,9 @@ const Assistants = Loadable(lazy(() => import('views/assistants'))) // credentials routing const Credentials = Loadable(lazy(() => import('views/credentials'))) +// variables routing +const Variables = Loadable(lazy(() => import('views/variables'))) + // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { @@ -55,6 +58,10 @@ const MainRoutes = { { path: '/credentials', element: + }, + { + path: '/variables', + element: } ] } diff --git a/packages/ui/src/views/variables/AddEditVariableDialog.js b/packages/ui/src/views/variables/AddEditVariableDialog.js new file mode 100644 index 00000000..e32ce663 --- /dev/null +++ b/packages/ui/src/views/variables/AddEditVariableDialog.js @@ -0,0 +1,265 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { useState, useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' + +// Material +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Typography, OutlinedInput } from '@mui/material' + +// Project imports +import { StyledButton } from 'ui-component/button/StyledButton' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' + +// Icons +import { IconX, IconVariable } from '@tabler/icons' + +// API +import variablesApi from 'api/variables' + +// Hooks + +// utils +import useNotifier from 'utils/useNotifier' + +// const +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' +import { TooltipWithParser } from '../../ui-component/tooltip/TooltipWithParser' +import { Dropdown } from '../../ui-component/dropdown/Dropdown' + +const variableTypes = [ + { + label: 'Static Variable', + name: 'static' + }, + { + label: 'Runtime Variable', + name: 'runtime' + } +] + +const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + + const dispatch = useDispatch() + + // ==============================|| Snackbar ||============================== // + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [name, setName] = useState('') + const [value, setValue] = useState('') + const [type, setType] = useState('') + + const [variable, setVariable] = useState({}) + + useEffect(() => { + if (dialogProps.type === 'EDIT' && dialogProps.data) { + // When variable dialog is opened from Variables dashboard + setName(dialogProps.data.name) + setValue(dialogProps.data.value) + setType(dialogProps.data.type) + //setVariable(dialogProps.data) + } else if (dialogProps.type === 'ADD' && dialogProps.data) { + // When variable dialog is to add a new variable + setName('') + setValue('') + setType('static') + //setVariable({ name: '', value: '', type: 'static' }) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dialogProps]) + + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + + const addNewVariable = async () => { + try { + const obj = { + name, + value, + type + } + const createResp = await variablesApi.createVariable(obj) + if (createResp.data) { + enqueueSnackbar({ + message: 'New Variable added', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(createResp.data.id) + } + } catch (err) { + const errorData = typeof err === 'string' ? err : err.response?.data || `${err.response?.status}: ${err.response?.statusText}` + enqueueSnackbar({ + message: `Failed to add new Variable: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const saveVariable = async () => { + try { + const saveObj = { + name, + value, + type + } + + const saveResp = await variablesApi.updateVariable(variable.id, saveObj) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Variable saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(saveResp.data.id) + } + } catch (error) { + const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}` + enqueueSnackbar({ + message: `Failed to save Variable: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const component = show ? ( + + +
+
+ +
+ {dialogProps.type === 'ADD' ? 'Add Variable' : 'Edit Variable'} +
+
+ + +
+ + Variable Name * + + +
+
+ setName(e.target.value)} value={name ?? ''} /> +
+ +
+ + Value + + +
+
+ setValue(e.target.value)} value={value ?? ''} /> + Leave the value empty for runtime variables. Will be populated at runtime. +
+ +
+ + Type * + +
+
+ setType(newValue)} + value={type ?? 'static'} + /> + + Runtime: Value would be populated from env. Static: Value would be used as is. + +
+
+ + (dialogProps.type === 'ADD' ? addNewVariable() : saveVariable())} + > + {dialogProps.confirmButtonName} + + + +
+ ) : null + + return createPortal(component, portalElement) +} + +AddEditVariableDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default AddEditVariableDialog diff --git a/packages/ui/src/views/variables/index.js b/packages/ui/src/views/variables/index.js new file mode 100644 index 00000000..ccec36d3 --- /dev/null +++ b/packages/ui/src/views/variables/index.js @@ -0,0 +1,301 @@ +import { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import moment from 'moment' + +// material-ui +import { + Button, + Box, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + IconButton, + Toolbar, + TextField, + InputAdornment, + ButtonGroup +} from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// project imports +import MainCard from 'ui-component/cards/MainCard' +import { StyledButton } from 'ui-component/button/StyledButton' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' + +// API +import variablesApi from 'api/variables' + +// Hooks +import useApi from 'hooks/useApi' +import useConfirm from 'hooks/useConfirm' + +// utils +import useNotifier from 'utils/useNotifier' + +// Icons +import { IconTrash, IconEdit, IconX, IconPlus, IconSearch, IconVariable } from '@tabler/icons' +import CredentialEmptySVG from 'assets/images/credential_empty.svg' + +// const +import AddEditVariableDialog from './AddEditVariableDialog' + +// ==============================|| Credentials ||============================== // + +const Variables = () => { + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const dispatch = useDispatch() + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [showVariableDialog, setShowVariableDialog] = useState(false) + const [variableDialogProps, setVariableDialogProps] = useState({}) + const [variables, setVariables] = useState([]) + + const { confirm } = useConfirm() + + const getAllVariables = useApi(variablesApi.getAllVariables) + + const [search, setSearch] = useState('') + const onSearchChange = (event) => { + setSearch(event.target.value) + } + function filterVariables(data) { + return data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 + } + + const addNew = () => { + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + data: {} + } + setVariableDialogProps(dialogProp) + setShowVariableDialog(true) + } + + const edit = (variable) => { + const dialogProp = { + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: variable + } + setVariableDialogProps(dialogProp) + setShowVariableDialog(true) + } + + const deleteVariable = async (credential) => { + const confirmPayload = { + title: `Delete`, + description: `Delete variable ${variable.name}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + const deleteResp = await variablesApi.deleteVariable(credential.id) + if (deleteResp.data) { + enqueueSnackbar({ + message: 'Variable deleted', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm() + } + } catch (error) { + const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}` + enqueueSnackbar({ + message: `Failed to delete Variable: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + } + + const onConfirm = () => { + setShowVariableDialog(false) + getAllVariables.request() + } + + useEffect(() => { + getAllVariables.request() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getAllVariables.data) { + setVariables(getAllVariables.data) + } + }, [getAllVariables.data]) + + return ( + <> + + + + +

Environment Variables 

+ + + + ) + }} + /> + + + + } + > + Add Variable + + + +
+
+
+ {variables.length <= 0 && ( + + + CredentialEmptySVG + +
No Variables Yet
+
+ )} + {variables.length > 0 && ( + + + + + Name + Value + Type + Last Updated + Created + + + + + + {variables.filter(filterVariables).map((variable, index) => ( + + +
+
+ +
+ {variable.name} +
+
+ {variable.value} + {variable.type === 'static' ? 'Static Variable' : 'Runtime Variable'} + {moment(variable.updatedDate).format('DD-MMM-YY')} + {moment(variable.createdDate).format('DD-MMM-YY')} + + edit(variable)}> + + + + + deleteVariable(variable)}> + + + +
+ ))} +
+
+
+ )} +
+ setShowVariableDialog(false)} + onConfirm={onConfirm} + > + + + ) +} + +export default Variables From 0bf5536095a31c8493e13e30e371258232052d58 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sun, 10 Dec 2023 22:39:06 +0530 Subject: [PATCH 042/268] Environment Variables: injection of variables into the custom script --- packages/components/nodes/tools/CustomTool/core.ts | 3 ++- packages/server/src/index.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 12dd72f1..343acafd 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -80,7 +80,8 @@ export class DynamicStructuredTool< sandbox[`$${item}`] = arg[item] } } - + sandbox['$env'] = { USER: 'VINOD' } + console.log('sandbox === ' + JSON.stringify(sandbox)) const defaultAllowBuiltInDep = [ 'assert', 'buffer', diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2b4e6436..39baaf8e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -62,7 +62,7 @@ import { sanitizeMiddleware } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' -import { Variable } from "./database/entities/Variable"; +import { Variable } from './database/entities/Variable' export class App { app: express.Application From f51c1d5b7a53cd877b000786d221298ed1766187 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 11 Dec 2023 20:35:30 +0000 Subject: [PATCH 043/268] check for array query parameter --- packages/server/src/utils/XSS.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index 3e96e6c8..5d8b81e9 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -6,8 +6,15 @@ export function sanitizeMiddleware(req: Request, res: Response, next: NextFuncti const decodedURI = decodeURI(req.url) req.url = sanitizeHtml(decodedURI) for (let p in req.query) { - req.query[p] = sanitizeHtml(req.query[p] as string) + if (Array.isArray(req.query[p])) { + const sanitizedQ = [] + for (const q of req.query[p] as string[]) { + sanitizedQ.push(sanitizeHtml(q)) + } + req.query[p] = sanitizedQ + } else { + req.query[p] = sanitizeHtml(req.query[p] as string) + } } - next() } From bfa870e56b0371e661ef5d980ded8140b78de42a Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 12 Dec 2023 12:19:35 +0530 Subject: [PATCH 044/268] Environment Variables: injection of variables ($env) into the custom tool and addition of ($flow) object --- .../nodes/tools/CustomTool/CustomTool.ts | 27 ++++++++++++++- .../components/nodes/tools/CustomTool/core.ts | 33 +++++++++++++++++-- packages/components/src/Interface.ts | 1 + packages/server/src/utils/index.ts | 4 ++- .../views/variables/AddEditVariableDialog.js | 16 ++++----- 5 files changed, 69 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index 541edcf0..aba803e4 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -80,7 +80,32 @@ class CustomTool_Tools implements INode { code: tool.func } if (customToolFunc) obj.code = customToolFunc - return new DynamicStructuredTool(obj) + + const variables = await appDataSource.getRepository(databaseEntities['Variable']).find() + + // override variables defined in overrideConfig + // nodeData.inputs.variables is an Object, check each property and override the variable + if (nodeData?.inputs?.variables) { + for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.variables)) { + const foundVar = variables.find((v) => v.name === propertyName) + if (foundVar) { + // even if the variable was defined as runtime, we override it with static value + foundVar.type = 'static' + foundVar.value = nodeData.inputs.variables[propertyName] + } else { + // add it the variables, if not found locally in the db + variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.variables[propertyName] }) + } + } + } + const flow = { + chatId: options.chatId, // id is uppercase (I) + chatflowId: options.chatflowid // id is lowercase (i) + } + let dynamicStructuredTool = new DynamicStructuredTool(obj) + dynamicStructuredTool.setVariables(variables) + dynamicStructuredTool.setFlowObject(flow) + return dynamicStructuredTool } catch (e) { throw new Error(e) } diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 343acafd..b7b1f6a6 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -2,6 +2,7 @@ import { z } from 'zod' import { CallbackManagerForToolRun } from 'langchain/callbacks' import { StructuredTool, ToolParams } from 'langchain/tools' import { NodeVM } from 'vm2' +import { logger } from "@zilliz/milvus2-sdk-node"; /* * List of dependencies allowed to be import in vm2 @@ -62,6 +63,8 @@ export class DynamicStructuredTool< func: DynamicStructuredToolInput['func'] schema: T + private variables: any[] + private flowObj: any constructor(fields: DynamicStructuredToolInput) { super(fields) @@ -80,8 +83,26 @@ export class DynamicStructuredTool< sandbox[`$${item}`] = arg[item] } } - sandbox['$env'] = { USER: 'VINOD' } - console.log('sandbox === ' + JSON.stringify(sandbox)) + //inject variables + let env = {} + if (this.variables) { + for (const item of this.variables) { + let value = item.value + if (item.type === 'runtime') { + value = process.env[item.name] + } + Object.defineProperty(env, item.name, { + enumerable: true, + configurable: true, + writable: true, + value: value + }) + } + } + sandbox['$env'] = env + if (this.flowObj) { + sandbox['$flow'] = this.flowObj + } const defaultAllowBuiltInDep = [ 'assert', 'buffer', @@ -118,4 +139,12 @@ export class DynamicStructuredTool< return response } + + setVariables(variables: any[]) { + this.variables = variables + } + + setFlowObject(flow: any) { + this.flowObj = flow + } } diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 6752f944..bc50155c 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -73,6 +73,7 @@ export interface INodeParams { additionalParams?: boolean loadMethod?: string hidden?: boolean + variables?: ICommonObject[] } export interface INodeExecutionData { diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2bf1c04a..adec01cd 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -37,6 +37,7 @@ import { Tool } from '../database/entities/Tool' import { Assistant } from '../database/entities/Assistant' import { DataSource } from 'typeorm' import { CachePool } from '../CachePool' +import { Variable } from '../database/entities/Variable' const QUESTION_VAR_PREFIX = 'question' const CHAT_HISTORY_VAR_PREFIX = 'chat_history' @@ -47,7 +48,8 @@ export const databaseEntities: IDatabaseEntity = { ChatMessage: ChatMessage, Tool: Tool, Credential: Credential, - Assistant: Assistant + Assistant: Assistant, + Variable: Variable } /** diff --git a/packages/ui/src/views/variables/AddEditVariableDialog.js b/packages/ui/src/views/variables/AddEditVariableDialog.js index e32ce663..db2116b0 100644 --- a/packages/ui/src/views/variables/AddEditVariableDialog.js +++ b/packages/ui/src/views/variables/AddEditVariableDialog.js @@ -62,13 +62,13 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { setName(dialogProps.data.name) setValue(dialogProps.data.value) setType(dialogProps.data.type) - //setVariable(dialogProps.data) + setVariable(dialogProps.data) } else if (dialogProps.type === 'ADD' && dialogProps.data) { // When variable dialog is to add a new variable setName('') setValue('') setType('static') - //setVariable({ name: '', value: '', type: 'static' }) + setVariable({ name: '', value: '', type: 'static' }) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -83,9 +83,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const addNewVariable = async () => { try { const obj = { - name, - value, - type + name: name, + value: value, + type: type } const createResp = await variablesApi.createVariable(obj) if (createResp.data) { @@ -125,9 +125,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const saveVariable = async () => { try { const saveObj = { - name, - value, - type + name: name, + value: value, + type: type } const saveResp = await variablesApi.updateVariable(variable.id, saveObj) From 947ab9dae656a472a3cba0f470409a86926009dc Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 12 Dec 2023 14:15:41 +0530 Subject: [PATCH 045/268] Environment Variables: handling of environment variables in user input --- packages/server/src/index.ts | 7 ++++++- packages/server/src/utils/index.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 39baaf8e..8f728f19 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -43,7 +43,8 @@ import { checkMemorySessionId, clearSessionMemoryFromViewMessageDialog, getUserHome, - replaceChatHistory + replaceChatHistory, + replaceEnvVariables } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -1381,6 +1382,10 @@ export class App { const chatflowid = req.params.id let incomingInput: IncomingInput = req.body + if (incomingInput.question) { + incomingInput.question = await replaceEnvVariables(incomingInput.question, this.AppDataSource) + } + let nodeToExecuteData: INodeData const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index adec01cd..d3575388 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -954,3 +954,29 @@ export const getAllValuesFromJson = (obj: any): any[] => { extractValues(obj) return values } + +export const replaceEnvVariables = async (question: string, appDataSource: DataSource): Promise => { + // the incoming question can have more than one env variable with the pattern {{ env.VARIABLE_NAME }} + // extract all the env variables from the question and iterate through them + const envVariables = question.match(/{{[^}]*}}/g) + if (envVariables) { + for (const envVariable of envVariables) { + // this is needed as the user can have spaces between the curly braces and the env keyword + // extract the variable name from the env variable + const variableName = envVariable.replace(/{{\s*env.|\s*}}/g, '') + // get the value of the env variable from the database + const variable = await appDataSource.getRepository(Variable).findOneBy({ + name: variableName + }) + if (variable) { + let value = variable.value + if (variable.type === 'runtime') { + value = process.env[variable.name] as string + } + // replace the env variable with the value from the database + question = question.replace(envVariable, value) + } + } + } + return question +} From c2e97b54acfa741f091881287e744a38b713c522 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 12 Dec 2023 16:19:52 +0530 Subject: [PATCH 046/268] Environment Variables: Validations and minor fixes --- .../components/nodes/tools/CustomTool/core.ts | 1 - .../views/variables/AddEditVariableDialog.js | 70 +++++++++++-------- packages/ui/src/views/variables/index.js | 4 +- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index b7b1f6a6..8bae726c 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -2,7 +2,6 @@ import { z } from 'zod' import { CallbackManagerForToolRun } from 'langchain/callbacks' import { StructuredTool, ToolParams } from 'langchain/tools' import { NodeVM } from 'vm2' -import { logger } from "@zilliz/milvus2-sdk-node"; /* * List of dependencies allowed to be import in vm2 diff --git a/packages/ui/src/views/variables/AddEditVariableDialog.js b/packages/ui/src/views/variables/AddEditVariableDialog.js index db2116b0..85c40e44 100644 --- a/packages/ui/src/views/variables/AddEditVariableDialog.js +++ b/packages/ui/src/views/variables/AddEditVariableDialog.js @@ -26,6 +26,7 @@ import useNotifier from 'utils/useNotifier' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' import { TooltipWithParser } from '../../ui-component/tooltip/TooltipWithParser' import { Dropdown } from '../../ui-component/dropdown/Dropdown' +import { SwitchInput } from '../../ui-component/switch/Switch' const variableTypes = [ { @@ -50,29 +51,29 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) - const [name, setName] = useState('') - const [value, setValue] = useState('') - const [type, setType] = useState('') + const [variableName, setVariableName] = useState('') + const [variableValue, setVariableValue] = useState('') + const [variableType, setVariableType] = useState('static') + const [dialogType, setDialogType] = useState('ADD') const [variable, setVariable] = useState({}) useEffect(() => { - if (dialogProps.type === 'EDIT' && dialogProps.data) { + if (dialogProps.type === 'EDIT') { // When variable dialog is opened from Variables dashboard - setName(dialogProps.data.name) - setValue(dialogProps.data.value) - setType(dialogProps.data.type) + setVariableName(dialogProps.data.name) + setVariableValue(dialogProps.data.value) + setVariableType(dialogProps.data.type) setVariable(dialogProps.data) - } else if (dialogProps.type === 'ADD' && dialogProps.data) { + setDialogType('EDIT') + } else if (dialogProps.type === 'ADD') { // When variable dialog is to add a new variable - setName('') - setValue('') - setType('static') - setVariable({ name: '', value: '', type: 'static' }) + setVariableName('') + setVariableValue('') + setVariableType('static') + setDialogType('ADD') } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dialogProps]) + }, [dialogProps.data, dialogProps.type]) useEffect(() => { if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) @@ -83,9 +84,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const addNewVariable = async () => { try { const obj = { - name: name, - value: value, - type: type + name: variableName, + value: variableValue, + type: variableType } const createResp = await variablesApi.createVariable(obj) if (createResp.data) { @@ -125,9 +126,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const saveVariable = async () => { try { const saveObj = { - name: name, - value: value, - type: type + name: variableName, + value: variableValue, + type: variableType } const saveResp = await variablesApi.updateVariable(variable.id, saveObj) @@ -207,7 +208,13 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- setName(e.target.value)} value={name ?? ''} /> + setVariableName(e.target.value)} + value={variableName ?? ''} + />
@@ -217,7 +224,13 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- setValue(e.target.value)} value={value ?? ''} /> + setVariableValue(e.target.value)} + value={variableValue ?? ''} + /> Leave the value empty for runtime variables. Will be populated at runtime.
@@ -228,11 +241,10 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
setType(newValue)} - value={type ?? 'static'} + onSelect={(newValue) => setVariableType(newValue)} + value={variableType} /> Runtime: Value would be populated from env. Static: Value would be used as is. @@ -241,9 +253,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { (dialogProps.type === 'ADD' ? addNewVariable() : saveVariable())} + onClick={() => (dialogType === 'ADD' ? addNewVariable() : saveVariable())} > {dialogProps.confirmButtonName} diff --git a/packages/ui/src/views/variables/index.js b/packages/ui/src/views/variables/index.js index ccec36d3..9399eb20 100644 --- a/packages/ui/src/views/variables/index.js +++ b/packages/ui/src/views/variables/index.js @@ -95,7 +95,7 @@ const Variables = () => { setShowVariableDialog(true) } - const deleteVariable = async (credential) => { + const deleteVariable = async (variable) => { const confirmPayload = { title: `Delete`, description: `Delete variable ${variable.name}?`, @@ -106,7 +106,7 @@ const Variables = () => { if (isConfirmed) { try { - const deleteResp = await variablesApi.deleteVariable(credential.id) + const deleteResp = await variablesApi.deleteVariable(variable.id) if (deleteResp.data) { enqueueSnackbar({ message: 'Variable deleted', From 790c7eabeb7d218639700ce265abda081f7cf813 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 12 Dec 2023 16:20:32 +0530 Subject: [PATCH 047/268] Environment Variables: Validations and minor fixes --- packages/ui/src/views/variables/AddEditVariableDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/views/variables/AddEditVariableDialog.js b/packages/ui/src/views/variables/AddEditVariableDialog.js index 85c40e44..86a121f6 100644 --- a/packages/ui/src/views/variables/AddEditVariableDialog.js +++ b/packages/ui/src/views/variables/AddEditVariableDialog.js @@ -26,7 +26,6 @@ import useNotifier from 'utils/useNotifier' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' import { TooltipWithParser } from '../../ui-component/tooltip/TooltipWithParser' import { Dropdown } from '../../ui-component/dropdown/Dropdown' -import { SwitchInput } from '../../ui-component/switch/Switch' const variableTypes = [ { From 8e1ef2d5337f8140e8789830b7c5844ee854cc24 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 12 Dec 2023 12:44:42 +0000 Subject: [PATCH 048/268] fix sanitized & --- packages/server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2ab454ad..2d40f32e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -200,7 +200,7 @@ export class App { // Get component credential via name this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => { - if (!req.params.name.includes('&')) { + if (!req.params.name.includes('&')) { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { return res.json(this.nodesPool.componentCredentials[req.params.name]) } else { @@ -208,7 +208,7 @@ export class App { } } else { const returnResponse = [] - for (const name of req.params.name.split('&')) { + for (const name of req.params.name.split('&')) { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) { returnResponse.push(this.nodesPool.componentCredentials[name]) } else { From ef3f1b34b149edb2064bc862abf6397522738cc1 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 12 Dec 2023 22:22:25 +0530 Subject: [PATCH 049/268] Environment Variables: renaming overrideConfig node to envVars --- packages/components/nodes/tools/CustomTool/CustomTool.ts | 8 ++++---- packages/server/src/index.ts | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index aba803e4..6b0397c8 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -85,16 +85,16 @@ class CustomTool_Tools implements INode { // override variables defined in overrideConfig // nodeData.inputs.variables is an Object, check each property and override the variable - if (nodeData?.inputs?.variables) { - for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.variables)) { + if (nodeData?.inputs?.envVars) { + for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.envVars)) { const foundVar = variables.find((v) => v.name === propertyName) if (foundVar) { // even if the variable was defined as runtime, we override it with static value foundVar.type = 'static' - foundVar.value = nodeData.inputs.variables[propertyName] + foundVar.value = nodeData.inputs.envVars[propertyName] } else { // add it the variables, if not found locally in the db - variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.variables[propertyName] }) + variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.envVars[propertyName] }) } } } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 8f728f19..34e461f7 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1382,10 +1382,6 @@ export class App { const chatflowid = req.params.id let incomingInput: IncomingInput = req.body - if (incomingInput.question) { - incomingInput.question = await replaceEnvVariables(incomingInput.question, this.AppDataSource) - } - let nodeToExecuteData: INodeData const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ From 2110e1146b5d7ca024411f5ec26be3dcee540972 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 12 Dec 2023 23:14:52 +0000 Subject: [PATCH 050/268] add a public endpoint to retrieve chatbotconfig --- packages/server/src/index.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2d40f32e..fb4a5f5a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -138,6 +138,7 @@ export class App { '/api/v1/verify/apikey/', '/api/v1/chatflows/apikey/', '/api/v1/public-chatflows', + '/api/v1/public-chatbotConfig', '/api/v1/prediction/', '/api/v1/vector/upsert/', '/api/v1/node-icon/', @@ -328,6 +329,23 @@ export class App { return res.status(404).send(`Chatflow ${req.params.id} not found`) }) + // Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat) + // Safe as public endpoint as chatbotConfig doesn't contain sensitive credential + this.app.get('/api/v1/public-chatbotConfig/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (chatflow && chatflow.chatbotConfig) { + try { + const parsedConfig = JSON.parse(chatflow.chatbotConfig) + return res.json(parsedConfig) + } catch (e) { + return res.status(500).send(`Error parsing Chatbot Config for Chatflow ${req.params.id}`) + } + } + return res.status(404).send(`Chatbot Config for Chatflow ${req.params.id} not found`) + }) + // Save chatflow this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => { const body = req.body From a504e4b38f72345d5be9c4f527f7f959eaa9d292 Mon Sep 17 00:00:00 2001 From: kzhang Date: Tue, 12 Dec 2023 23:46:15 -0600 Subject: [PATCH 051/268] Add windowSize input to avoid "token exceeded error" --- .../RedisBackedChatMemory/RedisBackedChatMemory.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 7fe447ad..a92d9bbb 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -57,6 +57,14 @@ class RedisBackedChatMemory_Memory implements INode { type: 'string', default: 'chat_history', additionalParams: true + }, + { + label: 'Window Size', + name: 'windowSize', + type: 'number', + description: 'Window of size k to surface the last k back-and-forth to use as memory.', + additionalParams: true, + optional: true } ] } @@ -89,6 +97,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const sessionId = nodeData.inputs?.sessionId as string const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string + const windowSize = nodeData.inputs?.windowSize as number const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false @@ -129,7 +138,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const redisChatMessageHistory = new RedisChatMessageHistory(obj) redisChatMessageHistory.getMessages = async (): Promise => { - const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, 0, -1) + const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) return orderedMessages.map(mapStoredMessageToChatMessage) } From 8f4dd8b50926842d6efc4b9600f4a52ef7892de2 Mon Sep 17 00:00:00 2001 From: tirongi Date: Wed, 13 Dec 2023 10:47:39 +0100 Subject: [PATCH 052/268] Fix wrong elasti client setup when use custom URL --- .../Elasticsearch/Elasticsearch.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts index 5f3cf206..04c90c6b 100644 --- a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts +++ b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts @@ -183,13 +183,26 @@ const prepareConnectionOptions = ( } else if (cloudId) { let username = getCredentialParam('username', credentialData, nodeData) let password = getCredentialParam('password', credentialData, nodeData) - elasticSearchClientOptions = { - cloud: { - id: cloudId - }, - auth: { - username: username, - password: password + if (cloudId.startsWith('http')) { + elasticSearchClientOptions = { + node: cloudId, + auth: { + username: username, + password: password + }, + tls: { + rejectUnauthorized: false + } + } + } else { + elasticSearchClientOptions = { + cloud: { + id: cloudId + }, + auth: { + username: username, + password: password + } } } } From d214ddfe5bb985d508b6a209d56fd88a9ca6fc15 Mon Sep 17 00:00:00 2001 From: abhishekshankr Date: Wed, 13 Dec 2023 12:45:00 -0500 Subject: [PATCH 053/268] Initial Icon Tests --- .../components/nodes/agents/BabyAGI/BabyAGI.ts | 2 +- .../components/nodes/agents/BabyAGI/babyagi.svg | 7 +++++++ .../components/nodes/chains/LLMChain/LLMChain.ts | 2 +- .../nodes/chains/LLMChain/LLM_Chain.svg | 6 ++++++ .../nodes/chains/RetrievalQAChain/QA_Chain.svg | 7 +++++++ .../chains/RetrievalQAChain/RetrievalQAChain.ts | 2 +- .../components/nodes/documentloaders/Pdf/pdf.svg | 16 +++++++++------- .../nodes/documentloaders/Text/Text.ts | 2 +- .../nodes/documentloaders/Text/Txt.svg | 10 ++++++++++ 9 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 packages/components/nodes/agents/BabyAGI/babyagi.svg create mode 100644 packages/components/nodes/chains/LLMChain/LLM_Chain.svg create mode 100644 packages/components/nodes/chains/RetrievalQAChain/QA_Chain.svg create mode 100644 packages/components/nodes/documentloaders/Text/Txt.svg diff --git a/packages/components/nodes/agents/BabyAGI/BabyAGI.ts b/packages/components/nodes/agents/BabyAGI/BabyAGI.ts index e31f31c6..f82f134b 100644 --- a/packages/components/nodes/agents/BabyAGI/BabyAGI.ts +++ b/packages/components/nodes/agents/BabyAGI/BabyAGI.ts @@ -20,7 +20,7 @@ class BabyAGI_Agents implements INode { this.version = 1.0 this.type = 'BabyAGI' this.category = 'Agents' - this.icon = 'babyagi.jpg' + this.icon = 'babyagi.svg' this.description = 'Task Driven Autonomous Agent which creates new task and reprioritizes task list based on objective' this.baseClasses = ['BabyAGI'] this.inputs = [ diff --git a/packages/components/nodes/agents/BabyAGI/babyagi.svg b/packages/components/nodes/agents/BabyAGI/babyagi.svg new file mode 100644 index 00000000..0dd731cc --- /dev/null +++ b/packages/components/nodes/agents/BabyAGI/babyagi.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index fd398151..b7c055e4 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -27,7 +27,7 @@ class LLMChain_Chains implements INode { this.name = 'llmChain' this.version = 3.0 this.type = 'LLMChain' - this.icon = 'chain.svg' + this.icon = 'LLM_Chain.svg' this.category = 'Chains' this.description = 'Chain to run queries against LLMs' this.baseClasses = [this.type, ...getBaseClasses(LLMChain)] diff --git a/packages/components/nodes/chains/LLMChain/LLM_Chain.svg b/packages/components/nodes/chains/LLMChain/LLM_Chain.svg new file mode 100644 index 00000000..f6c79d95 --- /dev/null +++ b/packages/components/nodes/chains/LLMChain/LLM_Chain.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/components/nodes/chains/RetrievalQAChain/QA_Chain.svg b/packages/components/nodes/chains/RetrievalQAChain/QA_Chain.svg new file mode 100644 index 00000000..7bea5c76 --- /dev/null +++ b/packages/components/nodes/chains/RetrievalQAChain/QA_Chain.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts index bff2a0a7..45cacad8 100644 --- a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts +++ b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts @@ -21,7 +21,7 @@ class RetrievalQAChain_Chains implements INode { this.name = 'retrievalQAChain' this.version = 1.0 this.type = 'RetrievalQAChain' - this.icon = 'chain.svg' + this.icon = 'QA_Chain.svg' this.category = 'Chains' this.description = 'QA chain to answer a question based on the retrieved documents' this.baseClasses = [this.type, ...getBaseClasses(RetrievalQAChain)] diff --git a/packages/components/nodes/documentloaders/Pdf/pdf.svg b/packages/components/nodes/documentloaders/Pdf/pdf.svg index 20af94f8..a937431b 100644 --- a/packages/components/nodes/documentloaders/Pdf/pdf.svg +++ b/packages/components/nodes/documentloaders/Pdf/pdf.svg @@ -1,7 +1,9 @@ - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/packages/components/nodes/documentloaders/Text/Text.ts b/packages/components/nodes/documentloaders/Text/Text.ts index 3f12e490..e41c5a9f 100644 --- a/packages/components/nodes/documentloaders/Text/Text.ts +++ b/packages/components/nodes/documentloaders/Text/Text.ts @@ -21,7 +21,7 @@ class Text_DocumentLoaders implements INode { this.name = 'textFile' this.version = 3.0 this.type = 'Document' - this.icon = 'textFile.svg' + this.icon = 'Txt.svg' this.category = 'Document Loaders' this.description = `Load data from text files` this.baseClasses = [this.type] diff --git a/packages/components/nodes/documentloaders/Text/Txt.svg b/packages/components/nodes/documentloaders/Text/Txt.svg new file mode 100644 index 00000000..c91819ba --- /dev/null +++ b/packages/components/nodes/documentloaders/Text/Txt.svg @@ -0,0 +1,10 @@ + + + + + + + + + + From 871dea249cdaa2ce2dcaf1e48577c03de5ff8471 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Wed, 13 Dec 2023 18:47:25 +0100 Subject: [PATCH 054/268] Added boolean flag which enables ssl connection for redis nodes. This is needed for example connecting to the azure redis sever. --- packages/components/credentials/RedisCacheApi.credential.ts | 5 +++++ packages/components/nodes/cache/RedisCache/RedisCache.ts | 6 +++++- .../nodes/cache/RedisCache/RedisEmbeddingsCache.ts | 6 +++++- .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 6 +++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/components/credentials/RedisCacheApi.credential.ts b/packages/components/credentials/RedisCacheApi.credential.ts index 4d1a2498..2b4ad618 100644 --- a/packages/components/credentials/RedisCacheApi.credential.ts +++ b/packages/components/credentials/RedisCacheApi.credential.ts @@ -35,6 +35,11 @@ class RedisCacheApi implements INodeCredential { name: 'redisCachePwd', type: 'password', placeholder: '' + }, + { + label: 'Use SSL', + name: 'redisCacheSslEnabled', + type: 'boolean' } ] } diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts index 8128b6e3..657fd1bc 100644 --- a/packages/components/nodes/cache/RedisCache/RedisCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -56,12 +56,16 @@ class RedisCache implements INode { const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) + + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; client = new Redis({ port: portStr ? parseInt(portStr) : 6379, host, username, - password + password, + ...tlsOptions }) } else { client = new Redis(redisUrl) diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts index f15869d7..5d6c4bfb 100644 --- a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts @@ -71,12 +71,16 @@ class RedisEmbeddingsCache implements INode { const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) + + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; client = new Redis({ port: portStr ? parseInt(portStr) : 6379, host, username, - password + password, + ...tlsOptions }) } else { client = new Redis(redisUrl) diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 7fe447ad..08a16631 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -103,12 +103,16 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) + + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; client = new Redis({ port: portStr ? parseInt(portStr) : 6379, host, username, - password + password, + ...tlsOptions }) } else { client = new Redis(redisUrl) From 966a7dc861bbe3ff02bfd0e4b1dc94b961dc9182 Mon Sep 17 00:00:00 2001 From: abhishekshankr Date: Wed, 13 Dec 2023 12:52:27 -0500 Subject: [PATCH 055/268] Added additional test icons --- .../nodes/chains/ConversationChain/ConversationChain.ts | 2 +- .../components/nodes/chains/ConversationChain/chain.svg | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 7887ce97..ee5bdfc5 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -26,7 +26,7 @@ class ConversationChain_Chains implements INode { this.name = 'conversationChain' this.version = 1.0 this.type = 'ConversationChain' - this.icon = 'chain.svg' + this.icon = 'Chain.svg' this.category = 'Chains' this.description = 'Chat models specific conversational chain with memory' this.baseClasses = [this.type, ...getBaseClasses(ConversationChain)] diff --git a/packages/components/nodes/chains/ConversationChain/chain.svg b/packages/components/nodes/chains/ConversationChain/chain.svg index a5b32f90..2bba4889 100644 --- a/packages/components/nodes/chains/ConversationChain/chain.svg +++ b/packages/components/nodes/chains/ConversationChain/chain.svg @@ -1,6 +1,3 @@ - - - - - - \ No newline at end of file + + + From a2404afc41c7dcbbfbf3e3a13a94d5ccb02212f9 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 14 Dec 2023 10:43:50 +0100 Subject: [PATCH 056/268] #1385 fixed linting --- packages/components/nodes/cache/RedisCache/RedisCache.ts | 2 +- .../components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts | 2 +- .../nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts index 657fd1bc..4e61c239 100644 --- a/packages/components/nodes/cache/RedisCache/RedisCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -58,7 +58,7 @@ class RedisCache implements INode { const host = getCredentialParam('redisCacheHost', credentialData, nodeData) const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) - const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {} client = new Redis({ port: portStr ? parseInt(portStr) : 6379, diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts index 5d6c4bfb..fe1b4df8 100644 --- a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts @@ -73,7 +73,7 @@ class RedisEmbeddingsCache implements INode { const host = getCredentialParam('redisCacheHost', credentialData, nodeData) const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) - const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {} client = new Redis({ port: portStr ? parseInt(portStr) : 6379, diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 08a16631..f772ae56 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -105,7 +105,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const host = getCredentialParam('redisCacheHost', credentialData, nodeData) const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) - const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {} client = new Redis({ port: portStr ? parseInt(portStr) : 6379, From e7f13c87d8b76909b31234447802aa7061b09038 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 15 Dec 2023 20:58:08 +0530 Subject: [PATCH 057/268] Addition of Google Gemini Chat Model, Credential and Associated Embeddings --- .../GoogleGenerativeAI.credential.ts | 25 ++++ .../ChatGoogleGenerativeAI.ts | 108 ++++++++++++++++++ .../ChatGoogleGenerativeAI/gemini.png | Bin 0 -> 57143 bytes .../GoogleGenerativeAIEmbedding.ts | 105 +++++++++++++++++ .../GoogleGenerativeAIEmbedding/gemini.png | Bin 0 -> 57143 bytes packages/components/package.json | 1 + 6 files changed, 239 insertions(+) create mode 100644 packages/components/credentials/GoogleGenerativeAI.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts create mode 100644 packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png create mode 100644 packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts create mode 100644 packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png diff --git a/packages/components/credentials/GoogleGenerativeAI.credential.ts b/packages/components/credentials/GoogleGenerativeAI.credential.ts new file mode 100644 index 00000000..9a1f3f28 --- /dev/null +++ b/packages/components/credentials/GoogleGenerativeAI.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleGenerativeAICredential implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Google Generative AI' + this.name = 'googleGenerativeAI' + this.version = 1.0 + this.description = 'Get your API Key here.' + this.inputs = [ + { + label: 'Google AI API Key', + name: 'googleGenerativeAPIKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: GoogleGenerativeAICredential } diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts new file mode 100644 index 00000000..26913424 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -0,0 +1,108 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { BaseCache } from 'langchain/schema' +import { ChatGoogleGenerativeAI } from '@langchain/google-genai' + +class GoogleGenerativeAI_ChatModels implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'ChatGoogleGenerativeAI' + this.name = 'chatGoogleGenerativeAI' + this.version = 2.0 + this.type = 'ChatGoogleGenerativeAI' + this.icon = 'gemini.png' + this.category = 'Chat Models' + this.description = 'Wrapper around Google Gemini large language models that use the Chat endpoint' + this.baseClasses = [this.type, ...getBaseClasses(ChatGoogleGenerativeAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleGenerativeAI'], + optional: true, + description: 'Google Generative AI credential.' + } + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'gemini-pro', + name: 'gemini-pro' + } + ], + default: 'gemini-pro', + optional: true + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Max Output Tokens', + name: 'maxOutputTokens', + type: 'number', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('googleGenerativeAPIKey', credentialData, nodeData) + + const temperature = nodeData.inputs?.temperature as string + const modelName = nodeData.inputs?.modelName as string + const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string + const topP = nodeData.inputs?.topP as string + const cache = nodeData.inputs?.cache as BaseCache + + const obj = { + apiKey: apiKey, + modelName: modelName, + maxOutputTokens: 2048 + } + + if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) + + const model = new ChatGoogleGenerativeAI(obj) + if (topP) model.topP = parseFloat(topP) + if (cache) model.cache = cache + if (temperature) model.temperature = parseInt(temperature) + return model + } +} + +module.exports = { nodeClass: GoogleGenerativeAI_ChatModels } diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0d60f44817cdbf1bd5483d88c1929831a84eed GIT binary patch literal 57143 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

1U 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 literal 0 HcmV?d00001 diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts new file mode 100644 index 00000000..7682b280 --- /dev/null +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -0,0 +1,105 @@ +import { GoogleVertexAIEmbeddings } from 'langchain/embeddings/googlevertexai' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { GoogleGenerativeAIEmbeddings, GoogleGenerativeAIEmbeddingsParams } from '@langchain/google-genai' +import { TaskType } from '@google/generative-ai' + +class GoogleGenerativeAIEmbedding_Embeddings implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'GoogleGenerativeAI Embeddings' + this.name = 'googleGenerativeAiEmbeddings' + this.version = 1.0 + this.type = 'GoogleGenerativeAiEmbeddings' + this.icon = 'gemini.png' + this.category = 'Embeddings' + this.description = 'Google Generative API to generate embeddings for a given text' + this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleGenerativeAI'], + optional: true, + description: 'Google Generative AI credential.' + } + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'embedding-001', + name: 'embedding-001' + } + ], + default: 'embedding-001' + }, + { + label: 'Task Type', + name: 'tasktype', + type: 'options', + description: 'Type of task for which the embedding will be used', + options: [ + { label: 'TASK_TYPE_UNSPECIFIED', name: 'TASK_TYPE_UNSPECIFIED' }, + { label: 'RETRIEVAL_QUERY', name: 'RETRIEVAL_QUERY' }, + { label: 'RETRIEVAL_DOCUMENT', name: 'RETRIEVAL_DOCUMENT' }, + { label: 'SEMANTIC_SIMILARITY', name: 'SEMANTIC_SIMILARITY' }, + { label: 'CLASSIFICATION', name: 'CLASSIFICATION' }, + { label: 'CLUSTERING', name: 'CLUSTERING' } + ], + default: 'TASK_TYPE_UNSPECIFIED' + } + ] + } + + // eslint-disable-next-line unused-imports/no-unused-vars + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const modelName = nodeData.inputs?.modelName as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('googleGenerativeAPIKey', credentialData, nodeData) + + let taskType: TaskType + switch (nodeData.inputs?.tasktype as string) { + case 'RETRIEVAL_QUERY': + taskType = TaskType.RETRIEVAL_QUERY + break + case 'RETRIEVAL_DOCUMENT': + taskType = TaskType.RETRIEVAL_DOCUMENT + break + case 'SEMANTIC_SIMILARITY': + taskType = TaskType.SEMANTIC_SIMILARITY + break + case 'CLASSIFICATION': + taskType = TaskType.CLASSIFICATION + break + case 'CLUSTERING': + taskType = TaskType.CLUSTERING + break + default: + taskType = TaskType.TASK_TYPE_UNSPECIFIED + break + } + const obj: GoogleGenerativeAIEmbeddingsParams = { + apiKey: apiKey, + modelName: modelName, + taskType: taskType + } + + const model = new GoogleGenerativeAIEmbeddings(obj) + return model + } +} + +module.exports = { nodeClass: GoogleGenerativeAIEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0d60f44817cdbf1bd5483d88c1929831a84eed GIT binary patch literal 57143 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

1U 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 literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 1874ca10..6bdc908a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -26,6 +26,7 @@ "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", + "@langchain/google-genai": "^0.0.3", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", From f475732e6ba1aa5e78d6b352519e86cca5f13a08 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 15 Dec 2023 22:20:51 +0530 Subject: [PATCH 058/268] Support for Google Gemini Models: Minor fixes --- .../ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts | 6 +++--- .../GoogleGenerativeAIEmbedding.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 26913424..95ee0575 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -18,7 +18,7 @@ class GoogleGenerativeAI_ChatModels implements INode { constructor() { this.label = 'ChatGoogleGenerativeAI' this.name = 'chatGoogleGenerativeAI' - this.version = 2.0 + this.version = 1.0 this.type = 'ChatGoogleGenerativeAI' this.icon = 'gemini.png' this.category = 'Chat Models' @@ -29,7 +29,7 @@ class GoogleGenerativeAI_ChatModels implements INode { name: 'credential', type: 'credential', credentialNames: ['googleGenerativeAI'], - optional: true, + optional: false, description: 'Google Generative AI credential.' } this.inputs = [ @@ -100,7 +100,7 @@ class GoogleGenerativeAI_ChatModels implements INode { const model = new ChatGoogleGenerativeAI(obj) if (topP) model.topP = parseFloat(topP) if (cache) model.cache = cache - if (temperature) model.temperature = parseInt(temperature) + if (temperature) model.temperature = parseFloat(temperature) return model } } diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts index 7682b280..86921b42 100644 --- a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -24,13 +24,13 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode { this.icon = 'gemini.png' this.category = 'Embeddings' this.description = 'Google Generative API to generate embeddings for a given text' - this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] + this.baseClasses = [this.type, ...getBaseClasses(GoogleGenerativeAIEmbeddings)] this.credential = { label: 'Connect Credential', name: 'credential', type: 'credential', credentialNames: ['googleGenerativeAI'], - optional: true, + optional: false, description: 'Google Generative AI credential.' } this.inputs = [ From 87233a0e365e7949b8d58aff270768f37ebf1f2d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 15 Dec 2023 16:59:08 +0000 Subject: [PATCH 059/268] Update GoogleGenerativeAIEmbedding.ts --- .../GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts index 86921b42..fa5cff45 100644 --- a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -1,4 +1,3 @@ -import { GoogleVertexAIEmbeddings } from 'langchain/embeddings/googlevertexai' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GoogleGenerativeAIEmbeddings, GoogleGenerativeAIEmbeddingsParams } from '@langchain/google-genai' From 911b4fe7fb77c9cfbf34d4929358c34a847e435c Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 15 Dec 2023 17:15:00 +0000 Subject: [PATCH 060/268] add new utilties - customfunction, setter/getter, replace code editor --- .../components/nodes/tools/CustomTool/core.ts | 32 +- .../CustomFunction/CustomFunction.ts | 124 ++++++++ .../CustomFunction/customfunction.svg | 1 + .../utilities/GetVariable/GetVariable.ts | 52 ++++ .../nodes/utilities/GetVariable/getvar.svg | 1 + .../utilities/SetVariable/SetVariable.ts | 56 ++++ .../nodes/utilities/SetVariable/setvar.svg | 1 + packages/components/src/utils.ts | 57 ++++ packages/server/src/index.ts | 22 ++ packages/server/src/utils/index.ts | 23 +- packages/ui/package.json | 11 +- packages/ui/src/api/nodes.js | 5 +- .../ui-component/dialog/ExpandTextDialog.js | 134 ++++++--- .../ui/src/ui-component/editor/CodeEditor.js | 48 +++ .../src/ui-component/editor/DarkCodeEditor.js | 43 --- .../ui-component/editor/LightCodeEditor.js | 43 --- .../ui/src/ui-component/editor/prism-dark.css | 275 ------------------ .../src/ui-component/editor/prism-light.css | 207 ------------- packages/ui/src/ui-component/input/Input.js | 32 +- .../src/ui-component/json/SelectVariable.js | 7 +- .../ui/src/views/canvas/NodeInputHandler.js | 40 ++- packages/ui/src/views/tools/ToolDialog.js | 39 +-- 22 files changed, 543 insertions(+), 710 deletions(-) create mode 100644 packages/components/nodes/utilities/CustomFunction/CustomFunction.ts create mode 100644 packages/components/nodes/utilities/CustomFunction/customfunction.svg create mode 100644 packages/components/nodes/utilities/GetVariable/GetVariable.ts create mode 100644 packages/components/nodes/utilities/GetVariable/getvar.svg create mode 100644 packages/components/nodes/utilities/SetVariable/SetVariable.ts create mode 100644 packages/components/nodes/utilities/SetVariable/setvar.svg create mode 100644 packages/ui/src/ui-component/editor/CodeEditor.js delete mode 100644 packages/ui/src/ui-component/editor/DarkCodeEditor.js delete mode 100644 packages/ui/src/ui-component/editor/LightCodeEditor.js delete mode 100644 packages/ui/src/ui-component/editor/prism-dark.css delete mode 100644 packages/ui/src/ui-component/editor/prism-light.css diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 12dd72f1..2aa06b54 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -2,37 +2,7 @@ import { z } from 'zod' import { CallbackManagerForToolRun } from 'langchain/callbacks' import { StructuredTool, ToolParams } from 'langchain/tools' import { NodeVM } from 'vm2' - -/* - * List of dependencies allowed to be import in vm2 - */ -const availableDependencies = [ - '@dqbd/tiktoken', - '@getzep/zep-js', - '@huggingface/inference', - '@pinecone-database/pinecone', - '@supabase/supabase-js', - 'axios', - 'cheerio', - 'chromadb', - 'cohere-ai', - 'd3-dsv', - 'form-data', - 'graphql', - 'html-to-text', - 'langchain', - 'linkifyjs', - 'mammoth', - 'moment', - 'node-fetch', - 'pdf-parse', - 'pdfjs-dist', - 'playwright', - 'puppeteer', - 'srt-parser-2', - 'typeorm', - 'weaviate-ts-client' -] +import { availableDependencies } from '../../../src/utils' export interface BaseDynamicToolInput extends ToolParams { name: string diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts new file mode 100644 index 00000000..b358b24b --- /dev/null +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -0,0 +1,124 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { NodeVM } from 'vm2' +import { availableDependencies, handleEscapeCharacters } from '../../../src/utils' + +class CustomFunction_Utilities implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Custom JS Function' + this.name = 'customFunction' + this.version = 1.0 + this.type = 'CustomFunction' + this.icon = 'customfunction.svg' + this.category = 'Utilities' + this.description = `Execute custom javascript function` + this.baseClasses = [this.type, 'Utilities'] + this.inputs = [ + { + label: 'Input Variables', + name: 'functionInputVariables', + description: 'Input variables can be used in the function with prefix $. For example: $var', + type: 'json', + optional: true, + acceptVariable: true, + list: true + }, + { + label: 'Function Name', + name: 'functionName', + type: 'string', + optional: true, + placeholder: 'My Function' + }, + { + label: 'Javascript Function', + name: 'javascriptFunction', + type: 'code' + } + ] + this.outputs = [ + { + label: 'Output', + name: 'output', + baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + } + ] + } + + async init(nodeData: INodeData, input: string): Promise { + const javascriptFunction = nodeData.inputs?.javascriptFunction as string + const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables + + let inputVars: ICommonObject = {} + if (functionInputVariablesRaw) { + try { + inputVars = + typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) + } catch (exception) { + throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) + } + } + + let sandbox: any = { $input: input } + + if (Object.keys(inputVars).length) { + for (const item in inputVars) { + sandbox[`$${item}`] = inputVars[item] + } + } + + const defaultAllowBuiltInDep = [ + 'assert', + 'buffer', + 'crypto', + 'events', + 'http', + 'https', + 'net', + 'path', + 'querystring', + 'timers', + 'tls', + 'url', + 'zlib' + ] + + const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP + ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) + : defaultAllowBuiltInDep + const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : [] + const deps = availableDependencies.concat(externalDeps) + + const nodeVMOptions = { + console: 'inherit', + sandbox, + require: { + external: { modules: deps }, + builtin: builtinDeps + } + } as any + + const vm = new NodeVM(nodeVMOptions) + try { + const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname) + if (typeof response === 'string') { + return handleEscapeCharacters(response, false) + } + return response + } catch (e) { + throw new Error(e) + } + } +} + +module.exports = { nodeClass: CustomFunction_Utilities } diff --git a/packages/components/nodes/utilities/CustomFunction/customfunction.svg b/packages/components/nodes/utilities/CustomFunction/customfunction.svg new file mode 100644 index 00000000..bf60fcae --- /dev/null +++ b/packages/components/nodes/utilities/CustomFunction/customfunction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/utilities/GetVariable/GetVariable.ts b/packages/components/nodes/utilities/GetVariable/GetVariable.ts new file mode 100644 index 00000000..dde5a2d9 --- /dev/null +++ b/packages/components/nodes/utilities/GetVariable/GetVariable.ts @@ -0,0 +1,52 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' + +class GetVariable_Utilities implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Get Variable' + this.name = 'getVariable' + this.version = 1.0 + this.type = 'GetVariable' + this.icon = 'getvar.svg' + this.category = 'Utilities' + this.description = `Get variable that was saved using Set Variable node` + this.baseClasses = [this.type, 'Utilities'] + this.inputs = [ + { + label: 'Variable Name', + name: 'variableName', + type: 'string', + placeholder: 'var1' + } + ] + this.outputs = [ + { + label: 'Output', + name: 'output', + baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const variableName = nodeData.inputs?.variableName as string + const dynamicVars = options.dynamicVariables as Record + + if (Object.prototype.hasOwnProperty.call(dynamicVars, variableName)) { + return dynamicVars[variableName] + } + return undefined + } +} + +module.exports = { nodeClass: GetVariable_Utilities } diff --git a/packages/components/nodes/utilities/GetVariable/getvar.svg b/packages/components/nodes/utilities/GetVariable/getvar.svg new file mode 100644 index 00000000..49e27ab1 --- /dev/null +++ b/packages/components/nodes/utilities/GetVariable/getvar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/utilities/SetVariable/SetVariable.ts b/packages/components/nodes/utilities/SetVariable/SetVariable.ts new file mode 100644 index 00000000..8542668c --- /dev/null +++ b/packages/components/nodes/utilities/SetVariable/SetVariable.ts @@ -0,0 +1,56 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' + +class SetVariable_Utilities implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Set Variable' + this.name = 'setVariable' + this.version = 1.0 + this.type = 'SetVariable' + this.icon = 'setvar.svg' + this.category = 'Utilities' + this.description = `Set variable which can be retrieved at a later stage. Variable is only available during runtime.` + this.baseClasses = [this.type, 'Utilities'] + this.inputs = [ + { + label: 'Input', + name: 'input', + type: 'string | number | boolean | json | array', + optional: true, + list: true + }, + { + label: 'Variable Name', + name: 'variableName', + type: 'string', + placeholder: 'var1' + } + ] + this.outputs = [ + { + label: 'Output', + name: 'output', + baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + } + ] + } + + async init(nodeData: INodeData): Promise { + const inputRaw = nodeData.inputs?.input + const variableName = nodeData.inputs?.variableName as string + + return { output: inputRaw, dynamicVariables: { [variableName]: inputRaw } } + } +} + +module.exports = { nodeClass: SetVariable_Utilities } diff --git a/packages/components/nodes/utilities/SetVariable/setvar.svg b/packages/components/nodes/utilities/SetVariable/setvar.svg new file mode 100644 index 00000000..c8d643c9 --- /dev/null +++ b/packages/components/nodes/utilities/SetVariable/setvar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 404f7c75..239b13ca 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -12,6 +12,63 @@ import { AIMessage, HumanMessage } from 'langchain/schema' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank +/* + * List of dependencies allowed to be import in vm2 + */ +export const availableDependencies = [ + '@aws-sdk/client-bedrock-runtime', + '@aws-sdk/client-dynamodb', + '@aws-sdk/client-s3', + '@elastic/elasticsearch', + '@dqbd/tiktoken', + '@getzep/zep-js', + '@gomomento/sdk', + '@gomomento/sdk-core', + '@google-ai/generativelanguage', + '@huggingface/inference', + '@notionhq/client', + '@opensearch-project/opensearch', + '@pinecone-database/pinecone', + '@qdrant/js-client-rest', + '@supabase/supabase-js', + '@upstash/redis', + '@zilliz/milvus2-sdk-node', + 'apify-client', + 'axios', + 'cheerio', + 'chromadb', + 'cohere-ai', + 'd3-dsv', + 'faiss-node', + 'form-data', + 'google-auth-library', + 'graphql', + 'html-to-text', + 'ioredis', + 'langchain', + 'langfuse', + 'langsmith', + 'linkifyjs', + 'llmonitor', + 'mammoth', + 'moment', + 'mongodb', + 'mysql2', + 'node-fetch', + 'node-html-markdown', + 'notion-to-md', + 'openai', + 'pdf-parse', + 'pdfjs-dist', + 'pg', + 'playwright', + 'puppeteer', + 'redis', + 'replicate', + 'srt-parser-2', + 'typeorm', + 'weaviate-ts-client' +] /** * Get base classes of components diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index fb4a5f5a..85e9eac4 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -281,6 +281,28 @@ export class App { } }) + // execute custom function node + this.app.post('/api/v1/node-custom-function', async (req: Request, res: Response) => { + const body = req.body + const nodeData = { inputs: body } + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, 'customFunction')) { + try { + const nodeInstanceFilePath = this.nodesPool.componentNodes['customFunction'].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + + const returnOptions: INodeOptionsValue[] = await newNodeInstance.init(nodeData) + + return res.json(returnOptions) + } catch (error) { + return res.status(500).send(`Error running custom function: ${error}`) + } + } else { + res.status(404).send(`Node customFunction not found`) + return + } + }) + // ---------------------------------------- // Chatflows // ---------------------------------------- diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2bf1c04a..d411050c 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -231,6 +231,7 @@ export const buildLangchain = async ( // Create a Queue and add our initial node in it const nodeQueue = [] as INodeQueue[] const exploredNode = {} as IExploredNode + const dynamicVariables = {} as Record // In the case of infinite loop, only max 3 loops will be executed const maxLoop = 3 @@ -267,20 +268,36 @@ export const buildLangchain = async ( appDataSource, databaseEntities, logger, - cachePool + cachePool, + dynamicVariables }) logger.debug(`[server]: Finished upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) break } else { logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) - flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { + let outputResult = await newNodeInstance.init(reactFlowNodeData, question, { chatId, chatflowid, appDataSource, databaseEntities, logger, - cachePool + cachePool, + dynamicVariables }) + + // Save dynamic variables + if (reactFlowNode.data.name === 'setVariable') { + const dynamicVars = outputResult?.dynamicVariables ?? {} + + for (const variableKey in dynamicVars) { + dynamicVariables[variableKey] = dynamicVars[variableKey] + } + + outputResult = outputResult?.output + } + + flowNodes[nodeIndex].data.instance = outputResult + logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) } } catch (e: any) { diff --git a/packages/ui/package.json b/packages/ui/package.json index 7a739978..2aed7d97 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -8,13 +8,20 @@ "email": "henryheng@flowiseai.com" }, "dependencies": { + "@codemirror/lang-javascript": "^6.2.1", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/view": "^6.22.3", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.0.3", - "@mui/material": "^5.11.12", + "@mui/lab": "^5.0.0-alpha.156", + "@mui/material": "^5.15.0", "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", + "@uiw/codemirror-theme-sublime": "^4.21.21", + "@uiw/codemirror-theme-vscode": "^4.21.21", + "@uiw/react-codemirror": "^4.21.21", "clsx": "^1.1.1", "flowise-embed": "*", "flowise-embed-react": "*", @@ -26,7 +33,6 @@ "lodash": "^4.17.21", "moment": "^2.29.3", "notistack": "^2.0.4", - "prismjs": "^1.28.0", "prop-types": "^15.7.2", "react": "^18.2.0", "react-code-blocks": "^0.0.9-0", @@ -39,7 +45,6 @@ "react-redux": "^8.0.5", "react-router": "~6.3.0", "react-router-dom": "~6.3.0", - "react-simple-code-editor": "^0.11.2", "react-syntax-highlighter": "^15.5.0", "reactflow": "^11.5.6", "redux": "^4.0.5", diff --git a/packages/ui/src/api/nodes.js b/packages/ui/src/api/nodes.js index 7eb4c351..3b7eacc5 100644 --- a/packages/ui/src/api/nodes.js +++ b/packages/ui/src/api/nodes.js @@ -4,7 +4,10 @@ const getAllNodes = () => client.get('/nodes') const getSpecificNode = (name) => client.get(`/nodes/${name}`) +const executeCustomFunctionNode = (body) => client.post(`/node-custom-function`, body) + export default { getAllNodes, - getSpecificNode + getSpecificNode, + executeCustomFunctionNode } diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js index 2a4ec4f5..0ef70e29 100644 --- a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js +++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js @@ -2,14 +2,24 @@ import { createPortal } from 'react-dom' import { useState, useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import PropTypes from 'prop-types' +import PerfectScrollbar from 'react-perfect-scrollbar' + +// MUI import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material' import { useTheme } from '@mui/material/styles' -import PerfectScrollbar from 'react-perfect-scrollbar' +import { LoadingButton } from '@mui/lab' + +// Project Import import { StyledButton } from 'ui-component/button/StyledButton' -import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' -import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' +import { CodeEditor } from 'ui-component/editor/CodeEditor' + +// Store import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' +// API +import nodesApi from 'api/nodes' +import useApi from 'hooks/useApi' + import './ExpandTextDialog.css' const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { @@ -18,18 +28,30 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const theme = useTheme() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) - const languageType = 'json' const [inputValue, setInputValue] = useState('') const [inputParam, setInputParam] = useState(null) + const [languageType, setLanguageType] = useState('json') + const [loading, setLoading] = useState(false) + const [codeExecutedResult, setCodeExecutedResult] = useState('') + + const executeCustomFunctionNodeApi = useApi(nodesApi.executeCustomFunctionNode) useEffect(() => { if (dialogProps.value) setInputValue(dialogProps.value) - if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) + if (dialogProps.inputParam) { + setInputParam(dialogProps.inputParam) + if (dialogProps.inputParam.type === 'code') { + setLanguageType('js') + } + } return () => { setInputValue('') + setLoading(false) setInputParam(null) + setLanguageType('json') + setCodeExecutedResult('') } }, [dialogProps]) @@ -39,11 +61,31 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { return () => dispatch({ type: HIDE_CANVAS_DIALOG }) }, [show, dispatch]) + useEffect(() => { + setLoading(executeCustomFunctionNodeApi.loading) + }, [executeCustomFunctionNodeApi.loading]) + + useEffect(() => { + if (executeCustomFunctionNodeApi.data) { + setCodeExecutedResult(executeCustomFunctionNodeApi.data) + } + }, [executeCustomFunctionNodeApi.data]) + + useEffect(() => { + if (executeCustomFunctionNodeApi.error) { + if (typeof executeCustomFunctionNodeApi.error === 'object' && executeCustomFunctionNodeApi.error?.response?.data) { + setCodeExecutedResult(executeCustomFunctionNodeApi.error?.response?.data) + } else if (typeof executeCustomFunctionNodeApi.error === 'string') { + setCodeExecutedResult(executeCustomFunctionNodeApi.error) + } + } + }, [executeCustomFunctionNodeApi.error]) + const component = show ? (

- {inputParam && inputParam.type === 'string' && ( + {inputParam && (inputParam.type === 'string' || inputParam.type === 'code') && (
{inputParam.label} @@ -54,42 +96,66 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { borderColor: theme.palette.grey['500'], borderRadius: '12px', height: '100%', - maxHeight: 'calc(100vh - 220px)', + maxHeight: languageType === 'js' ? 'calc(100vh - 250px)' : 'calc(100vh - 220px)', overflowX: 'hidden', backgroundColor: 'white' }} > - {customization.isDarkMode ? ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - ) : ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - )} + setInputValue(code)} + />
)}
+ {languageType === 'js' && ( + { + setLoading(true) + executeCustomFunctionNodeApi.request({ javascriptFunction: inputValue }) + }} + > + Execute + + )} + {codeExecutedResult && ( +
+ +
+ )}
diff --git a/packages/ui/src/ui-component/editor/CodeEditor.js b/packages/ui/src/ui-component/editor/CodeEditor.js new file mode 100644 index 00000000..120e19a0 --- /dev/null +++ b/packages/ui/src/ui-component/editor/CodeEditor.js @@ -0,0 +1,48 @@ +import PropTypes from 'prop-types' +import CodeMirror from '@uiw/react-codemirror' +import { javascript } from '@codemirror/lang-javascript' +import { json } from '@codemirror/lang-json' +import { vscodeDark } from '@uiw/codemirror-theme-vscode' +import { sublime } from '@uiw/codemirror-theme-sublime' +import { EditorView } from '@codemirror/view' + +export const CodeEditor = ({ value, height, theme, lang, placeholder, disabled = false, basicSetup = {}, onValueChange }) => { + const customStyle = EditorView.baseTheme({ + '&': { + color: '#191b1f', + padding: '10px' + }, + '.cm-placeholder': { + color: 'rgba(120, 120, 120, 0.5)' + } + }) + + return ( + + ) +} + +CodeEditor.propTypes = { + value: PropTypes.string, + height: PropTypes.string, + theme: PropTypes.string, + lang: PropTypes.string, + placeholder: PropTypes.string, + disabled: PropTypes.bool, + basicSetup: PropTypes.object, + onValueChange: PropTypes.func +} diff --git a/packages/ui/src/ui-component/editor/DarkCodeEditor.js b/packages/ui/src/ui-component/editor/DarkCodeEditor.js deleted file mode 100644 index bf0719dd..00000000 --- a/packages/ui/src/ui-component/editor/DarkCodeEditor.js +++ /dev/null @@ -1,43 +0,0 @@ -import Editor from 'react-simple-code-editor' -import { highlight, languages } from 'prismjs/components/prism-core' -import 'prismjs/components/prism-clike' -import 'prismjs/components/prism-javascript' -import 'prismjs/components/prism-json' -import 'prismjs/components/prism-markup' -import './prism-dark.css' -import PropTypes from 'prop-types' -import { useTheme } from '@mui/material/styles' - -export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { - const theme = useTheme() - - return ( - highlight(code, type === 'json' ? languages.json : languages.js)} - padding={10} - onValueChange={onValueChange} - onMouseUp={onMouseUp} - onBlur={onBlur} - tabSize={4} - style={{ - ...style, - background: theme.palette.codeEditor.main - }} - textareaClassName='editor__textarea' - /> - ) -} - -DarkCodeEditor.propTypes = { - value: PropTypes.string, - placeholder: PropTypes.string, - disabled: PropTypes.bool, - type: PropTypes.string, - style: PropTypes.object, - onValueChange: PropTypes.func, - onMouseUp: PropTypes.func, - onBlur: PropTypes.func -} diff --git a/packages/ui/src/ui-component/editor/LightCodeEditor.js b/packages/ui/src/ui-component/editor/LightCodeEditor.js deleted file mode 100644 index 14dcbf29..00000000 --- a/packages/ui/src/ui-component/editor/LightCodeEditor.js +++ /dev/null @@ -1,43 +0,0 @@ -import Editor from 'react-simple-code-editor' -import { highlight, languages } from 'prismjs/components/prism-core' -import 'prismjs/components/prism-clike' -import 'prismjs/components/prism-javascript' -import 'prismjs/components/prism-json' -import 'prismjs/components/prism-markup' -import './prism-light.css' -import PropTypes from 'prop-types' -import { useTheme } from '@mui/material/styles' - -export const LightCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { - const theme = useTheme() - - return ( - highlight(code, type === 'json' ? languages.json : languages.js)} - padding={10} - onValueChange={onValueChange} - onMouseUp={onMouseUp} - onBlur={onBlur} - tabSize={4} - style={{ - ...style, - background: theme.palette.card.main - }} - textareaClassName='editor__textarea' - /> - ) -} - -LightCodeEditor.propTypes = { - value: PropTypes.string, - placeholder: PropTypes.string, - disabled: PropTypes.bool, - type: PropTypes.string, - style: PropTypes.object, - onValueChange: PropTypes.func, - onMouseUp: PropTypes.func, - onBlur: PropTypes.func -} diff --git a/packages/ui/src/ui-component/editor/prism-dark.css b/packages/ui/src/ui-component/editor/prism-dark.css deleted file mode 100644 index c4bfb413..00000000 --- a/packages/ui/src/ui-component/editor/prism-dark.css +++ /dev/null @@ -1,275 +0,0 @@ -pre[class*='language-'], -code[class*='language-'] { - color: #d4d4d4; - font-size: 13px; - text-shadow: none; - font-family: Menlo, Monaco, Consolas, 'Andale Mono', 'Ubuntu Mono', 'Courier New', monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*='language-']::selection, -code[class*='language-']::selection, -pre[class*='language-'] *::selection, -code[class*='language-'] *::selection { - text-shadow: none; - background: #264f78; -} - -@media print { - pre[class*='language-'], - code[class*='language-'] { - text-shadow: none; - } -} - -pre[class*='language-'] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; - background: #1e1e1e; -} - -:not(pre) > code[class*='language-'] { - padding: 0.1em 0.3em; - border-radius: 0.3em; - color: #db4c69; - background: #1e1e1e; -} -/********************************************************* -* Tokens -*/ -.namespace { - opacity: 0.7; -} - -.token.doctype .token.doctype-tag { - color: #569cd6; -} - -.token.doctype .token.name { - color: #9cdcfe; -} - -.token.comment, -.token.prolog { - color: #6a9955; -} - -.token.punctuation, -.language-html .language-css .token.punctuation, -.language-html .language-javascript .token.punctuation { - color: #d4d4d4; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.inserted, -.token.unit { - color: #b5cea8; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.deleted { - color: #ce9178; -} - -.language-css .token.string.url { - text-decoration: underline; -} - -.token.operator, -.token.entity { - color: #d4d4d4; -} - -.token.operator.arrow { - color: #569cd6; -} - -.token.atrule { - color: #ce9178; -} - -.token.atrule .token.rule { - color: #c586c0; -} - -.token.atrule .token.url { - color: #9cdcfe; -} - -.token.atrule .token.url .token.function { - color: #dcdcaa; -} - -.token.atrule .token.url .token.punctuation { - color: #d4d4d4; -} - -.token.keyword { - color: #569cd6; -} - -.token.keyword.module, -.token.keyword.control-flow { - color: #c586c0; -} - -.token.function, -.token.function .token.maybe-class-name { - color: #dcdcaa; -} - -.token.regex { - color: #d16969; -} - -.token.important { - color: #569cd6; -} - -.token.italic { - font-style: italic; -} - -.token.constant { - color: #9cdcfe; -} - -.token.class-name, -.token.maybe-class-name { - color: #4ec9b0; -} - -.token.console { - color: #9cdcfe; -} - -.token.parameter { - color: #9cdcfe; -} - -.token.interpolation { - color: #9cdcfe; -} - -.token.punctuation.interpolation-punctuation { - color: #569cd6; -} - -.token.boolean { - color: #569cd6; -} - -.token.property, -.token.variable, -.token.imports .token.maybe-class-name, -.token.exports .token.maybe-class-name { - color: #9cdcfe; -} - -.token.selector { - color: #d7ba7d; -} - -.token.escape { - color: #d7ba7d; -} - -.token.tag { - color: #569cd6; -} - -.token.tag .token.punctuation { - color: #808080; -} - -.token.cdata { - color: #808080; -} - -.token.attr-name { - color: #9cdcfe; -} - -.token.attr-value, -.token.attr-value .token.punctuation { - color: #ce9178; -} - -.token.attr-value .token.punctuation.attr-equals { - color: #d4d4d4; -} - -.token.entity { - color: #569cd6; -} - -.token.namespace { - color: #4ec9b0; -} -/********************************************************* -* Language Specific -*/ - -pre[class*='language-javascript'], -code[class*='language-javascript'], -pre[class*='language-jsx'], -code[class*='language-jsx'], -pre[class*='language-typescript'], -code[class*='language-typescript'], -pre[class*='language-tsx'], -code[class*='language-tsx'] { - color: #9cdcfe; -} - -pre[class*='language-css'], -code[class*='language-css'] { - color: #ce9178; -} - -pre[class*='language-html'], -code[class*='language-html'] { - color: #d4d4d4; -} - -.language-regex .token.anchor { - color: #dcdcaa; -} - -.language-html .token.punctuation { - color: #808080; -} -/********************************************************* -* Line highlighting -*/ -pre[class*='language-'] > code[class*='language-'] { - position: relative; - z-index: 1; -} - -.line-highlight.line-highlight { - background: #f7ebc6; - box-shadow: inset 5px 0 0 #f7d87c; - z-index: 0; -} diff --git a/packages/ui/src/ui-component/editor/prism-light.css b/packages/ui/src/ui-component/editor/prism-light.css deleted file mode 100644 index 95d6d6eb..00000000 --- a/packages/ui/src/ui-component/editor/prism-light.css +++ /dev/null @@ -1,207 +0,0 @@ -code[class*='language-'], -pre[class*='language-'] { - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - color: #90a4ae; - background: #fafafa; - font-family: Roboto Mono, monospace; - font-size: 1em; - line-height: 1.5em; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -code[class*='language-']::-moz-selection, -pre[class*='language-']::-moz-selection, -code[class*='language-'] ::-moz-selection, -pre[class*='language-'] ::-moz-selection { - background: #cceae7; - color: #263238; -} - -code[class*='language-']::selection, -pre[class*='language-']::selection, -code[class*='language-'] ::selection, -pre[class*='language-'] ::selection { - background: #cceae7; - color: #263238; -} - -:not(pre) > code[class*='language-'] { - white-space: normal; - border-radius: 0.2em; - padding: 0.1em; -} - -pre[class*='language-'] { - overflow: auto; - position: relative; - margin: 0.5em 0; - padding: 1.25em 1em; -} - -.language-css > code, -.language-sass > code, -.language-scss > code { - color: #f76d47; -} - -[class*='language-'] .namespace { - opacity: 0.7; -} - -.token.atrule { - color: #7c4dff; -} - -.token.attr-name { - color: #39adb5; -} - -.token.attr-value { - color: #f6a434; -} - -.token.attribute { - color: #f6a434; -} - -.token.boolean { - color: #7c4dff; -} - -.token.builtin { - color: #39adb5; -} - -.token.cdata { - color: #39adb5; -} - -.token.char { - color: #39adb5; -} - -.token.class { - color: #39adb5; -} - -.token.class-name { - color: #6182b8; -} - -.token.comment { - color: #aabfc9; -} - -.token.constant { - color: #7c4dff; -} - -.token.deleted { - color: #e53935; -} - -.token.doctype { - color: #aabfc9; -} - -.token.entity { - color: #e53935; -} - -.token.function { - color: #7c4dff; -} - -.token.hexcode { - color: #f76d47; -} - -.token.id { - color: #7c4dff; - font-weight: bold; -} - -.token.important { - color: #7c4dff; - font-weight: bold; -} - -.token.inserted { - color: #39adb5; -} - -.token.keyword { - color: #7c4dff; -} - -.token.number { - color: #f76d47; -} - -.token.operator { - color: #39adb5; -} - -.token.prolog { - color: #aabfc9; -} - -.token.property { - color: #39adb5; -} - -.token.pseudo-class { - color: #f6a434; -} - -.token.pseudo-element { - color: #f6a434; -} - -.token.punctuation { - color: #39adb5; -} - -.token.regex { - color: #6182b8; -} - -.token.selector { - color: #e53935; -} - -.token.string { - color: #f6a434; -} - -.token.symbol { - color: #7c4dff; -} - -.token.tag { - color: #e53935; -} - -.token.unit { - color: #f76d47; -} - -.token.url { - color: #e53935; -} - -.token.variable { - color: #e53935; -} diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 6993847b..3e575938 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,23 +1,10 @@ import { useState, useEffect, useRef } from 'react' import PropTypes from 'prop-types' import { FormControl, OutlinedInput, Popover } from '@mui/material' -import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' import SelectVariable from 'ui-component/json/SelectVariable' import { getAvailableNodesForVariable } from 'utils/genericHelper' -export const Input = ({ - inputParam, - value, - nodes, - edges, - nodeId, - onChange, - disabled = false, - showDialog, - dialogProps, - onDialogCancel, - onDialogConfirm -}) => { +export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disabled = false }) => { const [myValue, setMyValue] = useState(value ?? '') const [anchorEl, setAnchorEl] = useState(null) const [availableNodesForVariable, setAvailableNodesForVariable] = useState([]) @@ -86,17 +73,6 @@ export const Input = ({ }} /> - {showDialog && ( - { - setMyValue(newValue) - onDialogConfirm(newValue, inputParamName) - }} - > - )}
{inputParam?.acceptVariable && ( diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 892a6273..18e82338 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -22,8 +22,11 @@ import { flowContext } from 'store/context/ReactFlowContext' import { isValidConnection } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import { CodeEditor } from 'ui-component/editor/CodeEditor' + import ToolDialog from 'views/tools/ToolDialog' import AssistantDialog from 'views/assistants/AssistantDialog' +import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog' import CredentialInputHandler from './CredentialInputHandler' @@ -83,7 +86,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA } } } - const onFormatPromptValuesClicked = (value, inputParam) => { + const onEditJSONClicked = (value, inputParam) => { // Preset values if the field is format prompt values let inputValue = value if (inputParam.name === 'promptValues' && !value) { @@ -255,7 +258,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam.description && }
- {inputParam.type === 'string' && inputParam.rows && ( + {((inputParam.type === 'string' && inputParam.rows) || inputParam.type === 'code') && ( (data.inputs[inputParam.name] = newValue)} /> )} + {inputParam.type === 'code' && ( + <> +
+
+ (data.inputs[inputParam.name] = code)} + basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} + /> +
+ + )} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( setShowExpandDialog(false)} - onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} /> )} {inputParam.type === 'json' && ( @@ -353,11 +369,11 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam?.acceptVariable && ( <> setAsyncOptionEditDialog('')} onConfirm={onConfirmAsyncOption} > + setShowExpandDialog(false)} + onConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} + >
) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index 398e9eb8..6272e05f 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -12,9 +12,7 @@ import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' import { GridActionsCellItem } from '@mui/x-data-grid' import DeleteIcon from '@mui/icons-material/Delete' import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' -import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' -import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' -import { useTheme } from '@mui/material/styles' +import { CodeEditor } from 'ui-component/editor/CodeEditor' // Icons import { IconX, IconFileExport } from '@tabler/icons' @@ -56,7 +54,6 @@ try { const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') - const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -490,32 +487,14 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = See Example )} - {customization.isDarkMode ? ( - setToolFunc(code)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%', - borderRadius: 5 - }} - /> - ) : ( - setToolFunc(code)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%', - border: `1px solid ${theme.palette.grey[300]}`, - borderRadius: 5 - }} - /> - )} + setToolFunc(code)} + />
From 6e4822c3bb9d4062528817dc157f69e86bebeda2 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 15 Dec 2023 17:49:29 +0000 Subject: [PATCH 061/268] update bedrock --- .../chatmodels/AWSBedrock/AWSChatBedrock.ts | 24 +++++++++++++------ .../AWSBedrockEmbedding.ts | 12 ++++++++-- .../nodes/llms/AWSBedrock/AWSBedrock.ts | 14 +++++++++-- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts index 956fcdb3..29faf524 100644 --- a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts +++ b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts @@ -1,9 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { ChatBedrock } from 'langchain/chat_models/bedrock' +import { BedrockChat } from 'langchain/chat_models/bedrock' import { BaseBedrockInput } from 'langchain/dist/util/bedrock' import { BaseCache } from 'langchain/schema' -import { BaseLLMParams } from 'langchain/llms/base' +import { BaseChatModelParams } from 'langchain/chat_models/base' /** * I had to run the following to build the component @@ -25,14 +25,14 @@ class AWSChatBedrock_ChatModels implements INode { inputs: INodeParams[] constructor() { - this.label = 'AWS Bedrock' + this.label = 'AWS ChatBedrock' this.name = 'awsChatBedrock' this.version = 3.0 this.type = 'AWSChatBedrock' this.icon = 'awsBedrock.png' this.category = 'Chat Models' this.description = 'Wrapper around AWS Bedrock large language models that use the Chat endpoint' - this.baseClasses = [this.type, ...getBaseClasses(ChatBedrock)] + this.baseClasses = [this.type, ...getBaseClasses(BedrockChat)] this.credential = { label: 'AWS Credential', name: 'credential', @@ -102,6 +102,13 @@ class AWSChatBedrock_ChatModels implements INode { ], default: 'anthropic.claude-v2' }, + { + label: 'Custom Model Name', + name: 'customModel', + description: 'If provided, will override model selected from Model Name option', + type: 'string', + optional: true + }, { label: 'Temperature', name: 'temperature', @@ -109,6 +116,7 @@ class AWSChatBedrock_ChatModels implements INode { step: 0.1, description: 'Temperature parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 0.7 }, { @@ -118,6 +126,7 @@ class AWSChatBedrock_ChatModels implements INode { step: 10, description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 200 } ] @@ -126,14 +135,15 @@ class AWSChatBedrock_ChatModels implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const iRegion = nodeData.inputs?.region as string const iModel = nodeData.inputs?.model as string + const customModel = nodeData.inputs?.customModel as string const iTemperature = nodeData.inputs?.temperature as string const iMax_tokens_to_sample = nodeData.inputs?.max_tokens_to_sample as string const cache = nodeData.inputs?.cache as BaseCache const streaming = nodeData.inputs?.streaming as boolean - const obj: BaseBedrockInput & BaseLLMParams = { + const obj: BaseBedrockInput & BaseChatModelParams = { region: iRegion, - model: iModel, + model: customModel ?? iModel, maxTokens: parseInt(iMax_tokens_to_sample, 10), temperature: parseFloat(iTemperature), streaming: streaming ?? true @@ -160,7 +170,7 @@ class AWSChatBedrock_ChatModels implements INode { } if (cache) obj.cache = cache - const amazonBedrock = new ChatBedrock(obj) + const amazonBedrock = new BedrockChat(obj) return amazonBedrock } } diff --git a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts index 8249d512..5f7ce17c 100644 --- a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts +++ b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts @@ -18,7 +18,7 @@ class AWSBedrockEmbedding_Embeddings implements INode { constructor() { this.label = 'AWS Bedrock Embeddings' this.name = 'AWSBedrockEmbeddings' - this.version = 2.0 + this.version = 3.0 this.type = 'AWSBedrockEmbeddings' this.icon = 'awsBedrock.png' this.category = 'Embeddings' @@ -86,6 +86,13 @@ class AWSBedrockEmbedding_Embeddings implements INode { { label: 'cohere.embed-multilingual-v3', name: 'cohere.embed-multilingual-v3' } ], default: 'amazon.titan-embed-text-v1' + }, + { + label: 'Custom Model Name', + name: 'customModel', + description: 'If provided, will override model selected from Model Name option', + type: 'string', + optional: true } ] } @@ -93,9 +100,10 @@ class AWSBedrockEmbedding_Embeddings implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const iRegion = nodeData.inputs?.region as string const iModel = nodeData.inputs?.model as string + const customModel = nodeData.inputs?.customModel as string const obj: BedrockEmbeddingsParams = { - model: iModel, + model: customModel ?? iModel, region: iRegion } diff --git a/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts b/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts index 177a32ef..459c4296 100644 --- a/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts +++ b/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts @@ -27,7 +27,7 @@ class AWSBedrock_LLMs implements INode { constructor() { this.label = 'AWS Bedrock' this.name = 'awsBedrock' - this.version = 2.0 + this.version = 3.0 this.type = 'AWSBedrock' this.icon = 'awsBedrock.png' this.category = 'LLMs' @@ -105,6 +105,13 @@ class AWSBedrock_LLMs implements INode { { label: 'ai21.j2-ultra', name: 'ai21.j2-ultra' } ] }, + { + label: 'Custom Model Name', + name: 'customModel', + description: 'If provided, will override model selected from Model Name option', + type: 'string', + optional: true + }, { label: 'Temperature', name: 'temperature', @@ -112,6 +119,7 @@ class AWSBedrock_LLMs implements INode { step: 0.1, description: 'Temperature parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 0.7 }, { @@ -121,6 +129,7 @@ class AWSBedrock_LLMs implements INode { step: 10, description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 200 } ] @@ -129,11 +138,12 @@ class AWSBedrock_LLMs implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const iRegion = nodeData.inputs?.region as string const iModel = nodeData.inputs?.model as string + const customModel = nodeData.inputs?.customModel as string const iTemperature = nodeData.inputs?.temperature as string const iMax_tokens_to_sample = nodeData.inputs?.max_tokens_to_sample as string const cache = nodeData.inputs?.cache as BaseCache const obj: Partial & BaseLLMParams = { - model: iModel, + model: customModel ?? iModel, region: iRegion, temperature: parseFloat(iTemperature), maxTokens: parseInt(iMax_tokens_to_sample, 10) From 05db5333968012abb18ea9e41c825c1e23b3f93f Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 15 Dec 2023 18:59:11 +0000 Subject: [PATCH 062/268] add mistral --- .../GoogleGenerativeAI.credential.ts | 3 +- .../credentials/MistralApi.credential.ts | 25 +++ .../ChatGoogleGenerativeAI.ts | 3 +- .../chatmodels/ChatMistral/ChatMistral.ts | 151 ++++++++++++++++++ .../chatmodels/ChatMistral/mistralai.png | Bin 0 -> 4542 bytes .../MistralEmbedding/MistralEmbedding.ts | 95 +++++++++++ .../embeddings/MistralEmbedding/mistralai.png | Bin 0 -> 4542 bytes packages/components/package.json | 1 + packages/server/src/utils/index.ts | 1 + 9 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 packages/components/credentials/MistralApi.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts create mode 100644 packages/components/nodes/chatmodels/ChatMistral/mistralai.png create mode 100644 packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts create mode 100644 packages/components/nodes/embeddings/MistralEmbedding/mistralai.png diff --git a/packages/components/credentials/GoogleGenerativeAI.credential.ts b/packages/components/credentials/GoogleGenerativeAI.credential.ts index 9a1f3f28..e5ad45bf 100644 --- a/packages/components/credentials/GoogleGenerativeAI.credential.ts +++ b/packages/components/credentials/GoogleGenerativeAI.credential.ts @@ -11,7 +11,8 @@ class GoogleGenerativeAICredential implements INodeCredential { this.label = 'Google Generative AI' this.name = 'googleGenerativeAI' this.version = 1.0 - this.description = 'Get your API Key here.' + this.description = + 'You can get your API key from official page here.' this.inputs = [ { label: 'Google AI API Key', diff --git a/packages/components/credentials/MistralApi.credential.ts b/packages/components/credentials/MistralApi.credential.ts new file mode 100644 index 00000000..a254f665 --- /dev/null +++ b/packages/components/credentials/MistralApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class MistralAICredential implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'MistralAI API' + this.name = 'mistralAIApi' + this.version = 1.0 + this.description = 'You can get your API key from official console here.' + this.inputs = [ + { + label: 'MistralAI API Key', + name: 'mistralAIAPIKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: MistralAICredential } diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 95ee0575..7044645f 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -49,8 +49,7 @@ class GoogleGenerativeAI_ChatModels implements INode { name: 'gemini-pro' } ], - default: 'gemini-pro', - optional: true + default: 'gemini-pro' }, { label: 'Temperature', diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts new file mode 100644 index 00000000..d9db85cd --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -0,0 +1,151 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { BaseCache } from 'langchain/schema' +import { ChatMistralAI, ChatMistralAIInput } from '@langchain/mistralai' + +class ChatMistral_ChatModels implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'ChatMistralAI' + this.name = 'chatMistralAI' + this.version = 1.0 + this.type = 'ChatMistralAI' + this.icon = 'mistralai.png' + this.category = 'Chat Models' + this.description = 'Wrapper around Mistral large language models that use the Chat endpoint' + this.baseClasses = [this.type, ...getBaseClasses(ChatMistralAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['mistralAIApi'] + } + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'mistral-tiny', + name: 'mistral-tiny' + }, + { + label: 'mistral-small', + name: 'mistral-small' + }, + { + label: 'mistral-medium', + name: 'mistral-medium' + } + ], + default: 'mistral-tiny' + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + description: + 'What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Max Output Tokens', + name: 'maxOutputTokens', + type: 'number', + description: 'The maximum number of tokens to generate in the completion.', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + description: + 'Nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Random Seed', + name: 'randomSeed', + type: 'number', + description: 'The seed to use for random sampling. If set, different calls will generate deterministic results.', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Safe Mode', + name: 'safeMode', + type: 'boolean', + description: 'Whether to inject a safety prompt before all conversations.', + optional: true, + additionalParams: true + }, + { + label: 'Override Endpoint', + name: 'overrideEndpoint', + type: 'string', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('mistralAIAPIKey', credentialData, nodeData) + + const temperature = nodeData.inputs?.temperature as string + const modelName = nodeData.inputs?.modelName as string + const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string + const topP = nodeData.inputs?.topP as string + const safeMode = nodeData.inputs?.safeMode as boolean + const randomSeed = nodeData.inputs?.safeMode as string + const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string + // Waiting fix from langchain to enable streaming + const streaming = nodeData.inputs?.streaming as boolean + + const cache = nodeData.inputs?.cache as BaseCache + + const obj: ChatMistralAIInput = { + apiKey: apiKey, + modelName: modelName + } + + if (maxOutputTokens) obj.maxTokens = parseInt(maxOutputTokens, 10) + if (topP) obj.topP = parseFloat(topP) + if (cache) obj.cache = cache + if (temperature) obj.temperature = parseFloat(temperature) + if (randomSeed) obj.randomSeed = parseFloat(randomSeed) + if (safeMode) obj.safeMode = safeMode + if (overrideEndpoint) obj.endpoint = overrideEndpoint + + const model = new ChatMistralAI(obj) + + return model + } +} + +module.exports = { nodeClass: ChatMistral_ChatModels } diff --git a/packages/components/nodes/chatmodels/ChatMistral/mistralai.png b/packages/components/nodes/chatmodels/ChatMistral/mistralai.png new file mode 100644 index 0000000000000000000000000000000000000000..1019f495d4d690dd639aa9f4e5751c403b2eff27 GIT binary patch literal 4542 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

GOhkykkrx z5<$gl4%{{1&%57c5Qv$XoY%TcILb6UdoL+VK0U}ckiTqHx51ZU zmwjXW&11_L&eEgCFq@TP2dU4yzzlh^iUi9p_NpSM8p1Mj^7zmjK?2}Op8*u3nr2;h Kf#<=y@BRlu0mfYb literal 0 HcmV?d00001 diff --git a/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts b/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts new file mode 100644 index 00000000..d0a0198c --- /dev/null +++ b/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts @@ -0,0 +1,95 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { MistralAIEmbeddings, MistralAIEmbeddingsParams } from '@langchain/mistralai' + +class MistralEmbedding_Embeddings implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'MistralAI Embeddings' + this.name = 'mistralAI Embeddings' + this.version = 1.0 + this.type = 'MistralAIEmbeddings' + this.icon = 'mistralai.png' + this.category = 'Embeddings' + this.description = 'MistralAI API to generate embeddings for a given text' + this.baseClasses = [this.type, ...getBaseClasses(MistralAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['mistralAIApi'] + } + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'mistral-embed', + name: 'mistral-embed' + } + ], + default: 'mistral-embed' + }, + { + label: 'Batch Size', + name: 'batchSize', + type: 'number', + step: 1, + default: 512, + optional: true, + additionalParams: true + }, + { + label: 'Strip New Lines', + name: 'stripNewLines', + type: 'boolean', + default: true, + optional: true, + additionalParams: true + }, + { + label: 'Override Endpoint', + name: 'overrideEndpoint', + type: 'string', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const modelName = nodeData.inputs?.modelName as string + const batchSize = nodeData.inputs?.batchSize as string + const stripNewLines = nodeData.inputs?.stripNewLines as boolean + const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('mistralAIAPIKey', credentialData, nodeData) + + const obj: MistralAIEmbeddingsParams = { + apiKey: apiKey, + modelName: modelName + } + + if (batchSize) obj.batchSize = parseInt(batchSize, 10) + if (stripNewLines) obj.stripNewLines = stripNewLines + if (overrideEndpoint) obj.endpoint = overrideEndpoint + + const model = new MistralAIEmbeddings(obj) + return model + } +} + +module.exports = { nodeClass: MistralEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png b/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png new file mode 100644 index 0000000000000000000000000000000000000000..1019f495d4d690dd639aa9f4e5751c403b2eff27 GIT binary patch literal 4542 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

GOhkykkrx z5<$gl4%{{1&%57c5Qv$XoY%TcILb6UdoL+VK0U}ckiTqHx51ZU zmwjXW&11_L&eEgCFq@TP2dU4yzzlh^iUi9p_NpSM8p1Mj^7zmjK?2}Op8*u3nr2;h Kf#<=y@BRlu0mfYb literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 6bdc908a..cbc347ff 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -27,6 +27,7 @@ "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", "@langchain/google-genai": "^0.0.3", + "@langchain/mistralai": "^0.0.2", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2bf1c04a..5f12c66e 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -711,6 +711,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component /** * Check to see if flow valid for stream + * TODO: perform check from component level. i.e: set streaming on component, and check here * @param {IReactFlowNode[]} reactFlowNodes * @param {INodeData} endingNodeData * @returns {boolean} From 5a66d238699212e1a759aa7d86261fbb8f065aeb Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 15 Dec 2023 23:53:47 +0000 Subject: [PATCH 063/268] Update package.json --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index cbc347ff..52e59b41 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -27,7 +27,7 @@ "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", "@langchain/google-genai": "^0.0.3", - "@langchain/mistralai": "^0.0.2", + "@langchain/mistralai": "^0.0.3", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", From 0707072a3e389dbeddf42a2a339ed40eb58fe6ca Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 15 Dec 2023 23:54:39 +0000 Subject: [PATCH 064/268] Update ChatMistral.ts --- packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts index d9db85cd..9e12251f 100644 --- a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -124,7 +124,7 @@ class ChatMistral_ChatModels implements INode { const safeMode = nodeData.inputs?.safeMode as boolean const randomSeed = nodeData.inputs?.safeMode as string const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string - // Waiting fix from langchain to enable streaming + // Waiting fix from langchain + mistral to enable streaming - https://github.com/mistralai/client-js/issues/18 const streaming = nodeData.inputs?.streaming as boolean const cache = nodeData.inputs?.cache as BaseCache From e8af8b007a8f8b06d5014f59f2bacd0bf6376414 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 17 Dec 2023 18:58:37 +0000 Subject: [PATCH 065/268] add marketplace template --- .../marketplaces/chatflows/SQL Prompt.json | 1237 +++++++++++++++++ packages/server/src/index.ts | 7 +- .../ui/src/views/canvas/NodeInputHandler.js | 1 + 3 files changed, 1242 insertions(+), 3 deletions(-) create mode 100644 packages/server/marketplaces/chatflows/SQL Prompt.json diff --git a/packages/server/marketplaces/chatflows/SQL Prompt.json b/packages/server/marketplaces/chatflows/SQL Prompt.json new file mode 100644 index 00000000..9244e8de --- /dev/null +++ b/packages/server/marketplaces/chatflows/SQL Prompt.json @@ -0,0 +1,1237 @@ +{ + "description": "Manually construct prompts to query a SQL database", + "badge": "new", + "nodes": [ + { + "width": 300, + "height": 511, + "id": "promptTemplate_0", + "position": { + "x": 638.5481508577102, + "y": 84.0454315632386 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "version": 1, + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Based on the provided SQL table schema and question below, return a SQL SELECT ALL query that would answer the user's question. For example: SELECT * FROM table WHERE id = '1'.\n------------\nSCHEMA: {schema}\n------------\nQUESTION: {question}\n------------\nSQL QUERY:", + "promptValues": "{\"schema\":\"{{setVariable_0.data.instance}}\",\"question\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 638.5481508577102, + "y": 84.0454315632386 + }, + "dragging": false + }, + { + "width": 300, + "height": 507, + "id": "llmChain_0", + "position": { + "x": 1095.1973126620626, + "y": -83.98379829183628 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_0-input-prompt-BasePromptTemplate" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "llmChain_0-input-inputModeration-Moderation" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "inputModeration": "", + "chainName": "SQL Query Chain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_0-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1095.1973126620626, + "y": -83.98379829183628 + }, + "dragging": false + }, + { + "width": 300, + "height": 574, + "id": "chatOpenAI_0", + "position": { + "x": 636.5762708317321, + "y": -543.3151550847003 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 2, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-1106-preview", + "name": "gpt-4-1106-preview" + }, + { + "label": "gpt-4-vision-preview", + "name": "gpt-4-vision-preview" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-1106", + "name": "gpt-3.5-turbo-1106" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 636.5762708317321, + "y": -543.3151550847003 + }, + "dragging": false + }, + { + "width": 300, + "height": 574, + "id": "chatOpenAI_1", + "position": { + "x": 2636.1598769864936, + "y": -653.0025971757484 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_1", + "label": "ChatOpenAI", + "version": 2, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_1-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-1106-preview", + "name": "gpt-4-1106-preview" + }, + { + "label": "gpt-4-vision-preview", + "name": "gpt-4-vision-preview" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-1106", + "name": "gpt-3.5-turbo-1106" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_1-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_1-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_1-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2636.1598769864936, + "y": -653.0025971757484 + }, + "dragging": false + }, + { + "width": 300, + "height": 507, + "id": "llmChain_1", + "position": { + "x": 3089.9937691022837, + "y": -109.24001734925716 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "version": 3, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_1-input-outputParser-BaseLLMOutputParser" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "llmChain_1-input-inputModeration-Moderation" + } + ], + "inputs": { + "model": "{{chatOpenAI_1.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", + "outputParser": "", + "inputModeration": "", + "chainName": "Final Chain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_1-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_1-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 3089.9937691022837, + "y": -109.24001734925716 + }, + "dragging": false + }, + { + "width": 300, + "height": 669, + "id": "customFunction_2", + "position": { + "x": -152.63957160907668, + "y": -212.74538890862547 + }, + "type": "customNode", + "data": { + "id": "customFunction_2", + "label": "Custom JS Function", + "version": 1, + "name": "customFunction", + "type": "CustomFunction", + "baseClasses": ["CustomFunction", "Utilities"], + "category": "Utilities", + "description": "Execute custom javascript function", + "inputParams": [ + { + "label": "Input Variables", + "name": "functionInputVariables", + "description": "Input variables can be used in the function with prefix $. For example: $var", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "customFunction_2-input-functionInputVariables-json" + }, + { + "label": "Function Name", + "name": "functionName", + "type": "string", + "placeholder": "My Function", + "id": "customFunction_2-input-functionName-string" + }, + { + "label": "Javascript Function", + "name": "javascriptFunction", + "type": "code", + "id": "customFunction_2-input-javascriptFunction-code" + } + ], + "inputAnchors": [], + "inputs": { + "functionInputVariables": "", + "functionName": "Get SQL Schema Prompt", + "javascriptFunction": "const HOST = 'singlestore-host';\nconst USER = 'admin';\nconst PASSWORD = 'mypassword';\nconst DATABASE = 'mydb';\nconst TABLE = 'samples';\nconst mysql = require('mysql2/promise');\n\nlet sqlSchemaPrompt;\n\n/**\n * Ideal prompt contains schema info and examples\n * Follows best practices as specified form https://arxiv.org/abs/2204.00498\n * =========================================\n * CREATE TABLE samples (firstName varchar NOT NULL, lastName varchar)\n * SELECT * FROM samples LIMIT 3\n * firstName lastName\n * Stephen Tyler\n * Jack McGinnis\n * Steven Repici\n * =========================================\n*/\nfunction getSQLPrompt() {\n return new Promise(async (resolve, reject) => {\n \n const singleStoreConnection = mysql.createPool({\n host: HOST,\n user: USER,\n password: PASSWORD,\n database: DATABASE,\n });\n\n // Get schema info\n const [schemaInfo] = await singleStoreConnection.execute(\n `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = \"${TABLE}\"`\n );\n\n const createColumns = [];\n const columnNames = [];\n\n for (const schemaData of schemaInfo) {\n columnNames.push(`${schemaData['COLUMN_NAME']}`);\n createColumns.push(`${schemaData['COLUMN_NAME']} ${schemaData['COLUMN_TYPE']} ${schemaData['IS_NULLABLE'] === 'NO' ? 'NOT NULL' : ''}`);\n }\n\n const sqlCreateTableQuery = `CREATE TABLE samples (${createColumns.join(', ')})`;\n const sqlSelectTableQuery = `SELECT * FROM samples LIMIT 3`;\n\n // Get first 3 rows\n const [rows] = await singleStoreConnection.execute(\n sqlSelectTableQuery,\n );\n \n const allValues = [];\n for (const row of rows) {\n const rowValues = [];\n for (const colName in row) {\n rowValues.push(row[colName]);\n }\n allValues.push(rowValues.join(' '));\n }\n\n sqlSchemaPrompt = sqlCreateTableQuery + '\\n' + sqlSelectTableQuery + '\\n' + columnNames.join(' ') + '\\n' + allValues.join('\\n');\n \n resolve();\n });\n}\n\nasync function main() {\n await getSQLPrompt();\n}\n\nawait main();\n\nreturn sqlSchemaPrompt;" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "customFunction_2-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": -152.63957160907668, + "y": -212.74538890862547 + }, + "dragging": false + }, + { + "width": 300, + "height": 669, + "id": "customFunction_1", + "position": { + "x": 1887.4670208331604, + "y": -275.95340782935716 + }, + "type": "customNode", + "data": { + "id": "customFunction_1", + "label": "Custom JS Function", + "version": 1, + "name": "customFunction", + "type": "CustomFunction", + "baseClasses": ["CustomFunction", "Utilities"], + "category": "Utilities", + "description": "Execute custom javascript function", + "inputParams": [ + { + "label": "Input Variables", + "name": "functionInputVariables", + "description": "Input variables can be used in the function with prefix $. For example: $var", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "customFunction_1-input-functionInputVariables-json" + }, + { + "label": "Function Name", + "name": "functionName", + "type": "string", + "placeholder": "My Function", + "id": "customFunction_1-input-functionName-string" + }, + { + "label": "Javascript Function", + "name": "javascriptFunction", + "type": "code", + "id": "customFunction_1-input-javascriptFunction-code" + } + ], + "inputAnchors": [], + "inputs": { + "functionInputVariables": "{\"sqlQuery\":\"{{setVariable_1.data.instance}}\"}", + "functionName": "Run SQL Query", + "javascriptFunction": "const HOST = 'singlestore-host';\nconst USER = 'admin';\nconst PASSWORD = 'mypassword';\nconst DATABASE = 'mydb';\nconst TABLE = 'samples';\nconst mysql = require('mysql2/promise');\n\nlet result;\n\nfunction getSQLResult() {\n return new Promise(async (resolve, reject) => {\n \n const singleStoreConnection = mysql.createPool({\n host: HOST,\n user: USER,\n password: PASSWORD,\n database: DATABASE,\n });\n \n const [rows] = await singleStoreConnection.execute(\n $sqlQuery\n );\n\n result = JSON.stringify(rows)\n \n resolve();\n });\n}\n\nasync function main() {\n await getSQLResult();\n}\n\nawait main();\n\nreturn result;" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "customFunction_1-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1887.4670208331604, + "y": -275.95340782935716 + }, + "dragging": false + }, + { + "width": 300, + "height": 511, + "id": "promptTemplate_1", + "position": { + "x": 2638.3935631956588, + "y": -18.55855423639423 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "version": 1, + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Based on the table schema below, question, SQL query, and SQL response, write a natural language response, be details as possible:\n------------\nSCHEMA: {schema}\n------------\nQUESTION: {question}\n------------\nSQL QUERY: {sqlQuery}\n------------\nSQL RESPONSE: {sqlResponse}\n------------\nNATURAL LANGUAGE RESPONSE:", + "promptValues": "{\"schema\":\"{{getVariable_0.data.instance}}\",\"question\":\"{{question}}\",\"sqlResponse\":\"{{customFunction_1.data.instance}}\",\"sqlQuery\":\"{{getVariable_1.data.instance}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 2638.3935631956588, + "y": -18.55855423639423 + } + }, + { + "width": 300, + "height": 355, + "id": "setVariable_0", + "position": { + "x": 247.02296459986826, + "y": -60.27462140472403 + }, + "type": "customNode", + "data": { + "id": "setVariable_0", + "label": "Set Variable", + "version": 1, + "name": "setVariable", + "type": "SetVariable", + "baseClasses": ["SetVariable", "Utilities"], + "category": "Utilities", + "description": "Set variable which can be retrieved at a later stage. Variable is only available during runtime.", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "setVariable_0-input-variableName-string" + } + ], + "inputAnchors": [ + { + "label": "Input", + "name": "input", + "type": "string | number | boolean | json | array", + "optional": true, + "list": true, + "id": "setVariable_0-input-input-string | number | boolean | json | array" + } + ], + "inputs": { + "input": ["{{customFunction_2.data.instance}}"], + "variableName": "schemaPrompt" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "setVariable_0-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 247.02296459986826, + "y": -60.27462140472403 + }, + "dragging": false + }, + { + "width": 300, + "height": 304, + "id": "getVariable_0", + "position": { + "x": 2248.4540716891547, + "y": -47.21232652005119 + }, + "type": "customNode", + "data": { + "id": "getVariable_0", + "label": "Get Variable", + "version": 1, + "name": "getVariable", + "type": "GetVariable", + "baseClasses": ["GetVariable", "Utilities"], + "category": "Utilities", + "description": "Get variable that was saved using Set Variable node", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "getVariable_0-input-variableName-string" + } + ], + "inputAnchors": [], + "inputs": { + "variableName": "schemaPrompt" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "getVariable_0-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "positionAbsolute": { + "x": 2248.4540716891547, + "y": -47.21232652005119 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 304, + "id": "getVariable_1", + "position": { + "x": 2256.0258940322105, + "y": 437.4363694364632 + }, + "type": "customNode", + "data": { + "id": "getVariable_1", + "label": "Get Variable", + "version": 1, + "name": "getVariable", + "type": "GetVariable", + "baseClasses": ["GetVariable", "Utilities"], + "category": "Utilities", + "description": "Get variable that was saved using Set Variable node", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "getVariable_1-input-variableName-string" + } + ], + "inputAnchors": [], + "inputs": { + "variableName": "sqlQuery" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "getVariable_1-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "positionAbsolute": { + "x": 2256.0258940322105, + "y": 437.4363694364632 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 355, + "id": "setVariable_1", + "position": { + "x": 1482.8091395089693, + "y": -33.943355212355016 + }, + "type": "customNode", + "data": { + "id": "setVariable_1", + "label": "Set Variable", + "version": 1, + "name": "setVariable", + "type": "SetVariable", + "baseClasses": ["SetVariable", "Utilities"], + "category": "Utilities", + "description": "Set variable which can be retrieved at a later stage. Variable is only available during runtime.", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "setVariable_1-input-variableName-string" + } + ], + "inputAnchors": [ + { + "label": "Input", + "name": "input", + "type": "string | number | boolean | json | array", + "optional": true, + "list": true, + "id": "setVariable_1-input-input-string | number | boolean | json | array" + } + ], + "inputs": { + "input": ["{{llmChain_0.data.instance}}"], + "variableName": "sqlQuery" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "setVariable_1-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1482.8091395089693, + "y": -33.943355212355016 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_1", + "sourceHandle": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_1-chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "customFunction_1", + "sourceHandle": "customFunction_1-output-output-string|number|boolean|json|array", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "customFunction_1-customFunction_1-output-output-string|number|boolean|json|array-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "customFunction_2", + "sourceHandle": "customFunction_2-output-output-string|number|boolean|json|array", + "target": "setVariable_0", + "targetHandle": "setVariable_0-input-input-string | number | boolean | json | array", + "type": "buttonedge", + "id": "customFunction_2-customFunction_2-output-output-string|number|boolean|json|array-setVariable_0-setVariable_0-input-input-string | number | boolean | json | array", + "data": { + "label": "" + } + }, + { + "source": "setVariable_0", + "sourceHandle": "setVariable_0-output-output-string|number|boolean|json|array", + "target": "promptTemplate_0", + "targetHandle": "promptTemplate_0-input-promptValues-json", + "type": "buttonedge", + "id": "setVariable_0-setVariable_0-output-output-string|number|boolean|json|array-promptTemplate_0-promptTemplate_0-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "getVariable_0", + "sourceHandle": "getVariable_0-output-output-string|number|boolean|json|array", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "getVariable_0-getVariable_0-output-output-string|number|boolean|json|array-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "getVariable_1", + "sourceHandle": "getVariable_1-output-output-string|number|boolean|json|array", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "getVariable_1-getVariable_1-output-output-string|number|boolean|json|array-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "llmChain_0", + "sourceHandle": "llmChain_0-output-outputPrediction-string|json", + "target": "setVariable_1", + "targetHandle": "setVariable_1-input-input-string | number | boolean | json | array", + "type": "buttonedge", + "id": "llmChain_0-llmChain_0-output-outputPrediction-string|json-setVariable_1-setVariable_1-input-input-string | number | boolean | json | array", + "data": { + "label": "" + } + }, + { + "source": "setVariable_1", + "sourceHandle": "setVariable_1-output-output-string|number|boolean|json|array", + "target": "customFunction_1", + "targetHandle": "customFunction_1-input-functionInputVariables-json", + "type": "buttonedge", + "id": "setVariable_1-setVariable_1-output-output-string|number|boolean|json|array-customFunction_1-customFunction_1-input-functionInputVariables-json", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 85e9eac4..9c31a333 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -55,7 +55,7 @@ import { Tool } from './database/entities/Tool' import { Assistant } from './database/entities/Assistant' import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' -import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components' +import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' import { sanitizeMiddleware } from './utils/XSS' @@ -291,9 +291,10 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() - const returnOptions: INodeOptionsValue[] = await newNodeInstance.init(nodeData) + const returnData = await newNodeInstance.init(nodeData) + const result = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData - return res.json(returnOptions) + return res.json(result) } catch (error) { return res.status(500).send(`Error running custom function: ${error}`) } diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 18e82338..92a43cf8 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -371,6 +371,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {dialogProps.type !== 'TEMPLATE' && (

{ value={variableName ?? ''} /> - -
- - Value - - -
-
- setVariableValue(e.target.value)} - value={variableValue ?? ''} - /> - Leave the value empty for runtime variables. Will be populated at runtime. -
@@ -245,14 +231,30 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { onSelect={(newValue) => setVariableType(newValue)} value={variableType} /> - - Runtime: Value would be populated from env. Static: Value would be used as is. - + {variableType === 'static' && ( + +
+ + Value * + +
+
+ setVariableValue(e.target.value)} + value={variableValue ?? ''} + /> +
+ )} (dialogType === 'ADD' ? addNewVariable() : saveVariable())} > diff --git a/packages/ui/src/views/variables/HowToUseVariablesDialog.js b/packages/ui/src/views/variables/HowToUseVariablesDialog.js new file mode 100644 index 00000000..f328f226 --- /dev/null +++ b/packages/ui/src/views/variables/HowToUseVariablesDialog.js @@ -0,0 +1,72 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { Dialog, DialogContent, DialogTitle } from '@mui/material' +import { CodeEditor } from 'ui-component/editor/CodeEditor' + +const overrideConfig = `{ + overrideConfig: { + vars: { + var1: 'abc' + } + } +}` + +const HowToUseVariablesDialog = ({ show, onCancel }) => { + const portalElement = document.getElementById('portal') + + const component = show ? ( + + + How To Use Variables + + +

Variables can be used in Custom Tool Function with the $ prefix.

+ `} + height={'50px'} + theme={'dark'} + lang={'js'} + basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} + /> +

+ If variable type is Static, the value will be retrieved as it is. If variable type is Runtime, the value will be + retrieved from .env file. +

+

+ You can also override variable values in API overrideConfig using vars: +

+ +

+ Read more from{' '} + + docs + +

+ + + ) : null + + return createPortal(component, portalElement) +} + +HowToUseVariablesDialog.propTypes = { + show: PropTypes.bool, + onCancel: PropTypes.func +} + +export default HowToUseVariablesDialog diff --git a/packages/ui/src/views/variables/index.js b/packages/ui/src/views/variables/index.js index 9399eb20..ca8a3dee 100644 --- a/packages/ui/src/views/variables/index.js +++ b/packages/ui/src/views/variables/index.js @@ -19,7 +19,8 @@ import { Toolbar, TextField, InputAdornment, - ButtonGroup + ButtonGroup, + Chip } from '@mui/material' import { useTheme } from '@mui/material/styles' @@ -40,10 +41,11 @@ import useNotifier from 'utils/useNotifier' // Icons import { IconTrash, IconEdit, IconX, IconPlus, IconSearch, IconVariable } from '@tabler/icons' -import CredentialEmptySVG from 'assets/images/credential_empty.svg' +import VariablesEmptySVG from 'assets/images/variables_empty.svg' // const import AddEditVariableDialog from './AddEditVariableDialog' +import HowToUseVariablesDialog from './HowToUseVariablesDialog' // ==============================|| Credentials ||============================== // @@ -60,6 +62,7 @@ const Variables = () => { const [showVariableDialog, setShowVariableDialog] = useState(false) const [variableDialogProps, setVariableDialogProps] = useState({}) const [variables, setVariables] = useState([]) + const [showHowToDialog, setShowHowToDialog] = useState(false) const { confirm } = useConfirm() @@ -173,7 +176,7 @@ const Variables = () => { width: '100%' }} > -

Environment Variables 

+

Variables 

{ }} /> + { CredentialEmptySVG
No Variables Yet
@@ -267,7 +273,13 @@ const Variables = () => {
{variable.value} - {variable.type === 'static' ? 'Static Variable' : 'Runtime Variable'} + + + {moment(variable.updatedDate).format('DD-MMM-YY')} {moment(variable.createdDate).format('DD-MMM-YY')} @@ -293,6 +305,7 @@ const Variables = () => { onCancel={() => setShowVariableDialog(false)} onConfirm={onConfirm} > + setShowHowToDialog(false)}> ) From 44294d00677e5b3df97370164af64caadcadbccc Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 21 Dec 2023 20:37:04 +0000 Subject: [PATCH 113/268] update default location for encryption key --- packages/components/src/utils.ts | 14 ++++++++++++-- packages/server/src/utils/index.ts | 14 +++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 98936817..757ecc65 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -428,7 +428,17 @@ export const getEnvironmentVariable = (name: string): string | undefined => { * @returns {string} */ const getEncryptionKeyFilePath = (): string => { - const checkPaths = [path.join(getUserHome(), '.flowise', 'encryption.key')] + const checkPaths = [ + path.join(__dirname, '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(getUserHome(), '.flowise', 'encryption.key') + ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { return checkPath @@ -437,7 +447,7 @@ const getEncryptionKeyFilePath = (): string => { return '' } -const getEncryptionKeyPath = (): string => { +export const getEncryptionKeyPath = (): string => { return process.env.SECRETKEY_PATH ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') : getEncryptionKeyFilePath() } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e0c91723..39bd0854 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -23,6 +23,7 @@ import { convertChatHistoryToText, getInputVariables, handleEscapeCharacters, + getEncryptionKeyPath, ICommonObject, IDatabaseEntity, IMessage @@ -852,16 +853,6 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod return isChatOrLLMsExist && isValidChainOrAgent && !isOutputParserExist } -/** - * Returns the path of encryption key - * @returns {string} - */ -export const getEncryptionKeyPath = (): string => { - return process.env.SECRETKEY_PATH - ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') - : path.join(getUserHome(), '.flowise', 'encryption.key') -} - /** * Generate an encryption key * @returns {string} @@ -882,7 +873,8 @@ export const getEncryptionKey = async (): Promise => { return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') } catch (error) { const encryptKey = generateEncryptKey() - await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey) + const defaultLocation = path.join(getUserHome(), '.flowise', 'encryption.key') + await fs.promises.writeFile(defaultLocation, encryptKey) return encryptKey } } From 1a4ead3544fa0be086cb8ec174b2da4d2c0cd647 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 22 Dec 2023 01:58:40 +0000 Subject: [PATCH 114/268] update S3 loader --- .../nodes/documentloaders/S3File/S3File.ts | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/packages/components/nodes/documentloaders/S3File/S3File.ts b/packages/components/nodes/documentloaders/S3File/S3File.ts index 58ffd8af..eadb4d99 100644 --- a/packages/components/nodes/documentloaders/S3File/S3File.ts +++ b/packages/components/nodes/documentloaders/S3File/S3File.ts @@ -30,7 +30,7 @@ class S3_DocumentLoaders implements INode { constructor() { this.label = 'S3' this.name = 'S3' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 's3.svg' this.category = 'Document Loaders' @@ -113,12 +113,62 @@ class S3_DocumentLoaders implements INode { optional: true }, { - label: 'NarrativeText Only', - name: 'narrativeTextOnly', + label: 'Element Type', + name: 'elementType', description: - 'Only load documents with NarrativeText metadata from Unstructured. See how Unstructured partition data here', - default: true, - type: 'boolean', + 'Unstructured partition document into different types, select the types to return. If not selected, all types will be returned', + type: 'multiOptions', + options: [ + { + label: 'FigureCaption', + name: 'FigureCaption' + }, + { + label: 'NarrativeText', + name: 'NarrativeText' + }, + { + label: 'ListItem', + name: 'ListItem' + }, + { + label: 'Title', + name: 'Title' + }, + { + label: 'Address', + name: 'Address' + }, + { + label: 'Table', + name: 'Table' + }, + { + label: 'PageBreak', + name: 'PageBreak' + }, + { + label: 'Header', + name: 'Header' + }, + { + label: 'Footer', + name: 'Footer' + }, + { + label: 'UncategorizedText', + name: 'UncategorizedText' + }, + { + label: 'Image', + name: 'Image' + }, + { + label: 'Formula', + name: 'Formula' + } + ], + default: [], optional: true, additionalParams: true }, @@ -138,7 +188,7 @@ class S3_DocumentLoaders implements INode { const unstructuredAPIUrl = nodeData.inputs?.unstructuredAPIUrl as string const unstructuredAPIKey = nodeData.inputs?.unstructuredAPIKey as string const metadata = nodeData.inputs?.metadata - const narrativeTextOnly = nodeData.inputs?.narrativeTextOnly as boolean + const elementType = nodeData.inputs?.elementType as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessKeyId = getCredentialParam('awsKey', credentialData, nodeData) @@ -169,6 +219,15 @@ class S3_DocumentLoaders implements INode { } } + let elementTypes: string[] = [] + if (elementType) { + try { + elementTypes = JSON.parse(elementType) + } catch (e) { + elementTypes = [] + } + } + loader.load = async () => { const tempDir = fsDefault.mkdtempSync(path.join(os.tmpdir(), 's3fileloader-')) @@ -235,10 +294,10 @@ class S3_DocumentLoaders implements INode { } } }) - return narrativeTextOnly ? finaldocs.filter((doc) => doc.metadata.category === 'NarrativeText') : finaldocs + return elementTypes.length ? finaldocs.filter((doc) => elementTypes.includes(doc.metadata.category)) : finaldocs } - return narrativeTextOnly ? docs.filter((doc) => doc.metadata.category === 'NarrativeText') : docs + return elementTypes.length ? docs.filter((doc) => elementTypes.includes(doc.metadata.category)) : docs } } module.exports = { nodeClass: S3_DocumentLoaders } From 1ffa56897446cb36bd1e0a5699b6bc9c89e844a4 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 09:08:53 +0530 Subject: [PATCH 115/268] Addition of Pinecone MMR search --- .../nodes/vectorstores/Pinecone/Pinecone.ts | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts index 4e6967bc..3f2e3ef1 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts @@ -23,11 +23,11 @@ class Pinecone_VectorStores implements INode { constructor() { this.label = 'Pinecone' this.name = 'pinecone' - this.version = 1.0 + this.version = 2.0 this.type = 'Pinecone' this.icon = 'pinecone.svg' this.category = 'Vector Stores' - this.description = `Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database` + this.description = `Upsert embedded data and perform search upon query using Pinecone, a leading fully managed hosted vector database` this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.badge = 'NEW' this.credential = { @@ -77,6 +77,43 @@ class Pinecone_VectorStores implements INode { type: 'number', additionalParams: true, optional: true + }, + { + label: 'Search Type', + name: 'searchType', + type: 'options', + default: 'similarity', + options: [ + { + label: 'Similarity', + name: 'similarity' + }, + { + label: 'Max Marginal Relevance', + name: 'mmr' + } + ], + additionalParams: true, + optional: true + }, + { + label: 'Fetch K (for MMR Search)', + name: 'fetchK', + description: 'Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR', + placeholder: '20', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'Lambda (for MMR Search)', + name: 'lambda', + description: + 'Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR', + placeholder: '0.5', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -141,6 +178,7 @@ class Pinecone_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const searchType = nodeData.outputs?.searchType as string const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 @@ -176,8 +214,25 @@ class Pinecone_VectorStores implements INode { const vectorStore = await PineconeStore.fromExistingIndex(embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever(k) - return retriever + if ('mmr' === searchType) { + const fetchK = nodeData.inputs?.fetchK as string + const lambda = nodeData.inputs?.lambda as string + const f = fetchK ? parseInt(fetchK) : 20 + const l = lambda ? parseFloat(lambda) : 0.5 + const retriever = vectorStore.asRetriever({ + searchType: 'mmr', + k: 5, + searchKwargs: { + fetchK: f, + lambda: l + } + }) + return retriever + } else { + // "searchType" is "similarity" + const retriever = vectorStore.asRetriever(k) + return retriever + } } else if (output === 'vectorStore') { ;(vectorStore as any).k = k return vectorStore From 579f66e57e3da71e0a53db10cf1baeb986d4bb31 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 09:20:55 +0530 Subject: [PATCH 116/268] Updating langchain to 0.0.198 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 9cb0bf1e..018d7a77 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -52,7 +52,7 @@ "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", - "langchain": "^0.0.196", + "langchain": "^0.0.198", "langfuse": "^1.2.0", "langfuse-langchain": "^1.0.31", "langsmith": "^0.0.49", From 56b043264a5370ac041f8fde1adf928b5068eace Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 12:08:48 +0530 Subject: [PATCH 117/268] MMR Search for Pinecone, Weaviate, Zep and Supabase --- .../nodes/vectorstores/Pinecone/Pinecone.ts | 73 ++---------------- .../nodes/vectorstores/Supabase/Supabase.ts | 18 ++--- .../nodes/vectorstores/VectorStoreUtils.ts | 76 +++++++++++++++++++ .../nodes/vectorstores/Weaviate/Weaviate.ts | 18 ++--- .../components/nodes/vectorstores/Zep/Zep.ts | 22 ++---- 5 files changed, 98 insertions(+), 109 deletions(-) create mode 100644 packages/components/nodes/vectorstores/VectorStoreUtils.ts diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts index 3f2e3ef1..4b91a9b5 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts @@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' class Pinecone_VectorStores implements INode { label: string @@ -23,11 +24,11 @@ class Pinecone_VectorStores implements INode { constructor() { this.label = 'Pinecone' this.name = 'pinecone' - this.version = 2.0 + this.version = 3.0 this.type = 'Pinecone' this.icon = 'pinecone.svg' this.category = 'Vector Stores' - this.description = `Upsert embedded data and perform search upon query using Pinecone, a leading fully managed hosted vector database` + this.description = `Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database` this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.badge = 'NEW' this.credential = { @@ -77,45 +78,9 @@ class Pinecone_VectorStores implements INode { type: 'number', additionalParams: true, optional: true - }, - { - label: 'Search Type', - name: 'searchType', - type: 'options', - default: 'similarity', - options: [ - { - label: 'Similarity', - name: 'similarity' - }, - { - label: 'Max Marginal Relevance', - name: 'mmr' - } - ], - additionalParams: true, - optional: true - }, - { - label: 'Fetch K (for MMR Search)', - name: 'fetchK', - description: 'Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR', - placeholder: '20', - type: 'number', - additionalParams: true, - optional: true - }, - { - label: 'Lambda (for MMR Search)', - name: 'lambda', - description: - 'Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR', - placeholder: '0.5', - type: 'number', - additionalParams: true, - optional: true } ] + addMMRInputParams(this.inputs) this.outputs = [ { label: 'Pinecone Retriever', @@ -177,10 +142,6 @@ class Pinecone_VectorStores implements INode { const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings - const output = nodeData.outputs?.output as string - const searchType = nodeData.outputs?.searchType as string - const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 const credentialData = await getCredentialData(nodeData.credential ?? '', options) const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) @@ -213,31 +174,7 @@ class Pinecone_VectorStores implements INode { const vectorStore = await PineconeStore.fromExistingIndex(embeddings, obj) - if (output === 'retriever') { - if ('mmr' === searchType) { - const fetchK = nodeData.inputs?.fetchK as string - const lambda = nodeData.inputs?.lambda as string - const f = fetchK ? parseInt(fetchK) : 20 - const l = lambda ? parseFloat(lambda) : 0.5 - const retriever = vectorStore.asRetriever({ - searchType: 'mmr', - k: 5, - searchKwargs: { - fetchK: f, - lambda: l - } - }) - return retriever - } else { - // "searchType" is "similarity" - const retriever = vectorStore.asRetriever(k) - return retriever - } - } else if (output === 'vectorStore') { - ;(vectorStore as any).k = k - return vectorStore - } - return vectorStore + return resolveVectorStoreOrRetriever(nodeData, vectorStore) } } diff --git a/packages/components/nodes/vectorstores/Supabase/Supabase.ts b/packages/components/nodes/vectorstores/Supabase/Supabase.ts index 13840ab7..a5477914 100644 --- a/packages/components/nodes/vectorstores/Supabase/Supabase.ts +++ b/packages/components/nodes/vectorstores/Supabase/Supabase.ts @@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { SupabaseLibArgs, SupabaseVectorStore } from 'langchain/vectorstores/supabase' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' class Supabase_VectorStores implements INode { label: string @@ -23,11 +24,11 @@ class Supabase_VectorStores implements INode { constructor() { this.label = 'Supabase' this.name = 'supabase' - this.version = 1.0 + this.version = 2.0 this.type = 'Supabase' this.icon = 'supabase.svg' this.category = 'Vector Stores' - this.description = 'Upsert embedded data and perform similarity search upon query using Supabase via pgvector extension' + this.description = 'Upsert embedded data and perform similarity or mmr search upon query using Supabase via pgvector extension' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.badge = 'NEW' this.credential = { @@ -81,6 +82,7 @@ class Supabase_VectorStores implements INode { optional: true } ] + addMMRInputParams(this.inputs) this.outputs = [ { label: 'Supabase Retriever', @@ -135,9 +137,6 @@ class Supabase_VectorStores implements INode { const queryName = nodeData.inputs?.queryName as string const embeddings = nodeData.inputs?.embeddings as Embeddings const supabaseMetadataFilter = nodeData.inputs?.supabaseMetadataFilter - const output = nodeData.outputs?.output as string - const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 const credentialData = await getCredentialData(nodeData.credential ?? '', options) const supabaseApiKey = getCredentialParam('supabaseApiKey', credentialData, nodeData) @@ -157,14 +156,7 @@ class Supabase_VectorStores implements INode { const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, obj) - if (output === 'retriever') { - const retriever = vectorStore.asRetriever(k) - return retriever - } else if (output === 'vectorStore') { - ;(vectorStore as any).k = k - return vectorStore - } - return vectorStore + return resolveVectorStoreOrRetriever(nodeData, vectorStore) } } diff --git a/packages/components/nodes/vectorstores/VectorStoreUtils.ts b/packages/components/nodes/vectorstores/VectorStoreUtils.ts new file mode 100644 index 00000000..b63a4121 --- /dev/null +++ b/packages/components/nodes/vectorstores/VectorStoreUtils.ts @@ -0,0 +1,76 @@ +import { INodeData } from '../../src' + +export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: any) => { + const output = nodeData.outputs?.output as string + const searchType = nodeData.outputs?.searchType as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + + if (output === 'retriever') { + if ('mmr' === searchType) { + const fetchK = nodeData.inputs?.fetchK as string + const lambda = nodeData.inputs?.lambda as string + const f = fetchK ? parseInt(fetchK) : 20 + const l = lambda ? parseFloat(lambda) : 0.5 + const retriever = vectorStore.asRetriever({ + searchType: 'mmr', + k: k, + searchKwargs: { + fetchK: f, + lambda: l + } + }) + return retriever + } else { + // "searchType" is "similarity" + return vectorStore.asRetriever(k) + } + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } +} + +export const addMMRInputParams = (inputs: any[]) => { + const mmrInputParams = [ + { + label: 'Search Type', + name: 'searchType', + type: 'options', + default: 'similarity', + options: [ + { + label: 'Similarity', + name: 'similarity' + }, + { + label: 'Max Marginal Relevance', + name: 'mmr' + } + ], + additionalParams: true, + optional: true + }, + { + label: 'Fetch K (for MMR Search)', + name: 'fetchK', + description: 'Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR', + placeholder: '20', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'Lambda (for MMR Search)', + name: 'lambda', + description: + 'Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR', + placeholder: '0.5', + type: 'number', + additionalParams: true, + optional: true + } + ] + + inputs.push(...mmrInputParams) +} diff --git a/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts b/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts index 5c31c737..0eface58 100644 --- a/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts +++ b/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts @@ -5,6 +5,7 @@ import { Document } from 'langchain/document' import { Embeddings } from 'langchain/embeddings/base' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' class Weaviate_VectorStores implements INode { label: string @@ -23,12 +24,12 @@ class Weaviate_VectorStores implements INode { constructor() { this.label = 'Weaviate' this.name = 'weaviate' - this.version = 1.0 + this.version = 2.0 this.type = 'Weaviate' this.icon = 'weaviate.png' this.category = 'Vector Stores' this.description = - 'Upsert embedded data and perform similarity search upon query using Weaviate, a scalable open-source vector database' + 'Upsert embedded data and perform similarity or mmr search using Weaviate, a scalable open-source vector database' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.badge = 'NEW' this.credential = { @@ -107,6 +108,7 @@ class Weaviate_VectorStores implements INode { optional: true } ] + addMMRInputParams(this.inputs) this.outputs = [ { label: 'Weaviate Retriever', @@ -174,9 +176,6 @@ class Weaviate_VectorStores implements INode { const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string const embeddings = nodeData.inputs?.embeddings as Embeddings - const output = nodeData.outputs?.output as string - const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 const credentialData = await getCredentialData(nodeData.credential ?? '', options) const weaviateApiKey = getCredentialParam('weaviateApiKey', credentialData, nodeData) @@ -199,14 +198,7 @@ class Weaviate_VectorStores implements INode { const vectorStore = await WeaviateStore.fromExistingIndex(embeddings, obj) - if (output === 'retriever') { - const retriever = vectorStore.asRetriever(k) - return retriever - } else if (output === 'vectorStore') { - ;(vectorStore as any).k = k - return vectorStore - } - return vectorStore + return resolveVectorStoreOrRetriever(nodeData, vectorStore) } } diff --git a/packages/components/nodes/vectorstores/Zep/Zep.ts b/packages/components/nodes/vectorstores/Zep/Zep.ts index ebb13c64..3d9f1978 100644 --- a/packages/components/nodes/vectorstores/Zep/Zep.ts +++ b/packages/components/nodes/vectorstores/Zep/Zep.ts @@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' class Zep_VectorStores implements INode { label: string @@ -23,12 +24,12 @@ class Zep_VectorStores implements INode { constructor() { this.label = 'Zep' this.name = 'zep' - this.version = 1.0 + this.version = 2.0 this.type = 'Zep' this.icon = 'zep.svg' this.category = 'Vector Stores' this.description = - 'Upsert embedded data and perform similarity search upon query using Zep, a fast and scalable building block for LLM apps' + 'Upsert embedded data and perform similarity or mmr search upon query using Zep, a fast and scalable building block for LLM apps' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.badge = 'NEW' this.credential = { @@ -88,6 +89,7 @@ class Zep_VectorStores implements INode { optional: true } ] + addMMRInputParams(this.inputs) this.outputs = [ { label: 'Zep Retriever', @@ -144,9 +146,6 @@ class Zep_VectorStores implements INode { const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter const dimension = nodeData.inputs?.dimension as number const embeddings = nodeData.inputs?.embeddings as Embeddings - const output = nodeData.outputs?.output as string - const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) @@ -165,14 +164,7 @@ class Zep_VectorStores implements INode { const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig) - if (output === 'retriever') { - const retriever = vectorStore.asRetriever(k) - return retriever - } else if (output === 'vectorStore') { - ;(vectorStore as any).k = k - return vectorStore - } - return vectorStore + return resolveVectorStoreOrRetriever(nodeData, vectorStore) } } @@ -210,7 +202,7 @@ class ZepExistingVS extends ZepVectorStore { this.args = args } - async initalizeCollection(args: IZepConfig & Partial) { + async initializeCollection(args: IZepConfig & Partial) { this.client = await ZepClient.init(args.apiUrl, args.apiKey) try { this.collection = await this.client.document.getCollection(args.collectionName) @@ -259,7 +251,7 @@ class ZepExistingVS extends ZepVectorStore { const newfilter = { where: { and: ANDFilters } } - await this.initalizeCollection(this.args!).catch((err) => { + await this.initializeCollection(this.args!).catch((err) => { console.error('Error initializing collection:', err) throw err }) From 94236c4b5fe80a3449eed29f36ddb9e620077abe Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 12:12:04 +0530 Subject: [PATCH 118/268] Compression Retriever - Cohere Rerank --- .../CohereRerankRetriever/CohereRerank.ts | 51 ++++++++++++ .../CohereRerankRetriever.ts | 77 +++++++++++++++++++ .../compressionRetriever.svg | 7 ++ 3 files changed, 135 insertions(+) create mode 100644 packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts create mode 100644 packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts create mode 100644 packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts new file mode 100644 index 00000000..612581ed --- /dev/null +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts @@ -0,0 +1,51 @@ +import { Callbacks } from 'langchain/callbacks' +import { Document } from 'langchain/document' +import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors' +import axios from 'axios' +export class CohereRerank extends BaseDocumentCompressor { + private cohereAPIKey: any + private COHERE_API_URL = 'https://api.cohere.ai/v1/rerank' + private model: string + + constructor(cohereAPIKey: string, model: string) { + super() + this.cohereAPIKey = cohereAPIKey + this.model = model + } + async compressDocuments( + documents: Document>[], + query: string, + _?: Callbacks | undefined + ): Promise>[]> { + // avoid empty api call + if (documents.length === 0) { + return [] + } + const config = { + headers: { + Authorization: `Bearer ${this.cohereAPIKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json' + } + } + const data = { + model: this.model, + max_chunks_per_doc: 10, + query: query, + return_documents: false, + documents: documents.map((doc) => doc.pageContent) + } + try { + let returnedDocs = await axios.post(this.COHERE_API_URL, data, config) + const finalResults: Document>[] = [] + returnedDocs.data.results.forEach((result: any) => { + const doc = documents[result.index] + doc.metadata.relevance_score = result.relevance_score + finalResults.push(doc) + }) + return finalResults + } catch (error) { + return documents + } + } +} diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts new file mode 100644 index 00000000..2e7090bc --- /dev/null +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts @@ -0,0 +1,77 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { BaseRetriever } from 'langchain/schema/retriever' +import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' +import { getCredentialData, getCredentialParam } from '../../../src' +import { CohereRerank } from './CohereRerank' + +class CohereRerankRetriever_Retrievers implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + credential: INodeParams + badge: string + + constructor() { + this.label = 'Cohere Rerank Retriever' + this.name = 'cohereRerankRetriever' + this.version = 1.0 + this.type = 'Cohere Rerank Retriever' + this.icon = 'compressionRetriever.svg' + this.category = 'Retrievers' + this.badge = 'NEW' + this.description = 'Cohere Rerank indexes the documents from most to least semantically relevant to the query.' + this.baseClasses = [this.type, 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['cohereApi'] + } + this.inputs = [ + { + label: 'Base Retriever', + name: 'baseRetriever', + type: 'VectorStoreRetriever' + }, + { + label: 'Model Name', + name: 'model', + type: 'options', + options: [ + { + label: 'rerank-english-v2.0', + name: 'rerank-english-v2.0' + }, + { + label: 'rerank-multilingual-v2.0', + name: 'rerank-multilingual-v2.0' + } + ], + default: 'rerank-english-v2.0', + optional: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever + const model = nodeData.inputs?.model as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) + + const cohereCompressor = new CohereRerank(cohereApiKey, model) + return new ContextualCompressionRetriever({ + baseCompressor: cohereCompressor, + baseRetriever: baseRetriever + }) + } +} + +module.exports = { nodeClass: CohereRerankRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg new file mode 100644 index 00000000..23c52d25 --- /dev/null +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From fb02632f4b682b8b0ee2d1ec1a79330118318eaa Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 12:12:16 +0530 Subject: [PATCH 119/268] Compression Retriever - Embeddings Filter --- .../EmbeddingsFilterRetriever.ts | 97 +++++++++++++++++++ .../compressionRetriever.svg | 7 ++ 2 files changed, 104 insertions(+) create mode 100644 packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts create mode 100644 packages/components/nodes/retrievers/EmbeddingsFilterRetriever/compressionRetriever.svg diff --git a/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts b/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts new file mode 100644 index 00000000..d373704c --- /dev/null +++ b/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts @@ -0,0 +1,97 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { BaseRetriever } from 'langchain/schema/retriever' +import { Embeddings } from 'langchain/embeddings/base' +import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' +import { EmbeddingsFilter } from 'langchain/retrievers/document_compressors/embeddings_filter' + +class EmbeddingsFilterRetriever_Retrievers implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + badge: string + + constructor() { + this.label = 'Embeddings Filter Retriever' + this.name = 'embeddingsFilterRetriever' + this.version = 1.0 + this.type = 'EmbeddingsFilterRetriever' + this.icon = 'compressionRetriever.svg' + this.category = 'Retrievers' + this.badge = 'NEW' + this.description = 'A document compressor that uses embeddings to drop documents unrelated to the query' + this.baseClasses = [this.type, 'BaseRetriever'] + this.inputs = [ + { + label: 'Base Retriever', + name: 'baseRetriever', + type: 'VectorStoreRetriever' + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings', + optional: false + }, + { + label: 'Similarity Threshold', + name: 'similarityThreshold', + description: + 'Threshold for determining when two documents are similar enough to be considered redundant. Must be specified if `k` is not set', + type: 'number', + default: 0.8, + step: 0.1, + optional: true + }, + { + label: 'K', + name: 'k', + description: + 'The number of relevant documents to return. Can be explicitly set to undefined, in which case similarity_threshold must be specified. Defaults to 20', + type: 'number', + default: 20, + step: 1, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever + const embeddings = nodeData.inputs?.embeddings as Embeddings + const similarityThreshold = nodeData.inputs?.similarityThreshold as string + const k = nodeData.inputs?.k as string + + if (k === undefined && similarityThreshold === undefined) { + throw new Error(`Must specify one of "k" or "similarity_threshold".`) + } + + let similarityThresholdNumber = 0.8 + if (similarityThreshold) { + similarityThresholdNumber = parseFloat(similarityThreshold) + } + let kNumber = 0.8 + if (k) { + kNumber = parseFloat(k) + } + const baseCompressor = new EmbeddingsFilter({ + embeddings: embeddings, + similarityThreshold: similarityThresholdNumber, + k: kNumber + }) + + return new ContextualCompressionRetriever({ + baseCompressor, + baseRetriever: baseRetriever + }) + } +} + +module.exports = { nodeClass: EmbeddingsFilterRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/compressionRetriever.svg new file mode 100644 index 00000000..23c52d25 --- /dev/null +++ b/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/compressionRetriever.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From f2f8ed6a9ce1ab41bd69193ce283757cd28cb16e Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 12:12:25 +0530 Subject: [PATCH 120/268] Compression Retriever - LLM filter --- .../LLMFilterCompressionRetriever.ts | 60 +++++++++++++++++++ .../compressionRetriever.svg | 7 +++ 2 files changed, 67 insertions(+) create mode 100644 packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts create mode 100644 packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg diff --git a/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts b/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts new file mode 100644 index 00000000..c421c7ce --- /dev/null +++ b/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts @@ -0,0 +1,60 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { BaseRetriever } from 'langchain/schema/retriever' +import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' +import { BaseLanguageModel } from 'langchain/base_language' +import { LLMChainExtractor } from 'langchain/retrievers/document_compressors/chain_extract' + +class LLMFilterCompressionRetriever_Retrievers implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + badge: string + + constructor() { + this.label = 'LLM Filter Retriever' + this.name = 'llmFilterRetriever' + this.version = 1.0 + this.type = 'LLMFilterRetriever' + this.icon = 'compressionRetriever.svg' + this.category = 'Retrievers' + this.badge = 'NEW' + this.description = + 'Iterate over the initially returned documents and extract, from each, only the content that is relevant to the query' + this.baseClasses = [this.type, 'BaseRetriever'] + this.inputs = [ + { + label: 'Base Retriever', + name: 'baseRetriever', + type: 'VectorStoreRetriever' + }, + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel', + optional: true + }, + ] + } + + async init(nodeData: INodeData): Promise { + const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever + const model = nodeData.inputs?.model as BaseLanguageModel + + if (model) { + return new ContextualCompressionRetriever({ + baseCompressor: LLMChainExtractor.fromLLM(model), + baseRetriever: baseRetriever + }) + } + return {} + } +} + +module.exports = { nodeClass: LLMFilterCompressionRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg new file mode 100644 index 00000000..23c52d25 --- /dev/null +++ b/packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From be79adb07c22586d4debd5384268463a874b878e Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 12:13:52 +0530 Subject: [PATCH 121/268] lint fixes --- .../LLMFilterRetriever/LLMFilterCompressionRetriever.ts | 2 +- packages/components/nodes/vectorstores/VectorStoreUtils.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts b/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts index c421c7ce..e044468f 100644 --- a/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts +++ b/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts @@ -39,7 +39,7 @@ class LLMFilterCompressionRetriever_Retrievers implements INode { name: 'model', type: 'BaseLanguageModel', optional: true - }, + } ] } diff --git a/packages/components/nodes/vectorstores/VectorStoreUtils.ts b/packages/components/nodes/vectorstores/VectorStoreUtils.ts index b63a4121..a01d43c4 100644 --- a/packages/components/nodes/vectorstores/VectorStoreUtils.ts +++ b/packages/components/nodes/vectorstores/VectorStoreUtils.ts @@ -1,4 +1,4 @@ -import { INodeData } from '../../src' +import { INodeData } from "../../src"; export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: any) => { const output = nodeData.outputs?.output as string @@ -12,7 +12,7 @@ export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: const lambda = nodeData.inputs?.lambda as string const f = fetchK ? parseInt(fetchK) : 20 const l = lambda ? parseFloat(lambda) : 0.5 - const retriever = vectorStore.asRetriever({ + return vectorStore.asRetriever({ searchType: 'mmr', k: k, searchKwargs: { @@ -20,7 +20,6 @@ export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: lambda: l } }) - return retriever } else { // "searchType" is "similarity" return vectorStore.asRetriever(k) From f6ee137ca3e60b350f794181b43453eff520474b Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 12:14:10 +0530 Subject: [PATCH 122/268] lint fixes --- packages/components/nodes/vectorstores/VectorStoreUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/vectorstores/VectorStoreUtils.ts b/packages/components/nodes/vectorstores/VectorStoreUtils.ts index a01d43c4..0d92587f 100644 --- a/packages/components/nodes/vectorstores/VectorStoreUtils.ts +++ b/packages/components/nodes/vectorstores/VectorStoreUtils.ts @@ -1,4 +1,4 @@ -import { INodeData } from "../../src"; +import { INodeData } from '../../src' export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: any) => { const output = nodeData.outputs?.output as string From fd90fef94c289271eb1781205b0d6afc764c4f8a Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 22 Dec 2023 14:50:10 +0530 Subject: [PATCH 123/268] Bugfix: Unncessary load of Prompts on chatflow open. This fix loads them on dialog open. --- .../dialog/PromptLangsmithHubDialog.js | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js index 35b4ead7..8d89efc9 100644 --- a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js @@ -92,24 +92,27 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const getAvailablePromptsApi = useApi(promptApi.getAvailablePrompts) useEffect(() => { - if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) - else dispatch({ type: HIDE_CANVAS_DIALOG }) + if (show) { + dispatch({ type: SHOW_CANVAS_DIALOG }) + } else dispatch({ type: HIDE_CANVAS_DIALOG }) return () => dispatch({ type: HIDE_CANVAS_DIALOG }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [show, dispatch]) useEffect(() => { - if (promptType) { + if (promptType && show) { + setLoading(true) getAvailablePromptsApi.request({ tags: promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' }) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [promptType]) + }, [promptType, show]) useEffect(() => { if (getAvailablePromptsApi.data && getAvailablePromptsApi.data.repos) { setAvailablePrompNameList(getAvailablePromptsApi.data.repos) if (getAvailablePromptsApi.data.repos?.length) handleListItemClick(0, getAvailablePromptsApi.data.repos) + setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -174,6 +177,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const [selectedPrompt, setSelectedPrompt] = useState({}) const [accordionExpanded, setAccordionExpanded] = useState(['prompt']) + const [loading, setLoading] = useState(false) const handleAccordionChange = (accordionName) => (event, isExpanded) => { const accordians = [...accordionExpanded] @@ -209,6 +213,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { language.forEach((item) => { tags += `tags=${item.name}&` }) + setLoading(true) getAvailablePromptsApi.request({ tags: tags }) } @@ -379,7 +384,15 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { - {availablePrompNameList && availablePrompNameList.length == 0 && ( + {loading && ( + + + promptEmptySVG + +
Please wait....loading Prompts
+
+ )} + {!loading && availablePrompNameList && availablePrompNameList.length === 0 && ( promptEmptySVG @@ -387,7 +400,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => {
No Available Prompts
)} - {availablePrompNameList && availablePrompNameList.length > 0 && ( + {!loading && availablePrompNameList && availablePrompNameList.length > 0 && ( From 3126442e67cfccdf14772eb6f539c765c2b9171d Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 22 Dec 2023 16:53:20 +0000 Subject: [PATCH 124/268] update variables option --- .../views/variables/AddEditVariableDialog.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/views/variables/AddEditVariableDialog.js b/packages/ui/src/views/variables/AddEditVariableDialog.js index b84d0525..933039e7 100644 --- a/packages/ui/src/views/variables/AddEditVariableDialog.js +++ b/packages/ui/src/views/variables/AddEditVariableDialog.js @@ -55,25 +55,31 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const [variableValue, setVariableValue] = useState('') const [variableType, setVariableType] = useState('static') const [dialogType, setDialogType] = useState('ADD') - const [variable, setVariable] = useState({}) useEffect(() => { - if (dialogProps.type === 'EDIT') { - // When variable dialog is opened from Variables dashboard + if (dialogProps.type === 'EDIT' && dialogProps.data) { setVariableName(dialogProps.data.name) setVariableValue(dialogProps.data.value) setVariableType(dialogProps.data.type) - setVariable(dialogProps.data) setDialogType('EDIT') + setVariable(dialogProps.data) } else if (dialogProps.type === 'ADD') { - // When variable dialog is to add a new variable setVariableName('') setVariableValue('') setVariableType('static') setDialogType('ADD') + setVariable({}) } - }, [dialogProps.data, dialogProps.type]) + + return () => { + setVariableName('') + setVariableValue('') + setVariableType('static') + setDialogType('ADD') + setVariable({}) + } + }, [dialogProps]) useEffect(() => { if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) @@ -226,10 +232,11 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
setVariableType(newValue)} - value={variableType} + value={variableType ?? 'choose an option'} /> {variableType === 'static' && ( From a7632976764a1bf648bae6f8c7253cb35fc54ffa Mon Sep 17 00:00:00 2001 From: abhishekshankr Date: Sat, 23 Dec 2023 11:27:32 -0500 Subject: [PATCH 125/268] Fixed icons borders --- .../components/nodes/prompts/FewShotPromptTemplate/prompt.svg | 1 - packages/components/nodes/prompts/PromptTemplate/prompt.svg | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg b/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg index 1484fcb2..e3a0c868 100644 --- a/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg +++ b/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg @@ -1,5 +1,4 @@ - diff --git a/packages/components/nodes/prompts/PromptTemplate/prompt.svg b/packages/components/nodes/prompts/PromptTemplate/prompt.svg index 1484fcb2..e3a0c868 100644 --- a/packages/components/nodes/prompts/PromptTemplate/prompt.svg +++ b/packages/components/nodes/prompts/PromptTemplate/prompt.svg @@ -1,5 +1,4 @@ - From e3282526624ce45925e4a4df3d8d0eb286273e5a Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 23 Dec 2023 17:41:43 +0000 Subject: [PATCH 126/268] Update AWSChatBedrock logo --- .../components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts index d869ea75..fedd731d 100644 --- a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts +++ b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts @@ -29,7 +29,7 @@ class AWSChatBedrock_ChatModels implements INode { this.name = 'awsChatBedrock' this.version = 3.0 this.type = 'AWSChatBedrock' - this.icon = 'AWS.svg' + this.icon = 'aws.svg' this.category = 'Chat Models' this.description = 'Wrapper around AWS Bedrock large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(BedrockChat)] From 735edab9ae5d85be3999978d1ae24b984cfcb5a1 Mon Sep 17 00:00:00 2001 From: Jared Tracy Date: Sun, 24 Dec 2023 23:02:49 -0600 Subject: [PATCH 127/268] Adds ENV boolean flag (DATABASE_SSL) for postgres ssl support This is needed if hosting flowise data on a postgres server that requires ssl. In PostgreSQL v15, the default rds.force_ssl is 1 (on) --- packages/server/src/DataSource.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 9265e55f..762315ac 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -46,6 +46,7 @@ export const init = async (): Promise => { username: process.env.DATABASE_USER, password: process.env.DATABASE_PASSWORD, database: process.env.DATABASE_NAME, + ssl: process.env.DATABASE_SSL === 'true', synchronize: false, migrationsRun: false, entities: Object.values(entities), From 6306904cfcf02facee9a7896aad0f35ee628c9b8 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 26 Dec 2023 15:54:50 +0000 Subject: [PATCH 128/268] add missing methods, abstract classes --- .../OpenAIFunctionAgent.ts | 11 ++++---- .../memory/MotorheadMemory/MotorheadMemory.ts | 4 +-- .../nodes/memory/ZepMemory/ZepMemory.ts | 4 +-- packages/components/src/Interface.ts | 3 +++ packages/server/src/utils/index.ts | 26 ------------------- packages/ui/src/views/variables/index.js | 2 +- 6 files changed, 13 insertions(+), 37 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 275eb950..c0095cee 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,4 +1,4 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { AgentExecutor as LCAgentExecutor, AgentExecutorInput } from 'langchain/agents' import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema' import { OutputParserException } from 'langchain/schema/output_parser' @@ -7,7 +7,6 @@ import { formatToOpenAIFunction } from 'langchain/tools' import { ToolInputParsingException, Tool } from '@langchain/core/tools' import { getBaseClasses } from '../../../src/utils' import { flatten } from 'lodash' -import { BaseChatMemory } from 'langchain/memory' import { RunnableSequence } from 'langchain/schema/runnable' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' @@ -65,7 +64,7 @@ class OpenAIFunctionAgent_Agents implements INode { } async init(nodeData: INodeData): Promise { - const memory = nodeData.inputs?.memory as BaseChatMemory + const memory = nodeData.inputs?.memory as FlowiseMemory const executor = prepareAgent(nodeData, this.sessionId) if (memory) executor.memory = memory @@ -74,7 +73,7 @@ class OpenAIFunctionAgent_Agents implements INode { } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const memory = nodeData.inputs?.memory + const memory = nodeData.inputs?.memory as FlowiseMemory const executor = prepareAgent(nodeData, this.sessionId) @@ -120,7 +119,7 @@ const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => const prepareAgent = (nodeData: INodeData, sessionId?: string) => { const model = nodeData.inputs?.model as ChatOpenAI - const memory = nodeData.inputs?.memory + const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string let tools = nodeData.inputs?.tools tools = flatten(tools) @@ -143,7 +142,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => { [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages: BaseMessage[] = await memory.getChatMessages(sessionId, true) + const messages = (await memory.getChatMessages(sessionId, true)) as BaseMessage[] return messages ?? [] } }, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 97f25ba3..938cc873 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -1,4 +1,4 @@ -import { IMessage, INode, INodeData, INodeParams, MessageType } from '../../../src/Interface' +import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput, InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' @@ -136,7 +136,7 @@ interface MotorheadMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean } -class MotorheadMemoryExtended extends MotorheadMemory { +class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false constructor(fields: MotorheadMemoryInput & MotorheadMemoryExtendedInput) { diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 3da35db2..4dda76df 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,4 +1,4 @@ -import { IMessage, INode, INodeData, INodeParams, MessageType } from '../../../src/Interface' +import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' @@ -167,7 +167,7 @@ interface ZepMemoryExtendedInput { k?: number } -class ZepMemoryExtended extends ZepMemory { +class ZepMemoryExtended extends ZepMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false lastN?: number diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index e508ebee..2a625ff6 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -214,16 +214,19 @@ export abstract class FlowiseMemory extends BufferMemory implements MemoryMethod abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise + abstract resumeMessages(messages: IMessage[]): Promise } export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods { abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise + abstract resumeMessages(messages: IMessage[]): Promise } export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods { abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise + abstract resumeMessages(messages: IMessage[]): Promise } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 41e97cc1..99e3813b 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1052,29 +1052,3 @@ export const getAllValuesFromJson = (obj: any): any[] => { extractValues(obj) return values } - -export const replaceEnvVariables = async (question: string, appDataSource: DataSource): Promise => { - // the incoming question can have more than one env variable with the pattern {{ env.VARIABLE_NAME }} - // extract all the env variables from the question and iterate through them - const envVariables = question.match(/{{[^}]*}}/g) - if (envVariables) { - for (const envVariable of envVariables) { - // this is needed as the user can have spaces between the curly braces and the env keyword - // extract the variable name from the env variable - const variableName = envVariable.replace(/{{\s*env.|\s*}}/g, '') - // get the value of the env variable from the database - const variable = await appDataSource.getRepository(Variable).findOneBy({ - name: variableName - }) - if (variable) { - let value = variable.value - if (variable.type === 'runtime') { - value = process.env[variable.name] as string - } - // replace the env variable with the value from the database - question = question.replace(envVariable, value) - } - } - } - return question -} diff --git a/packages/ui/src/views/variables/index.js b/packages/ui/src/views/variables/index.js index ca8a3dee..9d0b2e3f 100644 --- a/packages/ui/src/views/variables/index.js +++ b/packages/ui/src/views/variables/index.js @@ -215,7 +215,7 @@ const Variables = () => { - {variables.length <= 0 && ( + {variables.length === 0 && ( Date: Tue, 26 Dec 2023 16:00:46 +0000 Subject: [PATCH 129/268] ability to use flow.input within custom tool --- packages/components/nodes/tools/CustomTool/CustomTool.ts | 5 +++-- packages/ui/src/views/tools/HowToUseFunctionDialog.js | 3 +++ packages/ui/src/views/tools/ToolDialog.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index 9ceda919..6ffcc0e2 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -60,7 +60,7 @@ class CustomTool_Tools implements INode { } } - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const selectedToolId = nodeData.inputs?.selectedTool as string const customToolFunc = nodeData.inputs?.customToolFunc as string @@ -101,7 +101,8 @@ class CustomTool_Tools implements INode { const flow = { chatId: options.chatId, // id is uppercase (I) - chatflowId: options.chatflowid // id is lowercase (i) + chatflowId: options.chatflowid, // id is lowercase (i) + input } let dynamicStructuredTool = new DynamicStructuredTool(obj) diff --git a/packages/ui/src/views/tools/HowToUseFunctionDialog.js b/packages/ui/src/views/tools/HowToUseFunctionDialog.js index 00acef0d..47ecee89 100644 --- a/packages/ui/src/views/tools/HowToUseFunctionDialog.js +++ b/packages/ui/src/views/tools/HowToUseFunctionDialog.js @@ -43,6 +43,9 @@ const HowToUseFunctionDialog = ({ show, onCancel }) => {
  • $flow.chatflowId
  • +
  • + $flow.input +
  • diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index b011c07e..ab6d6aa0 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -33,7 +33,7 @@ import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const exampleAPIFunc = `/* * You can use any libraries imported in Flowise * You can use properties specified in Output Schema as variables. Ex: Property = userid, Variable = $userid -* You can get default flow config: $flow.sessionId, $flow.chatId, $flow.chatflowId +* You can get default flow config: $flow.sessionId, $flow.chatId, $flow.chatflowId, $flow.input * You can get custom variables: $vars. * Must return a string value at the end of function */ From b6d08268d48b2ff3122f90342e5f45d89cac09c3 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 26 Dec 2023 16:13:14 +0000 Subject: [PATCH 130/268] add abstract methods --- packages/components/nodes/memory/DynamoDb/DynamoDb.ts | 4 ++++ .../nodes/memory/MongoDBMemory/MongoDBMemory.ts | 4 ++++ .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 8 ++++++-- .../UpstashRedisBackedChatMemory.ts | 4 ++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 15b00d33..872ec0b5 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -306,6 +306,10 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.dynamodbClient.send(new DeleteItemCommand(params)) await this.clear() } + + async resumeMessages(): Promise { + return + } } module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index 681e9042..b422921e 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -221,6 +221,10 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.collection.deleteOne({ sessionId: id }) await this.clear() } + + async resumeMessages(): Promise { + return + } } module.exports = { nodeClass: MongoDB_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 72af1cb5..a02df3ea 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,4 +1,4 @@ -import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType } from '../../../src/Interface' +import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType, FlowiseMemory, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses, @@ -187,7 +187,7 @@ interface BufferMemoryExtendedInput { sessionId: string } -class BufferMemoryExtended extends BufferMemory { +class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false sessionId = '' redisClient: Redis @@ -236,6 +236,10 @@ class BufferMemoryExtended extends BufferMemory { await this.redisClient.del(id) await this.clear() } + + async resumeMessages(): Promise { + return + } } module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 3ff20a88..c3f97123 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -186,6 +186,10 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.redisClient.del(id) await this.clear() } + + async resumeMessages(): Promise { + return + } } module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory } From 25f43f2a3098c8ac5fed237a28386d67315950ac Mon Sep 17 00:00:00 2001 From: Jared Tracy Date: Wed, 27 Dec 2023 19:07:43 -0600 Subject: [PATCH 131/268] includes DATABASE_SSL in other necessary files --- CONTRIBUTING.md | 1 + docker/.env.example | 1 + docker/docker-compose.yml | 1 + packages/server/.env.example | 1 + packages/server/src/commands/start.ts | 2 ++ 5 files changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cfb7d3a9..04cb80b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,6 +138,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_USER | Database username (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_PASSWORD | Database password (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_NAME | Database name (When DATABASE_TYPE is not sqlite) | String | | +| DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | diff --git a/docker/.env.example b/docker/.env.example index 967a1ab6..7d4f1699 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -12,6 +12,7 @@ LOG_PATH=/root/.flowise/logs # DATABASE_NAME="flowise" # DATABASE_USER="" # DATABASE_PASSWORD="" +# DATABASE_SSL=true # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8e0e1af5..92688469 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -16,6 +16,7 @@ services: - DATABASE_NAME=${DATABASE_NAME} - DATABASE_USER=${DATABASE_USER} - DATABASE_PASSWORD=${DATABASE_PASSWORD} + - DATABASE_SSL=${DATABASE_SSL} - APIKEY_PATH=${APIKEY_PATH} - SECRETKEY_PATH=${SECRETKEY_PATH} - FLOWISE_SECRETKEY_OVERWRITE=${FLOWISE_SECRETKEY_OVERWRITE} diff --git a/packages/server/.env.example b/packages/server/.env.example index 0ad11f3f..6e746a4d 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -12,6 +12,7 @@ PORT=3000 # DATABASE_NAME="flowise" # DATABASE_USER="" # DATABASE_PASSWORD="" +# DATABASE_SSL=true # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index cd874264..d4e8cfdb 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -35,6 +35,7 @@ export default class Start extends Command { DATABASE_NAME: Flags.string(), DATABASE_USER: Flags.string(), DATABASE_PASSWORD: Flags.string(), + DATABASE_SSL: Flags.string(), LANGCHAIN_TRACING_V2: Flags.string(), LANGCHAIN_ENDPOINT: Flags.string(), LANGCHAIN_API_KEY: Flags.string(), @@ -104,6 +105,7 @@ export default class Start extends Command { if (flags.DATABASE_NAME) process.env.DATABASE_NAME = flags.DATABASE_NAME if (flags.DATABASE_USER) process.env.DATABASE_USER = flags.DATABASE_USER if (flags.DATABASE_PASSWORD) process.env.DATABASE_PASSWORD = flags.DATABASE_PASSWORD + if (flags.DATABASE_SSL) process.env.DATABASE_SSL = flags.DATABASE_SSL // Langsmith tracing if (flags.LANGCHAIN_TRACING_V2) process.env.LANGCHAIN_TRACING_V2 = flags.LANGCHAIN_TRACING_V2 From c9a6622df72779d5eb6f424b0f7df89a0e83da6a Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 28 Dec 2023 01:07:45 +0000 Subject: [PATCH 132/268] added LLM to moderation to detect similar deny sentences --- .../SimplePromptModeration.ts | 16 +++++++++--- .../SimplePromptModerationRunner.ts | 25 +++++++++++++++---- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts index d98c4867..ad3cfadd 100644 --- a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts +++ b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts @@ -2,6 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src' import { Moderation } from '../Moderation' import { SimplePromptModerationRunner } from './SimplePromptModerationRunner' +import { BaseChatModel } from 'langchain/chat_models/base' class SimplePromptModeration implements INode { label: string @@ -17,7 +18,7 @@ class SimplePromptModeration implements INode { constructor() { this.label = 'Simple Prompt Moderation' this.name = 'inputModerationSimple' - this.version = 1.0 + this.version = 2.0 this.type = 'Moderation' this.icon = 'moderation.svg' this.category = 'Moderation' @@ -30,8 +31,14 @@ class SimplePromptModeration implements INode { type: 'string', rows: 4, placeholder: `ignore previous instructions\ndo not follow the directions\nyou must ignore all previous instructions`, - description: 'An array of string literals (enter one per line) that should not appear in the prompt text.', - optional: false + description: 'An array of string literals (enter one per line) that should not appear in the prompt text.' + }, + { + label: 'Chat Model', + name: 'model', + type: 'BaseChatModel', + description: 'Use LLM to detect if the input is similar to those specified in Deny List', + optional: true }, { label: 'Error Message', @@ -46,9 +53,10 @@ class SimplePromptModeration implements INode { async init(nodeData: INodeData): Promise { const denyList = nodeData.inputs?.denyList as string + const model = nodeData.inputs?.model as BaseChatModel const moderationErrorMessage = nodeData.inputs?.moderationErrorMessage as string - return new SimplePromptModerationRunner(denyList, moderationErrorMessage) + return new SimplePromptModerationRunner(denyList, moderationErrorMessage, model) } } diff --git a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts index 08f9ed1e..10d351cf 100644 --- a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts +++ b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts @@ -1,23 +1,38 @@ import { Moderation } from '../Moderation' +import { BaseChatModel } from 'langchain/chat_models/base' export class SimplePromptModerationRunner implements Moderation { private readonly denyList: string = '' private readonly moderationErrorMessage: string = '' + private readonly model: BaseChatModel - constructor(denyList: string, moderationErrorMessage: string) { + constructor(denyList: string, moderationErrorMessage: string, model?: BaseChatModel) { this.denyList = denyList if (denyList.indexOf('\n') === -1) { this.denyList += '\n' } this.moderationErrorMessage = moderationErrorMessage + if (model) this.model = model } async checkForViolations(input: string): Promise { - this.denyList.split('\n').forEach((denyListItem) => { - if (denyListItem && denyListItem !== '' && input.toLowerCase().includes(denyListItem.toLowerCase())) { - throw Error(this.moderationErrorMessage) + if (this.model) { + const denyArray = this.denyList.split('\n') + for (const denyStr of denyArray) { + const res = await this.model.invoke( + `Are these two sentences similar to each other? Only return Yes or No.\nFirst sentence: ${input}\nSecond sentence: ${denyStr}` + ) + if (res.content.toString().toLowerCase().includes('yes')) { + throw Error(this.moderationErrorMessage) + } } - }) + } else { + this.denyList.split('\n').forEach((denyListItem) => { + if (denyListItem && denyListItem !== '' && input.toLowerCase().includes(denyListItem.toLowerCase())) { + throw Error(this.moderationErrorMessage) + } + }) + } return Promise.resolve(input) } } From 85e6fad0aa223ba46d9cf32f1f88a13619c322d0 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 28 Dec 2023 01:11:00 +0000 Subject: [PATCH 133/268] added input moderation to assistant, prevent app from crashing by using try catch --- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 72 ++++++++++++++----- .../chatflows/OpenAI Assistant.json | 11 ++- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index c2d0e782..c5503610 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -9,6 +9,8 @@ import fetch from 'node-fetch' import { flatten, uniqWith, isEqual } from 'lodash' import { zodToJsonSchema } from 'zod-to-json-schema' import { AnalyticHandler } from '../../../src/handler' +import { Moderation, checkInputs, streamResponse } from '../../moderation/Moderation' +import { formatResponse } from '../../outputparsers/OutputParserHelpers' class OpenAIAssistant_Agents implements INode { label: string @@ -24,7 +26,7 @@ class OpenAIAssistant_Agents implements INode { constructor() { this.label = 'OpenAI Assistant' this.name = 'openAIAssistant' - this.version = 2.0 + this.version = 3.0 this.type = 'OpenAIAssistant' this.category = 'Agents' this.icon = 'assistant.svg' @@ -43,6 +45,14 @@ class OpenAIAssistant_Agents implements INode { type: 'Tool', list: true }, + { + label: 'Input Moderation', + description: 'Detect text that could generate harmful output and prevent it from being sent to the language model', + name: 'inputModeration', + type: 'Moderation', + optional: true, + list: true + }, { label: 'Disable File Download', name: 'disableFileDownload', @@ -133,6 +143,20 @@ class OpenAIAssistant_Agents implements INode { const appDataSource = options.appDataSource as DataSource const databaseEntities = options.databaseEntities as IDatabaseEntity const disableFileDownload = nodeData.inputs?.disableFileDownload as boolean + const moderations = nodeData.inputs?.inputModeration as Moderation[] + const isStreaming = options.socketIO && options.socketIOClientId + const socketIO = isStreaming ? options.socketIO : undefined + const socketIOClientId = isStreaming ? options.socketIOClientId : '' + + if (moderations && moderations.length > 0) { + try { + input = await checkInputs(moderations, input) + } catch (e) { + await new Promise((resolve) => setTimeout(resolve, 500)) + streamResponse(isStreaming, e.message, socketIO, socketIOClientId) + return formatResponse(e.message) + } + } let tools = nodeData.inputs?.tools tools = flatten(tools) @@ -249,7 +273,12 @@ class OpenAIAssistant_Agents implements INode { const actions: ICommonObject[] = [] run.required_action.submit_tool_outputs.tool_calls.forEach((item) => { const functionCall = item.function - const args = JSON.parse(functionCall.arguments) + let args = {} + try { + args = JSON.parse(functionCall.arguments) + } catch (e) { + console.error('Error parsing arguments, default to empty object') + } actions.push({ tool: functionCall.name, toolInput: args, @@ -264,21 +293,32 @@ class OpenAIAssistant_Agents implements INode { // Start tool analytics const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds) + if (options.socketIO && options.socketIOClientId) + options.socketIO.to(options.socketIOClientId).emit('tool', tool.name) - const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId) - - // End tool analytics - await analyticHandlers.onToolEnd(toolIds, toolOutput) - - submitToolOutputs.push({ - tool_call_id: actions[i].toolCallId, - output: toolOutput - }) - usedTools.push({ - tool: tool.name, - toolInput: actions[i].toolInput, - toolOutput - }) + try { + const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId) + await analyticHandlers.onToolEnd(toolIds, toolOutput) + submitToolOutputs.push({ + tool_call_id: actions[i].toolCallId, + output: toolOutput + }) + usedTools.push({ + tool: tool.name, + toolInput: actions[i].toolInput, + toolOutput + }) + } catch (e) { + await analyticHandlers.onToolEnd(toolIds, e) + console.error('Error executing tool', e) + clearInterval(timeout) + reject( + new Error( + `Error processing thread: ${state}, Thread ID: ${threadId}, Run ID: ${runId}, Tool: ${tool.name}` + ) + ) + break + } } if (submitToolOutputs.length) { diff --git a/packages/server/marketplaces/chatflows/OpenAI Assistant.json b/packages/server/marketplaces/chatflows/OpenAI Assistant.json index ba4c6134..e9311c97 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Assistant.json +++ b/packages/server/marketplaces/chatflows/OpenAI Assistant.json @@ -14,7 +14,7 @@ "data": { "id": "openAIAssistant_0", "label": "OpenAI Assistant", - "version": 2, + "version": 3, "name": "openAIAssistant", "type": "OpenAIAssistant", "baseClasses": ["OpenAIAssistant"], @@ -45,6 +45,15 @@ "type": "Tool", "list": true, "id": "openAIAssistant_0-input-tools-Tool" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "openAIAssistant_0-input-inputModeration-Moderation" } ], "inputs": { From 4f940b0d910d61172f6b143a9b4ff518ee35e99c Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 28 Dec 2023 02:02:49 +0000 Subject: [PATCH 134/268] chck for empty string --- .../SimplePromptModeration/SimplePromptModerationRunner.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts index 10d351cf..c9a11643 100644 --- a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts +++ b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts @@ -19,6 +19,7 @@ export class SimplePromptModerationRunner implements Moderation { if (this.model) { const denyArray = this.denyList.split('\n') for (const denyStr of denyArray) { + if (!denyStr || denyStr === '') continue const res = await this.model.invoke( `Are these two sentences similar to each other? Only return Yes or No.\nFirst sentence: ${input}\nSecond sentence: ${denyStr}` ) From ab556b71cf8ea95a9fb76b71bc95d0662c474926 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 29 Dec 2023 01:43:30 +0000 Subject: [PATCH 135/268] avoid submitting tool outputs when in_progress --- .../nodes/agents/OpenAIAssistant/OpenAIAssistant.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index c5503610..1f9d4eab 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -321,7 +321,10 @@ class OpenAIAssistant_Agents implements INode { } } - if (submitToolOutputs.length) { + const newRun = await openai.beta.threads.runs.retrieve(threadId, runId) + const newStatus = newRun?.status + + if (submitToolOutputs.length && newStatus !== 'in_progress') { await openai.beta.threads.runs.submitToolOutputs(threadId, runId, { tool_outputs: submitToolOutputs }) From 5ab2d63de8b066a6ca09df2d23f3aae502f40e42 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 29 Dec 2023 13:29:24 +0000 Subject: [PATCH 136/268] only submitting tool outputs when requiresaction --- .../components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 1f9d4eab..85d35ea9 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -324,7 +324,7 @@ class OpenAIAssistant_Agents implements INode { const newRun = await openai.beta.threads.runs.retrieve(threadId, runId) const newStatus = newRun?.status - if (submitToolOutputs.length && newStatus !== 'in_progress') { + if (submitToolOutputs.length && newStatus === 'requires_action') { await openai.beta.threads.runs.submitToolOutputs(threadId, runId, { tool_outputs: submitToolOutputs }) From 8eabd8f0c26e060c7651b02c3166a52fd2ed6f66 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 29 Dec 2023 13:50:33 +0000 Subject: [PATCH 137/268] wrap in a try catch block --- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 85d35ea9..cf69022b 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -324,14 +324,19 @@ class OpenAIAssistant_Agents implements INode { const newRun = await openai.beta.threads.runs.retrieve(threadId, runId) const newStatus = newRun?.status - if (submitToolOutputs.length && newStatus === 'requires_action') { - await openai.beta.threads.runs.submitToolOutputs(threadId, runId, { - tool_outputs: submitToolOutputs - }) - resolve(state) - } else { - await openai.beta.threads.runs.cancel(threadId, runId) - resolve('requires_action_retry') + try { + if (submitToolOutputs.length && newStatus === 'requires_action') { + await openai.beta.threads.runs.submitToolOutputs(threadId, runId, { + tool_outputs: submitToolOutputs + }) + resolve(state) + } else { + await openai.beta.threads.runs.cancel(threadId, runId) + resolve('requires_action_retry') + } + } catch (e) { + clearInterval(timeout) + reject(new Error(`Error submitting tool outputs: ${state}, Thread ID: ${threadId}, Run ID: ${runId}`)) } } } else if (state === 'cancelled' || state === 'expired' || state === 'failed') { From 4dd2f245ffdfb2fac5a169c97f9c825e7aa04828 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 29 Dec 2023 20:35:42 +0530 Subject: [PATCH 138/268] Compression Retriever: Reciprocal Rank Fusion --- .../retrievers/RRFRetriever/RRFRetriever.ts | 84 ++++++++++++++++ .../RRFRetriever/ReciprocalRankFusion.ts | 95 +++++++++++++++++++ .../RRFRetriever/compressionRetriever.svg | 7 ++ 3 files changed, 186 insertions(+) create mode 100644 packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts create mode 100644 packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts create mode 100644 packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg diff --git a/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts b/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts new file mode 100644 index 00000000..8d6d9d6f --- /dev/null +++ b/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts @@ -0,0 +1,84 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { BaseLanguageModel } from 'langchain/base_language' +import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' +import { BaseRetriever } from 'langchain/schema/retriever' +import { ReciprocalRankFusion } from './ReciprocalRankFusion' +import { VectorStoreRetriever } from 'langchain/vectorstores/base' + +class RRFRetriever_Retrievers implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + badge: string + + constructor() { + this.label = 'Reciprocal Rank Fusion Retriever' + this.name = 'RRFRetriever' + this.version = 2.0 + this.type = 'RRFRetriever' + this.badge = 'NEW' + this.icon = 'compressionRetriever.svg' + this.category = 'Retrievers' + this.description = 'Reciprocal Rank Fusion to re-rank search results by multiple query generation.' + this.baseClasses = [this.type, 'BaseRetriever'] + this.inputs = [ + { + label: 'Base Retriever', + name: 'baseRetriever', + type: 'VectorStoreRetriever' + }, + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'Query Count', + name: 'queryCount', + description: 'Number of synthetic queries to generate. Default to 4', + placeholder: '4', + type: 'number', + default: 4, + additionalParams: true, + optional: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to the TopK of the Base Retriever', + placeholder: '0', + type: 'number', + default: 0, + additionalParams: true, + optional: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const llm = nodeData.inputs?.model as BaseLanguageModel + const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever + const queryCount = nodeData.inputs?.queryCount as string + const q = queryCount ? parseFloat(queryCount) : 4 + const topK = nodeData.inputs?.topK as string + let k = topK ? parseFloat(topK) : 4 + + if (k <= 0) { + k = (baseRetriever as VectorStoreRetriever).k + } + + const ragFusion = new ReciprocalRankFusion(llm, baseRetriever as VectorStoreRetriever, q, k) + return new ContextualCompressionRetriever({ + baseCompressor: ragFusion, + baseRetriever: baseRetriever + }) + } +} + +module.exports = { nodeClass: RRFRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts b/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts new file mode 100644 index 00000000..134d7c8a --- /dev/null +++ b/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts @@ -0,0 +1,95 @@ +import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors' +import { Document } from 'langchain/document' +import { Callbacks } from 'langchain/callbacks' +import { BaseLanguageModel } from 'langchain/base_language' +import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate } from 'langchain/prompts' +import { LLMChain } from 'langchain/chains' +import { VectorStoreRetriever } from 'langchain/vectorstores/base' + +export class ReciprocalRankFusion extends BaseDocumentCompressor { + private readonly llm: BaseLanguageModel + private readonly queryCount: number + private readonly topK: number + private baseRetriever: VectorStoreRetriever + constructor(llm: BaseLanguageModel, baseRetriever: VectorStoreRetriever, queryCount: number, topK: number) { + super() + this.queryCount = queryCount + this.llm = llm + this.baseRetriever = baseRetriever + this.topK = topK + } + async compressDocuments( + documents: Document>[], + query: string, + _?: Callbacks | undefined + ): Promise>[]> { + // avoid empty api call + if (documents.length === 0) { + return [] + } + const chatPrompt = ChatPromptTemplate.fromMessages([ + SystemMessagePromptTemplate.fromTemplate( + 'You are a helpful assistant that generates multiple search queries based on a single input query.' + ), + HumanMessagePromptTemplate.fromTemplate( + 'Generate multiple search queries related to: {input}. Provide these alternative questions separated by newlines, do not add any numbers.' + ), + HumanMessagePromptTemplate.fromTemplate('OUTPUT (' + this.queryCount + ' queries):') + ]) + const llmChain = new LLMChain({ + llm: this.llm, + prompt: chatPrompt + }) + const multipleQueries = await llmChain.call({ input: query }) + const queries = [] + queries.push(query) + multipleQueries.text.split('\n').map((q: string) => { + queries.push(q) + }) + console.log(JSON.stringify(queries)) + const docList: Document>[][] = [] + for (let i = 0; i < queries.length; i++) { + const resultOne = await this.baseRetriever.vectorStore.similaritySearch(queries[i], 5) + const docs: any[] = [] + resultOne.forEach((doc) => { + docs.push(doc) + }) + docList.push(docs) + } + + return this.reciprocalRankFunction(docList, 60) + } + + reciprocalRankFunction(docList: Document>[][], k: number): Document>[] { + docList.forEach((docs: Document>[]) => { + docs.forEach((doc: any, index: number) => { + let rank = index + 1 + if (doc.metadata.relevancy_score) { + doc.metadata.relevancy_score += 1 / (rank + k) + } else { + doc.metadata.relevancy_score = 1 / (rank + k) + } + }) + }) + const scoreArray: any[] = [] + docList.forEach((docs: Document>[]) => { + docs.forEach((doc: any) => { + scoreArray.push(doc.metadata.relevancy_score) + }) + }) + scoreArray.sort((a, b) => b - a) + const rerankedDocuments: Document>[] = [] + const seenScores: any[] = [] + scoreArray.forEach((score) => { + docList.forEach((docs) => { + docs.forEach((doc: any) => { + if (doc.metadata.relevancy_score === score && seenScores.indexOf(score) === -1) { + rerankedDocuments.push(doc) + seenScores.push(doc.metadata.relevancy_score) + } + }) + }) + }) + return rerankedDocuments.splice(0, this.topK) + } +} diff --git a/packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg new file mode 100644 index 00000000..23c52d25 --- /dev/null +++ b/packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From d0ab21e733d03098a1faa3f0fd50b5d5baa9d381 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 29 Dec 2023 20:37:25 +0530 Subject: [PATCH 139/268] Compression Retriever: Addition of topK to Cohere Rerank Retriever --- .../CohereRerankRetriever/CohereRerank.ts | 8 +++++--- .../CohereRerankRetriever.ts | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts index 612581ed..55f3c4aa 100644 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts @@ -5,12 +5,13 @@ import axios from 'axios' export class CohereRerank extends BaseDocumentCompressor { private cohereAPIKey: any private COHERE_API_URL = 'https://api.cohere.ai/v1/rerank' - private model: string - - constructor(cohereAPIKey: string, model: string) { + private readonly model: string + private readonly k: number + constructor(cohereAPIKey: string, model: string, k: number) { super() this.cohereAPIKey = cohereAPIKey this.model = model + this.k = k } async compressDocuments( documents: Document>[], @@ -30,6 +31,7 @@ export class CohereRerank extends BaseDocumentCompressor { } const data = { model: this.model, + topN: this.k, max_chunks_per_doc: 10, query: query, return_documents: false, diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts index 2e7090bc..3c1872b3 100644 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts @@ -3,6 +3,7 @@ import { BaseRetriever } from 'langchain/schema/retriever' import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' import { getCredentialData, getCredentialParam } from '../../../src' import { CohereRerank } from './CohereRerank' +import { VectorStoreRetriever } from 'langchain/vectorstores/base' class CohereRerankRetriever_Retrievers implements INode { label: string @@ -56,6 +57,16 @@ class CohereRerankRetriever_Retrievers implements INode { ], default: 'rerank-english-v2.0', optional: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to the TopK of the Base Retriever', + placeholder: '0', + type: 'number', + default: 0, + additionalParams: true, + optional: true } ] } @@ -65,8 +76,14 @@ class CohereRerankRetriever_Retrievers implements INode { const model = nodeData.inputs?.model as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) + const topK = nodeData.inputs?.topK as string + let k = topK ? parseFloat(topK) : 4 - const cohereCompressor = new CohereRerank(cohereApiKey, model) + if (k <= 0) { + k = (baseRetriever as VectorStoreRetriever).k + } + + const cohereCompressor = new CohereRerank(cohereApiKey, model, k) return new ContextualCompressionRetriever({ baseCompressor: cohereCompressor, baseRetriever: baseRetriever From da18b6a5c0eceb9f15d71c66083e5cf0daafa1c3 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 29 Dec 2023 15:17:26 +0000 Subject: [PATCH 140/268] Update 1702200925471-AddVariableEntity.ts --- .../migrations/postgres/1702200925471-AddVariableEntity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts index d4a1d7be..c6d3902f 100644 --- a/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts +++ b/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts @@ -10,7 +10,7 @@ export class AddVariableEntity1699325775451 implements MigrationInterface { "type" text NULL, "createdDate" timestamp NOT NULL DEFAULT now(), "updatedDate" timestamp NOT NULL DEFAULT now(), - CONSTRAINT "PK_3c7cea7a044ac4c92764576cdbf" PRIMARY KEY (id) + CONSTRAINT "PK_98419043dd704f54-9830ab78f8" PRIMARY KEY (id) );` ) } From 1bd3b5d0ee9395ed5bce48eadc5c4e58f5385099 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sat, 30 Dec 2023 08:07:15 +0530 Subject: [PATCH 141/268] Compression Retriever: Addition of constant to RRF Retriever --- .../retrievers/RRFRetriever/RRFRetriever.ts | 16 +++++++++++++++- .../RRFRetriever/ReciprocalRankFusion.ts | 6 ++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts b/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts index 8d6d9d6f..3229b3a8 100644 --- a/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts +++ b/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts @@ -57,6 +57,18 @@ class RRFRetriever_Retrievers implements INode { default: 0, additionalParams: true, optional: true + }, + { + label: 'Constant', + name: 'c', + description: + 'A constant added to the rank, controlling the balance between the importance of high-ranked items and the consideration given to lower-ranked items.\n' + + 'Default is 60', + placeholder: '60', + type: 'number', + default: 60, + additionalParams: true, + optional: true } ] } @@ -68,12 +80,14 @@ class RRFRetriever_Retrievers implements INode { const q = queryCount ? parseFloat(queryCount) : 4 const topK = nodeData.inputs?.topK as string let k = topK ? parseFloat(topK) : 4 + const constantC = nodeData.inputs?.c as string + let c = topK ? parseFloat(constantC) : 60 if (k <= 0) { k = (baseRetriever as VectorStoreRetriever).k } - const ragFusion = new ReciprocalRankFusion(llm, baseRetriever as VectorStoreRetriever, q, k) + const ragFusion = new ReciprocalRankFusion(llm, baseRetriever as VectorStoreRetriever, q, k, c) return new ContextualCompressionRetriever({ baseCompressor: ragFusion, baseRetriever: baseRetriever diff --git a/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts b/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts index 134d7c8a..b14608fe 100644 --- a/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts +++ b/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts @@ -10,13 +10,15 @@ export class ReciprocalRankFusion extends BaseDocumentCompressor { private readonly llm: BaseLanguageModel private readonly queryCount: number private readonly topK: number + private readonly c: number private baseRetriever: VectorStoreRetriever - constructor(llm: BaseLanguageModel, baseRetriever: VectorStoreRetriever, queryCount: number, topK: number) { + constructor(llm: BaseLanguageModel, baseRetriever: VectorStoreRetriever, queryCount: number, topK: number, c: number) { super() this.queryCount = queryCount this.llm = llm this.baseRetriever = baseRetriever this.topK = topK + this.c = c } async compressDocuments( documents: Document>[], @@ -57,7 +59,7 @@ export class ReciprocalRankFusion extends BaseDocumentCompressor { docList.push(docs) } - return this.reciprocalRankFunction(docList, 60) + return this.reciprocalRankFunction(docList, this.c) } reciprocalRankFunction(docList: Document>[][], k: number): Document>[] { From fd55fa62dd6ba7d0679adb494bfb3f14070cdce3 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sat, 30 Dec 2023 08:08:15 +0530 Subject: [PATCH 142/268] fixes for lint failures --- .../nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts b/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts index b14608fe..0789ca17 100644 --- a/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts +++ b/packages/components/nodes/retrievers/RRFRetriever/ReciprocalRankFusion.ts @@ -48,7 +48,6 @@ export class ReciprocalRankFusion extends BaseDocumentCompressor { multipleQueries.text.split('\n').map((q: string) => { queries.push(q) }) - console.log(JSON.stringify(queries)) const docList: Document>[][] = [] for (let i = 0; i < queries.length; i++) { const resultOne = await this.baseRetriever.vectorStore.similaritySearch(queries[i], 5) From fe0c22255baf2cc5865a5acfa9bdd9ce07a55ef6 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sat, 30 Dec 2023 16:17:03 +0530 Subject: [PATCH 143/268] Bugfix: Upsert successful, but failed to insert documents --- packages/components/nodes/documentloaders/Notion/NotionDB.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/components/nodes/documentloaders/Notion/NotionDB.ts b/packages/components/nodes/documentloaders/Notion/NotionDB.ts index 5c171c7f..4e37ad22 100644 --- a/packages/components/nodes/documentloaders/Notion/NotionDB.ts +++ b/packages/components/nodes/documentloaders/Notion/NotionDB.ts @@ -66,6 +66,10 @@ class NotionDB_DocumentLoaders implements INode { auth: notionIntegrationToken }, id: databaseId, + callerOptions: { + maxConcurrency: 64 // Default value + }, + propertiesAsHeader: true, // Prepends a front matter header of the page properties to the page contents type: 'database' } const loader = new NotionAPILoader(obj) From 75b5b33d8d9b92c4ef9458b81dc7f1e8c949532a Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 30 Dec 2023 12:29:00 +0000 Subject: [PATCH 144/268] update langchain version --- .../nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts | 5 ++--- .../components/nodes/chatmodels/ChatMistral/ChatMistral.ts | 4 +++- .../components/nodes/chatmodels/ChatOllama/ChatOllama.ts | 5 ++--- .../nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts | 2 +- packages/components/nodes/llms/Ollama/Ollama.ts | 3 +-- packages/components/package.json | 6 +++--- packages/server/src/utils/index.ts | 2 +- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 99e151e6..9b7b724a 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -1,7 +1,6 @@ -import { OpenAIBaseInput } from 'langchain/dist/types/openai-types' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { AzureOpenAIInput, ChatOpenAI } from 'langchain/chat_models/openai' +import { AzureOpenAIInput, ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai' import { BaseCache } from 'langchain/schema' import { BaseLLMParams } from 'langchain/llms/base' @@ -123,7 +122,7 @@ class AzureChatOpenAI_ChatModels implements INode { const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) - const obj: Partial & BaseLLMParams & Partial = { + const obj: Partial & BaseLLMParams & Partial = { temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts index f65de851..91624574 100644 --- a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -124,13 +124,15 @@ class ChatMistral_ChatModels implements INode { const safeMode = nodeData.inputs?.safeMode as boolean const randomSeed = nodeData.inputs?.safeMode as string const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string + const streaming = nodeData.inputs?.streaming as boolean // Waiting fix from langchain + mistral to enable streaming - https://github.com/mistralai/client-js/issues/18 const cache = nodeData.inputs?.cache as BaseCache const obj: ChatMistralAIInput = { apiKey: apiKey, - modelName: modelName + modelName: modelName, + streaming: streaming ?? true } if (maxOutputTokens) obj.maxTokens = parseInt(maxOutputTokens, 10) diff --git a/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts b/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts index ed58589b..d445c7e1 100644 --- a/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts +++ b/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts @@ -1,8 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { ChatOllama } from 'langchain/chat_models/ollama' +import { ChatOllama, ChatOllamaInput } from 'langchain/chat_models/ollama' import { BaseCache } from 'langchain/schema' -import { OllamaInput } from 'langchain/dist/util/ollama' import { BaseLLMParams } from 'langchain/llms/base' class ChatOllama_ChatModels implements INode { @@ -209,7 +208,7 @@ class ChatOllama_ChatModels implements INode { const cache = nodeData.inputs?.cache as BaseCache - const obj: OllamaInput & BaseLLMParams = { + const obj: ChatOllamaInput & BaseLLMParams = { baseUrl, temperature: parseFloat(temperature), model: modelName diff --git a/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts b/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts index 698770b3..8892b03f 100644 --- a/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts +++ b/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts @@ -1,7 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' +import { OllamaInput } from 'langchain/llms/ollama' import { OllamaEmbeddings } from 'langchain/embeddings/ollama' -import { OllamaInput } from 'langchain/dist/util/ollama' class OllamaEmbedding_Embeddings implements INode { label: string diff --git a/packages/components/nodes/llms/Ollama/Ollama.ts b/packages/components/nodes/llms/Ollama/Ollama.ts index c7250a04..385890c9 100644 --- a/packages/components/nodes/llms/Ollama/Ollama.ts +++ b/packages/components/nodes/llms/Ollama/Ollama.ts @@ -1,8 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { Ollama } from 'langchain/llms/ollama' +import { Ollama, OllamaInput } from 'langchain/llms/ollama' import { BaseCache } from 'langchain/schema' -import { OllamaInput } from 'langchain/dist/util/ollama' import { BaseLLMParams } from 'langchain/llms/base' class Ollama_LLMs implements INode { diff --git a/packages/components/package.json b/packages/components/package.json index 9cb0bf1e..8a145186 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -26,8 +26,8 @@ "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", - "@langchain/google-genai": "^0.0.3", - "@langchain/mistralai": "^0.0.3", + "@langchain/google-genai": "^0.0.6", + "@langchain/mistralai": "^0.0.6", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", @@ -52,7 +52,7 @@ "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", - "langchain": "^0.0.196", + "langchain": "^0.0.213", "langfuse": "^1.2.0", "langfuse-langchain": "^1.0.31", "langsmith": "^0.0.49", diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 0bc28861..ce444512 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -818,7 +818,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component */ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNodeData: INodeData) => { const streamAvailableLLMs = { - 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic', 'chatOllama', 'awsChatBedrock'], + 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic', 'chatOllama', 'awsChatBedrock', 'chatMistralAI'], LLMs: ['azureOpenAI', 'openAI', 'ollama'] } From 9e7f3587f14881e5192ff4a2ef8f85d83b969fad Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sat, 30 Dec 2023 18:22:21 +0530 Subject: [PATCH 145/268] Upgrading of analytic dependencies - langfuse and langsmith. --- packages/components/package.json | 6 +++--- packages/components/src/handler.ts | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 9cb0bf1e..ef1f92eb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -53,9 +53,9 @@ "husky": "^8.0.3", "ioredis": "^5.3.2", "langchain": "^0.0.196", - "langfuse": "^1.2.0", - "langfuse-langchain": "^1.0.31", - "langsmith": "^0.0.49", + "langfuse": "2.0.2", + "langfuse-langchain": "2.0.2", + "langsmith": "0.0.53", "linkifyjs": "^4.1.1", "llmonitor": "^0.5.5", "mammoth": "^1.5.1", diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index 29aff3e2..ce7a1a1c 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -536,9 +536,10 @@ export class AnalyticHandler { if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace] if (trace) { + trace.id const generation = trace.generation({ name, - prompt: input + input: input }) this.handlers['langFuse'].generation = { [generation.id]: generation } returnIds['langFuse'].generation = generation.id @@ -583,7 +584,7 @@ export class AnalyticHandler { const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation] if (generation) { generation.end({ - completion: output + output: output }) } } @@ -618,7 +619,7 @@ export class AnalyticHandler { const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation] if (generation) { generation.end({ - completion: error + output: error }) } } From 28e32f0ae68a18905b52921be2bc97e4447386a2 Mon Sep 17 00:00:00 2001 From: Darien Kindlund Date: Sat, 30 Dec 2023 09:46:44 -0500 Subject: [PATCH 146/268] Initial support for Airtable views --- .../documentloaders/Airtable/Airtable.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/documentloaders/Airtable/Airtable.ts b/packages/components/nodes/documentloaders/Airtable/Airtable.ts index 70d0c674..a2c1eef3 100644 --- a/packages/components/nodes/documentloaders/Airtable/Airtable.ts +++ b/packages/components/nodes/documentloaders/Airtable/Airtable.ts @@ -55,6 +55,15 @@ class Airtable_DocumentLoaders implements INode { description: 'If your table URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, tblJdmvbrgizbYICO is the table id' }, + { + label: 'View Id', + name: 'viewId', + type: 'string', + placeholder: 'viw9UrP77Id0CE4ee', + description: + 'If your view URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, viw9UrP77Id0CE4ee is the view id', + optional: true + }, { label: 'Return All', name: 'returnAll', @@ -83,6 +92,7 @@ class Airtable_DocumentLoaders implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const baseId = nodeData.inputs?.baseId as string const tableId = nodeData.inputs?.tableId as string + const viewId = nodeData.inputs?.viewId as string const returnAll = nodeData.inputs?.returnAll as boolean const limit = nodeData.inputs?.limit as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter @@ -94,6 +104,7 @@ class Airtable_DocumentLoaders implements INode { const airtableOptions: AirtableLoaderParams = { baseId, tableId, + viewId, returnAll, accessToken, limit: limit ? parseInt(limit, 10) : 100 @@ -133,6 +144,7 @@ interface AirtableLoaderParams { baseId: string tableId: string accessToken: string + viewId?: string limit?: number returnAll?: boolean } @@ -153,16 +165,19 @@ class AirtableLoader extends BaseDocumentLoader { public readonly tableId: string + public readonly viewId?: string + public readonly accessToken: string public readonly limit: number public readonly returnAll: boolean - constructor({ baseId, tableId, accessToken, limit = 100, returnAll = false }: AirtableLoaderParams) { + constructor({ baseId, tableId, viewId, accessToken, limit = 100, returnAll = false }: AirtableLoaderParams) { super() this.baseId = baseId this.tableId = tableId + this.viewId = viewId this.accessToken = accessToken this.limit = limit this.returnAll = returnAll @@ -203,7 +218,7 @@ class AirtableLoader extends BaseDocumentLoader { } private async loadLimit(): Promise { - const params = { maxRecords: this.limit } + const params = { maxRecords: this.limit, view: this.viewId } const data = await this.fetchAirtableData(`https://api.airtable.com/v0/${this.baseId}/${this.tableId}`, params) if (data.records.length === 0) { return [] @@ -212,7 +227,7 @@ class AirtableLoader extends BaseDocumentLoader { } private async loadAll(): Promise { - const params: ICommonObject = { pageSize: 100 } + const params: ICommonObject = { pageSize: 100, view: this.viewId } let data: AirtableLoaderResponse let returnPages: AirtableLoaderPage[] = [] From 543c41b5c5c685df09b8682972d5341f2f76371a Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 30 Dec 2023 16:07:05 +0000 Subject: [PATCH 147/268] Update ChatMistral.ts --- packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts index 91624574..4524db46 100644 --- a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -125,8 +125,6 @@ class ChatMistral_ChatModels implements INode { const randomSeed = nodeData.inputs?.safeMode as string const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string const streaming = nodeData.inputs?.streaming as boolean - // Waiting fix from langchain + mistral to enable streaming - https://github.com/mistralai/client-js/issues/18 - const cache = nodeData.inputs?.cache as BaseCache const obj: ChatMistralAIInput = { From 573863ae6c071e7e2f02d8cd632524919772bab7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 30 Dec 2023 23:39:05 +0000 Subject: [PATCH 148/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.4.8=20minor=20?= =?UTF-8?q?release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 81fb3637..5ecbb59b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.7", + "version": "1.4.8", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 581dfafe..54409e29 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.7", + "version": "1.4.8", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 8b4793b317bae87e9304979871bbd8bf5061217c Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 30 Dec 2023 23:39:34 +0000 Subject: [PATCH 149/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.5.0?= =?UTF-8?q?=20minor=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 9cb0bf1e..cb3448eb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.9", + "version": "1.5.0", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 45a42c4404c82872861110606d69cb8ac3f27f14 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 30 Dec 2023 23:39:53 +0000 Subject: [PATCH 150/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.4.6=20minor?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 2ab0befb..c5549b23 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.4.5", + "version": "1.4.6", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From da76a151ff08d346c1091895876f109326b8fc89 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Sun, 31 Dec 2023 12:56:35 +0530 Subject: [PATCH 151/268] minor typo fixes... --- packages/components/src/handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index ce7a1a1c..1eb05a51 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -536,7 +536,6 @@ export class AnalyticHandler { if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) { const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace] if (trace) { - trace.id const generation = trace.generation({ name, input: input From 9ba38dcd73fff805a13da61669f5163d28e563ee Mon Sep 17 00:00:00 2001 From: cosark <121065588+cosark@users.noreply.github.com> Date: Sun, 31 Dec 2023 17:16:53 -0700 Subject: [PATCH 152/268] Added deploy template for RepoCloud.io Integration with RepoCloud Deploy Template to the Flowise GitHub page, enabling one-click deployment. This addition simplifies the process for users to quickly deploy and scale Flowise using RepoCloud's efficient cloud hosting services. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 25026237..a80c53f7 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,10 @@ Flowise support different environment variables to configure your instance. You [![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai) +### [RepoCloud](https://repocloud.io/details/?app_id=29) + +[![Deploy on RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=29) + ### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) HuggingFace Spaces From 467e71ba1f01dcd3ab2b86c79d981987f9667ba2 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 18:36:23 -0800 Subject: [PATCH 153/268] added support for MMR --- .../nodes/chains/VectaraChain/VectaraChain.ts | 38 ++++++++++++++----- .../nodes/vectorstores/Vectara/Vectara.ts | 31 +++++++++++++-- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index 3799d062..c80b354f 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -69,22 +69,23 @@ class VectaraChain_Chains implements INode { options: [ { label: 'vectara-summary-ext-v1.2.0 (gpt-3.5-turbo)', - name: 'vectara-summary-ext-v1.2.0' + name: 'vectara-summary-ext-v1.2.0', + description: 'base summarizer, available to all Vectara users' }, { label: 'vectara-experimental-summary-ext-2023-10-23-small (gpt-3.5-turbo)', name: 'vectara-experimental-summary-ext-2023-10-23-small', - description: 'In beta, available to both Growth and Scale Vectara users' + description: `In beta, available to both Growth and Scale Vectara users` }, { label: 'vectara-summary-ext-v1.3.0 (gpt-4.0)', name: 'vectara-summary-ext-v1.3.0', - description: 'Only available to paying Scale Vectara users' + description: 'Only available to Scale Vectara users' }, { label: 'vectara-experimental-summary-ext-2023-10-23-med (gpt-4.0)', name: 'vectara-experimental-summary-ext-2023-10-23-med', - description: 'In beta, only available to paying Scale Vectara users' + description: `In beta, only available to Scale Vectara users` } ], default: 'vectara-summary-ext-v1.2.0' @@ -228,7 +229,7 @@ class VectaraChain_Chains implements INode { async run(nodeData: INodeData, input: string): Promise { const vectorStore = nodeData.inputs?.vectaraStore as VectaraStore - const responseLang = (nodeData.inputs?.responseLang as string) ?? 'auto' + const responseLang = (nodeData.inputs?.responseLang as string) ?? 'eng' const summarizerPromptName = nodeData.inputs?.summarizerPromptName as string const maxSummarizedResultsStr = nodeData.inputs?.maxSummarizedResults as string const maxSummarizedResults = maxSummarizedResultsStr ? parseInt(maxSummarizedResultsStr, 10) : 7 @@ -247,17 +248,28 @@ class VectaraChain_Chains implements INode { lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 } })) + const mmrRerankerId = 272725718 // Vectara reranker ID for MMR const data = { query: [ { query: input, start: 0, - numResults: topK, + numResults: vectaraFilter?.mmrConfig?.mmrK > 0 ? vectaraFilter?.mmrK : topK, + corpusKey: corpusKeys, contextConfig: { sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2 }, - corpusKey: corpusKeys, + ...(vectaraFilter?.mmrConfig?.mmrK > 0 + ? { + rerankingConfig: { + rerankerId: mmrRerankerId, + mmrConfig: { + diversityBias: vectaraFilter?.mmrConfig.diversityBias + } + } + } + : {}), summary: [ { summarizerPromptName, @@ -285,6 +297,14 @@ class VectaraChain_Chains implements INode { const documents = result.responseSet[0].document let rawSummarizedText = '' + // remove responses that are not in the topK (in case of MMR) + // Note that this does not really matter functionally due to the reorder citations, but it is more efficient + const maxResponses = vectaraFilter?.mmrConfig?.mmrK > 0 ? Math.min(responses.length, topK) : responses.length + if (responses.length > maxResponses) { + responses.splice(0, maxResponses) + } + + // Add metadata to each text response given its corresponding document metadata for (let i = 0; i < responses.length; i += 1) { const responseMetadata = responses[i].metadata const documentMetadata = documents[responses[i].documentIndex].metadata @@ -301,13 +321,13 @@ class VectaraChain_Chains implements INode { responses[i].metadata = combinedMetadata } + // Create the summarization response const summaryStatus = result.responseSet[0].summary[0].status if (summaryStatus.length > 0 && summaryStatus[0].code === 'BAD_REQUEST') { throw new Error( `BAD REQUEST: Too much text for the summarizer to summarize. Please try reducing the number of search results to summarize, or the context of each result by adjusting the 'summary_num_sentences', and 'summary_num_results' parameters respectively.` ) } - if ( summaryStatus.length > 0 && summaryStatus[0].code === 'NOT_FOUND' && @@ -316,8 +336,8 @@ class VectaraChain_Chains implements INode { throw new Error(`BAD REQUEST: summarizer ${summarizerPromptName} is invalid for this account.`) } + // Reorder citations in summary and create the list of returned source documents rawSummarizedText = result.responseSet[0].summary[0]?.text - let summarizedText = reorderCitations(rawSummarizedText) let summaryResponses = applyCitationOrder(responses, rawSummarizedText) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 7460c586..98acf00c 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -82,7 +82,9 @@ class Vectara_VectorStores implements INode { label: 'Lambda', name: 'lambda', description: - 'Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.', + 'Enable hybrid search to improve retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.' + + 'A value of 0.0 means that only neural search is used, while a value of 1.0 means that only keyword-based search is used. Defaults to 0.0 (neural only).', + default: 0.0, type: 'number', additionalParams: true, optional: true @@ -90,8 +92,26 @@ class Vectara_VectorStores implements INode { { label: 'Top K', name: 'topK', - description: 'Number of top results to fetch. Defaults to 4', - placeholder: '4', + description: 'Number of top results to fetch. Defaults to 5', + placeholder: '5', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'MMR K', + name: 'mmrK', + description: 'Number of top results to fetch for MMR. Defaults to 50', + placeholder: '50', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'MMR diversity bias', + name: 'mmrDiversityBias', + description: 'The diversity bias to use for MMR. Defaults to 0.3', + placeholder: '0.3', type: 'number', additionalParams: true, optional: true @@ -191,7 +211,9 @@ class Vectara_VectorStores implements INode { const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 + const k = topK ? parseFloat(topK) : 5 + const mmrK = nodeData.inputs?.mmrK as number + const mmrDiversityBias = nodeData.inputs?.mmrDiversityBias as number const vectaraArgs: VectaraLibArgs = { apiKey: apiKey, @@ -208,6 +230,7 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig + if (mmrK) vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } const vectorStore = new VectaraStore(vectaraArgs) From e5f0ca0c0ac869d1ab49e835e77d8e810199bd3f Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 18:59:49 -0800 Subject: [PATCH 154/268] bug fix --- .../components/nodes/chains/VectaraChain/VectaraChain.ts | 8 +++++--- packages/components/nodes/vectorstores/Vectara/Vectara.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index c80b354f..16257b69 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -249,18 +249,20 @@ class VectaraChain_Chains implements INode { })) const mmrRerankerId = 272725718 // Vectara reranker ID for MMR + const mmrEnabled = vectaraFilter?.mmrConfig?.mmrDiversityBias > 0 + const data = { query: [ { query: input, start: 0, - numResults: vectaraFilter?.mmrConfig?.mmrK > 0 ? vectaraFilter?.mmrK : topK, + numResults: mmrEnabled ? vectaraFilter?.mmrK : topK, corpusKey: corpusKeys, contextConfig: { sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2 }, - ...(vectaraFilter?.mmrConfig?.mmrK > 0 + ...(mmrEnabled ? { rerankingConfig: { rerankerId: mmrRerankerId, @@ -299,7 +301,7 @@ class VectaraChain_Chains implements INode { // remove responses that are not in the topK (in case of MMR) // Note that this does not really matter functionally due to the reorder citations, but it is more efficient - const maxResponses = vectaraFilter?.mmrConfig?.mmrK > 0 ? Math.min(responses.length, topK) : responses.length + const maxResponses = mmrEnabled ? Math.min(responses.length, topK) : responses.length if (responses.length > maxResponses) { responses.splice(0, maxResponses) } diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 98acf00c..488a8803 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -110,8 +110,8 @@ class Vectara_VectorStores implements INode { { label: 'MMR diversity bias', name: 'mmrDiversityBias', - description: 'The diversity bias to use for MMR. Defaults to 0.3', - placeholder: '0.3', + description: 'The diversity bias to use for MMR. Defaults to 0 (MMR disabled)', + placeholder: '0.0', type: 'number', additionalParams: true, optional: true @@ -230,7 +230,7 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig - if (mmrK) vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } + vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } const vectorStore = new VectaraStore(vectaraArgs) From dc3e4fd059b64d153de0e5c11ebb0836b59ca56f Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 19:18:33 -0800 Subject: [PATCH 155/268] bug fix 2 --- .../nodes/vectorstores/Vectara/Vectara.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 488a8803..d83f6cb9 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -1,5 +1,12 @@ import { flatten } from 'lodash' -import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile } from 'langchain/vectorstores/vectara' +import { + VectaraStore, + VectaraLibArgs, + VectaraFilter, + VectaraContextConfig, + VectaraFile, + VectaraMMRConfig +} from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { Embeddings } from 'langchain/embeddings/base' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' @@ -230,7 +237,10 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig - vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } + const mmrConfig: VectaraMMRConfig = {} + mmrConfig.mmrK = mmrK + mmrConfig.diversityBias = mmrDiversityBias + vectaraFilter.mmrConfig = mmrConfig const vectorStore = new VectaraStore(vectaraArgs) From b44a0f1d39f5c7f3c1c00a16900229fb6a3b7e08 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 21:53:48 -0800 Subject: [PATCH 156/268] bugfix --- .../nodes/chains/VectaraChain/VectaraChain.ts | 4 ++-- .../nodes/vectorstores/Vectara/Vectara.ts | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index 16257b69..986d587a 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -249,14 +249,14 @@ class VectaraChain_Chains implements INode { })) const mmrRerankerId = 272725718 // Vectara reranker ID for MMR - const mmrEnabled = vectaraFilter?.mmrConfig?.mmrDiversityBias > 0 + const mmrEnabled = vectaraFilter?.mmrConfig?.enabled const data = { query: [ { query: input, start: 0, - numResults: mmrEnabled ? vectaraFilter?.mmrK : topK, + numResults: mmrEnabled ? vectaraFilter?.mmrTopK : topK, corpusKey: corpusKeys, contextConfig: { sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index d83f6cb9..be63d582 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -1,12 +1,5 @@ import { flatten } from 'lodash' -import { - VectaraStore, - VectaraLibArgs, - VectaraFilter, - VectaraContextConfig, - VectaraFile, - VectaraMMRConfig -} from 'langchain/vectorstores/vectara' +import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile, MMRConfig } from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { Embeddings } from 'langchain/embeddings/base' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' @@ -237,8 +230,9 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig - const mmrConfig: VectaraMMRConfig = {} - mmrConfig.mmrK = mmrK + const mmrConfig: MMRConfig = {} + mmrConfig.enabled = mmrDiversityBias > 0 + mmrConfig.mmrTopK = mmrK mmrConfig.diversityBias = mmrDiversityBias vectaraFilter.mmrConfig = mmrConfig From e4ab1df4286bef22db119a6cc57f7c771d42b773 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 23:40:04 -0800 Subject: [PATCH 157/268] na --- package.json | 2 +- packages/components/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5ecbb59b..cac38984 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,5 @@ }, "engines": { "node": ">=18.15.0" - } + }, } diff --git a/packages/components/package.json b/packages/components/package.json index cb3448eb..72c8f815 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -47,12 +47,12 @@ "express": "^4.17.3", "faiss-node": "^0.2.2", "form-data": "^4.0.0", - "google-auth-library": "^9.0.0", + "google-auth-library": "^9.4.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", - "langchain": "^0.0.196", + "langchain": "^0.0.213", "langfuse": "^1.2.0", "langfuse-langchain": "^1.0.31", "langsmith": "^0.0.49", From 3f835fb50731f8eafede6bfeb59e01f8500b8b91 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 23:41:19 -0800 Subject: [PATCH 158/268] extra comma --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cac38984..5ecbb59b 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,5 @@ }, "engines": { "node": ">=18.15.0" - }, + } } From 383f612e114050eb71977bb0498ac356c3afbdf3 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 3 Jan 2024 00:35:55 +0000 Subject: [PATCH 159/268] add check for secretkey_path --- packages/server/src/utils/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 39bd0854..67187325 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -873,7 +873,9 @@ export const getEncryptionKey = async (): Promise => { return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') } catch (error) { const encryptKey = generateEncryptKey() - const defaultLocation = path.join(getUserHome(), '.flowise', 'encryption.key') + const defaultLocation = process.env.SECRETKEY_PATH + ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') + : path.join(getUserHome(), '.flowise', 'encryption.key') await fs.promises.writeFile(defaultLocation, encryptKey) return encryptKey } From e513b69e3eb50dd0b453338df30937a892d28496 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 3 Jan 2024 00:54:31 +0000 Subject: [PATCH 160/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.4.9=20bugfix?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ecbb59b..5a9bfcbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.8", + "version": "1.4.9", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 54409e29..f1c0b7f7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.8", + "version": "1.4.9", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 14aa19987880e62f0f623088c3a9fcd800b798e6 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 3 Jan 2024 17:40:39 +0000 Subject: [PATCH 161/268] no message --- packages/components/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 50377e1a..f3371d26 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -52,10 +52,10 @@ "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", - "langchain": "^0.0.213", + "langchain": "^0.0.214", "langfuse": "^1.2.0", "langfuse-langchain": "^1.0.31", - "langsmith": "^0.0.49", + "langsmith": "^0.0.53", "linkifyjs": "^4.1.1", "llmonitor": "^0.5.5", "mammoth": "^1.5.1", From c035363d6f5558c55e0ccf4308e3c87d80715e6e Mon Sep 17 00:00:00 2001 From: Darien Kindlund Date: Wed, 3 Jan 2024 13:20:39 -0500 Subject: [PATCH 162/268] Fixing linting issues using 'yarn lint-fix' --- .../nodes/documentloaders/Airtable/Airtable.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/documentloaders/Airtable/Airtable.ts b/packages/components/nodes/documentloaders/Airtable/Airtable.ts index a2c1eef3..a7cd5021 100644 --- a/packages/components/nodes/documentloaders/Airtable/Airtable.ts +++ b/packages/components/nodes/documentloaders/Airtable/Airtable.ts @@ -55,14 +55,14 @@ class Airtable_DocumentLoaders implements INode { description: 'If your table URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, tblJdmvbrgizbYICO is the table id' }, - { - label: 'View Id', - name: 'viewId', - type: 'string', - placeholder: 'viw9UrP77Id0CE4ee', - description: - 'If your view URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, viw9UrP77Id0CE4ee is the view id', - optional: true + { + label: 'View Id', + name: 'viewId', + type: 'string', + placeholder: 'viw9UrP77Id0CE4ee', + description: + 'If your view URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, viw9UrP77Id0CE4ee is the view id', + optional: true }, { label: 'Return All', From 53bfd07694b6bd6e1783dae08f7ebaa20ffa4f05 Mon Sep 17 00:00:00 2001 From: Darien Kindlund Date: Wed, 3 Jan 2024 21:23:43 -0500 Subject: [PATCH 163/268] Bumping version to reflect new feature --- packages/components/nodes/documentloaders/Airtable/Airtable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/documentloaders/Airtable/Airtable.ts b/packages/components/nodes/documentloaders/Airtable/Airtable.ts index a7cd5021..9a824ac9 100644 --- a/packages/components/nodes/documentloaders/Airtable/Airtable.ts +++ b/packages/components/nodes/documentloaders/Airtable/Airtable.ts @@ -20,7 +20,7 @@ class Airtable_DocumentLoaders implements INode { constructor() { this.label = 'Airtable' this.name = 'airtable' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 'airtable.svg' this.category = 'Document Loaders' From efe602970ce2df8ef1b4f186f3ea3f42ff217d6c Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 4 Jan 2024 14:19:46 +0100 Subject: [PATCH 164/268] Added ssl flag for postgres vectorstores --- .../nodes/vectorstores/Postgres/Postgres_Exisiting.ts | 11 ++++++++++- .../nodes/vectorstores/Postgres/Postgres_Upsert.ts | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts b/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts index 99794a0d..da3b1d18 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts @@ -52,6 +52,13 @@ class Postgres_Existing_VectorStores implements INode { name: 'database', type: 'string' }, + { + label: 'SSL Connection', + name: 'sslConnection', + type: 'boolean', + default: false, + optional: false + }, { label: 'Port', name: 'port', @@ -109,6 +116,7 @@ class Postgres_Existing_VectorStores implements INode { const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const sslConnection = nodeData.inputs?.sslConnection as boolean let additionalConfiguration = {} if (additionalConfig) { @@ -126,7 +134,8 @@ class Postgres_Existing_VectorStores implements INode { port: nodeData.inputs?.port as number, username: user, password: password, - database: nodeData.inputs?.database as string + database: nodeData.inputs?.database as string, + ssl: sslConnection } const args = { diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts b/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts index f706cbe8..25551517 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts @@ -59,6 +59,13 @@ class PostgresUpsert_VectorStores implements INode { name: 'database', type: 'string' }, + { + label: 'SSL Connection', + name: 'sslConnection', + type: 'boolean', + default: false, + optional: false + }, { label: 'Port', name: 'port', @@ -117,6 +124,7 @@ class PostgresUpsert_VectorStores implements INode { const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const sslConnection = nodeData.inputs?.sslConnection as boolean let additionalConfiguration = {} if (additionalConfig) { @@ -134,7 +142,8 @@ class PostgresUpsert_VectorStores implements INode { port: nodeData.inputs?.port as number, username: user, password: password, - database: nodeData.inputs?.database as string + database: nodeData.inputs?.database as string, + ssl: sslConnection } const args = { From b833cb80d4f5adeaed612db5159dd8f727935153 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 4 Jan 2024 14:51:41 +0100 Subject: [PATCH 165/268] Added ssl flag --- .../nodes/vectorstores/Postgres/Postgres.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres.ts b/packages/components/nodes/vectorstores/Postgres/Postgres.ts index ac4b80c3..75f9669a 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres.ts @@ -60,6 +60,13 @@ class Postgres_VectorStores implements INode { name: 'database', type: 'string' }, + { + label: 'SSL Connection', + name: 'sslConnection', + type: 'boolean', + default: false, + optional: false + }, { label: 'Port', name: 'port', @@ -117,6 +124,7 @@ class Postgres_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const additionalConfig = nodeData.inputs?.additionalConfig as string + const sslConnection = nodeData.inputs?.sslConnection as boolean let additionalConfiguration = {} if (additionalConfig) { @@ -134,7 +142,8 @@ class Postgres_VectorStores implements INode { port: nodeData.inputs?.port as number, username: user, password: password, - database: nodeData.inputs?.database as string + database: nodeData.inputs?.database as string, + ssl: sslConnection } const args = { From 609ae8703de416c1a62dab565f7e7aa13177b333 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 4 Jan 2024 15:58:18 +0100 Subject: [PATCH 166/268] Added a function which cheks which port to use when port is 443 or 80 --- .../nodes/vectorstores/Qdrant/Qdrant.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts index 6413f8bf..390e7fc9 100644 --- a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts +++ b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts @@ -149,9 +149,12 @@ class Qdrant_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) + const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl); + const client = new QdrantClient({ url: qdrantServerUrl, - apiKey: qdrantApiKey + apiKey: qdrantApiKey, + port: port }) const flattenDocs = docs && docs.length ? flatten(docs) : [] @@ -198,9 +201,12 @@ class Qdrant_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) + const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl); + const client = new QdrantClient({ url: qdrantServerUrl, - apiKey: qdrantApiKey + apiKey: qdrantApiKey, + port: port }) const dbConfig: QdrantLibArgs = { @@ -242,6 +248,25 @@ class Qdrant_VectorStores implements INode { } return vectorStore } + + /** + * Determine the port number from the given URL. + * + * The problem is when not doing this the qdrant-client.js will fall back on 6663 when you enter a port 443 and 80. + * See: https://stackoverflow.com/questions/59104197/nodejs-new-url-urlhttps-myurl-com80-lists-the-port-as-empty + * @param qdrantServerUrl the url to get the port from + */ + static determinePortByUrl(qdrantServerUrl: string) :number { + let port = 6333; + const parsedUrl = new URL(qdrantServerUrl); + if (parsedUrl.protocol === 'https:' && parsedUrl.port === '') { + port = 443; + } + if (parsedUrl.protocol === 'http:' && parsedUrl.port === '') { + port = 80; + } + return port; + } } module.exports = { nodeClass: Qdrant_VectorStores } From 2355cb2ec5c83fe7302c919d89361cd33db35fff Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 4 Jan 2024 16:07:54 +0100 Subject: [PATCH 167/268] Fixed port handling so it returns the correct port and not only 6663 --- packages/components/nodes/vectorstores/Qdrant/Qdrant.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts index 390e7fc9..54b55d34 100644 --- a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts +++ b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts @@ -257,14 +257,18 @@ class Qdrant_VectorStores implements INode { * @param qdrantServerUrl the url to get the port from */ static determinePortByUrl(qdrantServerUrl: string) :number { - let port = 6333; const parsedUrl = new URL(qdrantServerUrl); + + let port = parsedUrl.port ? parseInt(parsedUrl.port) : 6663 + if (parsedUrl.protocol === 'https:' && parsedUrl.port === '') { port = 443; } if (parsedUrl.protocol === 'http:' && parsedUrl.port === '') { port = 80; } + + return port; } } From a046d5296176b0649dcc6abd9d29f8ef8e003a4e Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 4 Jan 2024 16:23:08 +0100 Subject: [PATCH 168/268] Make the linter happy --- .../components/nodes/vectorstores/Qdrant/Qdrant.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts index 54b55d34..5e01b030 100644 --- a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts +++ b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts @@ -149,7 +149,7 @@ class Qdrant_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) - const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl); + const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl) const client = new QdrantClient({ url: qdrantServerUrl, @@ -201,7 +201,7 @@ class Qdrant_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) - const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl); + const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl) const client = new QdrantClient({ url: qdrantServerUrl, @@ -256,20 +256,20 @@ class Qdrant_VectorStores implements INode { * See: https://stackoverflow.com/questions/59104197/nodejs-new-url-urlhttps-myurl-com80-lists-the-port-as-empty * @param qdrantServerUrl the url to get the port from */ - static determinePortByUrl(qdrantServerUrl: string) :number { - const parsedUrl = new URL(qdrantServerUrl); + static determinePortByUrl(qdrantServerUrl: string): number { + const parsedUrl = new URL(qdrantServerUrl) let port = parsedUrl.port ? parseInt(parsedUrl.port) : 6663 if (parsedUrl.protocol === 'https:' && parsedUrl.port === '') { - port = 443; + port = 443 } if (parsedUrl.protocol === 'http:' && parsedUrl.port === '') { - port = 80; + port = 80 } - return port; + return port } } From e35faa57afb5243fe4b011d408b1d96eee263622 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 4 Jan 2024 16:27:53 +0100 Subject: [PATCH 169/268] One last linting --- packages/components/nodes/vectorstores/Qdrant/Qdrant.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts index 5e01b030..e07b728a 100644 --- a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts +++ b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts @@ -268,7 +268,6 @@ class Qdrant_VectorStores implements INode { port = 80 } - return port } } From d882ebfcb6a12f6fb51c5cda276e70b6fafa2f1b Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 4 Jan 2024 23:57:36 +0000 Subject: [PATCH 170/268] remove restrictions --- LICENSE.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 0f4afcd1..80800001 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,22 +2,6 @@ Version 2.0, January 2004 http://www.apache.org/licenses/ -Flowise is governed by the Apache License 2.0, with additional terms and conditions outlined below: - -Flowise can be used for commercial purposes for "backend-as-a-service" for your applications or as a development platform for enterprises. However, under specific conditions, you must reach out to the project's administrators to secure a commercial license: - -a. Multi-tenant SaaS service: Unless you have explicit written authorization from Flowise, you may not utilize the Flowise source code to operate a multi-tenant SaaS service that closely resembles the Flowise cloud-based services. -b. Logo and copyright information: While using Flowise in commercial application, you are prohibited from removing or altering the LOGO or copyright information displayed in the Flowise console and UI. - -For inquiries regarding licensing matters, please contact hello@flowiseai.com via email. - -Contributors are required to consent to the following terms related to their contributed code: - -a. The project maintainers have the authority to modify the open-source agreement to be more stringent or lenient. -b. Contributed code can be used for commercial purposes, including Flowise's cloud-based services. - -All other rights and restrictions are in accordance with the Apache License 2.0. - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. From 8cb939386210a62484cdf4bf5b11a7c455eafaa1 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 5 Jan 2024 02:46:36 +0000 Subject: [PATCH 171/268] add fix for passing json variable --- .../nodes/utilities/CustomFunction/CustomFunction.ts | 2 +- packages/server/src/utils/index.ts | 6 +++++- packages/ui/src/ui-component/dialog/ExpandTextDialog.js | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index b358b24b..37511e47 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -65,7 +65,7 @@ class CustomFunction_Utilities implements INode { inputVars = typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) } catch (exception) { - throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) + throw new Error('Invalid JSON in the Custom Function Input Variables: ' + exception) } } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e7a35c82..9c2d1d79 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -561,7 +561,11 @@ export const getVariableValue = ( variablePaths.forEach((path) => { const variableValue = variableDict[path] // Replace all occurrence - returnVal = returnVal.split(path).join(variableValue) + if (typeof variableValue === 'object') { + returnVal = returnVal.split(path).join(JSON.stringify(variableValue).replace(/"/g, '\\"')) + } else { + returnVal = returnVal.split(path).join(variableValue) + } }) return returnVal } diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js index 0ef70e29..f4fdb9f9 100644 --- a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js +++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js @@ -67,7 +67,11 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { useEffect(() => { if (executeCustomFunctionNodeApi.data) { - setCodeExecutedResult(executeCustomFunctionNodeApi.data) + if (typeof executeCustomFunctionNodeApi.data === 'object') { + setCodeExecutedResult(JSON.stringify(executeCustomFunctionNodeApi.data, null, 2)) + } else { + setCodeExecutedResult(executeCustomFunctionNodeApi.data) + } } }, [executeCustomFunctionNodeApi.data]) From 36ce6b7a853f8b8f507cf773373134722f4fe063 Mon Sep 17 00:00:00 2001 From: fanux Date: Fri, 5 Jan 2024 18:17:41 +0800 Subject: [PATCH 172/268] add one-click deploy on sealos --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 25026237..6e2ade7d 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,10 @@ Flowise support different environment variables to configure your instance. You HuggingFace Spaces +### Sealos + +[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) + ### [AWS](https://docs.flowiseai.com/deployment/aws) ### [Azure](https://docs.flowiseai.com/deployment/azure) From d8a778e4d989c87b18b24d5d35c52211c53e532a Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Fri, 5 Jan 2024 12:46:47 +0100 Subject: [PATCH 173/268] Bumped version from 1.0 to 2.0 --- packages/components/nodes/vectorstores/Postgres/Postgres.ts | 2 +- .../nodes/vectorstores/Postgres/Postgres_Exisiting.ts | 2 +- .../components/nodes/vectorstores/Postgres/Postgres_Upsert.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres.ts b/packages/components/nodes/vectorstores/Postgres/Postgres.ts index 75f9669a..4e8bae32 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres.ts @@ -24,7 +24,7 @@ class Postgres_VectorStores implements INode { constructor() { this.label = 'Postgres' this.name = 'postgres' - this.version = 1.0 + this.version = 2.0 this.type = 'Postgres' this.icon = 'postgres.svg' this.category = 'Vector Stores' diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts b/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts index da3b1d18..3fa8a107 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts @@ -23,7 +23,7 @@ class Postgres_Existing_VectorStores implements INode { constructor() { this.label = 'Postgres Load Existing Index' this.name = 'postgresExistingIndex' - this.version = 1.0 + this.version = 2.0 this.type = 'Postgres' this.icon = 'postgres.svg' this.category = 'Vector Stores' diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts b/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts index 25551517..d26a642d 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts @@ -24,7 +24,7 @@ class PostgresUpsert_VectorStores implements INode { constructor() { this.label = 'Postgres Upsert Document' this.name = 'postgresUpsert' - this.version = 1.0 + this.version = 2.0 this.type = 'Postgres' this.icon = 'postgres.svg' this.category = 'Vector Stores' From 595f1ed7f2ec634d25386b29d37febc1816abfb1 Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Sat, 6 Jan 2024 17:16:06 -0700 Subject: [PATCH 174/268] Introduce new credential for LocalAI, Pass optional auth to LocalAI, New env var --- .../credentials/LcoalAIApi.credential.ts | 23 +++++++++++++++ .../chatmodels/ChatLocalAI/ChatLocalAI.ts | 29 +++++++++++++++---- packages/server/.env.example | 2 ++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 packages/components/credentials/LcoalAIApi.credential.ts diff --git a/packages/components/credentials/LcoalAIApi.credential.ts b/packages/components/credentials/LcoalAIApi.credential.ts new file mode 100644 index 00000000..624e07fa --- /dev/null +++ b/packages/components/credentials/LcoalAIApi.credential.ts @@ -0,0 +1,23 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class LocalAIApi implements INodeCredential { + label: string + name: string + version: number + inputs: INodeParams[] + + constructor() { + this.label = 'LocalAI API' + this.name = 'LocalAIApi' + this.version = 1.0 + this.inputs = [ + { + label: 'LocalAI Api Key', + name: 'LocalAIApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: LocalAIApi } diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index 18ed409b..258db1f8 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIChat } from 'langchain/llms/openai' import { OpenAIChatInput } from 'langchain/chat_models/openai' import { BaseCache } from 'langchain/schema' @@ -14,6 +14,7 @@ class ChatLocalAI_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -25,6 +26,16 @@ class ChatLocalAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Use local LLMs like llama.cpp, gpt4all using LocalAI' this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(OpenAIChat)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['LocalAIApi'], + optional: true + } + + const modelOptions = JSON.parse(process.env.LOCALAI_CHAT_MODELS || '[]'); + this.inputs = [ { label: 'Cache', @@ -41,8 +52,10 @@ class ChatLocalAI_ChatModels implements INode { { label: 'Model Name', name: 'modelName', - type: 'string', - placeholder: 'gpt4all-lora-quantized.bin' + type: 'options', + options: modelOptions, + default: modelOptions.length > 0 ? modelOptions[0].name : '', + optional: true }, { label: 'Temperature', @@ -79,19 +92,23 @@ class ChatLocalAI_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) + const cache = nodeData.inputs?.cache as BaseCache const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, - openAIApiKey: 'sk-' + openAIApiKey } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/server/.env.example b/packages/server/.env.example index 6e746a4d..9b7be0ff 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -26,3 +26,5 @@ PORT=3000 # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_PROJECT=your_project + +# LOCALAI_CHAT_MODELS='[{"label": "model1", "name": "model1"}, {"label": "model2", "name": "model2"}]' From accea214d22356f780428f0d40a00b9d3353904f Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Sat, 6 Jan 2024 17:33:41 -0700 Subject: [PATCH 175/268] Updating docs --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04cb80b4..2c91906c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,6 +141,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | +| LOCALAI_CHAT_MODELS | JSON-encoded string representing an array of chat models for LocalAI. Each object in the array should have a 'label' and 'name' property. | String | '[]' (Empty Array) | You can also specify the env variables when using `npx`. For example: From 02482f1b3862779ea14e8a335f4299c76030a21e Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 8 Jan 2024 13:02:56 +0000 Subject: [PATCH 176/268] change agent/chain with memory to use runnable --- .../ConversationalAgent.ts | 163 +++-- .../ConversationalRetrievalAgent.ts | 127 ++-- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 75 ++- .../OpenAIFunctionAgent.ts | 273 +------- .../ConversationChain/ConversationChain.ts | 146 +++-- .../ConversationalRetrievalQAChain.ts | 380 +++++++---- .../ConversationalRetrievalQAChain/prompts.ts | 79 +-- .../nodes/memory/BufferMemory/BufferMemory.ts | 33 +- .../BufferWindowMemory/BufferWindowMemory.ts | 34 +- .../ConversationSummaryMemory.ts | 42 +- .../nodes/memory/DynamoDb/DynamoDb.ts | 49 +- .../memory/MongoDBMemory/MongoDBMemory.ts | 49 +- .../memory/MotorheadMemory/MotorheadMemory.ts | 92 ++- .../RedisBackedChatMemory.ts | 70 +- .../UpstashRedisBackedChatMemory.ts | 49 +- .../nodes/memory/ZepMemory/ZepMemory.ts | 46 +- .../nodes/tools/CustomTool/CustomTool.ts | 8 +- .../components/nodes/tools/CustomTool/core.ts | 17 +- packages/components/package.json | 1 + packages/components/src/Interface.ts | 28 +- packages/components/src/agents.ts | 615 ++++++++++++++++++ .../marketplaces/chatflows/API Agent.json | 2 +- .../chatflows/Chat with a Podcast.json | 56 +- .../marketplaces/chatflows/Claude LLM.json | 2 +- .../chatflows/Conversational Agent.json | 2 +- .../Conversational Retrieval QA Chain.json | 62 +- .../chatflows/Flowise Docs QnA.json | 61 +- .../marketplaces/chatflows/Local QnA.json | 61 +- .../chatflows/Long Term Memory.json | 63 +- .../chatflows/Metadata Filter.json | 61 +- .../chatflows/Multiple VectorDB.json | 2 +- .../chatflows/Simple Conversation Chain.json | 2 +- .../chatflows/Vectara LLM Chain Upload.json | 55 +- .../marketplaces/chatflows/WebBrowser.json | 2 +- .../marketplaces/chatflows/WebPage QnA.json | 63 +- packages/server/src/index.ts | 98 ++- packages/server/src/utils/index.ts | 175 ++--- .../ui/src/views/canvas/NodeInputHandler.js | 3 +- 38 files changed, 1752 insertions(+), 1394 deletions(-) create mode 100644 packages/components/src/agents.ts diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 8a2329b5..7f857b1c 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -1,11 +1,14 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecutorOptions } from 'langchain/agents' import { Tool } from 'langchain/tools' -import { BaseChatMemory } from 'langchain/memory' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { BaseChatModel } from 'langchain/chat_models/base' import { flatten } from 'lodash' -import { additionalCallbacks } from '../../../src/handler' +import { AgentStep, BaseMessage, ChainValues, AIMessage, HumanMessage } from 'langchain/schema' +import { RunnableSequence } from 'langchain/schema/runnable' +import { getBaseClasses } from '../../../src/utils' +import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { AgentExecutor } from '../../../src/agents' +import { ChatConversationalAgent } from 'langchain/agents' +import { renderTemplate } from '@langchain/core/prompts' const DEFAULT_PREFIX = `Assistant is a large language model trained by OpenAI. @@ -15,6 +18,15 @@ Assistant is constantly learning and improving, and its capabilities are constan Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.` +const TEMPLATE_TOOL_RESPONSE = `TOOL RESPONSE: +--------------------- +{observation} + +USER'S INPUT +-------------------- + +Okay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else.` + class ConversationalAgent_Agents implements INode { label: string name: string @@ -25,8 +37,9 @@ class ConversationalAgent_Agents implements INode { category: string baseClasses: string[] inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversational Agent' this.name = 'conversationalAgent' this.version = 2.0 @@ -43,7 +56,7 @@ class ConversationalAgent_Agents implements INode { list: true }, { - label: 'Language Model', + label: 'Chat Model', name: 'model', type: 'BaseChatModel' }, @@ -62,52 +75,114 @@ class ConversationalAgent_Agents implements INode { additionalParams: true } ] + this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseChatModel - let tools = nodeData.inputs?.tools as Tool[] - tools = flatten(tools) - const memory = nodeData.inputs?.memory as BaseChatMemory - const systemMessage = nodeData.inputs?.systemMessage as string - - const obj: InitializeAgentExecutorOptions = { - agentType: 'chat-conversational-react-description', - verbose: process.env.DEBUG === 'true' ? true : false - } - - const agentArgs: any = {} - if (systemMessage) { - agentArgs.systemMessage = systemMessage - } - - if (Object.keys(agentArgs).length) obj.agentArgs = agentArgs - - const executor = await initializeAgentExecutorWithOptions(tools, model, obj) - executor.memory = memory - return executor + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const executor = nodeData.instance as AgentExecutor - const memory = nodeData.inputs?.memory as BaseChatMemory - - if (options && options.chatHistory) { - const chatHistoryClassName = memory.chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - memory.chatHistory = mapChatHistory(options) - executor.memory = memory - } - } - - ;(executor.memory as any).returnMessages = true // Return true for BaseChatModel + const memory = nodeData.inputs?.memory as FlowiseMemory + const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) - const result = await executor.call({ input }, [...callbacks]) - return result?.output + let res: ChainValues = {} + + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) + } else { + res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) + } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res?.output, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res?.output } } +const prepareAgent = async ( + nodeData: INodeData, + flowObj: { sessionId?: string; chatId?: string; input?: string }, + chatHistory: IMessage[] = [] +) => { + const model = nodeData.inputs?.model as BaseChatModel + let tools = nodeData.inputs?.tools as Tool[] + tools = flatten(tools) + const memory = nodeData.inputs?.memory as FlowiseMemory + const systemMessage = nodeData.inputs?.systemMessage as string + const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history' + const inputKey = memory.inputKey ? memory.inputKey : 'input' + + /** Bind a stop token to the model */ + const modelWithStop = model.bind({ + stop: ['\nObservation'] + }) + + const outputParser = ChatConversationalAgent.getDefaultOutputParser({ + llm: model, + toolNames: tools.map((tool) => tool.name) + }) + + const prompt = ChatConversationalAgent.createPrompt(tools, { + systemMessage: systemMessage ? systemMessage : DEFAULT_PREFIX, + outputParser + }) + + const runnableAgent = RunnableSequence.from([ + { + [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, + agent_scratchpad: async (i: { input: string; steps: AgentStep[] }) => await constructScratchPad(i.steps), + [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { + const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + return messages ?? [] + } + }, + prompt, + modelWithStop, + outputParser + ]) + + const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, + sessionId: flowObj?.sessionId, + chatId: flowObj?.chatId, + input: flowObj?.input, + verbose: process.env.DEBUG === 'true' ? true : false + }) + + return executor +} + +const constructScratchPad = async (steps: AgentStep[]): Promise => { + const thoughts: BaseMessage[] = [] + for (const step of steps) { + thoughts.push(new AIMessage(step.action.log)) + thoughts.push( + new HumanMessage( + renderTemplate(TEMPLATE_TOOL_RESPONSE, 'f-string', { + observation: step.observation + }) + ) + ) + } + return thoughts +} + module.exports = { nodeClass: ConversationalAgent_Agents } diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 643c6a65..406a156f 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -1,9 +1,14 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' +import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema' import { flatten } from 'lodash' -import { BaseChatMemory } from 'langchain/memory' +import { ChatOpenAI } from 'langchain/chat_models/openai' +import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' +import { formatToOpenAIFunction } from 'langchain/tools' +import { RunnableSequence } from 'langchain/schema/runnable' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' +import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' +import { AgentExecutor, formatAgentSteps } from '../../../src/agents' const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.` @@ -17,8 +22,9 @@ class ConversationalRetrievalAgent_Agents implements INode { category: string baseClasses: string[] inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversational Retrieval Agent' this.name = 'conversationalRetrievalAgent' this.version = 3.0 @@ -54,55 +60,96 @@ class ConversationalRetrievalAgent_Agents implements INode { additionalParams: true } ] + this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model - const memory = nodeData.inputs?.memory as BaseChatMemory - const systemMessage = nodeData.inputs?.systemMessage as string - - let tools = nodeData.inputs?.tools - tools = flatten(tools) - - const executor = await initializeAgentExecutorWithOptions(tools, model, { - agentType: 'openai-functions', - verbose: process.env.DEBUG === 'true' ? true : false, - agentArgs: { - prefix: systemMessage ?? defaultMessage - }, - returnIntermediateSteps: true - }) - executor.memory = memory - return executor + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const executor = nodeData.instance as AgentExecutor - - if (executor.memory) { - ;(executor.memory as any).memoryKey = 'chat_history' - ;(executor.memory as any).outputKey = 'output' - ;(executor.memory as any).returnMessages = true - - const chatHistoryClassName = (executor.memory as any).chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - ;(executor.memory as any).chatHistory = mapChatHistory(options) - } - } + const memory = nodeData.inputs?.memory as FlowiseMemory + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) + let res: ChainValues = {} + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const result = await executor.call({ input }, [loggerHandler, handler, ...callbacks]) - return result?.output + res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) } else { - const result = await executor.call({ input }, [loggerHandler, ...callbacks]) - return result?.output + res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res?.output, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res?.output } } +const prepareAgent = ( + nodeData: INodeData, + flowObj: { sessionId?: string; chatId?: string; input?: string }, + chatHistory: IMessage[] = [] +) => { + const model = nodeData.inputs?.model as ChatOpenAI + const memory = nodeData.inputs?.memory as FlowiseMemory + const systemMessage = nodeData.inputs?.systemMessage as string + let tools = nodeData.inputs?.tools + tools = flatten(tools) + const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history' + const inputKey = memory.inputKey ? memory.inputKey : 'input' + + const prompt = ChatPromptTemplate.fromMessages([ + ['ai', systemMessage ? systemMessage : defaultMessage], + new MessagesPlaceholder(memoryKey), + ['human', `{${inputKey}}`], + new MessagesPlaceholder('agent_scratchpad') + ]) + + const modelWithFunctions = model.bind({ + functions: [...tools.map((tool: any) => formatToOpenAIFunction(tool))] + }) + + const runnableAgent = RunnableSequence.from([ + { + [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, + agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), + [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { + const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + return messages ?? [] + } + }, + prompt, + modelWithFunctions, + new OpenAIFunctionsAgentOutputParser() + ]) + + const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, + sessionId: flowObj?.sessionId, + chatId: flowObj?.chatId, + input: flowObj?.input, + returnIntermediateSteps: true, + verbose: process.env.DEBUG === 'true' ? true : false + }) + + return executor +} + module.exports = { nodeClass: ConversationalRetrievalAgent_Agents } diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index cf69022b..62ecec5b 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -96,45 +96,51 @@ class OpenAIAssistant_Agents implements INode { return null } - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const selectedAssistantId = nodeData.inputs?.selectedAssistant as string - const appDataSource = options.appDataSource as DataSource - const databaseEntities = options.databaseEntities as IDatabaseEntity - let sessionId = nodeData.inputs?.sessionId as string + async clearChatMessages(nodeData: INodeData, options: ICommonObject, sessionIdObj: { type: string; id: string }): Promise { + const selectedAssistantId = nodeData.inputs?.selectedAssistant as string + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity - const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ - id: selectedAssistantId + const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ + id: selectedAssistantId + }) + + if (!assistant) { + options.logger.error(`Assistant ${selectedAssistantId} not found`) + return + } + + if (!sessionIdObj) return + + let sessionId = '' + if (sessionIdObj.type === 'chatId') { + const chatId = sessionIdObj.id + const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ + chatId }) - - if (!assistant) { - options.logger.error(`Assistant ${selectedAssistantId} not found`) + if (!chatmsg) { + options.logger.error(`Chat Message with Chat Id: ${chatId} not found`) return } + sessionId = chatmsg.sessionId + } else if (sessionIdObj.type === 'threadId') { + sessionId = sessionIdObj.id + } - if (!sessionId && options.chatId) { - const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ - chatId: options.chatId - }) - if (!chatmsg) { - options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) - return - } - sessionId = chatmsg.sessionId - } + const credentialData = await getCredentialData(assistant.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + if (!openAIApiKey) { + options.logger.error(`OpenAI ApiKey not found`) + return + } - const credentialData = await getCredentialData(assistant.credential ?? '', options) - const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - if (!openAIApiKey) { - options.logger.error(`OpenAI ApiKey not found`) - return - } - - const openai = new OpenAI({ apiKey: openAIApiKey }) - options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + const openai = new OpenAI({ apiKey: openAIApiKey }) + options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + try { if (sessionId) await openai.beta.threads.del(sessionId) options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + } catch (e) { + throw new Error(e) } } @@ -297,7 +303,11 @@ class OpenAIAssistant_Agents implements INode { options.socketIO.to(options.socketIOClientId).emit('tool', tool.name) try { - const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId) + const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, { + sessionId: threadId, + chatId: options.chatId, + input + }) await analyticHandlers.onToolEnd(toolIds, toolOutput) submitToolOutputs.push({ tool_call_id: actions[i].toolCallId, @@ -462,6 +472,7 @@ class OpenAIAssistant_Agents implements INode { const imageRegex = /]*\/>/g let llmOutput = returnVal.replace(imageRegex, '') llmOutput = llmOutput.replace('
    ', '') + await analyticHandlers.onLLMEnd(llmIds, llmOutput) await analyticHandlers.onChainEnd(parentIds, messageData, true) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index c0095cee..135121d2 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,17 +1,14 @@ -import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { AgentExecutor as LCAgentExecutor, AgentExecutorInput } from 'langchain/agents' -import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema' -import { OutputParserException } from 'langchain/schema/output_parser' -import { CallbackManagerForChainRun } from 'langchain/callbacks' -import { formatToOpenAIFunction } from 'langchain/tools' -import { ToolInputParsingException, Tool } from '@langchain/core/tools' +import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema' import { getBaseClasses } from '../../../src/utils' import { flatten } from 'lodash' import { RunnableSequence } from 'langchain/schema/runnable' +import { formatToOpenAIFunction } from 'langchain/tools' +import { ChatOpenAI } from 'langchain/chat_models/openai' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' -import { ChatOpenAI } from 'langchain/chat_models/openai' import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' +import { AgentExecutor, formatAgentSteps } from '../../../src/agents' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -25,7 +22,7 @@ class OpenAIFunctionAgent_Agents implements INode { inputs: INodeParams[] sessionId?: string - constructor(fields: { sessionId?: string }) { + constructor(fields?: { sessionId?: string }) { this.label = 'OpenAI Function Agent' this.name = 'openAIFunctionAgent' this.version = 3.0 @@ -33,7 +30,7 @@ class OpenAIFunctionAgent_Agents implements INode { this.category = 'Agents' this.icon = 'function.svg' this.description = `An agent that uses Function Calling to pick the tool and args to call` - this.baseClasses = [this.type, ...getBaseClasses(LCAgentExecutor)] + this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] this.inputs = [ { label: 'Allowed Tools', @@ -63,19 +60,13 @@ class OpenAIFunctionAgent_Agents implements INode { this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const memory = nodeData.inputs?.memory as FlowiseMemory - - const executor = prepareAgent(nodeData, this.sessionId) - if (memory) executor.memory = memory - - return executor + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const memory = nodeData.inputs?.memory as FlowiseMemory - - const executor = prepareAgent(nodeData, this.sessionId) + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -107,17 +98,11 @@ class OpenAIFunctionAgent_Agents implements INode { } } -const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => - steps.flatMap(({ action, observation }) => { - if ('messageLog' in action && action.messageLog !== undefined) { - const log = action.messageLog as BaseMessage[] - return log.concat(new FunctionMessage(observation, action.tool)) - } else { - return [new AIMessage(action.log)] - } - }) - -const prepareAgent = (nodeData: INodeData, sessionId?: string) => { +const prepareAgent = ( + nodeData: INodeData, + flowObj: { sessionId?: string; chatId?: string; input?: string }, + chatHistory: IMessage[] = [] +) => { const model = nodeData.inputs?.model as ChatOpenAI const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -142,7 +127,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => { [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages = (await memory.getChatMessages(sessionId, true)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] return messages ?? [] } }, @@ -154,231 +139,13 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => { const executor = AgentExecutor.fromAgentAndTools({ agent: runnableAgent, tools, - sessionId + sessionId: flowObj?.sessionId, + chatId: flowObj?.chatId, + input: flowObj?.input, + verbose: process.env.DEBUG === 'true' ? true : false }) return executor } -type AgentExecutorOutput = ChainValues - -class AgentExecutor extends LCAgentExecutor { - sessionId?: string - - static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string }): AgentExecutor { - const newInstance = new AgentExecutor(fields) - if (fields.sessionId) newInstance.sessionId = fields.sessionId - return newInstance - } - - shouldContinueIteration(iterations: number): boolean { - return this.maxIterations === undefined || iterations < this.maxIterations - } - - async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise { - const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) - - const steps: AgentStep[] = [] - let iterations = 0 - - const getOutput = async (finishStep: AgentFinish): Promise => { - const { returnValues } = finishStep - const additional = await this.agent.prepareForOutput(returnValues, steps) - - if (this.returnIntermediateSteps) { - return { ...returnValues, intermediateSteps: steps, ...additional } - } - await runManager?.handleAgentEnd(finishStep) - return { ...returnValues, ...additional } - } - - while (this.shouldContinueIteration(iterations)) { - let output - try { - output = await this.agent.plan(steps, inputs, runManager?.getChild()) - } catch (e) { - if (e instanceof OutputParserException) { - let observation - let text = e.message - if (this.handleParsingErrors === true) { - if (e.sendToLLM) { - observation = e.observation - text = e.llmOutput ?? '' - } else { - observation = 'Invalid or incomplete response' - } - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - output = { - tool: '_Exception', - toolInput: observation, - log: text - } as AgentAction - } else { - throw e - } - } - // Check if the agent has finished - if ('returnValues' in output) { - return getOutput(output) - } - - let actions: AgentAction[] - if (Array.isArray(output)) { - actions = output as AgentAction[] - } else { - actions = [output as AgentAction] - } - - const newSteps = await Promise.all( - actions.map(async (action) => { - await runManager?.handleAgentAction(action) - const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()] - let observation - try { - // here we need to override Tool call method to include sessionId as parameter - observation = tool - ? // @ts-ignore - await tool.call(action.toolInput, runManager?.getChild(), undefined, this.sessionId) - : `${action.tool} is not a valid tool, try another one.` - } catch (e) { - if (e instanceof ToolInputParsingException) { - if (this.handleParsingErrors === true) { - observation = 'Invalid or incomplete tool input. Please try again.' - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - observation = await new ExceptionTool().call(observation, runManager?.getChild()) - return { action, observation: observation ?? '' } - } - } - return { action, observation: observation ?? '' } - }) - ) - - steps.push(...newSteps) - - const lastStep = steps[steps.length - 1] - const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()] - - if (lastTool?.returnDirect) { - return getOutput({ - returnValues: { [this.agent.returnValues[0]]: lastStep.observation }, - log: '' - }) - } - - iterations += 1 - } - - const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs) - - return getOutput(finish) - } - - async _takeNextStep( - nameToolMap: Record, - inputs: ChainValues, - intermediateSteps: AgentStep[], - runManager?: CallbackManagerForChainRun - ): Promise { - let output - try { - output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild()) - } catch (e) { - if (e instanceof OutputParserException) { - let observation - let text = e.message - if (this.handleParsingErrors === true) { - if (e.sendToLLM) { - observation = e.observation - text = e.llmOutput ?? '' - } else { - observation = 'Invalid or incomplete response' - } - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - output = { - tool: '_Exception', - toolInput: observation, - log: text - } as AgentAction - } else { - throw e - } - } - - if ('returnValues' in output) { - return output - } - - let actions: AgentAction[] - if (Array.isArray(output)) { - actions = output as AgentAction[] - } else { - actions = [output as AgentAction] - } - - const result: AgentStep[] = [] - for (const agentAction of actions) { - let observation = '' - if (runManager) { - await runManager?.handleAgentAction(agentAction) - } - if (agentAction.tool in nameToolMap) { - const tool = nameToolMap[agentAction.tool] - try { - // here we need to override Tool call method to include sessionId as parameter - // @ts-ignore - observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, this.sessionId) - } catch (e) { - if (e instanceof ToolInputParsingException) { - if (this.handleParsingErrors === true) { - observation = 'Invalid or incomplete tool input. Please try again.' - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - observation = await new ExceptionTool().call(observation, runManager?.getChild()) - } - } - } else { - observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}` - } - result.push({ - action: agentAction, - observation - }) - } - return result - } -} - -class ExceptionTool extends Tool { - name = '_Exception' - - description = 'Exception tool' - - async _call(query: string) { - return query - } -} - module.exports = { nodeClass: OpenAIFunctionAgent_Agents } diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 54d4252a..fcd9921e 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,14 +1,16 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' -import { BufferMemory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { flatten } from 'lodash' import { Document } from 'langchain/document' +import { RunnableSequence } from 'langchain/schema/runnable' +import { StringOutputParser } from 'langchain/schema/output_parser' let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` +const inputKey = 'input' class ConversationChain_Chains implements INode { label: string @@ -20,8 +22,9 @@ class ConversationChain_Chains implements INode { baseClasses: string[] description: string inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversation Chain' this.name = 'conversationChain' this.version = 1.0 @@ -32,7 +35,7 @@ class ConversationChain_Chains implements INode { this.baseClasses = [this.type, ...getBaseClasses(ConversationChain)] this.inputs = [ { - label: 'Language Model', + label: 'Chat Model', name: 'model', type: 'BaseChatModel' }, @@ -60,76 +63,99 @@ class ConversationChain_Chains implements INode { placeholder: 'You are a helpful assistant that write codes' } ] + this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseChatModel - const memory = nodeData.inputs?.memory as BufferMemory - const prompt = nodeData.inputs?.systemMessagePrompt as string - const docs = nodeData.inputs?.document as Document[] - - const flattenDocs = docs && docs.length ? flatten(docs) : [] - const finalDocs = [] - for (let i = 0; i < flattenDocs.length; i += 1) { - if (flattenDocs[i] && flattenDocs[i].pageContent) { - finalDocs.push(new Document(flattenDocs[i])) - } - } - - let finalText = '' - for (let i = 0; i < finalDocs.length; i += 1) { - finalText += finalDocs[i].pageContent - } - - const replaceChar: string[] = ['{', '}'] - for (const char of replaceChar) finalText = finalText.replaceAll(char, '') - - if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` - - const obj: any = { - llm: model, - memory, - verbose: process.env.DEBUG === 'true' ? true : false - } - - const chatPrompt = ChatPromptTemplate.fromMessages([ - SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage), - new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), - HumanMessagePromptTemplate.fromTemplate('{input}') - ]) - obj.prompt = chatPrompt - - const chain = new ConversationChain(obj) + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const chain = prepareChain(nodeData, this.sessionId, options.chatHistory) return chain } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const chain = nodeData.instance as ConversationChain - const memory = nodeData.inputs?.memory as BufferMemory - memory.returnMessages = true // Return true for BaseChatModel - - if (options && options.chatHistory) { - const chatHistoryClassName = memory.chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - memory.chatHistory = mapChatHistory(options) - } - } - - chain.memory = memory + const memory = nodeData.inputs?.memory + const chain = prepareChain(nodeData, this.sessionId, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) + let res = '' + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call({ input }, [loggerHandler, handler, ...callbacks]) - return res?.response + res = await chain.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) } else { - const res = await chain.call({ input }, [loggerHandler, ...callbacks]) - return res?.response + res = await chain.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res } } +const prepareChatPrompt = (nodeData: INodeData) => { + const memory = nodeData.inputs?.memory as FlowiseMemory + const prompt = nodeData.inputs?.systemMessagePrompt as string + const docs = nodeData.inputs?.document as Document[] + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + + let finalText = '' + for (let i = 0; i < finalDocs.length; i += 1) { + finalText += finalDocs[i].pageContent + } + + const replaceChar: string[] = ['{', '}'] + for (const char of replaceChar) finalText = finalText.replaceAll(char, '') + + if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` + + const chatPrompt = ChatPromptTemplate.fromMessages([ + SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage), + new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), + HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`) + ]) + + return chatPrompt +} + +const prepareChain = (nodeData: INodeData, sessionId?: string, chatHistory: IMessage[] = []) => { + const model = nodeData.inputs?.model as BaseChatModel + const memory = nodeData.inputs?.memory as FlowiseMemory + const memoryKey = memory.memoryKey ?? 'chat_history' + + const conversationChain = RunnableSequence.from([ + { + [inputKey]: (input: { input: string }) => input.input, + [memoryKey]: async () => { + const history = await memory.getChatMessages(sessionId, true, chatHistory) + return history + } + }, + prepareChatPrompt(nodeData), + model, + new StringOutputParser() + ]) + + return conversationChain +} + module.exports = { nodeClass: ConversationChain_Chains } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 36376e13..5f98cba1 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,20 +1,25 @@ import { BaseLanguageModel } from 'langchain/base_language' -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' -import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains' +import { ConversationalRetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema/retriever' -import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +import { BufferMemoryInput } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' -import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' -import { - default_map_reduce_template, - default_qa_template, - qa_template, - map_reduce_template, - CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT, - refine_question_template, - refine_template -} from './prompts' +import { QA_TEMPLATE, REPHRASE_TEMPLATE, RESPONSE_TEMPLATE } from './prompts' +import { Runnable, RunnableSequence, RunnableMap, RunnableBranch, RunnableLambda } from 'langchain/schema/runnable' +import { BaseMessage, HumanMessage, AIMessage } from 'langchain/schema' +import { StringOutputParser } from 'langchain/schema/output_parser' +import type { Document } from 'langchain/document' +import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' +import { applyPatch } from 'fast-json-patch' +import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { ConsoleCallbackHandler, additionalCallbacks } from '../../../src/handler' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' + +type RetrievalChainInput = { + chat_history: string + question: string +} + +const sourceRunnableName = 'FindDocs' class ConversationalRetrievalQAChain_Chains implements INode { label: string @@ -26,11 +31,12 @@ class ConversationalRetrievalQAChain_Chains implements INode { baseClasses: string[] description: string inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversational Retrieval QA Chain' this.name = 'conversationalRetrievalQAChain' - this.version = 1.0 + this.version = 2.0 this.type = 'ConversationalRetrievalQAChain' this.icon = 'qa.svg' this.category = 'Chains' @@ -38,9 +44,9 @@ class ConversationalRetrievalQAChain_Chains implements INode { this.baseClasses = [this.type, ...getBaseClasses(ConversationalRetrievalQAChain)] this.inputs = [ { - label: 'Language Model', + label: 'Chat Model', name: 'model', - type: 'BaseLanguageModel' + type: 'BaseChatModel' }, { label: 'Vector Store Retriever', @@ -60,6 +66,29 @@ class ConversationalRetrievalQAChain_Chains implements INode { type: 'boolean', optional: true }, + { + label: 'Rephrase Prompt', + name: 'rephrasePrompt', + type: 'string', + description: 'Using previous chat history, rephrase question into a standalone question', + warning: 'Prompt must include input variables: {chat_history} and {question}', + rows: 4, + additionalParams: true, + optional: true, + default: REPHRASE_TEMPLATE + }, + { + label: 'Response Prompt', + name: 'responsePrompt', + type: 'string', + description: 'Taking the rephrased question, search for answer from the provided context', + warning: 'Prompt must include input variable: {context}', + rows: 4, + additionalParams: true, + optional: true, + default: RESPONSE_TEMPLATE + } + /** Deprecated { label: 'System Message', name: 'systemMessagePrompt', @@ -70,6 +99,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { placeholder: 'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.' }, + // TODO: create standalone chains for these 3 modes as they are not compatible with memory { label: 'Chain Option', name: 'chainOption', @@ -95,124 +125,246 @@ class ConversationalRetrievalQAChain_Chains implements INode { additionalParams: true, optional: true } + */ ] + this.sessionId = fields?.sessionId } async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string - const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean - const chainOption = nodeData.inputs?.chainOption as string - const externalMemory = nodeData.inputs?.memory + const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string + const responsePrompt = nodeData.inputs?.responsePrompt as string - const obj: any = { - verbose: process.env.DEBUG === 'true' ? true : false, - questionGeneratorChainOptions: { - template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT - } + let customResponsePrompt = responsePrompt + // If the deprecated systemMessagePrompt is still exists + if (systemMessagePrompt) { + customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}` } - if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments - - if (chainOption === 'map_reduce') { - obj.qaChainOptions = { - type: 'map_reduce', - combinePrompt: PromptTemplate.fromTemplate( - systemMessagePrompt ? `${systemMessagePrompt}\n${map_reduce_template}` : default_map_reduce_template - ) - } as QAChainParams - } else if (chainOption === 'refine') { - const qprompt = new PromptTemplate({ - inputVariables: ['context', 'question'], - template: refine_question_template(systemMessagePrompt) - }) - const rprompt = new PromptTemplate({ - inputVariables: ['context', 'question', 'existing_answer'], - template: refine_template - }) - obj.qaChainOptions = { - type: 'refine', - questionPrompt: qprompt, - refinePrompt: rprompt - } as QAChainParams - } else { - obj.qaChainOptions = { - type: 'stuff', - prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template) - } as QAChainParams - } - - if (externalMemory) { - externalMemory.memoryKey = 'chat_history' - externalMemory.inputKey = 'question' - externalMemory.outputKey = 'text' - externalMemory.returnMessages = true - if (chainOption === 'refine') externalMemory.outputKey = 'output_text' - obj.memory = externalMemory - } else { - const fields: BufferMemoryInput = { - memoryKey: 'chat_history', - inputKey: 'question', - outputKey: 'text', - returnMessages: true - } - if (chainOption === 'refine') fields.outputKey = 'output_text' - obj.memory = new BufferMemory(fields) - } - - const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj) - return chain + const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt) + return answerChain } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const chain = nodeData.instance as ConversationalRetrievalQAChain + const model = nodeData.inputs?.model as BaseLanguageModel + const externalMemory = nodeData.inputs?.memory + const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever + const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string + const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string + const responsePrompt = nodeData.inputs?.responsePrompt as string const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean - const chainOption = nodeData.inputs?.chainOption as string - let model = nodeData.inputs?.model - - // Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 - model.streaming = false - chain.questionGeneratorChain.llm = model - - const obj = { question: input } - - if (options && options.chatHistory && chain.memory) { - const chatHistoryClassName = (chain.memory as any).chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - ;(chain.memory as any).chatHistory = mapChatHistory(options) - } + let customResponsePrompt = responsePrompt + // If the deprecated systemMessagePrompt is still exists + if (systemMessagePrompt) { + customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}` } + let memory: FlowiseMemory | undefined = externalMemory + if (!memory) { + memory = new BufferMemory({ + returnMessages: true, + memoryKey: 'chat_history', + inputKey: 'input' + }) + } + + const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt) + + const history = ((await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[]) ?? [] + const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) - if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler( - options.socketIO, - options.socketIOClientId, - chainOption === 'refine' ? 4 : undefined, - returnSourceDocuments - ) - const res = await chain.call(obj, [loggerHandler, handler, ...callbacks]) - if (chainOption === 'refine') { - if (res.output_text && res.sourceDocuments) { - return { - text: res.output_text, - sourceDocuments: res.sourceDocuments - } - } - return res?.output_text + const stream = answerChain.streamLog( + { question: input, chat_history: history }, + { callbacks: [loggerHandler, ...callbacks] }, + { + includeNames: [sourceRunnableName] + } + ) + + let streamedResponse: Record = {} + let sourceDocuments: ICommonObject[] = [] + let text = '' + let isStreamingStarted = false + const isStreamingEnabled = options.socketIO && options.socketIOClientId + + for await (const chunk of stream) { + streamedResponse = applyPatch(streamedResponse, chunk.ops).newDocument + + if (streamedResponse.final_output) { + text = streamedResponse.final_output?.output + if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('end') + if (Array.isArray(streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output)) { + sourceDocuments = streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output + if (isStreamingEnabled && returnSourceDocuments) + options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', sourceDocuments) + } + } + + if ( + Array.isArray(streamedResponse?.streamed_output) && + streamedResponse?.streamed_output.length && + !streamedResponse.final_output + ) { + const token = streamedResponse.streamed_output[streamedResponse.streamed_output.length - 1] + + if (!isStreamingStarted) { + isStreamingStarted = true + if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('start', token) + } + if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('token', token) } - if (res.text && res.sourceDocuments) return res - return res?.text - } else { - const res = await chain.call(obj, [loggerHandler, ...callbacks]) - if (res.text && res.sourceDocuments) return res - return res?.text } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: text, + type: 'apiMessage' + } + ], + this.sessionId + ) + + if (returnSourceDocuments) return { text, sourceDocuments } + else return { text } + } +} + +const createRetrieverChain = (llm: BaseLanguageModel, retriever: Runnable, rephrasePrompt: string) => { + // Small speed/accuracy optimization: no need to rephrase the first question + // since there shouldn't be any meta-references to prior chat history + const CONDENSE_QUESTION_PROMPT = PromptTemplate.fromTemplate(rephrasePrompt) + const condenseQuestionChain = RunnableSequence.from([CONDENSE_QUESTION_PROMPT, llm, new StringOutputParser()]).withConfig({ + runName: 'CondenseQuestion' + }) + + const hasHistoryCheckFn = RunnableLambda.from((input: RetrievalChainInput) => input.chat_history.length > 0).withConfig({ + runName: 'HasChatHistoryCheck' + }) + + const conversationChain = condenseQuestionChain.pipe(retriever).withConfig({ + runName: 'RetrievalChainWithHistory' + }) + + const basicRetrievalChain = RunnableLambda.from((input: RetrievalChainInput) => input.question) + .withConfig({ + runName: 'Itemgetter:question' + }) + .pipe(retriever) + .withConfig({ runName: 'RetrievalChainWithNoHistory' }) + + return RunnableBranch.from([[hasHistoryCheckFn, conversationChain], basicRetrievalChain]).withConfig({ runName: sourceRunnableName }) +} + +const formatDocs = (docs: Document[]) => { + return docs.map((doc, i) => `${doc.pageContent}`).join('\n') +} + +const formatChatHistoryAsString = (history: BaseMessage[]) => { + return history.map((message) => `${message._getType()}: ${message.content}`).join('\n') +} + +const serializeHistory = (input: any) => { + const chatHistory: IMessage[] = input.chat_history || [] + const convertedChatHistory = [] + for (const message of chatHistory) { + if (message.type === 'userMessage') { + convertedChatHistory.push(new HumanMessage({ content: message.message })) + } + if (message.type === 'apiMessage') { + convertedChatHistory.push(new AIMessage({ content: message.message })) + } + } + return convertedChatHistory +} + +const createChain = ( + llm: BaseLanguageModel, + retriever: Runnable, + rephrasePrompt = REPHRASE_TEMPLATE, + responsePrompt = RESPONSE_TEMPLATE +) => { + const retrieverChain = createRetrieverChain(llm, retriever, rephrasePrompt) + + const context = RunnableMap.from({ + context: RunnableSequence.from([ + ({ question, chat_history }) => ({ + question, + chat_history: formatChatHistoryAsString(chat_history) + }), + retrieverChain, + RunnableLambda.from(formatDocs).withConfig({ + runName: 'FormatDocumentChunks' + }) + ]), + question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({ + runName: 'Itemgetter:question' + }), + chat_history: RunnableLambda.from((input: RetrievalChainInput) => input.chat_history).withConfig({ + runName: 'Itemgetter:chat_history' + }) + }).withConfig({ tags: ['RetrieveDocs'] }) + + const prompt = ChatPromptTemplate.fromMessages([ + ['system', responsePrompt], + new MessagesPlaceholder('chat_history'), + ['human', `{question}`] + ]) + + const responseSynthesizerChain = RunnableSequence.from([prompt, llm, new StringOutputParser()]).withConfig({ + tags: ['GenerateResponse'] + }) + + const conversationalQAChain = RunnableSequence.from([ + { + question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({ + runName: 'Itemgetter:question' + }), + chat_history: RunnableLambda.from(serializeHistory).withConfig({ + runName: 'SerializeHistory' + }) + }, + context, + responseSynthesizerChain + ]) + + return conversationalQAChain +} + +class BufferMemory extends FlowiseMemory implements MemoryMethods { + constructor(fields: BufferMemoryInput) { + super(fields) + } + + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + + const memoryResult = await this.loadMemoryVariables({}) + const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return + } + + async clearChatMessages(): Promise { + await this.clear() } } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts index 132e3a97..dccc7358 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts @@ -1,64 +1,27 @@ -export const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. - -{context} - -Question: {question} -Helpful Answer:` - -export const qa_template = `Use the following pieces of context to answer the question at the end. - -{context} - -Question: {question} -Helpful Answer:` - -export const default_map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer. -If you don't know the answer, just say that you don't know. Don't try to make up an answer. - -{summaries} - -Question: {question} -Helpful Answer:` - -export const map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer. - -{summaries} - -Question: {question} -Helpful Answer:` - -export const refine_question_template = (sysPrompt?: string) => { - let returnPrompt = '' - if (sysPrompt) - returnPrompt = `Context information is below. ---------------------- -{context} ---------------------- -Given the context information and not prior knowledge, ${sysPrompt} -Answer the question: {question}. -Answer:` - if (!sysPrompt) - returnPrompt = `Context information is below. ---------------------- -{context} ---------------------- -Given the context information and not prior knowledge, answer the question: {question}. -Answer:` - return returnPrompt -} - -export const refine_template = `The original question is as follows: {question} -We have provided an existing answer: {existing_answer} -We have the opportunity to refine the existing answer (only if needed) with some more context below. ------------- -{context} ------------- -Given the new context, refine the original answer to better answer the question. -If you can't find answer from the context, return the original answer.` - export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, answer in the same language as the follow up question. include it in the standalone question. Chat History: {chat_history} Follow Up Input: {question} Standalone question:` + +export const RESPONSE_TEMPLATE = `I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". Using the provided context, answer the user's question to the best of your ability using the resources provided. +If there is nothing in the context relevant to the question at hand, just say "Hmm, I'm not sure" and stop after that. Refuse to answer any question not about the info. Never break character. +------------ +{context} +------------ +REMEMBER: If there is no relevant information within the context, just say "Hmm, I'm not sure". Don't try to make up an answer. Never break character.` + +export const QA_TEMPLATE = `Use the following pieces of context to answer the question at the end. + +{context} + +Question: {question} +Helpful Answer:` + +export const REPHRASE_TEMPLATE = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. + +Chat History: +{chat_history} +Follow Up Input: {question} +Standalone Question:` diff --git a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts index 0ad8adec..4a6252b5 100644 --- a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts +++ b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts @@ -1,4 +1,4 @@ -import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BaseMessage } from 'langchain/schema' @@ -55,36 +55,27 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { super(fields) } - async getChatMessages(_?: string, returnBaseMessages = false): Promise { + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + const memoryResult = await this.loadMemoryVariables({}) const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } - async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues) + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return } async clearChatMessages(): Promise { await this.clear() } - - async resumeMessages(messages: IMessage[]): Promise { - // Clear existing chatHistory to avoid duplication - if (messages.length) await this.clear() - - // Insert into chatHistory - for (const msg of messages) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } - } } module.exports = { nodeClass: BufferMemory_Memory } diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts index ca8d0ddf..c21405a4 100644 --- a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -1,4 +1,4 @@ -import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory' import { BaseMessage } from 'langchain/schema' @@ -67,36 +67,28 @@ class BufferWindowMemoryExtended extends FlowiseWindowMemory implements MemoryMe super(fields) } - async getChatMessages(_?: string, returnBaseMessages = false): Promise { + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + + // Insert into chatHistory + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + const memoryResult = await this.loadMemoryVariables({}) const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } - async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues) + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return } async clearChatMessages(): Promise { await this.clear() } - - async resumeMessages(messages: IMessage[]): Promise { - // Clear existing chatHistory to avoid duplication - if (messages.length) await this.clear() - - // Insert into chatHistory - for (const msg of messages) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } - } } module.exports = { nodeClass: BufferWindowMemory_Memory } diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts index 107ab7db..45d39326 100644 --- a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts +++ b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts @@ -1,4 +1,4 @@ -import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory' import { BaseLanguageModel } from 'langchain/base_language' @@ -66,40 +66,32 @@ class ConversationSummaryMemoryExtended extends FlowiseSummaryMemory implements super(fields) } - async getChatMessages(_?: string, returnBaseMessages = false): Promise { + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + this.buffer = '' + + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + + // Get summary + const chatMessages = await this.chatHistory.getMessages() + this.buffer = chatMessages.length ? await this.predictNewSummary(chatMessages.slice(-2), this.buffer) : '' + const memoryResult = await this.loadMemoryVariables({}) const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } - async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues) + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return } async clearChatMessages(): Promise { await this.clear() } - - async resumeMessages(messages: IMessage[]): Promise { - // Clear existing chatHistory to avoid duplication - if (messages.length) await this.clear() - - // Insert into chatHistory - for (const msg of messages) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } - - // Replace buffer - const chatMessages = await this.chatHistory.getMessages() - this.buffer = await this.predictNewSummary(chatMessages.slice(-2), this.buffer) - } } module.exports = { nodeClass: ConversationSummaryMemory_Memory } diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 872ec0b5..91c1d369 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -12,13 +12,7 @@ import { import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' class DynamoDb_Memory implements INode { @@ -70,7 +64,8 @@ class DynamoDb_Memory implements INode { label: 'Session ID', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -88,25 +83,6 @@ class DynamoDb_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initalizeDynamoDB(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const dynamodbMemory = await initalizeDynamoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) - await dynamodbMemory.clear() - options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const dynamodbMemory = await initalizeDynamoDB(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await dynamodbMemory.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): Promise => { @@ -114,17 +90,7 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P const partitionKey = nodeData.inputs?.partitionKey as string const region = nodeData.inputs?.region as string const memoryKey = nodeData.inputs?.memoryKey as string - const chatId = options.chatId - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData) @@ -150,7 +116,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P const memory = new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: dynamoDb, - isSessionIdUsingChatMessageId, sessionId, dynamodbClient: client }) @@ -158,7 +123,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean dynamodbClient: DynamoDBClient sessionId: string } @@ -178,7 +142,6 @@ interface DynamoDBSerializedChatMessage { } class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - isSessionIdUsingChatMessageId = false sessionId = '' dynamodbClient: DynamoDBClient @@ -306,10 +269,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.dynamodbClient.send(new DeleteItemCommand(params)) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index b422921e..c593c20d 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -2,13 +2,7 @@ import { MongoClient, Collection, Document } from 'mongodb' import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' class MongoDB_Memory implements INode { @@ -55,7 +49,8 @@ class MongoDB_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -73,42 +68,13 @@ class MongoDB_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initializeMongoDB(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const mongodbMemory = await initializeMongoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) - await mongodbMemory.clear() - options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const mongodbMemory = await initializeMongoDB(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await mongodbMemory.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): Promise => { const databaseName = nodeData.inputs?.databaseName as string const collectionName = nodeData.inputs?.collectionName as string const memoryKey = nodeData.inputs?.memoryKey as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) @@ -149,14 +115,12 @@ const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): P return new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: mongoDBChatMessageHistory, - isSessionIdUsingChatMessageId, sessionId, collection }) } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean collection: Collection sessionId: string } @@ -164,7 +128,6 @@ interface BufferMemoryExtendedInput { class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { sessionId = '' collection: Collection - isSessionIdUsingChatMessageId? = false constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) @@ -221,10 +184,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.collection.deleteOne({ sessionId: id }) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: MongoDB_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 938cc873..19506fc1 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -1,9 +1,14 @@ import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' -import { MotorheadMemory, MotorheadMemoryInput, InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' +import { MotorheadMemory, MotorheadMemoryInput, InputValues, OutputValues } from 'langchain/memory' import fetch from 'node-fetch' -import { BaseMessage } from 'langchain/schema' +import { AIMessage, BaseMessage, ChatMessage, HumanMessage } from 'langchain/schema' + +type MotorheadMessage = { + content: string + role: 'Human' | 'AI' +} class MotorMemory_Memory implements INode { label: string @@ -46,7 +51,8 @@ class MotorMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -64,49 +70,19 @@ class MotorMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initalizeMotorhead(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const motorhead = await initalizeMotorhead(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) - await motorhead.clear() - options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const motorhead = await initalizeMotorhead(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await motorhead.loadMemoryVariables({}) - return getBufferString(memoryResult[key]) - } - } } const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise => { const memoryKey = nodeData.inputs?.memoryKey as string const baseURL = nodeData.inputs?.baseURL as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const clientId = getCredentialParam('clientId', credentialData, nodeData) - let obj: MotorheadMemoryInput & MotorheadMemoryExtendedInput = { + let obj: MotorheadMemoryInput = { returnMessages: true, - isSessionIdUsingChatMessageId, sessionId, memoryKey } @@ -132,23 +108,9 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): return motorheadMemory } -interface MotorheadMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean -} - class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false - - constructor(fields: MotorheadMemoryInput & MotorheadMemoryExtendedInput) { + constructor(fields: MotorheadMemoryInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId - } - - async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise { - if (overrideSessionId) { - this.sessionId = overrideSessionId - } - return super.loadMemoryVariables({ values }) } async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise { @@ -180,9 +142,33 @@ class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { const id = overrideSessionId ?? this.sessionId - const memoryVariables = await this.loadMemoryVariables({}, id) - const baseMessages = memoryVariables[this.memoryKey] - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + try { + const resp = await this.caller.call(fetch, `${this.url}/sessions/${id}/memory`, { + //@ts-ignore + signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined, + headers: this._getHeaders() as ICommonObject, + method: 'GET' + }) + const data = await resp.json() + const rawStoredMessages: MotorheadMessage[] = data?.data?.messages ?? [] + + const baseMessages = rawStoredMessages.reverse().map((message) => { + const { content, role } = message + if (role === 'Human') { + return new HumanMessage(content) + } else if (role === 'AI') { + return new AIMessage(content) + } else { + // default to generic ChatMessage + return new ChatMessage(content, role) + } + }) + + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } catch (error) { + console.error('Error getting session: ', error) + return [] + } } async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index a02df3ea..baf4ea6b 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,15 +1,9 @@ -import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType, FlowiseMemory, MemoryMethods } from '../../../src/Interface' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { Redis } from 'ioredis' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' import { mapStoredMessageToChatMessage, BaseMessage, AIMessage, HumanMessage } from 'langchain/schema' -import { Redis } from 'ioredis' +import { INode, INodeData, INodeParams, ICommonObject, MessageType, IMessage, MemoryMethods, FlowiseMemory } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class RedisBackedChatMemory_Memory implements INode { label: string @@ -44,7 +38,8 @@ class RedisBackedChatMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -78,47 +73,19 @@ class RedisBackedChatMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return await initalizeRedis(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const redis = await initalizeRedis(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await redis.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string + const sessionId = nodeData.inputs?.sessionId as string const windowSize = nodeData.inputs?.windowSize as number - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) let client: Redis + if (!redisUrl || redisUrl === '') { const username = getCredentialParam('redisCacheUser', credentialData, nodeData) const password = getCredentialParam('redisCachePwd', credentialData, nodeData) @@ -153,7 +120,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const redisChatMessageHistory = new RedisChatMessageHistory(obj) - redisChatMessageHistory.getMessages = async (): Promise => { + /*redisChatMessageHistory.getMessages = async (): Promise => { const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) return orderedMessages.map(mapStoredMessageToChatMessage) @@ -169,44 +136,45 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom redisChatMessageHistory.clear = async (): Promise => { await client.del((redisChatMessageHistory as any).sessionId) - } + }*/ const memory = new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: redisChatMessageHistory, - isSessionIdUsingChatMessageId, sessionId, + windowSize, redisClient: client }) + return memory } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean redisClient: Redis sessionId: string + windowSize?: number } class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false sessionId = '' redisClient: Redis + windowSize?: number constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId this.sessionId = fields.sessionId this.redisClient = fields.redisClient + this.windowSize = fields.windowSize } - async getChatMessages(overrideSessionId = '', returnBaseMessage = false): Promise { + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { if (!this.redisClient) return [] const id = overrideSessionId ?? this.sessionId - const rawStoredMessages = await this.redisClient.lrange(id, 0, -1) + const rawStoredMessages = await this.redisClient.lrange(id, this.windowSize ? this.windowSize * -1 : 0, -1) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) const baseMessages = orderedMessages.map(mapStoredMessageToChatMessage) - return returnBaseMessage ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { @@ -236,10 +204,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.redisClient.del(id) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index c3f97123..3d7f6dbf 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -3,13 +3,7 @@ import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src/Interface' class UpstashRedisBackedChatMemory_Memory implements INode { @@ -51,7 +45,8 @@ class UpstashRedisBackedChatMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -70,40 +65,12 @@ class UpstashRedisBackedChatMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initalizeUpstashRedis(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const key = 'chat_history' - const memoryResult = await redis.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const baseURL = nodeData.inputs?.baseURL as string const sessionTTL = nodeData.inputs?.sessionTTL as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData) @@ -122,7 +89,6 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject const memory = new BufferMemoryExtended({ memoryKey: 'chat_history', chatHistory: redisChatMessageHistory, - isSessionIdUsingChatMessageId, sessionId, redisClient: client }) @@ -131,19 +97,16 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean redisClient: Redis sessionId: string } class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false sessionId = '' redisClient: Redis constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId this.sessionId = fields.sessionId this.redisClient = fields.redisClient } @@ -186,10 +149,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.redisClient.del(id) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 4dda76df..597eee8a 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -2,7 +2,7 @@ import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } f import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' -import { InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' +import { InputValues, MemoryVariables, OutputValues } from 'langchain/memory' import { BaseMessage } from 'langchain/schema' class ZepMemory_Memory implements INode { @@ -55,10 +55,9 @@ class ZepMemory_Memory implements INode { label: 'Size', name: 'k', type: 'number', - placeholder: '10', + default: '10', description: 'Window of size k to surface the last k back-and-forth to use as memory.', - additionalParams: true, - optional: true + additionalParams: true }, { label: 'AI Prefix', @@ -101,27 +100,6 @@ class ZepMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return await initalizeZep(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const zep = await initalizeZep(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) - await zep.clear() - options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const aiPrefix = nodeData.inputs?.aiPrefix as string - const humanPrefix = nodeData.inputs?.humanPrefix as string - const zep = await initalizeZep(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await zep.loadMemoryVariables({}) - return getBufferString(memoryResult[key], humanPrefix, aiPrefix) - } - } } const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promise => { @@ -131,30 +109,19 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis const memoryKey = nodeData.inputs?.memoryKey as string const inputKey = nodeData.inputs?.inputKey as string const k = nodeData.inputs?.k as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const obj: ZepMemoryInput & ZepMemoryExtendedInput = { baseURL, - sessionId, aiPrefix, humanPrefix, returnMessages: true, memoryKey, inputKey, - isSessionIdUsingChatMessageId, + sessionId, k: k ? parseInt(k, 10) : undefined } if (apiKey) obj.apiKey = apiKey @@ -163,17 +130,14 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis } interface ZepMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean k?: number } class ZepMemoryExtended extends ZepMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false lastN?: number constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId this.lastN = fields.k } diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index 6ffcc0e2..a983d0d9 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -60,7 +60,7 @@ class CustomTool_Tools implements INode { } } - async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const selectedToolId = nodeData.inputs?.selectedTool as string const customToolFunc = nodeData.inputs?.customToolFunc as string @@ -99,11 +99,7 @@ class CustomTool_Tools implements INode { } } - const flow = { - chatId: options.chatId, // id is uppercase (I) - chatflowId: options.chatflowid, // id is lowercase (i) - input - } + const flow = { chatflowId: options.chatflowid } let dynamicStructuredTool = new DynamicStructuredTool(obj) dynamicStructuredTool.setVariables(variables) diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 338b0ae9..b543aefa 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -55,7 +55,12 @@ export class DynamicStructuredTool< this.schema = fields.schema } - async call(arg: z.output, configArg?: RunnableConfig | Callbacks, tags?: string[], overrideSessionId?: string): Promise { + async call( + arg: z.output, + configArg?: RunnableConfig | Callbacks, + tags?: string[], + flowConfig?: { sessionId?: string; chatId?: string; input?: string } + ): Promise { const config = parseCallbackConfigArg(configArg) if (config.runName === undefined) { config.runName = this.name @@ -86,7 +91,7 @@ export class DynamicStructuredTool< ) let result try { - result = await this._call(parsed, runManager, overrideSessionId) + result = await this._call(parsed, runManager, flowConfig) } catch (e) { await runManager?.handleToolError(e) throw e @@ -95,7 +100,11 @@ export class DynamicStructuredTool< return result } - protected async _call(arg: z.output, _?: CallbackManagerForToolRun, overrideSessionId?: string): Promise { + protected async _call( + arg: z.output, + _?: CallbackManagerForToolRun, + flowConfig?: { sessionId?: string; chatId?: string; input?: string } + ): Promise { let sandbox: any = {} if (typeof arg === 'object' && Object.keys(arg).length) { for (const item in arg) { @@ -126,7 +135,7 @@ export class DynamicStructuredTool< // inject flow properties if (this.flowObj) { - sandbox['$flow'] = { ...this.flowObj, sessionId: overrideSessionId } + sandbox['$flow'] = { ...this.flowObj, ...flowConfig } } const defaultAllowBuiltInDep = [ diff --git a/packages/components/package.json b/packages/components/package.json index a2565430..a77d91e4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -46,6 +46,7 @@ "dotenv": "^16.0.0", "express": "^4.17.3", "faiss-node": "^0.2.2", + "fast-json-patch": "^3.1.1", "form-data": "^4.0.0", "google-auth-library": "^9.0.0", "graphql": "^16.6.0", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 2a625ff6..676618e5 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -108,10 +108,6 @@ export interface INode extends INodeProperties { search: (nodeData: INodeData, options?: ICommonObject) => Promise delete: (nodeData: INodeData, options?: ICommonObject) => Promise } - memoryMethods?: { - clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise - getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise - } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise } @@ -204,29 +200,37 @@ import { BaseMessage } from 'langchain/schema' import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory' export interface MemoryMethods { - getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean, prevHistory?: IMessage[]): Promise addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise clearChatMessages(overrideSessionId?: string): Promise - resumeMessages?(messages: IMessage[]): Promise } export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods { - abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract getChatMessages( + overrideSessionId?: string, + returnBaseMessages?: boolean, + prevHistory?: IMessage[] + ): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise - abstract resumeMessages(messages: IMessage[]): Promise } export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods { - abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract getChatMessages( + overrideSessionId?: string, + returnBaseMessages?: boolean, + prevHistory?: IMessage[] + ): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise - abstract resumeMessages(messages: IMessage[]): Promise } export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods { - abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract getChatMessages( + overrideSessionId?: string, + returnBaseMessages?: boolean, + prevHistory?: IMessage[] + ): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise - abstract resumeMessages(messages: IMessage[]): Promise } diff --git a/packages/components/src/agents.ts b/packages/components/src/agents.ts new file mode 100644 index 00000000..e30a0c43 --- /dev/null +++ b/packages/components/src/agents.ts @@ -0,0 +1,615 @@ +import { AgentExecutorInput, BaseSingleActionAgent, BaseMultiActionAgent, RunnableAgent, StoppingMethod } from 'langchain/agents' +import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema' +import { OutputParserException } from 'langchain/schema/output_parser' +import { CallbackManager, CallbackManagerForChainRun, Callbacks } from 'langchain/callbacks' +import { ToolInputParsingException, Tool } from '@langchain/core/tools' +import { Runnable } from 'langchain/schema/runnable' +import { BaseChain, SerializedLLMChain } from 'langchain/chains' +import { Serializable } from '@langchain/core/load/serializable' + +type AgentExecutorOutput = ChainValues + +interface AgentExecutorIteratorInput { + agentExecutor: AgentExecutor + inputs: Record + callbacks?: Callbacks + tags?: string[] + metadata?: Record + runName?: string + runManager?: CallbackManagerForChainRun +} + +//TODO: stream tools back +export class AgentExecutorIterator extends Serializable implements AgentExecutorIteratorInput { + lc_namespace = ['langchain', 'agents', 'executor_iterator'] + + agentExecutor: AgentExecutor + + inputs: Record + + callbacks: Callbacks + + tags: string[] | undefined + + metadata: Record | undefined + + runName: string | undefined + + private _finalOutputs: Record | undefined + + get finalOutputs(): Record | undefined { + return this._finalOutputs + } + + /** Intended to be used as a setter method, needs to be async. */ + async setFinalOutputs(value: Record | undefined) { + this._finalOutputs = undefined + if (value) { + const preparedOutputs: Record = await this.agentExecutor.prepOutputs(this.inputs, value, true) + this._finalOutputs = preparedOutputs + } + } + + runManager: CallbackManagerForChainRun | undefined + + intermediateSteps: AgentStep[] = [] + + iterations = 0 + + get nameToToolMap(): Record { + const toolMap = this.agentExecutor.tools.map((tool) => ({ + [tool.name]: tool + })) + return Object.assign({}, ...toolMap) + } + + constructor(fields: AgentExecutorIteratorInput) { + super(fields) + this.agentExecutor = fields.agentExecutor + this.inputs = fields.inputs + this.tags = fields.tags + this.metadata = fields.metadata + this.runName = fields.runName + this.runManager = fields.runManager + } + + /** + * Reset the iterator to its initial state, clearing intermediate steps, + * iterations, and the final output. + */ + reset(): void { + this.intermediateSteps = [] + this.iterations = 0 + this._finalOutputs = undefined + } + + updateIterations(): void { + this.iterations += 1 + } + + async *streamIterator() { + this.reset() + + // Loop to handle iteration + while (true) { + try { + if (this.iterations === 0) { + await this.onFirstStep() + } + + const result = await this._callNext() + yield result + } catch (e: any) { + if ('message' in e && e.message.startsWith('Final outputs already reached: ')) { + if (!this.finalOutputs) { + throw e + } + return this.finalOutputs + } + if (this.runManager) { + await this.runManager.handleChainError(e) + } + throw e + } + } + } + + /** + * Perform any necessary setup for the first step + * of the asynchronous iterator. + */ + async onFirstStep(): Promise { + if (this.iterations === 0) { + const callbackManager = await CallbackManager.configure( + this.callbacks, + this.agentExecutor.callbacks, + this.tags, + this.agentExecutor.tags, + this.metadata, + this.agentExecutor.metadata, + { + verbose: this.agentExecutor.verbose + } + ) + this.runManager = await callbackManager?.handleChainStart( + this.agentExecutor.toJSON(), + this.inputs, + undefined, + undefined, + this.tags, + this.metadata, + this.runName + ) + } + } + + /** + * Execute the next step in the chain using the + * AgentExecutor's _takeNextStep method. + */ + async _executeNextStep(runManager?: CallbackManagerForChainRun): Promise { + return this.agentExecutor._takeNextStep(this.nameToToolMap, this.inputs, this.intermediateSteps, runManager) + } + + /** + * Process the output of the next step, + * handling AgentFinish and tool return cases. + */ + async _processNextStepOutput( + nextStepOutput: AgentFinish | AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise> { + if ('returnValues' in nextStepOutput) { + const output = await this.agentExecutor._return(nextStepOutput as AgentFinish, this.intermediateSteps, runManager) + if (this.runManager) { + await this.runManager.handleChainEnd(output) + } + await this.setFinalOutputs(output) + return output + } + + this.intermediateSteps = this.intermediateSteps.concat(nextStepOutput as AgentStep[]) + + let output: Record = {} + if (Array.isArray(nextStepOutput) && nextStepOutput.length === 1) { + const nextStep = nextStepOutput[0] + const toolReturn = await this.agentExecutor._getToolReturn(nextStep) + if (toolReturn) { + output = await this.agentExecutor._return(toolReturn, this.intermediateSteps, runManager) + if (this.runManager) { + await this.runManager.handleChainEnd(output) + } + await this.setFinalOutputs(output) + } + } + output = { intermediateSteps: nextStepOutput as AgentStep[] } + return output + } + + async _stop(): Promise> { + const output = await this.agentExecutor.agent.returnStoppedResponse( + this.agentExecutor.earlyStoppingMethod, + this.intermediateSteps, + this.inputs + ) + const returnedOutput = await this.agentExecutor._return(output, this.intermediateSteps, this.runManager) + await this.setFinalOutputs(returnedOutput) + return returnedOutput + } + + async _callNext(): Promise> { + // final output already reached: stopiteration (final output) + if (this.finalOutputs) { + throw new Error(`Final outputs already reached: ${JSON.stringify(this.finalOutputs, null, 2)}`) + } + // timeout/max iterations: stopiteration (stopped response) + if (!this.agentExecutor.shouldContinueGetter(this.iterations)) { + return this._stop() + } + const nextStepOutput = await this._executeNextStep(this.runManager) + const output = await this._processNextStepOutput(nextStepOutput, this.runManager) + this.updateIterations() + return output + } +} + +export class AgentExecutor extends BaseChain { + static lc_name() { + return 'AgentExecutor' + } + + get lc_namespace() { + return ['langchain', 'agents', 'executor'] + } + + agent: BaseSingleActionAgent | BaseMultiActionAgent + + tools: this['agent']['ToolType'][] + + returnIntermediateSteps = false + + maxIterations?: number = 15 + + earlyStoppingMethod: StoppingMethod = 'force' + + sessionId?: string + + chatId?: string + + input?: string + + /** + * How to handle errors raised by the agent's output parser. + Defaults to `False`, which raises the error. + + If `true`, the error will be sent back to the LLM as an observation. + If a string, the string itself will be sent to the LLM as an observation. + If a callable function, the function will be called with the exception + as an argument, and the result of that function will be passed to the agent + as an observation. + */ + handleParsingErrors: boolean | string | ((e: OutputParserException | ToolInputParsingException) => string) = false + + get inputKeys() { + return this.agent.inputKeys + } + + get outputKeys() { + return this.agent.returnValues + } + + constructor(input: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }) { + let agent: BaseSingleActionAgent | BaseMultiActionAgent + if (Runnable.isRunnable(input.agent)) { + agent = new RunnableAgent({ runnable: input.agent }) + } else { + agent = input.agent + } + + super(input) + this.agent = agent + this.tools = input.tools + this.handleParsingErrors = input.handleParsingErrors ?? this.handleParsingErrors + /* Getting rid of this because RunnableAgent doesnt allow return direct + if (this.agent._agentActionType() === "multi") { + for (const tool of this.tools) { + if (tool.returnDirect) { + throw new Error( + `Tool with return direct ${tool.name} not supported for multi-action agent.` + ); + } + } + }*/ + this.returnIntermediateSteps = input.returnIntermediateSteps ?? this.returnIntermediateSteps + this.maxIterations = input.maxIterations ?? this.maxIterations + this.earlyStoppingMethod = input.earlyStoppingMethod ?? this.earlyStoppingMethod + this.sessionId = input.sessionId + this.chatId = input.chatId + this.input = input.input + } + + static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }): AgentExecutor { + const newInstance = new AgentExecutor(fields) + if (fields.sessionId) newInstance.sessionId = fields.sessionId + if (fields.chatId) newInstance.chatId = fields.chatId + if (fields.input) newInstance.input = fields.input + return newInstance + } + + get shouldContinueGetter() { + return this.shouldContinue.bind(this) + } + + /** + * Method that checks if the agent execution should continue based on the + * number of iterations. + * @param iterations The current number of iterations. + * @returns A boolean indicating whether the agent execution should continue. + */ + private shouldContinue(iterations: number): boolean { + return this.maxIterations === undefined || iterations < this.maxIterations + } + + async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise { + const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) + + const steps: AgentStep[] = [] + let iterations = 0 + + const getOutput = async (finishStep: AgentFinish): Promise => { + const { returnValues } = finishStep + const additional = await this.agent.prepareForOutput(returnValues, steps) + + if (this.returnIntermediateSteps) { + return { ...returnValues, intermediateSteps: steps, ...additional } + } + await runManager?.handleAgentEnd(finishStep) + return { ...returnValues, ...additional } + } + + while (this.shouldContinue(iterations)) { + let output + try { + output = await this.agent.plan(steps, inputs, runManager?.getChild()) + } catch (e) { + if (e instanceof OutputParserException) { + let observation + let text = e.message + if (this.handleParsingErrors === true) { + if (e.sendToLLM) { + observation = e.observation + text = e.llmOutput ?? '' + } else { + observation = 'Invalid or incomplete response' + } + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + output = { + tool: '_Exception', + toolInput: observation, + log: text + } as AgentAction + } else { + throw e + } + } + // Check if the agent has finished + if ('returnValues' in output) { + return getOutput(output) + } + + let actions: AgentAction[] + if (Array.isArray(output)) { + actions = output as AgentAction[] + } else { + actions = [output as AgentAction] + } + + const newSteps = await Promise.all( + actions.map(async (action) => { + await runManager?.handleAgentAction(action) + const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()] + let observation + try { + /* Here we need to override Tool call method to include sessionId, chatId, input as parameter + * Tool Call Parameters: + * - arg: z.output + * - configArg?: RunnableConfig | Callbacks + * - tags?: string[] + * - flowConfig?: { sessionId?: string, chatId?: string, input?: string } + */ + observation = tool + ? // @ts-ignore + await tool.call(action.toolInput, runManager?.getChild(), undefined, { + sessionId: this.sessionId, + chatId: this.chatId, + input: this.input + }) + : `${action.tool} is not a valid tool, try another one.` + } catch (e) { + if (e instanceof ToolInputParsingException) { + if (this.handleParsingErrors === true) { + observation = 'Invalid or incomplete tool input. Please try again.' + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + observation = await new ExceptionTool().call(observation, runManager?.getChild()) + return { action, observation: observation ?? '' } + } + } + return { action, observation: observation ?? '' } + }) + ) + + steps.push(...newSteps) + + const lastStep = steps[steps.length - 1] + const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()] + + if (lastTool?.returnDirect) { + return getOutput({ + returnValues: { [this.agent.returnValues[0]]: lastStep.observation }, + log: '' + }) + } + + iterations += 1 + } + + const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs) + + return getOutput(finish) + } + + async _takeNextStep( + nameToolMap: Record, + inputs: ChainValues, + intermediateSteps: AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise { + let output + try { + output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild()) + } catch (e) { + if (e instanceof OutputParserException) { + let observation + let text = e.message + if (this.handleParsingErrors === true) { + if (e.sendToLLM) { + observation = e.observation + text = e.llmOutput ?? '' + } else { + observation = 'Invalid or incomplete response' + } + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + output = { + tool: '_Exception', + toolInput: observation, + log: text + } as AgentAction + } else { + throw e + } + } + + if ('returnValues' in output) { + return output + } + + let actions: AgentAction[] + if (Array.isArray(output)) { + actions = output as AgentAction[] + } else { + actions = [output as AgentAction] + } + + const result: AgentStep[] = [] + for (const agentAction of actions) { + let observation = '' + if (runManager) { + await runManager?.handleAgentAction(agentAction) + } + if (agentAction.tool in nameToolMap) { + const tool = nameToolMap[agentAction.tool] + try { + /* Here we need to override Tool call method to include sessionId, chatId, input as parameter + * Tool Call Parameters: + * - arg: z.output + * - configArg?: RunnableConfig | Callbacks + * - tags?: string[] + * - flowConfig?: { sessionId?: string, chatId?: string, input?: string } + */ + // @ts-ignore + observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, { + sessionId: this.sessionId, + chatId: this.chatId, + input: this.input + }) + } catch (e) { + if (e instanceof ToolInputParsingException) { + if (this.handleParsingErrors === true) { + observation = 'Invalid or incomplete tool input. Please try again.' + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + observation = await new ExceptionTool().call(observation, runManager?.getChild()) + } + } + } else { + observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}` + } + result.push({ + action: agentAction, + observation + }) + } + return result + } + + async _return( + output: AgentFinish, + intermediateSteps: AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise { + if (runManager) { + await runManager.handleAgentEnd(output) + } + const finalOutput: Record = output.returnValues + if (this.returnIntermediateSteps) { + finalOutput.intermediateSteps = intermediateSteps + } + return finalOutput + } + + async _getToolReturn(nextStepOutput: AgentStep): Promise { + const { action, observation } = nextStepOutput + const nameToolMap = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) + const [returnValueKey = 'output'] = this.agent.returnValues + // Invalid tools won't be in the map, so we return False. + if (action.tool in nameToolMap) { + if (nameToolMap[action.tool].returnDirect) { + return { + returnValues: { [returnValueKey]: observation }, + log: '' + } + } + } + return null + } + + _returnStoppedResponse(earlyStoppingMethod: StoppingMethod) { + if (earlyStoppingMethod === 'force') { + return { + returnValues: { + output: 'Agent stopped due to iteration limit or time limit.' + }, + log: '' + } as AgentFinish + } + throw new Error(`Got unsupported early_stopping_method: ${earlyStoppingMethod}`) + } + + async *_streamIterator(inputs: Record): AsyncGenerator { + const agentExecutorIterator = new AgentExecutorIterator({ + inputs, + agentExecutor: this, + metadata: this.metadata, + tags: this.tags, + callbacks: this.callbacks + }) + const iterator = agentExecutorIterator.streamIterator() + for await (const step of iterator) { + if (!step) { + continue + } + yield step + } + } + + _chainType() { + return 'agent_executor' as const + } + + serialize(): SerializedLLMChain { + throw new Error('Cannot serialize an AgentExecutor') + } +} + +class ExceptionTool extends Tool { + name = '_Exception' + + description = 'Exception tool' + + async _call(query: string) { + return query + } +} + +export const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => + steps.flatMap(({ action, observation }) => { + if ('messageLog' in action && action.messageLog !== undefined) { + const log = action.messageLog as BaseMessage[] + return log.concat(new FunctionMessage(observation, action.tool)) + } else { + return [new AIMessage(action.log)] + } + }) diff --git a/packages/server/marketplaces/chatflows/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json index 93270848..eabc8f2e 100644 --- a/packages/server/marketplaces/chatflows/API Agent.json +++ b/packages/server/marketplaces/chatflows/API Agent.json @@ -936,7 +936,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Chat with a Podcast.json b/packages/server/marketplaces/chatflows/Chat with a Podcast.json index 2a9e05b5..0a5d4ac6 100644 --- a/packages/server/marketplaces/chatflows/Chat with a Podcast.json +++ b/packages/server/marketplaces/chatflows/Chat with a Podcast.json @@ -13,7 +13,7 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], @@ -28,47 +28,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -89,9 +78,8 @@ "model": "{{chatOpenAI_0.data.instance}}", "vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}", "memory": "", - "returnSourceDocuments": "", - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { @@ -625,9 +613,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Claude LLM.json b/packages/server/marketplaces/chatflows/Claude LLM.json index 0ead3dd8..39d4d400 100644 --- a/packages/server/marketplaces/chatflows/Claude LLM.json +++ b/packages/server/marketplaces/chatflows/Claude LLM.json @@ -90,7 +90,7 @@ ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationChain_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json index 8994594a..b27d3886 100644 --- a/packages/server/marketplaces/chatflows/Conversational Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Agent.json @@ -354,7 +354,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index 5c55d833..e2fd6421 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -249,10 +249,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -264,47 +264,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -325,16 +314,15 @@ "model": "{{chatOpenAI_0.data.instance}}", "vectorStoreRetriever": "{{pinecone_0.data.instance}}", "memory": "", - "returnSourceDocuments": "", - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -704,9 +692,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Flowise Docs QnA.json b/packages/server/marketplaces/chatflows/Flowise Docs QnA.json index ac84cf56..16f70801 100644 --- a/packages/server/marketplaces/chatflows/Flowise Docs QnA.json +++ b/packages/server/marketplaces/chatflows/Flowise Docs QnA.json @@ -156,9 +156,9 @@ "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", "name": "conversationalRetrievalQAChain", - "version": 1, + "version": 2, "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -170,47 +170,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -232,15 +221,15 @@ "vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}", "memory": "", "returnSourceDocuments": true, - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -668,9 +657,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json index e24ad7ca..6f78cb05 100644 --- a/packages/server/marketplaces/chatflows/Local QnA.json +++ b/packages/server/marketplaces/chatflows/Local QnA.json @@ -83,10 +83,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -98,47 +98,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -158,14 +147,16 @@ "inputs": { "model": "{{chatOllama_0.data.instance}}", "vectorStoreRetriever": "{{faiss_0.data.instance}}", - "memory": "" + "memory": "", + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -649,9 +640,9 @@ "source": "chatOllama_0", "sourceHandle": "chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOllama_0-chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOllama_0-chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Long Term Memory.json b/packages/server/marketplaces/chatflows/Long Term Memory.json index c39f746a..cf0fa4d4 100644 --- a/packages/server/marketplaces/chatflows/Long Term Memory.json +++ b/packages/server/marketplaces/chatflows/Long Term Memory.json @@ -13,10 +13,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -28,47 +28,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -89,14 +78,16 @@ "model": "{{chatOpenAI_0.data.instance}}", "vectorStoreRetriever": "{{qdrant_0.data.instance}}", "memory": "{{ZepMemory_0.data.instance}}", - "returnSourceDocuments": true + "returnSourceDocuments": true, + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -232,7 +223,7 @@ "label": "Session Id", "name": "sessionId", "type": "string", - "description": "if empty, chatId will be used automatically", + "description": "If not specified, a random id will be used. Learn more", "default": "", "additionalParams": true, "optional": true, @@ -709,9 +700,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Metadata Filter.json b/packages/server/marketplaces/chatflows/Metadata Filter.json index 9865ae70..abd85d36 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter.json @@ -249,10 +249,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -264,47 +264,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -323,14 +312,16 @@ ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{pinecone_0.data.instance}}" + "vectorStoreRetriever": "{{pinecone_0.data.instance}}", + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -763,9 +754,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json index a2a807cd..d53cb55e 100644 --- a/packages/server/marketplaces/chatflows/Multiple VectorDB.json +++ b/packages/server/marketplaces/chatflows/Multiple VectorDB.json @@ -1567,7 +1567,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index 2dac3823..2322136c 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -262,7 +262,7 @@ ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationChain_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index d9f9fb49..6f0edeea 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -190,7 +190,7 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], @@ -205,47 +205,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -267,8 +256,8 @@ "vectorStoreRetriever": "{{vectara_0.data.instance}}", "memory": "", "returnSourceDocuments": true, - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { @@ -427,9 +416,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json index 0547366a..d905b54b 100644 --- a/packages/server/marketplaces/chatflows/WebBrowser.json +++ b/packages/server/marketplaces/chatflows/WebBrowser.json @@ -578,7 +578,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index 9b1119b9..1b1d8de6 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -162,10 +162,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -177,47 +177,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -239,15 +228,15 @@ "vectorStoreRetriever": "{{pinecone_0.data.instance}}", "memory": "{{RedisBackedChatMemory_0.data.instance}}", "returnSourceDocuments": true, - "systemMessagePrompt": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given context. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Do not make up any information that is not in the context. Refuse to answer any question not about the info. Never break character.", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -589,7 +578,7 @@ "label": "Session Id", "name": "sessionId", "type": "string", - "description": "If not specified, the first CHAT_MESSAGE_ID will be used as sessionId", + "description": "If not specified, a random id will be used. Learn more", "default": "", "additionalParams": true, "optional": true, @@ -772,9 +761,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1ad4c795..8f5ab5db 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -20,7 +20,6 @@ import { ICredentialReturnResponse, chatType, IChatMessage, - IReactFlowEdge, IDepthQueue, INodeDirectedGraph } from './Interface' @@ -39,14 +38,14 @@ import { databaseEntities, transformToCredentialEntity, decryptCredentialData, - clearAllSessionMemory, replaceInputsWithConfig, getEncryptionKey, - checkMemorySessionId, - clearSessionMemoryFromViewMessageDialog, + getMemorySessionId, getUserHome, - replaceChatHistory, - getAllConnectedNodes + getSessionChatHistory, + getAllConnectedNodes, + clearSessionMemory, + findMemoryNode } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -532,17 +531,18 @@ export class App { const parsedFlowData: IReactFlowObject = JSON.parse(flowData) const nodes = parsedFlowData.nodes - if (isClearFromViewMessageDialog) { - await clearSessionMemoryFromViewMessageDialog( + try { + await clearSessionMemory( nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId, - memoryType + memoryType, + isClearFromViewMessageDialog ) - } else { - await clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId) + } catch (e) { + return res.status(500).send('Error clearing chat messages') } const deleteOptions: FindOptionsWhere = { chatflowid, chatId } @@ -1397,26 +1397,6 @@ export class App { return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) } - /** - * Method that find memory label that is connected within chatflow - * In a chatflow, there should only be 1 memory node - * @param {IReactFlowNode[]} nodes - * @param {IReactFlowEdge[]} edges - * @returns {string | undefined} - */ - findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined { - const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') - const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) - - for (const edge of edges) { - if (memoryNodeIds.includes(edge.source)) { - const memoryNode = nodes.find((node) => node.data.id === edge.source) - return memoryNode - } - } - return undefined - } - async upsertVector(req: Request, res: Response, isInternal: boolean = false) { try { const chatflowid = req.params.id @@ -1585,7 +1565,6 @@ export class App { * - Still in sync (i.e the flow has not been modified since) * - Existing overrideConfig and new overrideConfig are the same * - Flow doesn't start with/contain nodes that depend on incomingInput.question - * - Its not an Upsert request * TODO: convert overrideConfig to hash when we no longer store base64 string but filepath ***/ const isFlowReusable = () => { @@ -1639,22 +1618,28 @@ export class App { isStreamValid = isFlowValidForStream(nodes, endingNodeData) } - let chatHistory: IMessage[] | string = incomingInput.history + let chatHistory: IMessage[] = incomingInput.history ?? [] - // When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory + // When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory node for (const endingNode of endingNodes) { const endingNodeData = endingNode.data + if (!endingNodeData.inputs?.memory) continue - if ( - endingNodeData.inputs?.memory && - !incomingInput.history && - (incomingInput.chatId || incomingInput.overrideConfig?.sessionId) - ) { - const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') - const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) - if (memoryNode) { - chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) - } + + const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') + const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) + + if (!memoryNode) continue + + if (!chatHistory.length && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { + chatHistory = await getSessionChatHistory( + memoryNode, + this.nodesPool.componentNodes, + incomingInput, + this.AppDataSource, + databaseEntities, + logger + ) } } @@ -1713,16 +1698,11 @@ export class App { logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - let sessionId = undefined - if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) - - const memoryNode = this.findMemoryLabel(nodes, edges) + const memoryNode = findMemoryNode(nodes, edges) const memoryType = memoryNode?.data.label - let chatHistory: IMessage[] | string = incomingInput.history - if (memoryNode && !incomingInput.history && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { - chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) - } + let sessionId = undefined + if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string const nodeModule = await import(nodeInstanceFilePath) @@ -1730,24 +1710,24 @@ export class App { let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatId, chatflowid, - chatHistory, - socketIO, - socketIOClientId: incomingInput.socketIOClientId, + chatHistory: incomingInput.history, logger, appDataSource: this.AppDataSource, databaseEntities, analytic: chatflow.analytic, - chatId + socketIO, + socketIOClientId: incomingInput.socketIOClientId }) : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatId, chatflowid, - chatHistory, + chatHistory: incomingInput.history, logger, appDataSource: this.AppDataSource, databaseEntities, - analytic: chatflow.analytic, - chatId + analytic: chatflow.analytic }) result = typeof result === 'string' ? { text: result } : result diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e7a35c82..7569d541 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -26,7 +26,8 @@ import { getEncryptionKeyPath, ICommonObject, IDatabaseEntity, - IMessage + IMessage, + FlowiseMemory } from 'flowise-components' import { randomBytes } from 'crypto' import { AES, enc } from 'crypto-js' @@ -270,7 +271,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, - chatHistory: IMessage[] | string, + chatHistory: IMessage[], chatId: string, chatflowid: string, appDataSource: DataSource, @@ -317,9 +318,10 @@ export const buildLangchain = async ( await newNodeInstance.vectorStoreMethods!['upsert']!.call(newNodeInstance, reactFlowNodeData, { chatId, chatflowid, + chatHistory, + logger, appDataSource, databaseEntities, - logger, cachePool, dynamicVariables }) @@ -330,9 +332,10 @@ export const buildLangchain = async ( let outputResult = await newNodeInstance.init(reactFlowNodeData, question, { chatId, chatflowid, + chatHistory, + logger, appDataSource, databaseEntities, - logger, cachePool, dynamicVariables }) @@ -424,66 +427,52 @@ export const buildLangchain = async ( } /** - * Clear all session memories on the canvas - * @param {IReactFlowNode[]} reactFlowNodes - * @param {IComponentNodes} componentNodes - * @param {string} chatId - * @param {DataSource} appDataSource - * @param {string} sessionId - */ -export const clearAllSessionMemory = async ( - reactFlowNodes: IReactFlowNode[], - componentNodes: IComponentNodes, - chatId: string, - appDataSource: DataSource, - sessionId?: string -) => { - for (const node of reactFlowNodes) { - 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 (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { - await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) - } - } -} - -/** - * Clear specific session memory from View Message Dialog UI + * Clear session memories * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodes} componentNodes * @param {string} chatId * @param {DataSource} appDataSource * @param {string} sessionId * @param {string} memoryType + * @param {string} isClearFromViewMessageDialog */ -export const clearSessionMemoryFromViewMessageDialog = async ( +export const clearSessionMemory = async ( reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodes, chatId: string, appDataSource: DataSource, sessionId?: string, - memoryType?: string + memoryType?: string, + isClearFromViewMessageDialog?: string ) => { - if (!sessionId) return for (const node of reactFlowNodes) { if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue - if (memoryType && node.data.label !== memoryType) continue + + // Only clear specific session memory from View Message Dialog UI + if (isClearFromViewMessageDialog && 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() + const options: ICommonObject = { chatId, appDataSource, databaseEntities, logger } - if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - - if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { - await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) - return + // SessionId always take priority first because it is the sessionId used for 3rd party memory node + if (sessionId && node.data.inputs) { + if (node.data.type === 'OpenAIAssistant') { + await newNodeInstance.clearChatMessages(node.data, options, { type: 'threadId', id: sessionId }) + } else { + node.data.inputs.sessionId = sessionId + const initializedInstance: FlowiseMemory = await newNodeInstance.init(node.data, '', options) + await initializedInstance.clearChatMessages(sessionId) + } + } else if (chatId && node.data.inputs) { + if (node.data.type === 'OpenAIAssistant') { + await newNodeInstance.clearChatMessages(node.data, options, { type: 'chatId', id: chatId }) + } else { + node.data.inputs.sessionId = chatId + const initializedInstance: FlowiseMemory = await newNodeInstance.init(node.data, '', options) + await initializedInstance.clearChatMessages(chatId) + } } } } @@ -500,7 +489,7 @@ export const getVariableValue = ( paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] | string, + chatHistory: IMessage[], isAcceptVariable = false ) => { let returnVal = paramValue @@ -533,10 +522,7 @@ export const getVariableValue = ( } if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { - variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters( - typeof chatHistory === 'string' ? chatHistory : convertChatHistoryToText(chatHistory), - false - ) + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) } // Split by first occurrence of '.' to get just nodeId @@ -579,7 +565,7 @@ export const resolveVariables = ( reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] | string + chatHistory: IMessage[] ): INodeData => { let flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' @@ -966,21 +952,43 @@ export const redactCredentialWithPasswordType = ( } /** - * Replace sessionId with new chatId - * Ex: after clear chat history, use the new chatId as sessionId + * Get sessionId + * Hierarchy of sessionId (top down) + * API/Embed: + * (1) Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' } + * (2) Provided in API body - incomingInput.chatId + * + * API/Embed + UI: + * (3) Hard-coded sessionId in UI + * (4) Not specified on UI nor API, default to chatId * @param {any} instance + * @param {IncomingInput} incomingInput * @param {string} chatId */ -export const checkMemorySessionId = (instance: any, chatId: string): string | undefined => { - if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) { - instance.memory.sessionId = chatId - instance.memory.chatHistory.sessionId = chatId +export const getMemorySessionId = ( + memoryNode: IReactFlowNode, + incomingInput: IncomingInput, + chatId: string, + isInternal: boolean +): string | undefined => { + if (!isInternal) { + // Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' } + if (incomingInput.overrideConfig?.sessionId) { + return incomingInput.overrideConfig?.sessionId + } + // Provided in API body - incomingInput.chatId + if (incomingInput.chatId) { + return incomingInput.chatId + } } - if (instance.memory && instance.memory.sessionId) return instance.memory.sessionId - else if (instance.memory && instance.memory.chatHistory && instance.memory.chatHistory.sessionId) - return instance.memory.chatHistory.sessionId - return undefined + // Hard-coded sessionId in UI + if (memoryNode.data.inputs?.sessionId) { + return memoryNode.data.inputs.sessionId + } + + // Default chatId + return chatId } /** @@ -992,31 +1000,52 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un * @param {any} logger * @returns {string} */ -export const replaceChatHistory = async ( +export const getSessionChatHistory = async ( memoryNode: IReactFlowNode, + componentNodes: IComponentNodes, incomingInput: IncomingInput, appDataSource: DataSource, databaseEntities: IDatabaseEntity, logger: any -): Promise => { - const nodeInstanceFilePath = memoryNode.data.filePath as string +): Promise => { + const nodeInstanceFilePath = componentNodes[memoryNode.data.name].filePath as string const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() + // Replace memory's sessionId/chatId if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId + } else if (incomingInput.chatId && memoryNode.data.inputs) { + memoryNode.data.inputs.sessionId = incomingInput.chatId } - if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.getChatMessages) { - return await newNodeInstance.memoryMethods.getChatMessages(memoryNode.data, { - chatId: incomingInput.chatId, - appDataSource, - databaseEntities, - logger - }) - } + const initializedInstance: FlowiseMemory = await newNodeInstance.init(memoryNode.data, '', { + appDataSource, + databaseEntities, + logger + }) - return '' + return (await initializedInstance.getChatMessages()) as IMessage[] +} + +/** + * Method that find memory that is connected within chatflow + * In a chatflow, there should only be 1 memory node + * @param {IReactFlowNode[]} nodes + * @param {IReactFlowEdge[]} edges + * @returns {string | undefined} + */ +export const findMemoryNode = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined => { + const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') + const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) + + for (const edge of edges) { + if (memoryNodeIds.includes(edge.source)) { + const memoryNode = nodes.find((node) => node.data.id === edge.source) + return memoryNode + } + } + return undefined } /** diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 617d1066..a673d6b7 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -280,6 +280,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA style={{ display: 'flex', flexDirection: 'row', + alignItems: 'center', borderRadius: 10, background: 'rgb(254,252,191)', padding: 10, @@ -287,7 +288,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA marginBottom: 10 }} > - + {inputParam.warning} )} From 244093923d77f0b2a09d3ffbecb30d74777b5102 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 07:25:54 -0800 Subject: [PATCH 177/268] updates per PR comments --- .../components/nodes/chains/VectaraChain/VectaraChain.ts | 3 ++- packages/components/nodes/vectorstores/Vectara/Vectara.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index 986d587a..7d65c9cd 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -248,7 +248,8 @@ class VectaraChain_Chains implements INode { lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 } })) - const mmrRerankerId = 272725718 // Vectara reranker ID for MMR + // Vectara reranker ID for MMR (https://docs.vectara.com/docs/api-reference/search-apis/reranking#maximal-marginal-relevance-mmr-reranker) + const mmrRerankerId = 272725718 const mmrEnabled = vectaraFilter?.mmrConfig?.enabled const data = { diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index be63d582..df709e0b 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -110,7 +110,10 @@ class Vectara_VectorStores implements INode { { label: 'MMR diversity bias', name: 'mmrDiversityBias', - description: 'The diversity bias to use for MMR. Defaults to 0 (MMR disabled)', + description: + 'The diversity bias to use for MMR. This is a value between 0.0 and 1.0' + + 'Values closer to 1.0 optimize for the most diverse results.' + + 'Defaults to 0 (MMR disabled)', placeholder: '0.0', type: 'number', additionalParams: true, From bb77e3f591b6bb03b00ca7fc3901984ed9d8241f Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 8 Jan 2024 17:13:07 +0000 Subject: [PATCH 178/268] fix chatbot config --- packages/server/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1ad4c795..b6f59191 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -362,7 +362,8 @@ export class App { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ id: req.params.id }) - if (chatflow && chatflow.chatbotConfig) { + if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`) + if (chatflow.chatbotConfig) { try { const parsedConfig = JSON.parse(chatflow.chatbotConfig) return res.json(parsedConfig) @@ -370,7 +371,7 @@ export class App { return res.status(500).send(`Error parsing Chatbot Config for Chatflow ${req.params.id}`) } } - return res.status(404).send(`Chatbot Config for Chatflow ${req.params.id} not found`) + return res.status(200).send('OK') }) // Save chatflow From 78a6926ca3f049007bd9777316f6083aee733220 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 10:26:17 -0800 Subject: [PATCH 179/268] added step to diversityBias --- packages/components/nodes/vectorstores/Vectara/Vectara.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index df709e0b..45825b4f 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -110,6 +110,7 @@ class Vectara_VectorStores implements INode { { label: 'MMR diversity bias', name: 'mmrDiversityBias', + step: 0.1, description: 'The diversity bias to use for MMR. This is a value between 0.0 and 1.0' + 'Values closer to 1.0 optimize for the most diverse results.' + From 4622fd8a02a39517f264af63acc411f0b282d89c Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 8 Jan 2024 18:39:36 +0000 Subject: [PATCH 180/268] update self-host readme --- README.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 82e92832..3e6b7e56 100644 --- a/README.md +++ b/README.md @@ -145,37 +145,40 @@ Flowise support different environment variables to configure your instance. You ## 🌐 Self Host -### [Railway](https://docs.flowiseai.com/deployment/railway) +Deploy Flowise self-hosted in your existing infrastructure, we support various [deployments](https://docs.flowiseai.com/configuration/deployment) -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) +- [AWS](https://docs.flowiseai.com/deployment/aws) +- [Azure](https://docs.flowiseai.com/deployment/azure) +- [Digital Ocean](https://docs.flowiseai.com/deployment/digital-ocean) +- [GCP](https://docs.flowiseai.com/deployment/gcp) +-
    + Others -### [Render](https://docs.flowiseai.com/deployment/render) + - [Railway](https://docs.flowiseai.com/deployment/railway) -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) + [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) -### [Elestio](https://elest.io/open-source/flowiseai) + - [Render](https://docs.flowiseai.com/deployment/render) -[![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai) + [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) -### [RepoCloud](https://repocloud.io/details/?app_id=29) + - [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) -[![Deploy on RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=29) + HuggingFace Spaces -### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) + - [Elestio](https://elest.io/open-source/flowiseai) -HuggingFace Spaces + [![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai) -### Sealos + - [Sealos](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) -[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) + [![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) -### [AWS](https://docs.flowiseai.com/deployment/aws) + - [RepoCloud](https://repocloud.io/details/?app_id=29) -### [Azure](https://docs.flowiseai.com/deployment/azure) + [![Deploy on RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=29) -### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) - -### [GCP](https://docs.flowiseai.com/deployment/gcp) +
    ## 💻 Cloud Hosted From 07411a78a3378e1eac112c337c131fb11a44b9d6 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 8 Jan 2024 23:34:53 +0000 Subject: [PATCH 181/268] removed zapier nla credential --- .../credentials/ZapierNLAApi.credential.ts | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 packages/components/credentials/ZapierNLAApi.credential.ts diff --git a/packages/components/credentials/ZapierNLAApi.credential.ts b/packages/components/credentials/ZapierNLAApi.credential.ts deleted file mode 100644 index 72035660..00000000 --- a/packages/components/credentials/ZapierNLAApi.credential.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { INodeParams, INodeCredential } from '../src/Interface' - -class ZapierNLAApi implements INodeCredential { - label: string - name: string - version: number - description: string - inputs: INodeParams[] - - constructor() { - this.label = 'Zapier NLA API' - this.name = 'zapierNLAApi' - this.version = 1.0 - this.inputs = [ - { - label: 'Zapier NLA Api Key', - name: 'zapierNLAApiKey', - type: 'password' - } - ] - } -} - -module.exports = { credClass: ZapierNLAApi } From a26167ac5afff1df527f19d60e3b94c4ca539ac7 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 16:09:50 -0800 Subject: [PATCH 182/268] updated component to V2.0 Updated marketplace "Chain Upload" JSON file --- .../nodes/vectorstores/Vectara/Vectara.ts | 2 +- .../chatflows/Vectara LLM Chain Upload.json | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 45825b4f..939a4ac3 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -22,7 +22,7 @@ class Vectara_VectorStores implements INode { constructor() { this.label = 'Vectara' this.name = 'vectara' - this.version = 1.0 + this.version = 2.0 this.type = 'Vectara' this.icon = 'vectara.png' this.category = 'Vector Stores' diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index d9f9fb49..3f6fcda5 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -361,13 +361,36 @@ { "label": "Top K", "name": "topK", - "description": "Number of top results to fetch. Defaults to 4", - "placeholder": "4", + "description": "Number of top results to fetch. Defaults to 5", + "placeholder": "5", "type": "number", "additionalParams": true, "optional": true, "id": "vectara_0-input-topK-number" + }, + { + "label": "MMR K", + "name": "mmrK", + "description": "The number of results to rerank if MMR is enabled.", + "placeholder": "50", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectara_0-input-mmrK-number" + }, + { + "label": "MMR Diversity Bias", + "name": "mmrDiversityBias", + "step": 0.1, + "description": "Diversity Bias parameter for MMR, if enabled. 0.0 means no diversiry bias, 1.0 means maximum diversity bias. Defaults to 0.0 (MMR disabled).", + "placeholder": "0.0", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectara_0-input-mmrDiversityBias-number" } + + ], "inputAnchors": [ { @@ -385,7 +408,9 @@ "sentencesBefore": "", "sentencesAfter": "", "lambda": "", - "topK": "" + "topK": "", + "mmrK": "", + "mmrDiversityBias": "" }, "outputAnchors": [ { From b5bcfc0d5c32f0ed91b950479555fb93a0c5d087 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 16:46:36 -0800 Subject: [PATCH 183/268] after yarn lint-fix --- .../marketplaces/chatflows/Vectara LLM Chain Upload.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 3f6fcda5..33b93578 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -377,7 +377,7 @@ "additionalParams": true, "optional": true, "id": "vectara_0-input-mmrK-number" - }, + }, { "label": "MMR Diversity Bias", "name": "mmrDiversityBias", @@ -389,8 +389,6 @@ "optional": true, "id": "vectara_0-input-mmrDiversityBias-number" } - - ], "inputAnchors": [ { From 6ec1c9249b58e1a05c9bcb70c67aa8417bd6155a Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Mon, 8 Jan 2024 17:53:18 -0700 Subject: [PATCH 184/268] Revert model var to string, refactor for case without a key and just override if so --- CONTRIBUTING.md | 1 - .../nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts | 17 ++++++----------- packages/server/.env.example | 2 -- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c91906c..04cb80b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,7 +141,6 @@ Flowise support different environment variables to configure your instance. You | DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | -| LOCALAI_CHAT_MODELS | JSON-encoded string representing an array of chat models for LocalAI. Each object in the array should have a 'label' and 'name' property. | String | '[]' (Empty Array) | You can also specify the env variables when using `npx`. For example: diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index 258db1f8..c44f03ce 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -33,9 +33,6 @@ class ChatLocalAI_ChatModels implements INode { credentialNames: ['LocalAIApi'], optional: true } - - const modelOptions = JSON.parse(process.env.LOCALAI_CHAT_MODELS || '[]'); - this.inputs = [ { label: 'Cache', @@ -52,10 +49,8 @@ class ChatLocalAI_ChatModels implements INode { { label: 'Model Name', name: 'modelName', - type: 'options', - options: modelOptions, - default: modelOptions.length > 0 ? modelOptions[0].name : '', - optional: true + type: 'string', + placeholder: 'gpt4all-lora-quantized.bin' }, { label: 'Temperature', @@ -99,22 +94,22 @@ class ChatLocalAI_ChatModels implements INode { const topP = nodeData.inputs?.topP as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string - const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const openAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) + const localAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) const cache = nodeData.inputs?.cache as BaseCache - const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { + const obj: Partial & BaseLLMParams & { localAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, - openAIApiKey + openAIApiKey: 'sk-' } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseFloat(topP) if (timeout) obj.timeout = parseInt(timeout, 10) if (cache) obj.cache = cache + if (localAIApiKey) obj.openAIApiKey = localAIApiKey const model = new OpenAIChat(obj, { basePath }) diff --git a/packages/server/.env.example b/packages/server/.env.example index 9b7be0ff..6e746a4d 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -26,5 +26,3 @@ PORT=3000 # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_PROJECT=your_project - -# LOCALAI_CHAT_MODELS='[{"label": "model1", "name": "model1"}, {"label": "model2", "name": "model2"}]' From 6a114b3717206e742187593a0ddb0ba5f964ae58 Mon Sep 17 00:00:00 2001 From: Carson Yang Date: Tue, 9 Jan 2024 13:58:40 +0800 Subject: [PATCH 185/268] Update README-ZH.md --- README-ZH.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README-ZH.md b/README-ZH.md index 2805ef9b..208eee92 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -153,6 +153,10 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package [![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) +### [Sealos](https://docs.flowiseai.com/configuration/deployment/sealos) + +[![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://template.cloud.sealos.io/deploy?templateName=flowise) + ### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) HuggingFace Spaces From 1dc96906966394cd999a8b69676076274c9a38d3 Mon Sep 17 00:00:00 2001 From: YISH Date: Wed, 10 Jan 2024 17:32:46 +0800 Subject: [PATCH 186/268] Add milvusTextField configuration for Milvus langchain python use `text` https://github.com/langchain-ai/langchain/blob/master/libs/community/langchain_community/vectorstores/milvus.py#L119 while langchian js use `langchain` https://github.com/langchain-ai/langchainjs/blob/main/libs/langchain-community/src/vectorstores/milvus.ts#L61 so it is necessary to add milvusTextField configuration for Milvus. --- .../components/nodes/vectorstores/Milvus/Milvus.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/vectorstores/Milvus/Milvus.ts b/packages/components/nodes/vectorstores/Milvus/Milvus.ts index 090f35f7..7566f8a8 100644 --- a/packages/components/nodes/vectorstores/Milvus/Milvus.ts +++ b/packages/components/nodes/vectorstores/Milvus/Milvus.ts @@ -65,6 +65,14 @@ class Milvus_VectorStores implements INode { name: 'milvusCollection', type: 'string' }, + { + label: 'Milvus Text Field', + name: 'milvusTextField', + type: 'string', + placeholder: 'langchain_text', + optional: true, + additionalParams: true + }, { label: 'Milvus Filter', name: 'milvusFilter', @@ -150,6 +158,7 @@ class Milvus_VectorStores implements INode { const address = nodeData.inputs?.milvusServerUrl as string const collectionName = nodeData.inputs?.milvusCollection as string const milvusFilter = nodeData.inputs?.milvusFilter as string + const textField = nodeData.inputs?.milvusTextField as string // embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings @@ -169,7 +178,8 @@ class Milvus_VectorStores implements INode { // init MilvusLibArgs const milVusArgs: MilvusLibArgs = { url: address, - collectionName: collectionName + collectionName: collectionName, + textField: textField } if (milvusUser) milVusArgs.username = milvusUser From be0fff4d9d9cc2f910fad491b7af07549f52e82a Mon Sep 17 00:00:00 2001 From: YISH Date: Wed, 10 Jan 2024 17:41:53 +0800 Subject: [PATCH 187/268] Fix OpenAIFunctionAgent that function not return string result refer to https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/agents/format_scratchpad/openai_functions.py#L29 and fix the role of systemMessage from `ai` to `system`. --- .../OpenAIFunctionAgent/OpenAIFunctionAgent.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index c0095cee..ac000c1e 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -109,9 +109,18 @@ class OpenAIFunctionAgent_Agents implements INode { const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => steps.flatMap(({ action, observation }) => { + const create_function_message = (observation: string, action: AgentAction) => { + let content: string + if (typeof observation !== 'string') { + content = JSON.stringify(observation) + } else { + content = observation + } + return new FunctionMessage(content, action.tool) + } if ('messageLog' in action && action.messageLog !== undefined) { const log = action.messageLog as BaseMessage[] - return log.concat(new FunctionMessage(observation, action.tool)) + return log.concat(create_function_message(observation, action)) } else { return [new AIMessage(action.log)] } @@ -127,7 +136,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => { const inputKey = memory.inputKey ? memory.inputKey : 'input' const prompt = ChatPromptTemplate.fromMessages([ - ['ai', systemMessage ? systemMessage : `You are a helpful AI assistant.`], + ['system', systemMessage ? systemMessage : `You are a helpful AI assistant.`], new MessagesPlaceholder(memoryKey), ['human', `{${inputKey}}`], new MessagesPlaceholder('agent_scratchpad') From 3bc52426bace3a79613e4b4587ad7961ac551cd4 Mon Sep 17 00:00:00 2001 From: Joshua Carter Date: Wed, 10 Jan 2024 12:30:01 -0800 Subject: [PATCH 188/268] Correct DockerHub link in docs from private to public repo page --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index d3ad1c19..11b29cf3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,6 +1,6 @@ # Flowise Docker Hub Image -Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/flowiseai/flowise/general) +Starts Flowise from [DockerHub Image](https://hub.docker.com/r/flowiseai/flowise) ## Usage From d70c097341fcee2f8d9210c70d74402b84f4e829 Mon Sep 17 00:00:00 2001 From: Ilango Date: Thu, 11 Jan 2024 14:37:10 +0530 Subject: [PATCH 189/268] Add a sticky note node under tools --- .../nodes/tools/StickyNotes/StickyNote.ts | 42 ++++++ .../nodes/tools/StickyNotes/stickyNote.svg | 5 + packages/ui/src/views/canvas/CanvasNode.js | 120 +++++++++--------- 3 files changed, 110 insertions(+), 57 deletions(-) create mode 100644 packages/components/nodes/tools/StickyNotes/StickyNote.ts create mode 100644 packages/components/nodes/tools/StickyNotes/stickyNote.svg diff --git a/packages/components/nodes/tools/StickyNotes/StickyNote.ts b/packages/components/nodes/tools/StickyNotes/StickyNote.ts new file mode 100644 index 00000000..0ae980a0 --- /dev/null +++ b/packages/components/nodes/tools/StickyNotes/StickyNote.ts @@ -0,0 +1,42 @@ +import { INode, INodeOutputsValue, INodeParams } from '../../../src/Interface' + +class StickyNote implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + disableOutput: boolean + + constructor() { + this.label = 'Sticky Note' + this.name = 'stickyNote' + this.version = 1.0 + this.type = 'StickyNote' + this.icon = 'stickyNote.svg' + this.category = 'Tools' + this.description = 'Add a note about a node' + this.inputs = [ + { + label: 'Note', + name: 'note', + type: 'string', + rows: 4, + placeholder: 'Input your notes', + optional: true + } + ] + this.disableOutput = true + this.baseClasses = [this.type] + } + + async init(): Promise { + return new StickyNote() + } +} + +module.exports = { nodeClass: StickyNote } diff --git a/packages/components/nodes/tools/StickyNotes/stickyNote.svg b/packages/components/nodes/tools/StickyNotes/stickyNote.svg new file mode 100644 index 00000000..81c45058 --- /dev/null +++ b/packages/components/nodes/tools/StickyNotes/stickyNote.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/packages/ui/src/views/canvas/CanvasNode.js b/packages/ui/src/views/canvas/CanvasNode.js index e52de640..a039f6d1 100644 --- a/packages/ui/src/views/canvas/CanvasNode.js +++ b/packages/ui/src/views/canvas/CanvasNode.js @@ -54,6 +54,7 @@ const CanvasNode = ({ data }) => { const [infoDialogProps, setInfoDialogProps] = useState({}) const [warningMessage, setWarningMessage] = useState('') const [open, setOpen] = useState(false) + const isNote = data.type === 'StickyNote' const handleClose = () => { setOpen(false) @@ -150,47 +151,49 @@ const CanvasNode = ({ data }) => { placement='right-start' > -
    - -
    - Notification -
    -
    - - - {data.label} - - - {warningMessage && ( - <> -
    - {warningMessage}} placement='top'> - - - - - - )} -
    - {(data.inputAnchors.length > 0 || data.inputParams.length > 0) && ( + {!isNote && ( +
    + +
    + Notification +
    +
    + + + {data.label} + + + {warningMessage && ( + <> +
    + {warningMessage}} placement='top'> + + + + + + )} +
    + )} + {(data.inputAnchors.length > 0 || data.inputParams.length > 0) && !isNote && ( <> @@ -230,22 +233,25 @@ const CanvasNode = ({ data }) => { )} - - - - Output - - - - - {data.outputAnchors.map((outputAnchor, index) => ( - - ))} + {!isNote && ( + <> + + + + Output + + + + {data.outputAnchors.map((outputAnchor, index) => ( + + ))} + + )} From 22ebf95a598323e70b9ec6ddf6b7e2f25c7016df Mon Sep 17 00:00:00 2001 From: Ilango Date: Thu, 11 Jan 2024 21:15:29 +0530 Subject: [PATCH 190/268] Rename StickyNote node and move to utilities category --- .../{tools/StickyNotes => utilities/StickyNote}/StickyNote.ts | 4 ++-- .../StickyNotes => utilities/StickyNote}/stickyNote.svg | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/components/nodes/{tools/StickyNotes => utilities/StickyNote}/StickyNote.ts (89%) rename packages/components/nodes/{tools/StickyNotes => utilities/StickyNote}/stickyNote.svg (100%) diff --git a/packages/components/nodes/tools/StickyNotes/StickyNote.ts b/packages/components/nodes/utilities/StickyNote/StickyNote.ts similarity index 89% rename from packages/components/nodes/tools/StickyNotes/StickyNote.ts rename to packages/components/nodes/utilities/StickyNote/StickyNote.ts index 0ae980a0..270e2524 100644 --- a/packages/components/nodes/tools/StickyNotes/StickyNote.ts +++ b/packages/components/nodes/utilities/StickyNote/StickyNote.ts @@ -1,4 +1,4 @@ -import { INode, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { INode, INodeParams } from '../../../src/Interface' class StickyNote implements INode { label: string @@ -18,7 +18,7 @@ class StickyNote implements INode { this.version = 1.0 this.type = 'StickyNote' this.icon = 'stickyNote.svg' - this.category = 'Tools' + this.category = 'Utilities' this.description = 'Add a note about a node' this.inputs = [ { diff --git a/packages/components/nodes/tools/StickyNotes/stickyNote.svg b/packages/components/nodes/utilities/StickyNote/stickyNote.svg similarity index 100% rename from packages/components/nodes/tools/StickyNotes/stickyNote.svg rename to packages/components/nodes/utilities/StickyNote/stickyNote.svg From 6462ba0faeca7411de33ac3c384f2fe80f0a498e Mon Sep 17 00:00:00 2001 From: Ilango Date: Thu, 11 Jan 2024 21:20:47 +0530 Subject: [PATCH 191/268] Add todo for category type in INode --- packages/components/src/Interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 2a625ff6..9da26f82 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -90,7 +90,7 @@ export interface INodeProperties { type: string icon: string version: number - category: string + category: string // TODO: use enum instead of string baseClasses: string[] description?: string filePath?: string From e634a6b584b9d01381b5d31eeab68445a78763dd Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 00:31:21 +0500 Subject: [PATCH 192/268] feature: Integrate Astra Vectorstore --- .../credentials/AstraApi.credential.ts | 34 ++++ .../nodes/vectorstores/Astra/Astra.ts | 190 ++++++++++++++++++ .../nodes/vectorstores/Astra/astra.svg | 1 + packages/components/package.json | 2 + 4 files changed, 227 insertions(+) create mode 100644 packages/components/credentials/AstraApi.credential.ts create mode 100644 packages/components/nodes/vectorstores/Astra/Astra.ts create mode 100644 packages/components/nodes/vectorstores/Astra/astra.svg diff --git a/packages/components/credentials/AstraApi.credential.ts b/packages/components/credentials/AstraApi.credential.ts new file mode 100644 index 00000000..ad4c65a8 --- /dev/null +++ b/packages/components/credentials/AstraApi.credential.ts @@ -0,0 +1,34 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class AstraApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Astra API' + this.name = 'AstraApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Colection Name', + name: 'collectionName', + type: 'string' + }, + { + label: 'Astra DB Application Token', + name: 'applicationToken', + type: 'password' + }, + { + label: 'Astra DB Api Endpoint', + name: 'dbEndPoint', + type: 'string' + } + ] + } +} + +module.exports = { credClass: AstraApi } diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts new file mode 100644 index 00000000..648a8b49 --- /dev/null +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -0,0 +1,190 @@ +import { flatten } from 'lodash' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData } from '../../../src/utils' +import { AstraDBVectorStore, AstraLibArgs } from '@langchain/community/vectorstores/astradb' + +class Astra_VectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + badge: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Astra' + this.name = 'Astra' + this.version = 1.0 + this.type = 'Astra' + this.icon = 'astra.svg' + this.category = 'Vector Stores' + this.description = `Upsert embedded data and perform similarity search upon query using DataStax Astra DB, a serverless vector database that’s perfect for managing mission-critical AI workloads` + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.badge = 'NEW' + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['AstraApi'] + } + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true, + optional: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Vector Dimension', + name: 'vectorDimension', + type: 'number', + placeholder: '1536', + optional: true, + description: 'Dimension used for storing vector embedding' + }, + { + label: 'Similarity Metric', + name: 'similarityMetric', + type: 'string', + placeholder: 'cosine', + optional: true, + description: 'cosine | euclidean | dot_product' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Astra Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Astra Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(AstraDBVectorStore)] + } + ] + } + + //@ts-ignore + vectorStoreMethods = { + async upsert(nodeData: INodeData, options: ICommonObject): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const vectorDimension = nodeData.inputs?.vectorDimension as number + const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + + const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product'] + if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) { + throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`) + } + + const clientConfig = { + token: credentialData?.applicationToken ?? 'dummy', + endpoint: credentialData?.dbEndPoint ?? 'dummy' + } + + const astraConfig: AstraLibArgs = { + ...clientConfig, + collection: credentialData.collectionName ?? 'flowise_test', + collectionOptions: { + vector: { + dimension: vectorDimension ?? 1536, + metric: similarityMetric ?? 'cosine' + } + } + } + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + + try { + await AstraDBVectorStore.fromDocuments(finalDocs, embeddings, astraConfig) + } catch (e) { + throw new Error(e) + } + } + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const vectorDimension = nodeData.inputs?.vectorDimension as number + const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + + const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product'] + if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) { + throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`) + } + + const clientConfig = { + token: credentialData?.applicationToken ?? 'dummy', + endpoint: credentialData?.dbEndPoint ?? 'dummy' + } + + const astraConfig: AstraLibArgs = { + ...clientConfig, + collection: credentialData.collectionName ?? 'flowise_test', + collectionOptions: { + vector: { + dimension: vectorDimension ?? 1536, + metric: similarityMetric ?? 'cosine' + } + } + } + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + + const vectorStore = await AstraDBVectorStore.fromExistingIndex(embeddings, astraConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Astra_VectorStores } diff --git a/packages/components/nodes/vectorstores/Astra/astra.svg b/packages/components/nodes/vectorstores/Astra/astra.svg new file mode 100644 index 00000000..59c2fc3f --- /dev/null +++ b/packages/components/nodes/vectorstores/Astra/astra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index a2565430..07b2c3df 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -19,6 +19,7 @@ "@aws-sdk/client-bedrock-runtime": "3.422.0", "@aws-sdk/client-dynamodb": "^3.360.0", "@aws-sdk/client-s3": "^3.427.0", + "@datastax/astra-db-ts": "^0.1.2", "@dqbd/tiktoken": "^1.0.7", "@elastic/elasticsearch": "^8.9.0", "@getzep/zep-js": "^0.9.0", @@ -26,6 +27,7 @@ "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", + "@langchain/community": "^0.0.16", "@langchain/google-genai": "^0.0.6", "@langchain/mistralai": "^0.0.6", "@notionhq/client": "^2.2.8", From 8a470a85b77499c2ad4ff5773b508b7d9e99aefe Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 01:36:57 +0500 Subject: [PATCH 193/268] chore: refactoring (naming convention) --- packages/components/credentials/AstraApi.credential.ts | 10 +++++----- packages/components/nodes/vectorstores/Astra/Astra.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/credentials/AstraApi.credential.ts b/packages/components/credentials/AstraApi.credential.ts index ad4c65a8..3bec1a49 100644 --- a/packages/components/credentials/AstraApi.credential.ts +++ b/packages/components/credentials/AstraApi.credential.ts @@ -1,6 +1,6 @@ import { INodeParams, INodeCredential } from '../src/Interface' -class AstraApi implements INodeCredential { +class AstraDBApi implements INodeCredential { label: string name: string version: number @@ -8,12 +8,12 @@ class AstraApi implements INodeCredential { inputs: INodeParams[] constructor() { - this.label = 'Astra API' - this.name = 'AstraApi' + this.label = 'Astra DB API' + this.name = 'AstraDBApi' this.version = 1.0 this.inputs = [ { - label: 'Colection Name', + label: 'Collection Name', name: 'collectionName', type: 'string' }, @@ -31,4 +31,4 @@ class AstraApi implements INodeCredential { } } -module.exports = { credClass: AstraApi } +module.exports = { credClass: AstraDBApi } diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts index 648a8b49..e3377cb5 100644 --- a/packages/components/nodes/vectorstores/Astra/Astra.ts +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -33,7 +33,7 @@ class Astra_VectorStores implements INode { label: 'Connect Credential', name: 'credential', type: 'credential', - credentialNames: ['AstraApi'] + credentialNames: ['AstraDBApi'] } this.inputs = [ { From e2365ff22000b1942bf1523ac58136afc46873af Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 01:43:22 +0500 Subject: [PATCH 194/268] Update AstraApi.credential.ts --- packages/components/credentials/AstraApi.credential.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/credentials/AstraApi.credential.ts b/packages/components/credentials/AstraApi.credential.ts index 3bec1a49..a89a259f 100644 --- a/packages/components/credentials/AstraApi.credential.ts +++ b/packages/components/credentials/AstraApi.credential.ts @@ -13,7 +13,7 @@ class AstraDBApi implements INodeCredential { this.version = 1.0 this.inputs = [ { - label: 'Collection Name', + label: 'Astra DB Collection Name', name: 'collectionName', type: 'string' }, From 9aaa4313cbddac3bf66a977ba4e7ee11d1b22dbd Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 02:43:43 +0500 Subject: [PATCH 195/268] svg added and refactored again --- .../components/nodes/vectorstores/Astra/Astra.ts | 8 ++++---- .../components/nodes/vectorstores/Astra/astra.svg | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts index e3377cb5..865f1044 100644 --- a/packages/components/nodes/vectorstores/Astra/Astra.ts +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -103,8 +103,8 @@ class Astra_VectorStores implements INode { } const clientConfig = { - token: credentialData?.applicationToken ?? 'dummy', - endpoint: credentialData?.dbEndPoint ?? 'dummy' + token: credentialData?.applicationToken, + endpoint: credentialData?.dbEndPoint } const astraConfig: AstraLibArgs = { @@ -151,8 +151,8 @@ class Astra_VectorStores implements INode { } const clientConfig = { - token: credentialData?.applicationToken ?? 'dummy', - endpoint: credentialData?.dbEndPoint ?? 'dummy' + token: credentialData?.applicationToken, + endpoint: credentialData?.dbEndPoint } const astraConfig: AstraLibArgs = { diff --git a/packages/components/nodes/vectorstores/Astra/astra.svg b/packages/components/nodes/vectorstores/Astra/astra.svg index 59c2fc3f..de58397d 100644 --- a/packages/components/nodes/vectorstores/Astra/astra.svg +++ b/packages/components/nodes/vectorstores/Astra/astra.svg @@ -1 +1,12 @@ - \ No newline at end of file + + + + + + + + + + + + From 42f80fade23c0799e311c13f9ff482306d13a7c2 Mon Sep 17 00:00:00 2001 From: Ilango Date: Fri, 12 Jan 2024 13:34:52 +0530 Subject: [PATCH 196/268] Add new node type for canvas - sticky note --- packages/ui/src/views/canvas/CardWrapper.js | 21 ++++ packages/ui/src/views/canvas/LightTooltip.js | 12 +++ packages/ui/src/views/canvas/StickyNote.js | 105 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 packages/ui/src/views/canvas/CardWrapper.js create mode 100644 packages/ui/src/views/canvas/LightTooltip.js create mode 100644 packages/ui/src/views/canvas/StickyNote.js diff --git a/packages/ui/src/views/canvas/CardWrapper.js b/packages/ui/src/views/canvas/CardWrapper.js new file mode 100644 index 00000000..3e010899 --- /dev/null +++ b/packages/ui/src/views/canvas/CardWrapper.js @@ -0,0 +1,21 @@ +// material-ui +import { styled } from '@mui/material/styles' + +// project imports +import MainCard from '../../ui-component/cards/MainCard' + +const CardWrapper = styled(MainCard)(({ theme }) => ({ + background: theme.palette.card.main, + color: theme.darkTextPrimary, + border: 'solid 1px', + borderColor: theme.palette.primary[200] + 75, + width: '300px', + height: 'auto', + padding: '10px', + boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)', + '&:hover': { + borderColor: theme.palette.primary.main + } +})) + +export default CardWrapper diff --git a/packages/ui/src/views/canvas/LightTooltip.js b/packages/ui/src/views/canvas/LightTooltip.js new file mode 100644 index 00000000..32e34aae --- /dev/null +++ b/packages/ui/src/views/canvas/LightTooltip.js @@ -0,0 +1,12 @@ +import { styled } from '@mui/material/styles' +import Tooltip, { tooltipClasses } from '@mui/material/Tooltip' + +const LightTooltip = styled(({ className, ...props }) => )(({ theme }) => ({ + [`& .${tooltipClasses.tooltip}`]: { + backgroundColor: theme.palette.nodeToolTip.background, + color: theme.palette.nodeToolTip.color, + boxShadow: theme.shadows[1] + } +})) + +export default LightTooltip diff --git a/packages/ui/src/views/canvas/StickyNote.js b/packages/ui/src/views/canvas/StickyNote.js new file mode 100644 index 00000000..53406513 --- /dev/null +++ b/packages/ui/src/views/canvas/StickyNote.js @@ -0,0 +1,105 @@ +import PropTypes from 'prop-types' +import { useContext, useState } from 'react' +import { useSelector } from 'react-redux' + +// material-ui +import { useTheme } from '@mui/material/styles' + +// project imports +import CardWrapper from './CardWrapper' +import LightTooltip from './LightTooltip' +import { IconButton, Box } from '@mui/material' +import { IconCopy, IconTrash } from '@tabler/icons' +import { Input } from 'ui-component/input/Input' + +// const +import { flowContext } from '../../store/context/ReactFlowContext' + +const StickyNote = ({ data }) => { + const theme = useTheme() + const canvas = useSelector((state) => state.canvas) + const { deleteNode, duplicateNode } = useContext(flowContext) + const [inputParam] = data.inputParams + + const [open, setOpen] = useState(false) + + const handleClose = () => { + setOpen(false) + } + + const handleOpen = () => { + setOpen(true) + } + + console.log(data.id) + + return ( + <> + + + { + duplicateNode(data.id) + }} + sx={{ height: '35px', width: '35px', '&:hover': { color: theme?.palette.primary.main } }} + color={theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit'} + > + + + { + deleteNode(data.id) + }} + sx={{ height: '35px', width: '35px', '&:hover': { color: 'red' } }} + color={theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit'} + > + + + + } + placement='right-start' + > + + (data.inputs[inputParam.name] = newValue)} + value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} + nodes={inputParam?.acceptVariable && reactFlowInstance ? reactFlowInstance.getNodes() : []} + edges={inputParam?.acceptVariable && reactFlowInstance ? reactFlowInstance.getEdges() : []} + nodeId={data.id} + /> + + + + + ) +} + +StickyNote.propTypes = { + data: PropTypes.object +} + +export default StickyNote From 1544f6140784a4f6e96cbb106da4e765733bdb6f Mon Sep 17 00:00:00 2001 From: Ilango Date: Fri, 12 Jan 2024 13:41:58 +0530 Subject: [PATCH 197/268] Refactor CanvasNode to use the new sticky node --- packages/ui/src/views/canvas/CanvasNode.js | 148 +++++++++------------ packages/ui/src/views/canvas/index.js | 5 +- 2 files changed, 63 insertions(+), 90 deletions(-) diff --git a/packages/ui/src/views/canvas/CanvasNode.js b/packages/ui/src/views/canvas/CanvasNode.js index a039f6d1..043baaea 100644 --- a/packages/ui/src/views/canvas/CanvasNode.js +++ b/packages/ui/src/views/canvas/CanvasNode.js @@ -3,12 +3,13 @@ import { useContext, useState, useEffect } from 'react' import { useSelector } from 'react-redux' // material-ui -import { styled, useTheme } from '@mui/material/styles' +import { useTheme } from '@mui/material/styles' import { IconButton, Box, Typography, Divider, Button } from '@mui/material' -import Tooltip, { tooltipClasses } from '@mui/material/Tooltip' +import Tooltip from '@mui/material/Tooltip' // project imports -import MainCard from 'ui-component/cards/MainCard' +import CardWrapper from './CardWrapper' +import LightTooltip from './LightTooltip' import NodeInputHandler from './NodeInputHandler' import NodeOutputHandler from './NodeOutputHandler' import AdditionalParamsDialog from 'ui-component/dialog/AdditionalParamsDialog' @@ -19,28 +20,6 @@ import { baseURL } from 'store/constant' import { IconTrash, IconCopy, IconInfoCircle, IconAlertTriangle } from '@tabler/icons' import { flowContext } from 'store/context/ReactFlowContext' -const CardWrapper = styled(MainCard)(({ theme }) => ({ - background: theme.palette.card.main, - color: theme.darkTextPrimary, - border: 'solid 1px', - borderColor: theme.palette.primary[200] + 75, - width: '300px', - height: 'auto', - padding: '10px', - boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)', - '&:hover': { - borderColor: theme.palette.primary.main - } -})) - -const LightTooltip = styled(({ className, ...props }) => )(({ theme }) => ({ - [`& .${tooltipClasses.tooltip}`]: { - backgroundColor: theme.palette.nodeToolTip.background, - color: theme.palette.nodeToolTip.color, - boxShadow: theme.shadows[1] - } -})) - // ===========================|| CANVAS NODE ||=========================== // const CanvasNode = ({ data }) => { @@ -54,7 +33,6 @@ const CanvasNode = ({ data }) => { const [infoDialogProps, setInfoDialogProps] = useState({}) const [warningMessage, setWarningMessage] = useState('') const [open, setOpen] = useState(false) - const isNote = data.type === 'StickyNote' const handleClose = () => { setOpen(false) @@ -151,49 +129,47 @@ const CanvasNode = ({ data }) => { placement='right-start' > - {!isNote && ( -
    - -
    - Notification -
    -
    - - - {data.label} - - - {warningMessage && ( - <> -
    - {warningMessage}} placement='top'> - - - - - - )} -
    - )} - {(data.inputAnchors.length > 0 || data.inputParams.length > 0) && !isNote && ( +
    + +
    + Notification +
    +
    + + + {data.label} + + + {warningMessage && ( + <> +
    + {warningMessage}} placement='top'> + + + + + + )} +
    + {(data.inputAnchors.length > 0 || data.inputParams.length > 0) && ( <> @@ -233,25 +209,21 @@ const CanvasNode = ({ data }) => { )} - {!isNote && ( - <> - - - - Output - - - - {data.outputAnchors.map((outputAnchor, index) => ( - - ))} - - )} + + + + Output + + + + {data.outputAnchors.map((outputAnchor, index) => ( + + ))} diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 9aa53cc6..29f83ea4 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -21,6 +21,7 @@ import { useTheme } from '@mui/material/styles' // project imports import CanvasNode from './CanvasNode' import ButtonEdge from './ButtonEdge' +import StickyNote from './StickyNote' import CanvasHeader from './CanvasHeader' import AddNodes from './AddNodes' import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' @@ -46,7 +47,7 @@ import useNotifier from 'utils/useNotifier' // const import { FLOWISE_CREDENTIAL_ID } from 'store/constant' -const nodeTypes = { customNode: CanvasNode } +const nodeTypes = { customNode: CanvasNode, stickyNote: StickyNote } const edgeTypes = { buttonedge: ButtonEdge } // ==============================|| CANVAS ||============================== // @@ -276,7 +277,7 @@ const Canvas = () => { const newNode = { id: newNodeId, position, - type: 'customNode', + type: nodeData.type !== 'StickyNote' ? 'customNode' : 'stickyNote', data: initNode(nodeData, newNodeId) } From 951fda75c8255e7f306925d1572f5771dc132c4d Mon Sep 17 00:00:00 2001 From: Ilango Date: Fri, 12 Jan 2024 13:44:24 +0530 Subject: [PATCH 198/268] Update node input component --- packages/ui/src/ui-component/input/Input.js | 80 ++++++++++++++------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 3e575938..b8434ff2 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from 'react' import PropTypes from 'prop-types' -import { FormControl, OutlinedInput, Popover } from '@mui/material' +import { FormControl, OutlinedInput, InputBase, Popover } from '@mui/material' import SelectVariable from 'ui-component/json/SelectVariable' import { getAvailableNodesForVariable } from 'utils/genericHelper' @@ -50,29 +50,61 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disab return ( <> - - { - setMyValue(e.target.value) - onChange(e.target.value) - }} - inputProps={{ - step: inputParam.step ?? 1, - style: { - height: inputParam.rows ? '90px' : 'inherit' - } - }} - /> - + {inputParam.name === 'note' ? ( + + { + setMyValue(e.target.value) + onChange(e.target.value) + }} + inputProps={{ + step: inputParam.step ?? 1, + style: { + border: 'none', + background: 'none' + } + }} + sx={{ + border: 'none', + background: 'none', + padding: '10px 14px' + }} + /> + + ) : ( + + { + setMyValue(e.target.value) + onChange(e.target.value) + }} + inputProps={{ + step: inputParam.step ?? 1, + style: { + height: inputParam.rows ? '90px' : 'inherit' + } + }} + /> + + )}
    {inputParam?.acceptVariable && ( Date: Fri, 12 Jan 2024 13:45:06 +0530 Subject: [PATCH 199/268] Update sticky note node configuration --- .../nodes/utilities/StickyNote/StickyNote.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/utilities/StickyNote/StickyNote.ts b/packages/components/nodes/utilities/StickyNote/StickyNote.ts index 270e2524..8b0ec208 100644 --- a/packages/components/nodes/utilities/StickyNote/StickyNote.ts +++ b/packages/components/nodes/utilities/StickyNote/StickyNote.ts @@ -10,7 +10,6 @@ class StickyNote implements INode { category: string baseClasses: string[] inputs: INodeParams[] - disableOutput: boolean constructor() { this.label = 'Sticky Note' @@ -19,18 +18,17 @@ class StickyNote implements INode { this.type = 'StickyNote' this.icon = 'stickyNote.svg' this.category = 'Utilities' - this.description = 'Add a note about a node' + this.description = 'Add a sticky note' this.inputs = [ { - label: 'Note', + label: '', name: 'note', type: 'string', - rows: 4, - placeholder: 'Input your notes', + rows: 1, + placeholder: 'Type something here', optional: true } ] - this.disableOutput = true this.baseClasses = [this.type] } From 2742a26c6ab6eac830868065c3b81b0dfb91fc26 Mon Sep 17 00:00:00 2001 From: Ilango Date: Fri, 12 Jan 2024 14:08:55 +0530 Subject: [PATCH 200/268] Fix console error and remove console.log statements --- packages/ui/src/ui-component/input/Input.js | 2 +- packages/ui/src/views/canvas/StickyNote.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index b8434ff2..b7a1912d 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -53,7 +53,7 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disab {inputParam.name === 'note' ? ( { setOpen(true) } - console.log(data.id) - return ( <> Date: Fri, 12 Jan 2024 16:50:47 +0530 Subject: [PATCH 201/268] Fix sticky note text color in dark mode --- packages/ui/src/ui-component/input/Input.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index b7a1912d..806ab53d 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -70,7 +70,8 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disab step: inputParam.step ?? 1, style: { border: 'none', - background: 'none' + background: 'none', + color: '#212121' } }} sx={{ From e104af43463f66c7266d2eec4d9b1a1b336cc19f Mon Sep 17 00:00:00 2001 From: Ilango Date: Fri, 12 Jan 2024 17:09:36 +0530 Subject: [PATCH 202/268] Fix placeholder color for sticky note node --- packages/ui/src/ui-component/input/Input.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 806ab53d..e59f012c 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -77,7 +77,12 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disab sx={{ border: 'none', background: 'none', - padding: '10px 14px' + padding: '10px 14px', + textarea: { + '&::placeholder': { + color: '#616161' + } + } }} /> From c15250f28be3995ac3147c1a57e5b2811aae4d2e Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sat, 13 Jan 2024 16:18:35 +0800 Subject: [PATCH 203/268] add TopK and safetySettings --- .../ChatGoogleGenerativeAI.ts | 87 ++++++++++++++++++- packages/components/src/utils.ts | 17 ++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 546fa224..311b4e8d 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { convertStringToArrayString, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { BaseCache } from 'langchain/schema' import { ChatGoogleGenerativeAI } from '@langchain/google-genai' +import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai' class GoogleGenerativeAI_ChatModels implements INode { label: string @@ -74,6 +75,72 @@ class GoogleGenerativeAI_ChatModels implements INode { step: 0.1, optional: true, additionalParams: true + }, + { + label: 'topK', + name: 'topK', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Harm Category', + name: 'harmCategory', + type: 'multiOptions', + description: + 'Refer to official guide on how to use Harm Category', + options: [ + { + label: 'Dangerous', + name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT + }, + { + label: 'Harassment', + name: HarmCategory.HARM_CATEGORY_HARASSMENT + }, + { + label: 'Hate Speech', + name: HarmCategory.HARM_CATEGORY_HATE_SPEECH + }, + { + label: 'Sexually Explicit', + name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT + } + ], + optional: true, + additionalParams: true + }, + { + label: 'Harm Block Threshold', + name: 'harmBlockThreshold', + type: 'multiOptions', + description: + 'Refer to official guide on how to use Harm Block Threshold', + options: [ + { + label: 'Low and Above', + name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE + }, + { + label: 'Medium and Above', + name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE + }, + { + label: 'None', + name: HarmBlockThreshold.BLOCK_NONE + }, + { + label: 'Only High', + name: HarmBlockThreshold.BLOCK_ONLY_HIGH + }, + { + label: 'Threshold Unspecified', + name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED + } + ], + optional: true, + additionalParams: true } ] } @@ -86,6 +153,9 @@ class GoogleGenerativeAI_ChatModels implements INode { const modelName = nodeData.inputs?.modelName as string const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string const topP = nodeData.inputs?.topP as string + const topK = nodeData.inputs?.topK as string + const harmCategory = nodeData.inputs?.harmCategory as string + const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string const cache = nodeData.inputs?.cache as BaseCache const obj = { @@ -98,8 +168,23 @@ class GoogleGenerativeAI_ChatModels implements INode { const model = new ChatGoogleGenerativeAI(obj) if (topP) model.topP = parseFloat(topP) + if (topK) model.topP = parseFloat(topK) if (cache) model.cache = cache if (temperature) model.temperature = parseFloat(temperature) + + // safetySettings + let harmCategories: string[] = convertStringToArrayString(harmCategory) + let harmBlockThresholds: string[] = convertStringToArrayString(harmBlockThreshold) + if (harmCategories.length != harmBlockThresholds.length) + throw new Error(`Harm Category & Harm Block Threshold are not the same length`) + const safetySettings = harmCategories.map((value, index) => { + return { + category: value, + threshold: harmBlockThresholds[index] + } + }) + if (safetySettings) model.safetySettings = safetySettings + return model } } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 22fa6f4a..88c553cf 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -673,3 +673,20 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[ } return formatmessages } + +/** + * Convert String to Array String + * @param {string} inputString + * @returns {string[]} + */ +export const convertStringToArrayString = (inputString: string): string[] => { + let ArrayString: string[] = [] + if (inputString) { + try { + ArrayString = JSON.parse(inputString) + } catch (e) { + ArrayString = [] + } + } + return ArrayString +} From bdc892df2b36917df5586742c6eaa2fb1528e9fd Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 14 Jan 2024 00:51:28 +0800 Subject: [PATCH 204/268] fix error TS2322: Type '{ category: string; threshold: string; }[]' is not assignable to type 'SafetySetting[] --- .../ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 311b4e8d..ddae6780 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -177,13 +177,13 @@ class GoogleGenerativeAI_ChatModels implements INode { let harmBlockThresholds: string[] = convertStringToArrayString(harmBlockThreshold) if (harmCategories.length != harmBlockThresholds.length) throw new Error(`Harm Category & Harm Block Threshold are not the same length`) - const safetySettings = harmCategories.map((value, index) => { + const safetySettings: typeof model.safetySettings = harmCategories.map((value, index) => { return { category: value, threshold: harmBlockThresholds[index] } }) - if (safetySettings) model.safetySettings = safetySettings + if (safetySettings.length > 0) model.safetySettings = safetySettings return model } From 203b182cd54154d451369edc26291cc1f2f0b1cf Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 14 Jan 2024 01:35:39 +0800 Subject: [PATCH 205/268] fix error TS2322 part 2 --- .../ChatGoogleGenerativeAI.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index ddae6780..737b0bf2 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -1,7 +1,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { convertStringToArrayString, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { BaseCache } from 'langchain/schema' -import { ChatGoogleGenerativeAI } from '@langchain/google-genai' +import { ChatGoogleGenerativeAI, GoogleGenerativeAIChatInput } from '@langchain/google-genai' import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai' class GoogleGenerativeAI_ChatModels implements INode { @@ -158,10 +158,23 @@ class GoogleGenerativeAI_ChatModels implements INode { const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string const cache = nodeData.inputs?.cache as BaseCache - const obj = { + // safetySettings + let harmCategories: string[] = convertStringToArrayString(harmCategory) + let harmBlockThresholds: string[] = convertStringToArrayString(harmBlockThreshold) + if (harmCategories.length != harmBlockThresholds.length) + throw new Error(`Harm Category & Harm Block Threshold are not the same length`) + const safetySettings = harmCategories.map((value, index) => { + return { + category: value, + threshold: harmBlockThresholds[index] + } + }) + + const obj: Partial = { apiKey: apiKey, modelName: modelName, - maxOutputTokens: 2048 + maxOutputTokens: 2048, + safetySettings: safetySettings.length > 0 ? safetySettings : undefined } if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) @@ -172,19 +185,6 @@ class GoogleGenerativeAI_ChatModels implements INode { if (cache) model.cache = cache if (temperature) model.temperature = parseFloat(temperature) - // safetySettings - let harmCategories: string[] = convertStringToArrayString(harmCategory) - let harmBlockThresholds: string[] = convertStringToArrayString(harmBlockThreshold) - if (harmCategories.length != harmBlockThresholds.length) - throw new Error(`Harm Category & Harm Block Threshold are not the same length`) - const safetySettings: typeof model.safetySettings = harmCategories.map((value, index) => { - return { - category: value, - threshold: harmBlockThresholds[index] - } - }) - if (safetySettings.length > 0) model.safetySettings = safetySettings - return model } } From 4d15e886fe156e3d89c728f406aee0fb1cd91253 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 14 Jan 2024 03:38:14 +0800 Subject: [PATCH 206/268] fix error TS2322 part 3 --- .../ChatGoogleGenerativeAI.ts | 73 +++++++++++++++---- packages/components/package.json | 1 + packages/components/src/utils.ts | 4 +- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 737b0bf2..bd660b47 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { convertStringToArrayString, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { convertMultiOptionsToStringArray, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { BaseCache } from 'langchain/schema' import { ChatGoogleGenerativeAI, GoogleGenerativeAIChatInput } from '@langchain/google-genai' import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai' +import type { SafetySetting } from '@google/generative-ai' class GoogleGenerativeAI_ChatModels implements INode { label: string @@ -158,23 +159,10 @@ class GoogleGenerativeAI_ChatModels implements INode { const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string const cache = nodeData.inputs?.cache as BaseCache - // safetySettings - let harmCategories: string[] = convertStringToArrayString(harmCategory) - let harmBlockThresholds: string[] = convertStringToArrayString(harmBlockThreshold) - if (harmCategories.length != harmBlockThresholds.length) - throw new Error(`Harm Category & Harm Block Threshold are not the same length`) - const safetySettings = harmCategories.map((value, index) => { - return { - category: value, - threshold: harmBlockThresholds[index] - } - }) - const obj: Partial = { apiKey: apiKey, modelName: modelName, - maxOutputTokens: 2048, - safetySettings: safetySettings.length > 0 ? safetySettings : undefined + maxOutputTokens: 2048 } if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) @@ -185,8 +173,63 @@ class GoogleGenerativeAI_ChatModels implements INode { if (cache) model.cache = cache if (temperature) model.temperature = parseFloat(temperature) + // Safety Settings + let harmCategories: string[] = convertMultiOptionsToStringArray(harmCategory) + let harmBlockThresholds: string[] = convertMultiOptionsToStringArray(harmBlockThreshold) + if (harmCategories.length != harmBlockThresholds.length) + throw new Error(`Harm Category & Harm Block Threshold are not the same length`) + const safetySettings: SafetySetting[] = harmCategories.map((value, index) => { + return { + category: categoryInput(value), + threshold: thresholdInput(harmBlockThresholds[index]) + } + }) + if (safetySettings.length > 0) model.safetySettings = safetySettings + return model } } +const categoryInput = (categoryInput: string): HarmCategory => { + let categoryOutput: HarmCategory + switch (categoryInput) { + case HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: + categoryOutput = HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT + break + case HarmCategory.HARM_CATEGORY_HATE_SPEECH: + categoryOutput = HarmCategory.HARM_CATEGORY_HATE_SPEECH + break + case HarmCategory.HARM_CATEGORY_HARASSMENT: + categoryOutput = HarmCategory.HARM_CATEGORY_HARASSMENT + break + case HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: + categoryOutput = HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT + break + default: + categoryOutput = HarmCategory.HARM_CATEGORY_UNSPECIFIED + } + return categoryOutput +} + +const thresholdInput = (thresholdInput: string): HarmBlockThreshold => { + let thresholdOutput: HarmBlockThreshold + switch (thresholdInput) { + case HarmBlockThreshold.BLOCK_LOW_AND_ABOVE: + thresholdOutput = HarmBlockThreshold.BLOCK_LOW_AND_ABOVE + break + case HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: + thresholdOutput = HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE + break + case HarmBlockThreshold.BLOCK_NONE: + thresholdOutput = HarmBlockThreshold.BLOCK_NONE + break + case HarmBlockThreshold.BLOCK_ONLY_HIGH: + thresholdOutput = HarmBlockThreshold.BLOCK_ONLY_HIGH + break + default: + thresholdOutput = HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED + } + return thresholdOutput +} + module.exports = { nodeClass: GoogleGenerativeAI_ChatModels } diff --git a/packages/components/package.json b/packages/components/package.json index a2565430..55e84074 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -25,6 +25,7 @@ "@gomomento/sdk": "^1.51.1", "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", + "@google/generative-ai": "^0.1.3", "@huggingface/inference": "^2.6.1", "@langchain/google-genai": "^0.0.6", "@langchain/mistralai": "^0.0.6", diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 88c553cf..2d983562 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -675,11 +675,11 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[ } /** - * Convert String to Array String + * Convert MultiOptions String to String Array * @param {string} inputString * @returns {string[]} */ -export const convertStringToArrayString = (inputString: string): string[] => { +export const convertMultiOptionsToStringArray = (inputString: string): string[] => { let ArrayString: string[] = [] if (inputString) { try { From 8a4b6a72247d6dfb36a088b861cbc0efd3cde5e4 Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Sat, 13 Jan 2024 19:14:45 -0700 Subject: [PATCH 207/268] Fixing naming, handling embeddings for LocalAI also --- ....credential.ts => LocalAIApi.credential.ts} | 4 ++-- .../chatmodels/ChatLocalAI/ChatLocalAI.ts | 6 +++--- .../LocalAIEmbedding/LocalAIEmbedding.ts | 18 ++++++++++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) rename packages/components/credentials/{LcoalAIApi.credential.ts => LocalAIApi.credential.ts} (86%) diff --git a/packages/components/credentials/LcoalAIApi.credential.ts b/packages/components/credentials/LocalAIApi.credential.ts similarity index 86% rename from packages/components/credentials/LcoalAIApi.credential.ts rename to packages/components/credentials/LocalAIApi.credential.ts index 624e07fa..4aafe040 100644 --- a/packages/components/credentials/LcoalAIApi.credential.ts +++ b/packages/components/credentials/LocalAIApi.credential.ts @@ -8,12 +8,12 @@ class LocalAIApi implements INodeCredential { constructor() { this.label = 'LocalAI API' - this.name = 'LocalAIApi' + this.name = 'localAIApi' this.version = 1.0 this.inputs = [ { label: 'LocalAI Api Key', - name: 'LocalAIApiKey', + name: 'localAIApiKey', type: 'password' } ] diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index c44f03ce..f2825d0d 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -30,7 +30,7 @@ class ChatLocalAI_ChatModels implements INode { label: 'Connect Credential', name: 'credential', type: 'credential', - credentialNames: ['LocalAIApi'], + credentialNames: ['localAIApi'], optional: true } this.inputs = [ @@ -95,11 +95,11 @@ class ChatLocalAI_ChatModels implements INode { const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const localAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) + const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData) const cache = nodeData.inputs?.cache as BaseCache - const obj: Partial & BaseLLMParams & { localAIApiKey?: string } = { + const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, openAIApiKey: 'sk-' diff --git a/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts b/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts index 557e35d6..24efaf8c 100644 --- a/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts +++ b/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts @@ -1,4 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' class LocalAIEmbedding_Embeddings implements INode { @@ -10,6 +11,7 @@ class LocalAIEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,6 +23,13 @@ class LocalAIEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Use local embeddings models like llama.cpp' this.baseClasses = [this.type, 'Embeddings'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['localAIApi'], + optional: true + } this.inputs = [ { label: 'Base Path', @@ -37,15 +46,20 @@ class LocalAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string const basePath = nodeData.inputs?.basePath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { modelName, openAIApiKey: 'sk-' } + if (localAIApiKey) obj.openAIApiKey = localAIApiKey + const model = new OpenAIEmbeddings(obj, { basePath }) return model From ec50493851332e49b9e896713cdcc33a24b23241 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 14 Jan 2024 11:57:53 +0000 Subject: [PATCH 208/268] delete message API --- packages/server/src/index.ts | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b6f59191..cdb3dc3e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -523,7 +523,7 @@ export class App { res.status(404).send(`Chatflow ${chatflowid} not found`) return } - const chatId = (req.query?.chatId as string) ?? (await getChatId(chatflowid)) + const chatId = req.query?.chatId as string const memoryType = req.query?.memoryType as string | undefined const sessionId = req.query?.sessionId as string | undefined const chatType = req.query?.chatType as string | undefined @@ -546,7 +546,8 @@ export class App { await clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId) } - const deleteOptions: FindOptionsWhere = { chatflowid, chatId } + const deleteOptions: FindOptionsWhere = { chatflowid } + if (chatId) deleteOptions.chatId = chatId if (memoryType) deleteOptions.memoryType = memoryType if (sessionId) deleteOptions.sessionId = sessionId if (chatType) deleteOptions.chatType = chatType @@ -634,7 +635,7 @@ export class App { return res.json(result) }) - // Delete all chatmessages from chatflowid + // Delete all credentials from chatflowid this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id }) return res.json(results) @@ -1811,23 +1812,6 @@ export class App { } } -/** - * Get first chat message id - * @param {string} chatflowid - * @returns {string} - */ -export async function getChatId(chatflowid: string): Promise { - // first chatmessage id as the unique chat id - const firstChatMessage = await getDataSource() - .getRepository(ChatMessage) - .createQueryBuilder('cm') - .select('cm.id') - .where('chatflowid = :chatflowid', { chatflowid }) - .orderBy('cm.createdDate', 'ASC') - .getOne() - return firstChatMessage ? firstChatMessage.id : '' -} - let serverApp: App | undefined export async function getAllChatFlow(): Promise { From 04ef695b3f9b2ab0ff7d42f65a5d4f19178d8664 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Jan 2024 02:46:21 +0000 Subject: [PATCH 209/268] update README-ZH md --- README-ZH.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/README-ZH.md b/README-ZH.md index 208eee92..8750ebc7 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -145,29 +145,40 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package ## 🌐 自托管 -### [Railway](https://docs.flowiseai.com/deployment/railway) +在您现有的基础设施中部署自托管的 Flowise,我们支持各种[部署](https://docs.flowiseai.com/configuration/deployment) -[![在 Railway 上部署](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) +- [AWS](https://docs.flowiseai.com/deployment/aws) +- [Azure](https://docs.flowiseai.com/deployment/azure) +- [Digital Ocean](https://docs.flowiseai.com/deployment/digital-ocean) +- [GCP](https://docs.flowiseai.com/deployment/gcp) +-
    + 其他 -### [Render](https://docs.flowiseai.com/deployment/render) + - [Railway](https://docs.flowiseai.com/deployment/railway) -[![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) + [![在 Railway 上部署](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) -### [Sealos](https://docs.flowiseai.com/configuration/deployment/sealos) + - [Render](https://docs.flowiseai.com/deployment/render) -[![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://template.cloud.sealos.io/deploy?templateName=flowise) + [![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) -### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) + - [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) -HuggingFace Spaces + HuggingFace Spaces -### [AWS](https://docs.flowiseai.com/deployment/aws) + - [Elestio](https://elest.io/open-source/flowiseai) -### [Azure](https://docs.flowiseai.com/deployment/azure) + [![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai) -### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) + - [Sealos](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) -### [GCP](https://docs.flowiseai.com/deployment/gcp) + [![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) + + - [RepoCloud](https://repocloud.io/details/?app_id=29) + + [![部署到 RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=29) + +
    ## 💻 云托管 From 4ef9c1f5118bbf4f910215e1cd56e1f6e0762891 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Jan 2024 18:28:46 +0000 Subject: [PATCH 210/268] add sessionId tracking --- packages/components/package.json | 2 +- packages/components/src/handler.ts | 42 +++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index a2565430..86405c2b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -54,7 +54,7 @@ "ioredis": "^5.3.2", "langchain": "^0.0.214", "langfuse": "2.0.2", - "langfuse-langchain": "2.0.2", + "langfuse-langchain": "2.3.3", "langsmith": "0.0.53", "linkifyjs": "^4.1.1", "llmonitor": "^0.5.5", diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index 1eb05a51..df72a685 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -1,13 +1,13 @@ -import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks' +import { BaseTracer, Run, BaseCallbackHandler, LangChainTracer } from 'langchain/callbacks' import { AgentAction, ChainValues } from 'langchain/schema' import { Logger } from 'winston' import { Server } from 'socket.io' import { Client } from 'langsmith' -import { LangChainTracer } from 'langchain/callbacks' -import { LLMonitorHandler } from 'langchain/callbacks/handlers/llmonitor' +import { LLMonitorHandler, LLMonitorHandlerFields } from 'langchain/callbacks/handlers/llmonitor' import { getCredentialData, getCredentialParam } from './utils' import { ICommonObject, INodeData } from './Interface' import CallbackHandler from 'langfuse-langchain' +import { LangChainTracerFields } from '@langchain/core/tracers/tracer_langchain' import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith' import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse' import monitor from 'llmonitor' @@ -235,11 +235,16 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO apiKey: langSmithApiKey }) - const tracer = new LangChainTracer({ + let langSmithField: LangChainTracerFields = { projectName: langSmithProject ?? 'default', - //@ts-ignore client - }) + } + + if (nodeData?.inputs?.analytics?.langSmith) { + langSmithField = { ...langSmithField, ...nodeData?.inputs?.analytics?.langSmith } + } + + const tracer = new LangChainTracer(langSmithField) callbacks.push(tracer) } else if (provider === 'langFuse') { const release = analytic[provider].release as string @@ -248,13 +253,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData) const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData) - const langFuseOptions: any = { + let langFuseOptions: any = { secretKey: langFuseSecretKey, publicKey: langFusePublicKey, baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com' } if (release) langFuseOptions.release = release - if (options.chatId) langFuseOptions.userId = options.chatId + if (options.chatId) langFuseOptions.sessionId = options.chatId + + if (nodeData?.inputs?.analytics?.langFuse) { + langFuseOptions = { ...langFuseOptions, ...nodeData?.inputs?.analytics?.langFuse } + } const handler = new CallbackHandler(langFuseOptions) callbacks.push(handler) @@ -262,11 +271,15 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, nodeData) const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, nodeData) - const llmonitorFields: ICommonObject = { + let llmonitorFields: LLMonitorHandlerFields = { appId: llmonitorAppId, apiUrl: llmonitorEndpoint ?? 'https://app.llmonitor.com' } + if (nodeData?.inputs?.analytics?.llmonitor) { + llmonitorFields = { ...llmonitorFields, ...nodeData?.inputs?.analytics?.llmonitor } + } + const handler = new LLMonitorHandler(llmonitorFields) callbacks.push(handler) } @@ -360,7 +373,8 @@ export class AnalyticHandler { }, serialized: {}, project_name: this.handlers['langSmith'].langSmithProject, - client: this.handlers['langSmith'].client + client: this.handlers['langSmith'].client, + ...this.nodeData?.inputs?.analytics?.langSmith } const parentRun = new RunTree(parentRunConfig) await parentRun.postRun() @@ -390,8 +404,9 @@ export class AnalyticHandler { const langfuse: Langfuse = this.handlers['langFuse'].client langfuseTraceClient = langfuse.trace({ name, - userId: this.options.chatId, - metadata: { tags: ['openai-assistant'] } + sessionId: this.options.chatId, + metadata: { tags: ['openai-assistant'] }, + ...this.nodeData?.inputs?.analytics?.langFuse }) } else { langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']] @@ -420,7 +435,8 @@ export class AnalyticHandler { runId, name, userId: this.options.chatId, - input + input, + ...this.nodeData?.inputs?.analytics?.llmonitor }) this.handlers['llmonitor'].chainEvent = { [runId]: runId } returnIds['llmonitor'].chainEvent = runId From b3ab8527f5c03d4dda4ab22ded7a648cb6cc44d0 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Jan 2024 18:36:31 +0000 Subject: [PATCH 211/268] add ts-ignore --- packages/components/src/handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index df72a685..5d2b53f6 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -237,6 +237,7 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO let langSmithField: LangChainTracerFields = { projectName: langSmithProject ?? 'default', + //@ts-ignore client } From d7a998f26649c9878bfbc5140f1c65323e6f480a Mon Sep 17 00:00:00 2001 From: automaton82 Date: Mon, 15 Jan 2024 23:27:33 -0500 Subject: [PATCH 212/268] Update package.json Updating turbo to the latest compatible version to fix a bug with github actions not able to build arm64 docker images. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a9bfcbf..12fbe20f 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", - "turbo": "1.7.4", + "turbo": "^1.7.4", "typescript": "^4.8.4" }, "engines": { From f66c03ab0ae3f83f7f4b03f6e2a9cf9a8b9212f6 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 8 Jan 2024 13:02:56 +0000 Subject: [PATCH 213/268] change agent/chain with memory to use runnable --- .../ConversationalAgent.ts | 163 +++-- .../ConversationalRetrievalAgent.ts | 127 ++-- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 75 ++- .../OpenAIFunctionAgent.ts | 273 +------- .../ConversationChain/ConversationChain.ts | 146 +++-- .../ConversationalRetrievalQAChain.ts | 380 +++++++---- .../ConversationalRetrievalQAChain/prompts.ts | 79 +-- .../nodes/memory/BufferMemory/BufferMemory.ts | 33 +- .../BufferWindowMemory/BufferWindowMemory.ts | 34 +- .../ConversationSummaryMemory.ts | 42 +- .../nodes/memory/DynamoDb/DynamoDb.ts | 49 +- .../memory/MongoDBMemory/MongoDBMemory.ts | 49 +- .../memory/MotorheadMemory/MotorheadMemory.ts | 92 ++- .../RedisBackedChatMemory.ts | 70 +- .../UpstashRedisBackedChatMemory.ts | 49 +- .../nodes/memory/ZepMemory/ZepMemory.ts | 46 +- .../nodes/tools/CustomTool/CustomTool.ts | 8 +- .../components/nodes/tools/CustomTool/core.ts | 17 +- packages/components/package.json | 1 + packages/components/src/Interface.ts | 28 +- packages/components/src/agents.ts | 615 ++++++++++++++++++ .../marketplaces/chatflows/API Agent.json | 2 +- .../chatflows/Chat with a Podcast.json | 56 +- .../marketplaces/chatflows/Claude LLM.json | 2 +- .../chatflows/Conversational Agent.json | 2 +- .../Conversational Retrieval QA Chain.json | 62 +- .../chatflows/Flowise Docs QnA.json | 61 +- .../marketplaces/chatflows/Local QnA.json | 61 +- .../chatflows/Long Term Memory.json | 63 +- .../chatflows/Metadata Filter.json | 61 +- .../chatflows/Multiple VectorDB.json | 2 +- .../chatflows/Simple Conversation Chain.json | 2 +- .../chatflows/Vectara LLM Chain Upload.json | 55 +- .../marketplaces/chatflows/WebBrowser.json | 2 +- .../marketplaces/chatflows/WebPage QnA.json | 63 +- packages/server/src/index.ts | 98 ++- packages/server/src/utils/index.ts | 175 ++--- .../ui/src/views/canvas/NodeInputHandler.js | 3 +- 38 files changed, 1752 insertions(+), 1394 deletions(-) create mode 100644 packages/components/src/agents.ts diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 8a2329b5..7f857b1c 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -1,11 +1,14 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecutorOptions } from 'langchain/agents' import { Tool } from 'langchain/tools' -import { BaseChatMemory } from 'langchain/memory' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { BaseChatModel } from 'langchain/chat_models/base' import { flatten } from 'lodash' -import { additionalCallbacks } from '../../../src/handler' +import { AgentStep, BaseMessage, ChainValues, AIMessage, HumanMessage } from 'langchain/schema' +import { RunnableSequence } from 'langchain/schema/runnable' +import { getBaseClasses } from '../../../src/utils' +import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { AgentExecutor } from '../../../src/agents' +import { ChatConversationalAgent } from 'langchain/agents' +import { renderTemplate } from '@langchain/core/prompts' const DEFAULT_PREFIX = `Assistant is a large language model trained by OpenAI. @@ -15,6 +18,15 @@ Assistant is constantly learning and improving, and its capabilities are constan Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.` +const TEMPLATE_TOOL_RESPONSE = `TOOL RESPONSE: +--------------------- +{observation} + +USER'S INPUT +-------------------- + +Okay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else.` + class ConversationalAgent_Agents implements INode { label: string name: string @@ -25,8 +37,9 @@ class ConversationalAgent_Agents implements INode { category: string baseClasses: string[] inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversational Agent' this.name = 'conversationalAgent' this.version = 2.0 @@ -43,7 +56,7 @@ class ConversationalAgent_Agents implements INode { list: true }, { - label: 'Language Model', + label: 'Chat Model', name: 'model', type: 'BaseChatModel' }, @@ -62,52 +75,114 @@ class ConversationalAgent_Agents implements INode { additionalParams: true } ] + this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseChatModel - let tools = nodeData.inputs?.tools as Tool[] - tools = flatten(tools) - const memory = nodeData.inputs?.memory as BaseChatMemory - const systemMessage = nodeData.inputs?.systemMessage as string - - const obj: InitializeAgentExecutorOptions = { - agentType: 'chat-conversational-react-description', - verbose: process.env.DEBUG === 'true' ? true : false - } - - const agentArgs: any = {} - if (systemMessage) { - agentArgs.systemMessage = systemMessage - } - - if (Object.keys(agentArgs).length) obj.agentArgs = agentArgs - - const executor = await initializeAgentExecutorWithOptions(tools, model, obj) - executor.memory = memory - return executor + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const executor = nodeData.instance as AgentExecutor - const memory = nodeData.inputs?.memory as BaseChatMemory - - if (options && options.chatHistory) { - const chatHistoryClassName = memory.chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - memory.chatHistory = mapChatHistory(options) - executor.memory = memory - } - } - - ;(executor.memory as any).returnMessages = true // Return true for BaseChatModel + const memory = nodeData.inputs?.memory as FlowiseMemory + const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) - const result = await executor.call({ input }, [...callbacks]) - return result?.output + let res: ChainValues = {} + + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) + } else { + res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) + } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res?.output, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res?.output } } +const prepareAgent = async ( + nodeData: INodeData, + flowObj: { sessionId?: string; chatId?: string; input?: string }, + chatHistory: IMessage[] = [] +) => { + const model = nodeData.inputs?.model as BaseChatModel + let tools = nodeData.inputs?.tools as Tool[] + tools = flatten(tools) + const memory = nodeData.inputs?.memory as FlowiseMemory + const systemMessage = nodeData.inputs?.systemMessage as string + const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history' + const inputKey = memory.inputKey ? memory.inputKey : 'input' + + /** Bind a stop token to the model */ + const modelWithStop = model.bind({ + stop: ['\nObservation'] + }) + + const outputParser = ChatConversationalAgent.getDefaultOutputParser({ + llm: model, + toolNames: tools.map((tool) => tool.name) + }) + + const prompt = ChatConversationalAgent.createPrompt(tools, { + systemMessage: systemMessage ? systemMessage : DEFAULT_PREFIX, + outputParser + }) + + const runnableAgent = RunnableSequence.from([ + { + [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, + agent_scratchpad: async (i: { input: string; steps: AgentStep[] }) => await constructScratchPad(i.steps), + [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { + const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + return messages ?? [] + } + }, + prompt, + modelWithStop, + outputParser + ]) + + const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, + sessionId: flowObj?.sessionId, + chatId: flowObj?.chatId, + input: flowObj?.input, + verbose: process.env.DEBUG === 'true' ? true : false + }) + + return executor +} + +const constructScratchPad = async (steps: AgentStep[]): Promise => { + const thoughts: BaseMessage[] = [] + for (const step of steps) { + thoughts.push(new AIMessage(step.action.log)) + thoughts.push( + new HumanMessage( + renderTemplate(TEMPLATE_TOOL_RESPONSE, 'f-string', { + observation: step.observation + }) + ) + ) + } + return thoughts +} + module.exports = { nodeClass: ConversationalAgent_Agents } diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 643c6a65..406a156f 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -1,9 +1,14 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' +import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema' import { flatten } from 'lodash' -import { BaseChatMemory } from 'langchain/memory' +import { ChatOpenAI } from 'langchain/chat_models/openai' +import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' +import { formatToOpenAIFunction } from 'langchain/tools' +import { RunnableSequence } from 'langchain/schema/runnable' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' +import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' +import { AgentExecutor, formatAgentSteps } from '../../../src/agents' const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.` @@ -17,8 +22,9 @@ class ConversationalRetrievalAgent_Agents implements INode { category: string baseClasses: string[] inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversational Retrieval Agent' this.name = 'conversationalRetrievalAgent' this.version = 3.0 @@ -54,55 +60,96 @@ class ConversationalRetrievalAgent_Agents implements INode { additionalParams: true } ] + this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model - const memory = nodeData.inputs?.memory as BaseChatMemory - const systemMessage = nodeData.inputs?.systemMessage as string - - let tools = nodeData.inputs?.tools - tools = flatten(tools) - - const executor = await initializeAgentExecutorWithOptions(tools, model, { - agentType: 'openai-functions', - verbose: process.env.DEBUG === 'true' ? true : false, - agentArgs: { - prefix: systemMessage ?? defaultMessage - }, - returnIntermediateSteps: true - }) - executor.memory = memory - return executor + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const executor = nodeData.instance as AgentExecutor - - if (executor.memory) { - ;(executor.memory as any).memoryKey = 'chat_history' - ;(executor.memory as any).outputKey = 'output' - ;(executor.memory as any).returnMessages = true - - const chatHistoryClassName = (executor.memory as any).chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - ;(executor.memory as any).chatHistory = mapChatHistory(options) - } - } + const memory = nodeData.inputs?.memory as FlowiseMemory + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) + let res: ChainValues = {} + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const result = await executor.call({ input }, [loggerHandler, handler, ...callbacks]) - return result?.output + res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) } else { - const result = await executor.call({ input }, [loggerHandler, ...callbacks]) - return result?.output + res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res?.output, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res?.output } } +const prepareAgent = ( + nodeData: INodeData, + flowObj: { sessionId?: string; chatId?: string; input?: string }, + chatHistory: IMessage[] = [] +) => { + const model = nodeData.inputs?.model as ChatOpenAI + const memory = nodeData.inputs?.memory as FlowiseMemory + const systemMessage = nodeData.inputs?.systemMessage as string + let tools = nodeData.inputs?.tools + tools = flatten(tools) + const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history' + const inputKey = memory.inputKey ? memory.inputKey : 'input' + + const prompt = ChatPromptTemplate.fromMessages([ + ['ai', systemMessage ? systemMessage : defaultMessage], + new MessagesPlaceholder(memoryKey), + ['human', `{${inputKey}}`], + new MessagesPlaceholder('agent_scratchpad') + ]) + + const modelWithFunctions = model.bind({ + functions: [...tools.map((tool: any) => formatToOpenAIFunction(tool))] + }) + + const runnableAgent = RunnableSequence.from([ + { + [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, + agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), + [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { + const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + return messages ?? [] + } + }, + prompt, + modelWithFunctions, + new OpenAIFunctionsAgentOutputParser() + ]) + + const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, + sessionId: flowObj?.sessionId, + chatId: flowObj?.chatId, + input: flowObj?.input, + returnIntermediateSteps: true, + verbose: process.env.DEBUG === 'true' ? true : false + }) + + return executor +} + module.exports = { nodeClass: ConversationalRetrievalAgent_Agents } diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index cf69022b..62ecec5b 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -96,45 +96,51 @@ class OpenAIAssistant_Agents implements INode { return null } - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const selectedAssistantId = nodeData.inputs?.selectedAssistant as string - const appDataSource = options.appDataSource as DataSource - const databaseEntities = options.databaseEntities as IDatabaseEntity - let sessionId = nodeData.inputs?.sessionId as string + async clearChatMessages(nodeData: INodeData, options: ICommonObject, sessionIdObj: { type: string; id: string }): Promise { + const selectedAssistantId = nodeData.inputs?.selectedAssistant as string + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity - const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ - id: selectedAssistantId + const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ + id: selectedAssistantId + }) + + if (!assistant) { + options.logger.error(`Assistant ${selectedAssistantId} not found`) + return + } + + if (!sessionIdObj) return + + let sessionId = '' + if (sessionIdObj.type === 'chatId') { + const chatId = sessionIdObj.id + const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ + chatId }) - - if (!assistant) { - options.logger.error(`Assistant ${selectedAssistantId} not found`) + if (!chatmsg) { + options.logger.error(`Chat Message with Chat Id: ${chatId} not found`) return } + sessionId = chatmsg.sessionId + } else if (sessionIdObj.type === 'threadId') { + sessionId = sessionIdObj.id + } - if (!sessionId && options.chatId) { - const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ - chatId: options.chatId - }) - if (!chatmsg) { - options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) - return - } - sessionId = chatmsg.sessionId - } + const credentialData = await getCredentialData(assistant.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + if (!openAIApiKey) { + options.logger.error(`OpenAI ApiKey not found`) + return + } - const credentialData = await getCredentialData(assistant.credential ?? '', options) - const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - if (!openAIApiKey) { - options.logger.error(`OpenAI ApiKey not found`) - return - } - - const openai = new OpenAI({ apiKey: openAIApiKey }) - options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + const openai = new OpenAI({ apiKey: openAIApiKey }) + options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + try { if (sessionId) await openai.beta.threads.del(sessionId) options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + } catch (e) { + throw new Error(e) } } @@ -297,7 +303,11 @@ class OpenAIAssistant_Agents implements INode { options.socketIO.to(options.socketIOClientId).emit('tool', tool.name) try { - const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId) + const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, { + sessionId: threadId, + chatId: options.chatId, + input + }) await analyticHandlers.onToolEnd(toolIds, toolOutput) submitToolOutputs.push({ tool_call_id: actions[i].toolCallId, @@ -462,6 +472,7 @@ class OpenAIAssistant_Agents implements INode { const imageRegex = /]*\/>/g let llmOutput = returnVal.replace(imageRegex, '') llmOutput = llmOutput.replace('
    ', '') + await analyticHandlers.onLLMEnd(llmIds, llmOutput) await analyticHandlers.onChainEnd(parentIds, messageData, true) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index c0095cee..135121d2 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,17 +1,14 @@ -import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { AgentExecutor as LCAgentExecutor, AgentExecutorInput } from 'langchain/agents' -import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema' -import { OutputParserException } from 'langchain/schema/output_parser' -import { CallbackManagerForChainRun } from 'langchain/callbacks' -import { formatToOpenAIFunction } from 'langchain/tools' -import { ToolInputParsingException, Tool } from '@langchain/core/tools' +import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema' import { getBaseClasses } from '../../../src/utils' import { flatten } from 'lodash' import { RunnableSequence } from 'langchain/schema/runnable' +import { formatToOpenAIFunction } from 'langchain/tools' +import { ChatOpenAI } from 'langchain/chat_models/openai' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' -import { ChatOpenAI } from 'langchain/chat_models/openai' import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' +import { AgentExecutor, formatAgentSteps } from '../../../src/agents' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -25,7 +22,7 @@ class OpenAIFunctionAgent_Agents implements INode { inputs: INodeParams[] sessionId?: string - constructor(fields: { sessionId?: string }) { + constructor(fields?: { sessionId?: string }) { this.label = 'OpenAI Function Agent' this.name = 'openAIFunctionAgent' this.version = 3.0 @@ -33,7 +30,7 @@ class OpenAIFunctionAgent_Agents implements INode { this.category = 'Agents' this.icon = 'function.svg' this.description = `An agent that uses Function Calling to pick the tool and args to call` - this.baseClasses = [this.type, ...getBaseClasses(LCAgentExecutor)] + this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] this.inputs = [ { label: 'Allowed Tools', @@ -63,19 +60,13 @@ class OpenAIFunctionAgent_Agents implements INode { this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const memory = nodeData.inputs?.memory as FlowiseMemory - - const executor = prepareAgent(nodeData, this.sessionId) - if (memory) executor.memory = memory - - return executor + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const memory = nodeData.inputs?.memory as FlowiseMemory - - const executor = prepareAgent(nodeData, this.sessionId) + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -107,17 +98,11 @@ class OpenAIFunctionAgent_Agents implements INode { } } -const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => - steps.flatMap(({ action, observation }) => { - if ('messageLog' in action && action.messageLog !== undefined) { - const log = action.messageLog as BaseMessage[] - return log.concat(new FunctionMessage(observation, action.tool)) - } else { - return [new AIMessage(action.log)] - } - }) - -const prepareAgent = (nodeData: INodeData, sessionId?: string) => { +const prepareAgent = ( + nodeData: INodeData, + flowObj: { sessionId?: string; chatId?: string; input?: string }, + chatHistory: IMessage[] = [] +) => { const model = nodeData.inputs?.model as ChatOpenAI const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -142,7 +127,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => { [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages = (await memory.getChatMessages(sessionId, true)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] return messages ?? [] } }, @@ -154,231 +139,13 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => { const executor = AgentExecutor.fromAgentAndTools({ agent: runnableAgent, tools, - sessionId + sessionId: flowObj?.sessionId, + chatId: flowObj?.chatId, + input: flowObj?.input, + verbose: process.env.DEBUG === 'true' ? true : false }) return executor } -type AgentExecutorOutput = ChainValues - -class AgentExecutor extends LCAgentExecutor { - sessionId?: string - - static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string }): AgentExecutor { - const newInstance = new AgentExecutor(fields) - if (fields.sessionId) newInstance.sessionId = fields.sessionId - return newInstance - } - - shouldContinueIteration(iterations: number): boolean { - return this.maxIterations === undefined || iterations < this.maxIterations - } - - async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise { - const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) - - const steps: AgentStep[] = [] - let iterations = 0 - - const getOutput = async (finishStep: AgentFinish): Promise => { - const { returnValues } = finishStep - const additional = await this.agent.prepareForOutput(returnValues, steps) - - if (this.returnIntermediateSteps) { - return { ...returnValues, intermediateSteps: steps, ...additional } - } - await runManager?.handleAgentEnd(finishStep) - return { ...returnValues, ...additional } - } - - while (this.shouldContinueIteration(iterations)) { - let output - try { - output = await this.agent.plan(steps, inputs, runManager?.getChild()) - } catch (e) { - if (e instanceof OutputParserException) { - let observation - let text = e.message - if (this.handleParsingErrors === true) { - if (e.sendToLLM) { - observation = e.observation - text = e.llmOutput ?? '' - } else { - observation = 'Invalid or incomplete response' - } - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - output = { - tool: '_Exception', - toolInput: observation, - log: text - } as AgentAction - } else { - throw e - } - } - // Check if the agent has finished - if ('returnValues' in output) { - return getOutput(output) - } - - let actions: AgentAction[] - if (Array.isArray(output)) { - actions = output as AgentAction[] - } else { - actions = [output as AgentAction] - } - - const newSteps = await Promise.all( - actions.map(async (action) => { - await runManager?.handleAgentAction(action) - const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()] - let observation - try { - // here we need to override Tool call method to include sessionId as parameter - observation = tool - ? // @ts-ignore - await tool.call(action.toolInput, runManager?.getChild(), undefined, this.sessionId) - : `${action.tool} is not a valid tool, try another one.` - } catch (e) { - if (e instanceof ToolInputParsingException) { - if (this.handleParsingErrors === true) { - observation = 'Invalid or incomplete tool input. Please try again.' - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - observation = await new ExceptionTool().call(observation, runManager?.getChild()) - return { action, observation: observation ?? '' } - } - } - return { action, observation: observation ?? '' } - }) - ) - - steps.push(...newSteps) - - const lastStep = steps[steps.length - 1] - const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()] - - if (lastTool?.returnDirect) { - return getOutput({ - returnValues: { [this.agent.returnValues[0]]: lastStep.observation }, - log: '' - }) - } - - iterations += 1 - } - - const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs) - - return getOutput(finish) - } - - async _takeNextStep( - nameToolMap: Record, - inputs: ChainValues, - intermediateSteps: AgentStep[], - runManager?: CallbackManagerForChainRun - ): Promise { - let output - try { - output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild()) - } catch (e) { - if (e instanceof OutputParserException) { - let observation - let text = e.message - if (this.handleParsingErrors === true) { - if (e.sendToLLM) { - observation = e.observation - text = e.llmOutput ?? '' - } else { - observation = 'Invalid or incomplete response' - } - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - output = { - tool: '_Exception', - toolInput: observation, - log: text - } as AgentAction - } else { - throw e - } - } - - if ('returnValues' in output) { - return output - } - - let actions: AgentAction[] - if (Array.isArray(output)) { - actions = output as AgentAction[] - } else { - actions = [output as AgentAction] - } - - const result: AgentStep[] = [] - for (const agentAction of actions) { - let observation = '' - if (runManager) { - await runManager?.handleAgentAction(agentAction) - } - if (agentAction.tool in nameToolMap) { - const tool = nameToolMap[agentAction.tool] - try { - // here we need to override Tool call method to include sessionId as parameter - // @ts-ignore - observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, this.sessionId) - } catch (e) { - if (e instanceof ToolInputParsingException) { - if (this.handleParsingErrors === true) { - observation = 'Invalid or incomplete tool input. Please try again.' - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e) - } else { - throw e - } - observation = await new ExceptionTool().call(observation, runManager?.getChild()) - } - } - } else { - observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}` - } - result.push({ - action: agentAction, - observation - }) - } - return result - } -} - -class ExceptionTool extends Tool { - name = '_Exception' - - description = 'Exception tool' - - async _call(query: string) { - return query - } -} - module.exports = { nodeClass: OpenAIFunctionAgent_Agents } diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 54d4252a..fcd9921e 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,14 +1,16 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' -import { BufferMemory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { flatten } from 'lodash' import { Document } from 'langchain/document' +import { RunnableSequence } from 'langchain/schema/runnable' +import { StringOutputParser } from 'langchain/schema/output_parser' let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` +const inputKey = 'input' class ConversationChain_Chains implements INode { label: string @@ -20,8 +22,9 @@ class ConversationChain_Chains implements INode { baseClasses: string[] description: string inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversation Chain' this.name = 'conversationChain' this.version = 1.0 @@ -32,7 +35,7 @@ class ConversationChain_Chains implements INode { this.baseClasses = [this.type, ...getBaseClasses(ConversationChain)] this.inputs = [ { - label: 'Language Model', + label: 'Chat Model', name: 'model', type: 'BaseChatModel' }, @@ -60,76 +63,99 @@ class ConversationChain_Chains implements INode { placeholder: 'You are a helpful assistant that write codes' } ] + this.sessionId = fields?.sessionId } - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseChatModel - const memory = nodeData.inputs?.memory as BufferMemory - const prompt = nodeData.inputs?.systemMessagePrompt as string - const docs = nodeData.inputs?.document as Document[] - - const flattenDocs = docs && docs.length ? flatten(docs) : [] - const finalDocs = [] - for (let i = 0; i < flattenDocs.length; i += 1) { - if (flattenDocs[i] && flattenDocs[i].pageContent) { - finalDocs.push(new Document(flattenDocs[i])) - } - } - - let finalText = '' - for (let i = 0; i < finalDocs.length; i += 1) { - finalText += finalDocs[i].pageContent - } - - const replaceChar: string[] = ['{', '}'] - for (const char of replaceChar) finalText = finalText.replaceAll(char, '') - - if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` - - const obj: any = { - llm: model, - memory, - verbose: process.env.DEBUG === 'true' ? true : false - } - - const chatPrompt = ChatPromptTemplate.fromMessages([ - SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage), - new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), - HumanMessagePromptTemplate.fromTemplate('{input}') - ]) - obj.prompt = chatPrompt - - const chain = new ConversationChain(obj) + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const chain = prepareChain(nodeData, this.sessionId, options.chatHistory) return chain } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const chain = nodeData.instance as ConversationChain - const memory = nodeData.inputs?.memory as BufferMemory - memory.returnMessages = true // Return true for BaseChatModel - - if (options && options.chatHistory) { - const chatHistoryClassName = memory.chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - memory.chatHistory = mapChatHistory(options) - } - } - - chain.memory = memory + const memory = nodeData.inputs?.memory + const chain = prepareChain(nodeData, this.sessionId, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) + let res = '' + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call({ input }, [loggerHandler, handler, ...callbacks]) - return res?.response + res = await chain.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) } else { - const res = await chain.call({ input }, [loggerHandler, ...callbacks]) - return res?.response + res = await chain.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res } } +const prepareChatPrompt = (nodeData: INodeData) => { + const memory = nodeData.inputs?.memory as FlowiseMemory + const prompt = nodeData.inputs?.systemMessagePrompt as string + const docs = nodeData.inputs?.document as Document[] + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + + let finalText = '' + for (let i = 0; i < finalDocs.length; i += 1) { + finalText += finalDocs[i].pageContent + } + + const replaceChar: string[] = ['{', '}'] + for (const char of replaceChar) finalText = finalText.replaceAll(char, '') + + if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` + + const chatPrompt = ChatPromptTemplate.fromMessages([ + SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage), + new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), + HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`) + ]) + + return chatPrompt +} + +const prepareChain = (nodeData: INodeData, sessionId?: string, chatHistory: IMessage[] = []) => { + const model = nodeData.inputs?.model as BaseChatModel + const memory = nodeData.inputs?.memory as FlowiseMemory + const memoryKey = memory.memoryKey ?? 'chat_history' + + const conversationChain = RunnableSequence.from([ + { + [inputKey]: (input: { input: string }) => input.input, + [memoryKey]: async () => { + const history = await memory.getChatMessages(sessionId, true, chatHistory) + return history + } + }, + prepareChatPrompt(nodeData), + model, + new StringOutputParser() + ]) + + return conversationChain +} + module.exports = { nodeClass: ConversationChain_Chains } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 36376e13..5f98cba1 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,20 +1,25 @@ import { BaseLanguageModel } from 'langchain/base_language' -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' -import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains' +import { ConversationalRetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema/retriever' -import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +import { BufferMemoryInput } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' -import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' -import { - default_map_reduce_template, - default_qa_template, - qa_template, - map_reduce_template, - CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT, - refine_question_template, - refine_template -} from './prompts' +import { QA_TEMPLATE, REPHRASE_TEMPLATE, RESPONSE_TEMPLATE } from './prompts' +import { Runnable, RunnableSequence, RunnableMap, RunnableBranch, RunnableLambda } from 'langchain/schema/runnable' +import { BaseMessage, HumanMessage, AIMessage } from 'langchain/schema' +import { StringOutputParser } from 'langchain/schema/output_parser' +import type { Document } from 'langchain/document' +import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' +import { applyPatch } from 'fast-json-patch' +import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { ConsoleCallbackHandler, additionalCallbacks } from '../../../src/handler' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' + +type RetrievalChainInput = { + chat_history: string + question: string +} + +const sourceRunnableName = 'FindDocs' class ConversationalRetrievalQAChain_Chains implements INode { label: string @@ -26,11 +31,12 @@ class ConversationalRetrievalQAChain_Chains implements INode { baseClasses: string[] description: string inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields?: { sessionId?: string }) { this.label = 'Conversational Retrieval QA Chain' this.name = 'conversationalRetrievalQAChain' - this.version = 1.0 + this.version = 2.0 this.type = 'ConversationalRetrievalQAChain' this.icon = 'qa.svg' this.category = 'Chains' @@ -38,9 +44,9 @@ class ConversationalRetrievalQAChain_Chains implements INode { this.baseClasses = [this.type, ...getBaseClasses(ConversationalRetrievalQAChain)] this.inputs = [ { - label: 'Language Model', + label: 'Chat Model', name: 'model', - type: 'BaseLanguageModel' + type: 'BaseChatModel' }, { label: 'Vector Store Retriever', @@ -60,6 +66,29 @@ class ConversationalRetrievalQAChain_Chains implements INode { type: 'boolean', optional: true }, + { + label: 'Rephrase Prompt', + name: 'rephrasePrompt', + type: 'string', + description: 'Using previous chat history, rephrase question into a standalone question', + warning: 'Prompt must include input variables: {chat_history} and {question}', + rows: 4, + additionalParams: true, + optional: true, + default: REPHRASE_TEMPLATE + }, + { + label: 'Response Prompt', + name: 'responsePrompt', + type: 'string', + description: 'Taking the rephrased question, search for answer from the provided context', + warning: 'Prompt must include input variable: {context}', + rows: 4, + additionalParams: true, + optional: true, + default: RESPONSE_TEMPLATE + } + /** Deprecated { label: 'System Message', name: 'systemMessagePrompt', @@ -70,6 +99,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { placeholder: 'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.' }, + // TODO: create standalone chains for these 3 modes as they are not compatible with memory { label: 'Chain Option', name: 'chainOption', @@ -95,124 +125,246 @@ class ConversationalRetrievalQAChain_Chains implements INode { additionalParams: true, optional: true } + */ ] + this.sessionId = fields?.sessionId } async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string - const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean - const chainOption = nodeData.inputs?.chainOption as string - const externalMemory = nodeData.inputs?.memory + const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string + const responsePrompt = nodeData.inputs?.responsePrompt as string - const obj: any = { - verbose: process.env.DEBUG === 'true' ? true : false, - questionGeneratorChainOptions: { - template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT - } + let customResponsePrompt = responsePrompt + // If the deprecated systemMessagePrompt is still exists + if (systemMessagePrompt) { + customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}` } - if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments - - if (chainOption === 'map_reduce') { - obj.qaChainOptions = { - type: 'map_reduce', - combinePrompt: PromptTemplate.fromTemplate( - systemMessagePrompt ? `${systemMessagePrompt}\n${map_reduce_template}` : default_map_reduce_template - ) - } as QAChainParams - } else if (chainOption === 'refine') { - const qprompt = new PromptTemplate({ - inputVariables: ['context', 'question'], - template: refine_question_template(systemMessagePrompt) - }) - const rprompt = new PromptTemplate({ - inputVariables: ['context', 'question', 'existing_answer'], - template: refine_template - }) - obj.qaChainOptions = { - type: 'refine', - questionPrompt: qprompt, - refinePrompt: rprompt - } as QAChainParams - } else { - obj.qaChainOptions = { - type: 'stuff', - prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template) - } as QAChainParams - } - - if (externalMemory) { - externalMemory.memoryKey = 'chat_history' - externalMemory.inputKey = 'question' - externalMemory.outputKey = 'text' - externalMemory.returnMessages = true - if (chainOption === 'refine') externalMemory.outputKey = 'output_text' - obj.memory = externalMemory - } else { - const fields: BufferMemoryInput = { - memoryKey: 'chat_history', - inputKey: 'question', - outputKey: 'text', - returnMessages: true - } - if (chainOption === 'refine') fields.outputKey = 'output_text' - obj.memory = new BufferMemory(fields) - } - - const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj) - return chain + const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt) + return answerChain } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const chain = nodeData.instance as ConversationalRetrievalQAChain + const model = nodeData.inputs?.model as BaseLanguageModel + const externalMemory = nodeData.inputs?.memory + const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever + const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string + const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string + const responsePrompt = nodeData.inputs?.responsePrompt as string const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean - const chainOption = nodeData.inputs?.chainOption as string - let model = nodeData.inputs?.model - - // Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 - model.streaming = false - chain.questionGeneratorChain.llm = model - - const obj = { question: input } - - if (options && options.chatHistory && chain.memory) { - const chatHistoryClassName = (chain.memory as any).chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - ;(chain.memory as any).chatHistory = mapChatHistory(options) - } + let customResponsePrompt = responsePrompt + // If the deprecated systemMessagePrompt is still exists + if (systemMessagePrompt) { + customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}` } + let memory: FlowiseMemory | undefined = externalMemory + if (!memory) { + memory = new BufferMemory({ + returnMessages: true, + memoryKey: 'chat_history', + inputKey: 'input' + }) + } + + const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt) + + const history = ((await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[]) ?? [] + const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) - if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler( - options.socketIO, - options.socketIOClientId, - chainOption === 'refine' ? 4 : undefined, - returnSourceDocuments - ) - const res = await chain.call(obj, [loggerHandler, handler, ...callbacks]) - if (chainOption === 'refine') { - if (res.output_text && res.sourceDocuments) { - return { - text: res.output_text, - sourceDocuments: res.sourceDocuments - } - } - return res?.output_text + const stream = answerChain.streamLog( + { question: input, chat_history: history }, + { callbacks: [loggerHandler, ...callbacks] }, + { + includeNames: [sourceRunnableName] + } + ) + + let streamedResponse: Record = {} + let sourceDocuments: ICommonObject[] = [] + let text = '' + let isStreamingStarted = false + const isStreamingEnabled = options.socketIO && options.socketIOClientId + + for await (const chunk of stream) { + streamedResponse = applyPatch(streamedResponse, chunk.ops).newDocument + + if (streamedResponse.final_output) { + text = streamedResponse.final_output?.output + if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('end') + if (Array.isArray(streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output)) { + sourceDocuments = streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output + if (isStreamingEnabled && returnSourceDocuments) + options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', sourceDocuments) + } + } + + if ( + Array.isArray(streamedResponse?.streamed_output) && + streamedResponse?.streamed_output.length && + !streamedResponse.final_output + ) { + const token = streamedResponse.streamed_output[streamedResponse.streamed_output.length - 1] + + if (!isStreamingStarted) { + isStreamingStarted = true + if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('start', token) + } + if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('token', token) } - if (res.text && res.sourceDocuments) return res - return res?.text - } else { - const res = await chain.call(obj, [loggerHandler, ...callbacks]) - if (res.text && res.sourceDocuments) return res - return res?.text } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: text, + type: 'apiMessage' + } + ], + this.sessionId + ) + + if (returnSourceDocuments) return { text, sourceDocuments } + else return { text } + } +} + +const createRetrieverChain = (llm: BaseLanguageModel, retriever: Runnable, rephrasePrompt: string) => { + // Small speed/accuracy optimization: no need to rephrase the first question + // since there shouldn't be any meta-references to prior chat history + const CONDENSE_QUESTION_PROMPT = PromptTemplate.fromTemplate(rephrasePrompt) + const condenseQuestionChain = RunnableSequence.from([CONDENSE_QUESTION_PROMPT, llm, new StringOutputParser()]).withConfig({ + runName: 'CondenseQuestion' + }) + + const hasHistoryCheckFn = RunnableLambda.from((input: RetrievalChainInput) => input.chat_history.length > 0).withConfig({ + runName: 'HasChatHistoryCheck' + }) + + const conversationChain = condenseQuestionChain.pipe(retriever).withConfig({ + runName: 'RetrievalChainWithHistory' + }) + + const basicRetrievalChain = RunnableLambda.from((input: RetrievalChainInput) => input.question) + .withConfig({ + runName: 'Itemgetter:question' + }) + .pipe(retriever) + .withConfig({ runName: 'RetrievalChainWithNoHistory' }) + + return RunnableBranch.from([[hasHistoryCheckFn, conversationChain], basicRetrievalChain]).withConfig({ runName: sourceRunnableName }) +} + +const formatDocs = (docs: Document[]) => { + return docs.map((doc, i) => `${doc.pageContent}`).join('\n') +} + +const formatChatHistoryAsString = (history: BaseMessage[]) => { + return history.map((message) => `${message._getType()}: ${message.content}`).join('\n') +} + +const serializeHistory = (input: any) => { + const chatHistory: IMessage[] = input.chat_history || [] + const convertedChatHistory = [] + for (const message of chatHistory) { + if (message.type === 'userMessage') { + convertedChatHistory.push(new HumanMessage({ content: message.message })) + } + if (message.type === 'apiMessage') { + convertedChatHistory.push(new AIMessage({ content: message.message })) + } + } + return convertedChatHistory +} + +const createChain = ( + llm: BaseLanguageModel, + retriever: Runnable, + rephrasePrompt = REPHRASE_TEMPLATE, + responsePrompt = RESPONSE_TEMPLATE +) => { + const retrieverChain = createRetrieverChain(llm, retriever, rephrasePrompt) + + const context = RunnableMap.from({ + context: RunnableSequence.from([ + ({ question, chat_history }) => ({ + question, + chat_history: formatChatHistoryAsString(chat_history) + }), + retrieverChain, + RunnableLambda.from(formatDocs).withConfig({ + runName: 'FormatDocumentChunks' + }) + ]), + question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({ + runName: 'Itemgetter:question' + }), + chat_history: RunnableLambda.from((input: RetrievalChainInput) => input.chat_history).withConfig({ + runName: 'Itemgetter:chat_history' + }) + }).withConfig({ tags: ['RetrieveDocs'] }) + + const prompt = ChatPromptTemplate.fromMessages([ + ['system', responsePrompt], + new MessagesPlaceholder('chat_history'), + ['human', `{question}`] + ]) + + const responseSynthesizerChain = RunnableSequence.from([prompt, llm, new StringOutputParser()]).withConfig({ + tags: ['GenerateResponse'] + }) + + const conversationalQAChain = RunnableSequence.from([ + { + question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({ + runName: 'Itemgetter:question' + }), + chat_history: RunnableLambda.from(serializeHistory).withConfig({ + runName: 'SerializeHistory' + }) + }, + context, + responseSynthesizerChain + ]) + + return conversationalQAChain +} + +class BufferMemory extends FlowiseMemory implements MemoryMethods { + constructor(fields: BufferMemoryInput) { + super(fields) + } + + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + + const memoryResult = await this.loadMemoryVariables({}) + const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return + } + + async clearChatMessages(): Promise { + await this.clear() } } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts index 132e3a97..dccc7358 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts @@ -1,64 +1,27 @@ -export const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. - -{context} - -Question: {question} -Helpful Answer:` - -export const qa_template = `Use the following pieces of context to answer the question at the end. - -{context} - -Question: {question} -Helpful Answer:` - -export const default_map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer. -If you don't know the answer, just say that you don't know. Don't try to make up an answer. - -{summaries} - -Question: {question} -Helpful Answer:` - -export const map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer. - -{summaries} - -Question: {question} -Helpful Answer:` - -export const refine_question_template = (sysPrompt?: string) => { - let returnPrompt = '' - if (sysPrompt) - returnPrompt = `Context information is below. ---------------------- -{context} ---------------------- -Given the context information and not prior knowledge, ${sysPrompt} -Answer the question: {question}. -Answer:` - if (!sysPrompt) - returnPrompt = `Context information is below. ---------------------- -{context} ---------------------- -Given the context information and not prior knowledge, answer the question: {question}. -Answer:` - return returnPrompt -} - -export const refine_template = `The original question is as follows: {question} -We have provided an existing answer: {existing_answer} -We have the opportunity to refine the existing answer (only if needed) with some more context below. ------------- -{context} ------------- -Given the new context, refine the original answer to better answer the question. -If you can't find answer from the context, return the original answer.` - export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, answer in the same language as the follow up question. include it in the standalone question. Chat History: {chat_history} Follow Up Input: {question} Standalone question:` + +export const RESPONSE_TEMPLATE = `I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". Using the provided context, answer the user's question to the best of your ability using the resources provided. +If there is nothing in the context relevant to the question at hand, just say "Hmm, I'm not sure" and stop after that. Refuse to answer any question not about the info. Never break character. +------------ +{context} +------------ +REMEMBER: If there is no relevant information within the context, just say "Hmm, I'm not sure". Don't try to make up an answer. Never break character.` + +export const QA_TEMPLATE = `Use the following pieces of context to answer the question at the end. + +{context} + +Question: {question} +Helpful Answer:` + +export const REPHRASE_TEMPLATE = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. + +Chat History: +{chat_history} +Follow Up Input: {question} +Standalone Question:` diff --git a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts index 0ad8adec..4a6252b5 100644 --- a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts +++ b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts @@ -1,4 +1,4 @@ -import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BaseMessage } from 'langchain/schema' @@ -55,36 +55,27 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { super(fields) } - async getChatMessages(_?: string, returnBaseMessages = false): Promise { + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + const memoryResult = await this.loadMemoryVariables({}) const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } - async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues) + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return } async clearChatMessages(): Promise { await this.clear() } - - async resumeMessages(messages: IMessage[]): Promise { - // Clear existing chatHistory to avoid duplication - if (messages.length) await this.clear() - - // Insert into chatHistory - for (const msg of messages) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } - } } module.exports = { nodeClass: BufferMemory_Memory } diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts index ca8d0ddf..c21405a4 100644 --- a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -1,4 +1,4 @@ -import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory' import { BaseMessage } from 'langchain/schema' @@ -67,36 +67,28 @@ class BufferWindowMemoryExtended extends FlowiseWindowMemory implements MemoryMe super(fields) } - async getChatMessages(_?: string, returnBaseMessages = false): Promise { + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + + // Insert into chatHistory + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + const memoryResult = await this.loadMemoryVariables({}) const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } - async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues) + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return } async clearChatMessages(): Promise { await this.clear() } - - async resumeMessages(messages: IMessage[]): Promise { - // Clear existing chatHistory to avoid duplication - if (messages.length) await this.clear() - - // Insert into chatHistory - for (const msg of messages) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } - } } module.exports = { nodeClass: BufferWindowMemory_Memory } diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts index 107ab7db..45d39326 100644 --- a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts +++ b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts @@ -1,4 +1,4 @@ -import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory' import { BaseLanguageModel } from 'langchain/base_language' @@ -66,40 +66,32 @@ class ConversationSummaryMemoryExtended extends FlowiseSummaryMemory implements super(fields) } - async getChatMessages(_?: string, returnBaseMessages = false): Promise { + async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { + await this.chatHistory.clear() + this.buffer = '' + + for (const msg of prevHistory) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + + // Get summary + const chatMessages = await this.chatHistory.getMessages() + this.buffer = chatMessages.length ? await this.predictNewSummary(chatMessages.slice(-2), this.buffer) : '' + const memoryResult = await this.loadMemoryVariables({}) const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } - async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues) + async addChatMessages(): Promise { + // adding chat messages will be done on the fly in getChatMessages() + return } async clearChatMessages(): Promise { await this.clear() } - - async resumeMessages(messages: IMessage[]): Promise { - // Clear existing chatHistory to avoid duplication - if (messages.length) await this.clear() - - // Insert into chatHistory - for (const msg of messages) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } - - // Replace buffer - const chatMessages = await this.chatHistory.getMessages() - this.buffer = await this.predictNewSummary(chatMessages.slice(-2), this.buffer) - } } module.exports = { nodeClass: ConversationSummaryMemory_Memory } diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 872ec0b5..91c1d369 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -12,13 +12,7 @@ import { import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' class DynamoDb_Memory implements INode { @@ -70,7 +64,8 @@ class DynamoDb_Memory implements INode { label: 'Session ID', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -88,25 +83,6 @@ class DynamoDb_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initalizeDynamoDB(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const dynamodbMemory = await initalizeDynamoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) - await dynamodbMemory.clear() - options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const dynamodbMemory = await initalizeDynamoDB(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await dynamodbMemory.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): Promise => { @@ -114,17 +90,7 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P const partitionKey = nodeData.inputs?.partitionKey as string const region = nodeData.inputs?.region as string const memoryKey = nodeData.inputs?.memoryKey as string - const chatId = options.chatId - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData) @@ -150,7 +116,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P const memory = new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: dynamoDb, - isSessionIdUsingChatMessageId, sessionId, dynamodbClient: client }) @@ -158,7 +123,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean dynamodbClient: DynamoDBClient sessionId: string } @@ -178,7 +142,6 @@ interface DynamoDBSerializedChatMessage { } class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - isSessionIdUsingChatMessageId = false sessionId = '' dynamodbClient: DynamoDBClient @@ -306,10 +269,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.dynamodbClient.send(new DeleteItemCommand(params)) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index b422921e..c593c20d 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -2,13 +2,7 @@ import { MongoClient, Collection, Document } from 'mongodb' import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' class MongoDB_Memory implements INode { @@ -55,7 +49,8 @@ class MongoDB_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -73,42 +68,13 @@ class MongoDB_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initializeMongoDB(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const mongodbMemory = await initializeMongoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) - await mongodbMemory.clear() - options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const mongodbMemory = await initializeMongoDB(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await mongodbMemory.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): Promise => { const databaseName = nodeData.inputs?.databaseName as string const collectionName = nodeData.inputs?.collectionName as string const memoryKey = nodeData.inputs?.memoryKey as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) @@ -149,14 +115,12 @@ const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): P return new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: mongoDBChatMessageHistory, - isSessionIdUsingChatMessageId, sessionId, collection }) } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean collection: Collection sessionId: string } @@ -164,7 +128,6 @@ interface BufferMemoryExtendedInput { class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { sessionId = '' collection: Collection - isSessionIdUsingChatMessageId? = false constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) @@ -221,10 +184,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.collection.deleteOne({ sessionId: id }) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: MongoDB_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 938cc873..19506fc1 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -1,9 +1,14 @@ import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' -import { MotorheadMemory, MotorheadMemoryInput, InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' +import { MotorheadMemory, MotorheadMemoryInput, InputValues, OutputValues } from 'langchain/memory' import fetch from 'node-fetch' -import { BaseMessage } from 'langchain/schema' +import { AIMessage, BaseMessage, ChatMessage, HumanMessage } from 'langchain/schema' + +type MotorheadMessage = { + content: string + role: 'Human' | 'AI' +} class MotorMemory_Memory implements INode { label: string @@ -46,7 +51,8 @@ class MotorMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -64,49 +70,19 @@ class MotorMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initalizeMotorhead(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const motorhead = await initalizeMotorhead(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) - await motorhead.clear() - options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const motorhead = await initalizeMotorhead(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await motorhead.loadMemoryVariables({}) - return getBufferString(memoryResult[key]) - } - } } const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise => { const memoryKey = nodeData.inputs?.memoryKey as string const baseURL = nodeData.inputs?.baseURL as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const clientId = getCredentialParam('clientId', credentialData, nodeData) - let obj: MotorheadMemoryInput & MotorheadMemoryExtendedInput = { + let obj: MotorheadMemoryInput = { returnMessages: true, - isSessionIdUsingChatMessageId, sessionId, memoryKey } @@ -132,23 +108,9 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): return motorheadMemory } -interface MotorheadMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean -} - class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false - - constructor(fields: MotorheadMemoryInput & MotorheadMemoryExtendedInput) { + constructor(fields: MotorheadMemoryInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId - } - - async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise { - if (overrideSessionId) { - this.sessionId = overrideSessionId - } - return super.loadMemoryVariables({ values }) } async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise { @@ -180,9 +142,33 @@ class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { const id = overrideSessionId ?? this.sessionId - const memoryVariables = await this.loadMemoryVariables({}, id) - const baseMessages = memoryVariables[this.memoryKey] - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + try { + const resp = await this.caller.call(fetch, `${this.url}/sessions/${id}/memory`, { + //@ts-ignore + signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined, + headers: this._getHeaders() as ICommonObject, + method: 'GET' + }) + const data = await resp.json() + const rawStoredMessages: MotorheadMessage[] = data?.data?.messages ?? [] + + const baseMessages = rawStoredMessages.reverse().map((message) => { + const { content, role } = message + if (role === 'Human') { + return new HumanMessage(content) + } else if (role === 'AI') { + return new AIMessage(content) + } else { + // default to generic ChatMessage + return new ChatMessage(content, role) + } + }) + + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } catch (error) { + console.error('Error getting session: ', error) + return [] + } } async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index a02df3ea..baf4ea6b 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,15 +1,9 @@ -import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType, FlowiseMemory, MemoryMethods } from '../../../src/Interface' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { Redis } from 'ioredis' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' import { mapStoredMessageToChatMessage, BaseMessage, AIMessage, HumanMessage } from 'langchain/schema' -import { Redis } from 'ioredis' +import { INode, INodeData, INodeParams, ICommonObject, MessageType, IMessage, MemoryMethods, FlowiseMemory } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class RedisBackedChatMemory_Memory implements INode { label: string @@ -44,7 +38,8 @@ class RedisBackedChatMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -78,47 +73,19 @@ class RedisBackedChatMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return await initalizeRedis(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const redis = await initalizeRedis(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await redis.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string + const sessionId = nodeData.inputs?.sessionId as string const windowSize = nodeData.inputs?.windowSize as number - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) let client: Redis + if (!redisUrl || redisUrl === '') { const username = getCredentialParam('redisCacheUser', credentialData, nodeData) const password = getCredentialParam('redisCachePwd', credentialData, nodeData) @@ -153,7 +120,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const redisChatMessageHistory = new RedisChatMessageHistory(obj) - redisChatMessageHistory.getMessages = async (): Promise => { + /*redisChatMessageHistory.getMessages = async (): Promise => { const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) return orderedMessages.map(mapStoredMessageToChatMessage) @@ -169,44 +136,45 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom redisChatMessageHistory.clear = async (): Promise => { await client.del((redisChatMessageHistory as any).sessionId) - } + }*/ const memory = new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: redisChatMessageHistory, - isSessionIdUsingChatMessageId, sessionId, + windowSize, redisClient: client }) + return memory } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean redisClient: Redis sessionId: string + windowSize?: number } class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false sessionId = '' redisClient: Redis + windowSize?: number constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId this.sessionId = fields.sessionId this.redisClient = fields.redisClient + this.windowSize = fields.windowSize } - async getChatMessages(overrideSessionId = '', returnBaseMessage = false): Promise { + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { if (!this.redisClient) return [] const id = overrideSessionId ?? this.sessionId - const rawStoredMessages = await this.redisClient.lrange(id, 0, -1) + const rawStoredMessages = await this.redisClient.lrange(id, this.windowSize ? this.windowSize * -1 : 0, -1) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) const baseMessages = orderedMessages.map(mapStoredMessageToChatMessage) - return returnBaseMessage ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) } async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { @@ -236,10 +204,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.redisClient.del(id) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index c3f97123..3d7f6dbf 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -3,13 +3,7 @@ import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' -import { - convertBaseMessagetoIMessage, - getBaseClasses, - getCredentialData, - getCredentialParam, - serializeChatHistory -} from '../../../src/utils' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src/Interface' class UpstashRedisBackedChatMemory_Memory implements INode { @@ -51,7 +45,8 @@ class UpstashRedisBackedChatMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + description: + 'If not specified, a random id will be used. Learn more', default: '', additionalParams: true, optional: true @@ -70,40 +65,12 @@ class UpstashRedisBackedChatMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return initalizeUpstashRedis(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const key = 'chat_history' - const memoryResult = await redis.loadMemoryVariables({}) - return serializeChatHistory(memoryResult[key]) - } - } } const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const baseURL = nodeData.inputs?.baseURL as string const sessionTTL = nodeData.inputs?.sessionTTL as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData) @@ -122,7 +89,6 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject const memory = new BufferMemoryExtended({ memoryKey: 'chat_history', chatHistory: redisChatMessageHistory, - isSessionIdUsingChatMessageId, sessionId, redisClient: client }) @@ -131,19 +97,16 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject } interface BufferMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean redisClient: Redis sessionId: string } class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false sessionId = '' redisClient: Redis constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId this.sessionId = fields.sessionId this.redisClient = fields.redisClient } @@ -186,10 +149,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { await this.redisClient.del(id) await this.clear() } - - async resumeMessages(): Promise { - return - } } module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 4dda76df..597eee8a 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -2,7 +2,7 @@ import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } f import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' -import { InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' +import { InputValues, MemoryVariables, OutputValues } from 'langchain/memory' import { BaseMessage } from 'langchain/schema' class ZepMemory_Memory implements INode { @@ -55,10 +55,9 @@ class ZepMemory_Memory implements INode { label: 'Size', name: 'k', type: 'number', - placeholder: '10', + default: '10', description: 'Window of size k to surface the last k back-and-forth to use as memory.', - additionalParams: true, - optional: true + additionalParams: true }, { label: 'AI Prefix', @@ -101,27 +100,6 @@ class ZepMemory_Memory implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { return await initalizeZep(nodeData, options) } - - //@ts-ignore - memoryMethods = { - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const zep = await initalizeZep(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) - await zep.clear() - options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) - }, - async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const aiPrefix = nodeData.inputs?.aiPrefix as string - const humanPrefix = nodeData.inputs?.humanPrefix as string - const zep = await initalizeZep(nodeData, options) - const key = memoryKey ?? 'chat_history' - const memoryResult = await zep.loadMemoryVariables({}) - return getBufferString(memoryResult[key], humanPrefix, aiPrefix) - } - } } const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promise => { @@ -131,30 +109,19 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis const memoryKey = nodeData.inputs?.memoryKey as string const inputKey = nodeData.inputs?.inputKey as string const k = nodeData.inputs?.k as string - const chatId = options?.chatId as string - - let isSessionIdUsingChatMessageId = false - let sessionId = '' - - if (!nodeData.inputs?.sessionId && chatId) { - isSessionIdUsingChatMessageId = true - sessionId = chatId - } else { - sessionId = nodeData.inputs?.sessionId - } + const sessionId = nodeData.inputs?.sessionId as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const obj: ZepMemoryInput & ZepMemoryExtendedInput = { baseURL, - sessionId, aiPrefix, humanPrefix, returnMessages: true, memoryKey, inputKey, - isSessionIdUsingChatMessageId, + sessionId, k: k ? parseInt(k, 10) : undefined } if (apiKey) obj.apiKey = apiKey @@ -163,17 +130,14 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis } interface ZepMemoryExtendedInput { - isSessionIdUsingChatMessageId: boolean k?: number } class ZepMemoryExtended extends ZepMemory implements MemoryMethods { - isSessionIdUsingChatMessageId? = false lastN?: number constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId this.lastN = fields.k } diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index 6ffcc0e2..a983d0d9 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -60,7 +60,7 @@ class CustomTool_Tools implements INode { } } - async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const selectedToolId = nodeData.inputs?.selectedTool as string const customToolFunc = nodeData.inputs?.customToolFunc as string @@ -99,11 +99,7 @@ class CustomTool_Tools implements INode { } } - const flow = { - chatId: options.chatId, // id is uppercase (I) - chatflowId: options.chatflowid, // id is lowercase (i) - input - } + const flow = { chatflowId: options.chatflowid } let dynamicStructuredTool = new DynamicStructuredTool(obj) dynamicStructuredTool.setVariables(variables) diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 338b0ae9..b543aefa 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -55,7 +55,12 @@ export class DynamicStructuredTool< this.schema = fields.schema } - async call(arg: z.output, configArg?: RunnableConfig | Callbacks, tags?: string[], overrideSessionId?: string): Promise { + async call( + arg: z.output, + configArg?: RunnableConfig | Callbacks, + tags?: string[], + flowConfig?: { sessionId?: string; chatId?: string; input?: string } + ): Promise { const config = parseCallbackConfigArg(configArg) if (config.runName === undefined) { config.runName = this.name @@ -86,7 +91,7 @@ export class DynamicStructuredTool< ) let result try { - result = await this._call(parsed, runManager, overrideSessionId) + result = await this._call(parsed, runManager, flowConfig) } catch (e) { await runManager?.handleToolError(e) throw e @@ -95,7 +100,11 @@ export class DynamicStructuredTool< return result } - protected async _call(arg: z.output, _?: CallbackManagerForToolRun, overrideSessionId?: string): Promise { + protected async _call( + arg: z.output, + _?: CallbackManagerForToolRun, + flowConfig?: { sessionId?: string; chatId?: string; input?: string } + ): Promise { let sandbox: any = {} if (typeof arg === 'object' && Object.keys(arg).length) { for (const item in arg) { @@ -126,7 +135,7 @@ export class DynamicStructuredTool< // inject flow properties if (this.flowObj) { - sandbox['$flow'] = { ...this.flowObj, sessionId: overrideSessionId } + sandbox['$flow'] = { ...this.flowObj, ...flowConfig } } const defaultAllowBuiltInDep = [ diff --git a/packages/components/package.json b/packages/components/package.json index a2565430..a77d91e4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -46,6 +46,7 @@ "dotenv": "^16.0.0", "express": "^4.17.3", "faiss-node": "^0.2.2", + "fast-json-patch": "^3.1.1", "form-data": "^4.0.0", "google-auth-library": "^9.0.0", "graphql": "^16.6.0", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 9da26f82..d74ba1b4 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -108,10 +108,6 @@ export interface INode extends INodeProperties { search: (nodeData: INodeData, options?: ICommonObject) => Promise delete: (nodeData: INodeData, options?: ICommonObject) => Promise } - memoryMethods?: { - clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise - getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise - } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise } @@ -204,29 +200,37 @@ import { BaseMessage } from 'langchain/schema' import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory' export interface MemoryMethods { - getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean, prevHistory?: IMessage[]): Promise addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise clearChatMessages(overrideSessionId?: string): Promise - resumeMessages?(messages: IMessage[]): Promise } export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods { - abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract getChatMessages( + overrideSessionId?: string, + returnBaseMessages?: boolean, + prevHistory?: IMessage[] + ): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise - abstract resumeMessages(messages: IMessage[]): Promise } export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods { - abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract getChatMessages( + overrideSessionId?: string, + returnBaseMessages?: boolean, + prevHistory?: IMessage[] + ): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise - abstract resumeMessages(messages: IMessage[]): Promise } export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods { - abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract getChatMessages( + overrideSessionId?: string, + returnBaseMessages?: boolean, + prevHistory?: IMessage[] + ): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise - abstract resumeMessages(messages: IMessage[]): Promise } diff --git a/packages/components/src/agents.ts b/packages/components/src/agents.ts new file mode 100644 index 00000000..e30a0c43 --- /dev/null +++ b/packages/components/src/agents.ts @@ -0,0 +1,615 @@ +import { AgentExecutorInput, BaseSingleActionAgent, BaseMultiActionAgent, RunnableAgent, StoppingMethod } from 'langchain/agents' +import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema' +import { OutputParserException } from 'langchain/schema/output_parser' +import { CallbackManager, CallbackManagerForChainRun, Callbacks } from 'langchain/callbacks' +import { ToolInputParsingException, Tool } from '@langchain/core/tools' +import { Runnable } from 'langchain/schema/runnable' +import { BaseChain, SerializedLLMChain } from 'langchain/chains' +import { Serializable } from '@langchain/core/load/serializable' + +type AgentExecutorOutput = ChainValues + +interface AgentExecutorIteratorInput { + agentExecutor: AgentExecutor + inputs: Record + callbacks?: Callbacks + tags?: string[] + metadata?: Record + runName?: string + runManager?: CallbackManagerForChainRun +} + +//TODO: stream tools back +export class AgentExecutorIterator extends Serializable implements AgentExecutorIteratorInput { + lc_namespace = ['langchain', 'agents', 'executor_iterator'] + + agentExecutor: AgentExecutor + + inputs: Record + + callbacks: Callbacks + + tags: string[] | undefined + + metadata: Record | undefined + + runName: string | undefined + + private _finalOutputs: Record | undefined + + get finalOutputs(): Record | undefined { + return this._finalOutputs + } + + /** Intended to be used as a setter method, needs to be async. */ + async setFinalOutputs(value: Record | undefined) { + this._finalOutputs = undefined + if (value) { + const preparedOutputs: Record = await this.agentExecutor.prepOutputs(this.inputs, value, true) + this._finalOutputs = preparedOutputs + } + } + + runManager: CallbackManagerForChainRun | undefined + + intermediateSteps: AgentStep[] = [] + + iterations = 0 + + get nameToToolMap(): Record { + const toolMap = this.agentExecutor.tools.map((tool) => ({ + [tool.name]: tool + })) + return Object.assign({}, ...toolMap) + } + + constructor(fields: AgentExecutorIteratorInput) { + super(fields) + this.agentExecutor = fields.agentExecutor + this.inputs = fields.inputs + this.tags = fields.tags + this.metadata = fields.metadata + this.runName = fields.runName + this.runManager = fields.runManager + } + + /** + * Reset the iterator to its initial state, clearing intermediate steps, + * iterations, and the final output. + */ + reset(): void { + this.intermediateSteps = [] + this.iterations = 0 + this._finalOutputs = undefined + } + + updateIterations(): void { + this.iterations += 1 + } + + async *streamIterator() { + this.reset() + + // Loop to handle iteration + while (true) { + try { + if (this.iterations === 0) { + await this.onFirstStep() + } + + const result = await this._callNext() + yield result + } catch (e: any) { + if ('message' in e && e.message.startsWith('Final outputs already reached: ')) { + if (!this.finalOutputs) { + throw e + } + return this.finalOutputs + } + if (this.runManager) { + await this.runManager.handleChainError(e) + } + throw e + } + } + } + + /** + * Perform any necessary setup for the first step + * of the asynchronous iterator. + */ + async onFirstStep(): Promise { + if (this.iterations === 0) { + const callbackManager = await CallbackManager.configure( + this.callbacks, + this.agentExecutor.callbacks, + this.tags, + this.agentExecutor.tags, + this.metadata, + this.agentExecutor.metadata, + { + verbose: this.agentExecutor.verbose + } + ) + this.runManager = await callbackManager?.handleChainStart( + this.agentExecutor.toJSON(), + this.inputs, + undefined, + undefined, + this.tags, + this.metadata, + this.runName + ) + } + } + + /** + * Execute the next step in the chain using the + * AgentExecutor's _takeNextStep method. + */ + async _executeNextStep(runManager?: CallbackManagerForChainRun): Promise { + return this.agentExecutor._takeNextStep(this.nameToToolMap, this.inputs, this.intermediateSteps, runManager) + } + + /** + * Process the output of the next step, + * handling AgentFinish and tool return cases. + */ + async _processNextStepOutput( + nextStepOutput: AgentFinish | AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise> { + if ('returnValues' in nextStepOutput) { + const output = await this.agentExecutor._return(nextStepOutput as AgentFinish, this.intermediateSteps, runManager) + if (this.runManager) { + await this.runManager.handleChainEnd(output) + } + await this.setFinalOutputs(output) + return output + } + + this.intermediateSteps = this.intermediateSteps.concat(nextStepOutput as AgentStep[]) + + let output: Record = {} + if (Array.isArray(nextStepOutput) && nextStepOutput.length === 1) { + const nextStep = nextStepOutput[0] + const toolReturn = await this.agentExecutor._getToolReturn(nextStep) + if (toolReturn) { + output = await this.agentExecutor._return(toolReturn, this.intermediateSteps, runManager) + if (this.runManager) { + await this.runManager.handleChainEnd(output) + } + await this.setFinalOutputs(output) + } + } + output = { intermediateSteps: nextStepOutput as AgentStep[] } + return output + } + + async _stop(): Promise> { + const output = await this.agentExecutor.agent.returnStoppedResponse( + this.agentExecutor.earlyStoppingMethod, + this.intermediateSteps, + this.inputs + ) + const returnedOutput = await this.agentExecutor._return(output, this.intermediateSteps, this.runManager) + await this.setFinalOutputs(returnedOutput) + return returnedOutput + } + + async _callNext(): Promise> { + // final output already reached: stopiteration (final output) + if (this.finalOutputs) { + throw new Error(`Final outputs already reached: ${JSON.stringify(this.finalOutputs, null, 2)}`) + } + // timeout/max iterations: stopiteration (stopped response) + if (!this.agentExecutor.shouldContinueGetter(this.iterations)) { + return this._stop() + } + const nextStepOutput = await this._executeNextStep(this.runManager) + const output = await this._processNextStepOutput(nextStepOutput, this.runManager) + this.updateIterations() + return output + } +} + +export class AgentExecutor extends BaseChain { + static lc_name() { + return 'AgentExecutor' + } + + get lc_namespace() { + return ['langchain', 'agents', 'executor'] + } + + agent: BaseSingleActionAgent | BaseMultiActionAgent + + tools: this['agent']['ToolType'][] + + returnIntermediateSteps = false + + maxIterations?: number = 15 + + earlyStoppingMethod: StoppingMethod = 'force' + + sessionId?: string + + chatId?: string + + input?: string + + /** + * How to handle errors raised by the agent's output parser. + Defaults to `False`, which raises the error. + + If `true`, the error will be sent back to the LLM as an observation. + If a string, the string itself will be sent to the LLM as an observation. + If a callable function, the function will be called with the exception + as an argument, and the result of that function will be passed to the agent + as an observation. + */ + handleParsingErrors: boolean | string | ((e: OutputParserException | ToolInputParsingException) => string) = false + + get inputKeys() { + return this.agent.inputKeys + } + + get outputKeys() { + return this.agent.returnValues + } + + constructor(input: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }) { + let agent: BaseSingleActionAgent | BaseMultiActionAgent + if (Runnable.isRunnable(input.agent)) { + agent = new RunnableAgent({ runnable: input.agent }) + } else { + agent = input.agent + } + + super(input) + this.agent = agent + this.tools = input.tools + this.handleParsingErrors = input.handleParsingErrors ?? this.handleParsingErrors + /* Getting rid of this because RunnableAgent doesnt allow return direct + if (this.agent._agentActionType() === "multi") { + for (const tool of this.tools) { + if (tool.returnDirect) { + throw new Error( + `Tool with return direct ${tool.name} not supported for multi-action agent.` + ); + } + } + }*/ + this.returnIntermediateSteps = input.returnIntermediateSteps ?? this.returnIntermediateSteps + this.maxIterations = input.maxIterations ?? this.maxIterations + this.earlyStoppingMethod = input.earlyStoppingMethod ?? this.earlyStoppingMethod + this.sessionId = input.sessionId + this.chatId = input.chatId + this.input = input.input + } + + static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }): AgentExecutor { + const newInstance = new AgentExecutor(fields) + if (fields.sessionId) newInstance.sessionId = fields.sessionId + if (fields.chatId) newInstance.chatId = fields.chatId + if (fields.input) newInstance.input = fields.input + return newInstance + } + + get shouldContinueGetter() { + return this.shouldContinue.bind(this) + } + + /** + * Method that checks if the agent execution should continue based on the + * number of iterations. + * @param iterations The current number of iterations. + * @returns A boolean indicating whether the agent execution should continue. + */ + private shouldContinue(iterations: number): boolean { + return this.maxIterations === undefined || iterations < this.maxIterations + } + + async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise { + const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) + + const steps: AgentStep[] = [] + let iterations = 0 + + const getOutput = async (finishStep: AgentFinish): Promise => { + const { returnValues } = finishStep + const additional = await this.agent.prepareForOutput(returnValues, steps) + + if (this.returnIntermediateSteps) { + return { ...returnValues, intermediateSteps: steps, ...additional } + } + await runManager?.handleAgentEnd(finishStep) + return { ...returnValues, ...additional } + } + + while (this.shouldContinue(iterations)) { + let output + try { + output = await this.agent.plan(steps, inputs, runManager?.getChild()) + } catch (e) { + if (e instanceof OutputParserException) { + let observation + let text = e.message + if (this.handleParsingErrors === true) { + if (e.sendToLLM) { + observation = e.observation + text = e.llmOutput ?? '' + } else { + observation = 'Invalid or incomplete response' + } + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + output = { + tool: '_Exception', + toolInput: observation, + log: text + } as AgentAction + } else { + throw e + } + } + // Check if the agent has finished + if ('returnValues' in output) { + return getOutput(output) + } + + let actions: AgentAction[] + if (Array.isArray(output)) { + actions = output as AgentAction[] + } else { + actions = [output as AgentAction] + } + + const newSteps = await Promise.all( + actions.map(async (action) => { + await runManager?.handleAgentAction(action) + const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()] + let observation + try { + /* Here we need to override Tool call method to include sessionId, chatId, input as parameter + * Tool Call Parameters: + * - arg: z.output + * - configArg?: RunnableConfig | Callbacks + * - tags?: string[] + * - flowConfig?: { sessionId?: string, chatId?: string, input?: string } + */ + observation = tool + ? // @ts-ignore + await tool.call(action.toolInput, runManager?.getChild(), undefined, { + sessionId: this.sessionId, + chatId: this.chatId, + input: this.input + }) + : `${action.tool} is not a valid tool, try another one.` + } catch (e) { + if (e instanceof ToolInputParsingException) { + if (this.handleParsingErrors === true) { + observation = 'Invalid or incomplete tool input. Please try again.' + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + observation = await new ExceptionTool().call(observation, runManager?.getChild()) + return { action, observation: observation ?? '' } + } + } + return { action, observation: observation ?? '' } + }) + ) + + steps.push(...newSteps) + + const lastStep = steps[steps.length - 1] + const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()] + + if (lastTool?.returnDirect) { + return getOutput({ + returnValues: { [this.agent.returnValues[0]]: lastStep.observation }, + log: '' + }) + } + + iterations += 1 + } + + const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs) + + return getOutput(finish) + } + + async _takeNextStep( + nameToolMap: Record, + inputs: ChainValues, + intermediateSteps: AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise { + let output + try { + output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild()) + } catch (e) { + if (e instanceof OutputParserException) { + let observation + let text = e.message + if (this.handleParsingErrors === true) { + if (e.sendToLLM) { + observation = e.observation + text = e.llmOutput ?? '' + } else { + observation = 'Invalid or incomplete response' + } + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + output = { + tool: '_Exception', + toolInput: observation, + log: text + } as AgentAction + } else { + throw e + } + } + + if ('returnValues' in output) { + return output + } + + let actions: AgentAction[] + if (Array.isArray(output)) { + actions = output as AgentAction[] + } else { + actions = [output as AgentAction] + } + + const result: AgentStep[] = [] + for (const agentAction of actions) { + let observation = '' + if (runManager) { + await runManager?.handleAgentAction(agentAction) + } + if (agentAction.tool in nameToolMap) { + const tool = nameToolMap[agentAction.tool] + try { + /* Here we need to override Tool call method to include sessionId, chatId, input as parameter + * Tool Call Parameters: + * - arg: z.output + * - configArg?: RunnableConfig | Callbacks + * - tags?: string[] + * - flowConfig?: { sessionId?: string, chatId?: string, input?: string } + */ + // @ts-ignore + observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, { + sessionId: this.sessionId, + chatId: this.chatId, + input: this.input + }) + } catch (e) { + if (e instanceof ToolInputParsingException) { + if (this.handleParsingErrors === true) { + observation = 'Invalid or incomplete tool input. Please try again.' + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + observation = await new ExceptionTool().call(observation, runManager?.getChild()) + } + } + } else { + observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}` + } + result.push({ + action: agentAction, + observation + }) + } + return result + } + + async _return( + output: AgentFinish, + intermediateSteps: AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise { + if (runManager) { + await runManager.handleAgentEnd(output) + } + const finalOutput: Record = output.returnValues + if (this.returnIntermediateSteps) { + finalOutput.intermediateSteps = intermediateSteps + } + return finalOutput + } + + async _getToolReturn(nextStepOutput: AgentStep): Promise { + const { action, observation } = nextStepOutput + const nameToolMap = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) + const [returnValueKey = 'output'] = this.agent.returnValues + // Invalid tools won't be in the map, so we return False. + if (action.tool in nameToolMap) { + if (nameToolMap[action.tool].returnDirect) { + return { + returnValues: { [returnValueKey]: observation }, + log: '' + } + } + } + return null + } + + _returnStoppedResponse(earlyStoppingMethod: StoppingMethod) { + if (earlyStoppingMethod === 'force') { + return { + returnValues: { + output: 'Agent stopped due to iteration limit or time limit.' + }, + log: '' + } as AgentFinish + } + throw new Error(`Got unsupported early_stopping_method: ${earlyStoppingMethod}`) + } + + async *_streamIterator(inputs: Record): AsyncGenerator { + const agentExecutorIterator = new AgentExecutorIterator({ + inputs, + agentExecutor: this, + metadata: this.metadata, + tags: this.tags, + callbacks: this.callbacks + }) + const iterator = agentExecutorIterator.streamIterator() + for await (const step of iterator) { + if (!step) { + continue + } + yield step + } + } + + _chainType() { + return 'agent_executor' as const + } + + serialize(): SerializedLLMChain { + throw new Error('Cannot serialize an AgentExecutor') + } +} + +class ExceptionTool extends Tool { + name = '_Exception' + + description = 'Exception tool' + + async _call(query: string) { + return query + } +} + +export const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => + steps.flatMap(({ action, observation }) => { + if ('messageLog' in action && action.messageLog !== undefined) { + const log = action.messageLog as BaseMessage[] + return log.concat(new FunctionMessage(observation, action.tool)) + } else { + return [new AIMessage(action.log)] + } + }) diff --git a/packages/server/marketplaces/chatflows/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json index 93270848..eabc8f2e 100644 --- a/packages/server/marketplaces/chatflows/API Agent.json +++ b/packages/server/marketplaces/chatflows/API Agent.json @@ -936,7 +936,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Chat with a Podcast.json b/packages/server/marketplaces/chatflows/Chat with a Podcast.json index 2a9e05b5..0a5d4ac6 100644 --- a/packages/server/marketplaces/chatflows/Chat with a Podcast.json +++ b/packages/server/marketplaces/chatflows/Chat with a Podcast.json @@ -13,7 +13,7 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], @@ -28,47 +28,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -89,9 +78,8 @@ "model": "{{chatOpenAI_0.data.instance}}", "vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}", "memory": "", - "returnSourceDocuments": "", - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { @@ -625,9 +613,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Claude LLM.json b/packages/server/marketplaces/chatflows/Claude LLM.json index 0ead3dd8..39d4d400 100644 --- a/packages/server/marketplaces/chatflows/Claude LLM.json +++ b/packages/server/marketplaces/chatflows/Claude LLM.json @@ -90,7 +90,7 @@ ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationChain_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json index 8994594a..b27d3886 100644 --- a/packages/server/marketplaces/chatflows/Conversational Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Agent.json @@ -354,7 +354,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index 5c55d833..e2fd6421 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -249,10 +249,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -264,47 +264,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -325,16 +314,15 @@ "model": "{{chatOpenAI_0.data.instance}}", "vectorStoreRetriever": "{{pinecone_0.data.instance}}", "memory": "", - "returnSourceDocuments": "", - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -704,9 +692,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Flowise Docs QnA.json b/packages/server/marketplaces/chatflows/Flowise Docs QnA.json index ac84cf56..16f70801 100644 --- a/packages/server/marketplaces/chatflows/Flowise Docs QnA.json +++ b/packages/server/marketplaces/chatflows/Flowise Docs QnA.json @@ -156,9 +156,9 @@ "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", "name": "conversationalRetrievalQAChain", - "version": 1, + "version": 2, "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -170,47 +170,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -232,15 +221,15 @@ "vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}", "memory": "", "returnSourceDocuments": true, - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -668,9 +657,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json index e24ad7ca..6f78cb05 100644 --- a/packages/server/marketplaces/chatflows/Local QnA.json +++ b/packages/server/marketplaces/chatflows/Local QnA.json @@ -83,10 +83,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -98,47 +98,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -158,14 +147,16 @@ "inputs": { "model": "{{chatOllama_0.data.instance}}", "vectorStoreRetriever": "{{faiss_0.data.instance}}", - "memory": "" + "memory": "", + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -649,9 +640,9 @@ "source": "chatOllama_0", "sourceHandle": "chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOllama_0-chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOllama_0-chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Long Term Memory.json b/packages/server/marketplaces/chatflows/Long Term Memory.json index c39f746a..cf0fa4d4 100644 --- a/packages/server/marketplaces/chatflows/Long Term Memory.json +++ b/packages/server/marketplaces/chatflows/Long Term Memory.json @@ -13,10 +13,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -28,47 +28,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -89,14 +78,16 @@ "model": "{{chatOpenAI_0.data.instance}}", "vectorStoreRetriever": "{{qdrant_0.data.instance}}", "memory": "{{ZepMemory_0.data.instance}}", - "returnSourceDocuments": true + "returnSourceDocuments": true, + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -232,7 +223,7 @@ "label": "Session Id", "name": "sessionId", "type": "string", - "description": "if empty, chatId will be used automatically", + "description": "If not specified, a random id will be used. Learn more", "default": "", "additionalParams": true, "optional": true, @@ -709,9 +700,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Metadata Filter.json b/packages/server/marketplaces/chatflows/Metadata Filter.json index 9865ae70..abd85d36 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter.json @@ -249,10 +249,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -264,47 +264,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -323,14 +312,16 @@ ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{pinecone_0.data.instance}}" + "vectorStoreRetriever": "{{pinecone_0.data.instance}}", + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -763,9 +754,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json index a2a807cd..d53cb55e 100644 --- a/packages/server/marketplaces/chatflows/Multiple VectorDB.json +++ b/packages/server/marketplaces/chatflows/Multiple VectorDB.json @@ -1567,7 +1567,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index 2dac3823..2322136c 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -262,7 +262,7 @@ ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationChain_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index d9f9fb49..6f0edeea 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -190,7 +190,7 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], @@ -205,47 +205,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -267,8 +256,8 @@ "vectorStoreRetriever": "{{vectara_0.data.instance}}", "memory": "", "returnSourceDocuments": true, - "systemMessagePrompt": "", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { @@ -427,9 +416,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json index 0547366a..d905b54b 100644 --- a/packages/server/marketplaces/chatflows/WebBrowser.json +++ b/packages/server/marketplaces/chatflows/WebBrowser.json @@ -578,7 +578,7 @@ "id": "conversationalAgent_0-input-tools-Tool" }, { - "label": "Language Model", + "label": "Chat Model", "name": "model", "type": "BaseChatModel", "id": "conversationalAgent_0-input-model-BaseChatModel" diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index 9b1119b9..1b1d8de6 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -162,10 +162,10 @@ "data": { "id": "conversationalRetrievalQAChain_0", "label": "Conversational Retrieval QA Chain", - "version": 1, + "version": 2, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -177,47 +177,36 @@ "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" }, { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Rephrase Prompt", + "name": "rephrasePrompt", "type": "string", + "description": "Using previous chat history, rephrase question into a standalone question", + "warning": "Prompt must include input variables: {chat_history} and {question}", "rows": 4, "additionalParams": true, "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string" }, { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], + "label": "Response Prompt", + "name": "responsePrompt", + "type": "string", + "description": "Taking the rephrased question, search for answer from the provided context", + "warning": "Prompt must include input variable: {context}", + "rows": 4, "additionalParams": true, "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.", + "id": "conversationalRetrievalQAChain_0-input-responsePrompt-string" } ], "inputAnchors": [ { - "label": "Language Model", + "label": "Chat Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel" }, { "label": "Vector Store Retriever", @@ -239,15 +228,15 @@ "vectorStoreRetriever": "{{pinecone_0.data.instance}}", "memory": "{{RedisBackedChatMemory_0.data.instance}}", "returnSourceDocuments": true, - "systemMessagePrompt": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given context. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Do not make up any information that is not in the context. Refuse to answer any question not about the info. Never break character.", - "chainOption": "" + "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:", + "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer." }, "outputAnchors": [ { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", "name": "conversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" } ], "outputs": {}, @@ -589,7 +578,7 @@ "label": "Session Id", "name": "sessionId", "type": "string", - "description": "If not specified, the first CHAT_MESSAGE_ID will be used as sessionId", + "description": "If not specified, a random id will be used. Learn more", "default": "", "additionalParams": true, "optional": true, @@ -772,9 +761,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b6f59191..b446445e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -20,7 +20,6 @@ import { ICredentialReturnResponse, chatType, IChatMessage, - IReactFlowEdge, IDepthQueue, INodeDirectedGraph } from './Interface' @@ -39,14 +38,14 @@ import { databaseEntities, transformToCredentialEntity, decryptCredentialData, - clearAllSessionMemory, replaceInputsWithConfig, getEncryptionKey, - checkMemorySessionId, - clearSessionMemoryFromViewMessageDialog, + getMemorySessionId, getUserHome, - replaceChatHistory, - getAllConnectedNodes + getSessionChatHistory, + getAllConnectedNodes, + clearSessionMemory, + findMemoryNode } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -533,17 +532,18 @@ export class App { const parsedFlowData: IReactFlowObject = JSON.parse(flowData) const nodes = parsedFlowData.nodes - if (isClearFromViewMessageDialog) { - await clearSessionMemoryFromViewMessageDialog( + try { + await clearSessionMemory( nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId, - memoryType + memoryType, + isClearFromViewMessageDialog ) - } else { - await clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId) + } catch (e) { + return res.status(500).send('Error clearing chat messages') } const deleteOptions: FindOptionsWhere = { chatflowid, chatId } @@ -1398,26 +1398,6 @@ export class App { return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) } - /** - * Method that find memory label that is connected within chatflow - * In a chatflow, there should only be 1 memory node - * @param {IReactFlowNode[]} nodes - * @param {IReactFlowEdge[]} edges - * @returns {string | undefined} - */ - findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined { - const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') - const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) - - for (const edge of edges) { - if (memoryNodeIds.includes(edge.source)) { - const memoryNode = nodes.find((node) => node.data.id === edge.source) - return memoryNode - } - } - return undefined - } - async upsertVector(req: Request, res: Response, isInternal: boolean = false) { try { const chatflowid = req.params.id @@ -1586,7 +1566,6 @@ export class App { * - Still in sync (i.e the flow has not been modified since) * - Existing overrideConfig and new overrideConfig are the same * - Flow doesn't start with/contain nodes that depend on incomingInput.question - * - Its not an Upsert request * TODO: convert overrideConfig to hash when we no longer store base64 string but filepath ***/ const isFlowReusable = () => { @@ -1640,22 +1619,28 @@ export class App { isStreamValid = isFlowValidForStream(nodes, endingNodeData) } - let chatHistory: IMessage[] | string = incomingInput.history + let chatHistory: IMessage[] = incomingInput.history ?? [] - // When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory + // When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory node for (const endingNode of endingNodes) { const endingNodeData = endingNode.data + if (!endingNodeData.inputs?.memory) continue - if ( - endingNodeData.inputs?.memory && - !incomingInput.history && - (incomingInput.chatId || incomingInput.overrideConfig?.sessionId) - ) { - const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') - const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) - if (memoryNode) { - chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) - } + + const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') + const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) + + if (!memoryNode) continue + + if (!chatHistory.length && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { + chatHistory = await getSessionChatHistory( + memoryNode, + this.nodesPool.componentNodes, + incomingInput, + this.AppDataSource, + databaseEntities, + logger + ) } } @@ -1714,16 +1699,11 @@ export class App { logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - let sessionId = undefined - if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) - - const memoryNode = this.findMemoryLabel(nodes, edges) + const memoryNode = findMemoryNode(nodes, edges) const memoryType = memoryNode?.data.label - let chatHistory: IMessage[] | string = incomingInput.history - if (memoryNode && !incomingInput.history && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { - chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) - } + let sessionId = undefined + if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string const nodeModule = await import(nodeInstanceFilePath) @@ -1731,24 +1711,24 @@ export class App { let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatId, chatflowid, - chatHistory, - socketIO, - socketIOClientId: incomingInput.socketIOClientId, + chatHistory: incomingInput.history, logger, appDataSource: this.AppDataSource, databaseEntities, analytic: chatflow.analytic, - chatId + socketIO, + socketIOClientId: incomingInput.socketIOClientId }) : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatId, chatflowid, - chatHistory, + chatHistory: incomingInput.history, logger, appDataSource: this.AppDataSource, databaseEntities, - analytic: chatflow.analytic, - chatId + analytic: chatflow.analytic }) result = typeof result === 'string' ? { text: result } : result diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 9c2d1d79..dafe612c 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -26,7 +26,8 @@ import { getEncryptionKeyPath, ICommonObject, IDatabaseEntity, - IMessage + IMessage, + FlowiseMemory } from 'flowise-components' import { randomBytes } from 'crypto' import { AES, enc } from 'crypto-js' @@ -270,7 +271,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, - chatHistory: IMessage[] | string, + chatHistory: IMessage[], chatId: string, chatflowid: string, appDataSource: DataSource, @@ -317,9 +318,10 @@ export const buildLangchain = async ( await newNodeInstance.vectorStoreMethods!['upsert']!.call(newNodeInstance, reactFlowNodeData, { chatId, chatflowid, + chatHistory, + logger, appDataSource, databaseEntities, - logger, cachePool, dynamicVariables }) @@ -330,9 +332,10 @@ export const buildLangchain = async ( let outputResult = await newNodeInstance.init(reactFlowNodeData, question, { chatId, chatflowid, + chatHistory, + logger, appDataSource, databaseEntities, - logger, cachePool, dynamicVariables }) @@ -424,66 +427,52 @@ export const buildLangchain = async ( } /** - * Clear all session memories on the canvas - * @param {IReactFlowNode[]} reactFlowNodes - * @param {IComponentNodes} componentNodes - * @param {string} chatId - * @param {DataSource} appDataSource - * @param {string} sessionId - */ -export const clearAllSessionMemory = async ( - reactFlowNodes: IReactFlowNode[], - componentNodes: IComponentNodes, - chatId: string, - appDataSource: DataSource, - sessionId?: string -) => { - for (const node of reactFlowNodes) { - 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 (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { - await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) - } - } -} - -/** - * Clear specific session memory from View Message Dialog UI + * Clear session memories * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodes} componentNodes * @param {string} chatId * @param {DataSource} appDataSource * @param {string} sessionId * @param {string} memoryType + * @param {string} isClearFromViewMessageDialog */ -export const clearSessionMemoryFromViewMessageDialog = async ( +export const clearSessionMemory = async ( reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodes, chatId: string, appDataSource: DataSource, sessionId?: string, - memoryType?: string + memoryType?: string, + isClearFromViewMessageDialog?: string ) => { - if (!sessionId) return for (const node of reactFlowNodes) { if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue - if (memoryType && node.data.label !== memoryType) continue + + // Only clear specific session memory from View Message Dialog UI + if (isClearFromViewMessageDialog && 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() + const options: ICommonObject = { chatId, appDataSource, databaseEntities, logger } - if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - - if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { - await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) - return + // SessionId always take priority first because it is the sessionId used for 3rd party memory node + if (sessionId && node.data.inputs) { + if (node.data.type === 'OpenAIAssistant') { + await newNodeInstance.clearChatMessages(node.data, options, { type: 'threadId', id: sessionId }) + } else { + node.data.inputs.sessionId = sessionId + const initializedInstance: FlowiseMemory = await newNodeInstance.init(node.data, '', options) + await initializedInstance.clearChatMessages(sessionId) + } + } else if (chatId && node.data.inputs) { + if (node.data.type === 'OpenAIAssistant') { + await newNodeInstance.clearChatMessages(node.data, options, { type: 'chatId', id: chatId }) + } else { + node.data.inputs.sessionId = chatId + const initializedInstance: FlowiseMemory = await newNodeInstance.init(node.data, '', options) + await initializedInstance.clearChatMessages(chatId) + } } } } @@ -500,7 +489,7 @@ export const getVariableValue = ( paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] | string, + chatHistory: IMessage[], isAcceptVariable = false ) => { let returnVal = paramValue @@ -533,10 +522,7 @@ export const getVariableValue = ( } if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { - variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters( - typeof chatHistory === 'string' ? chatHistory : convertChatHistoryToText(chatHistory), - false - ) + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) } // Split by first occurrence of '.' to get just nodeId @@ -583,7 +569,7 @@ export const resolveVariables = ( reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] | string + chatHistory: IMessage[] ): INodeData => { let flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' @@ -970,21 +956,43 @@ export const redactCredentialWithPasswordType = ( } /** - * Replace sessionId with new chatId - * Ex: after clear chat history, use the new chatId as sessionId + * Get sessionId + * Hierarchy of sessionId (top down) + * API/Embed: + * (1) Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' } + * (2) Provided in API body - incomingInput.chatId + * + * API/Embed + UI: + * (3) Hard-coded sessionId in UI + * (4) Not specified on UI nor API, default to chatId * @param {any} instance + * @param {IncomingInput} incomingInput * @param {string} chatId */ -export const checkMemorySessionId = (instance: any, chatId: string): string | undefined => { - if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) { - instance.memory.sessionId = chatId - instance.memory.chatHistory.sessionId = chatId +export const getMemorySessionId = ( + memoryNode: IReactFlowNode, + incomingInput: IncomingInput, + chatId: string, + isInternal: boolean +): string | undefined => { + if (!isInternal) { + // Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' } + if (incomingInput.overrideConfig?.sessionId) { + return incomingInput.overrideConfig?.sessionId + } + // Provided in API body - incomingInput.chatId + if (incomingInput.chatId) { + return incomingInput.chatId + } } - if (instance.memory && instance.memory.sessionId) return instance.memory.sessionId - else if (instance.memory && instance.memory.chatHistory && instance.memory.chatHistory.sessionId) - return instance.memory.chatHistory.sessionId - return undefined + // Hard-coded sessionId in UI + if (memoryNode.data.inputs?.sessionId) { + return memoryNode.data.inputs.sessionId + } + + // Default chatId + return chatId } /** @@ -996,31 +1004,52 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un * @param {any} logger * @returns {string} */ -export const replaceChatHistory = async ( +export const getSessionChatHistory = async ( memoryNode: IReactFlowNode, + componentNodes: IComponentNodes, incomingInput: IncomingInput, appDataSource: DataSource, databaseEntities: IDatabaseEntity, logger: any -): Promise => { - const nodeInstanceFilePath = memoryNode.data.filePath as string +): Promise => { + const nodeInstanceFilePath = componentNodes[memoryNode.data.name].filePath as string const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() + // Replace memory's sessionId/chatId if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId + } else if (incomingInput.chatId && memoryNode.data.inputs) { + memoryNode.data.inputs.sessionId = incomingInput.chatId } - if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.getChatMessages) { - return await newNodeInstance.memoryMethods.getChatMessages(memoryNode.data, { - chatId: incomingInput.chatId, - appDataSource, - databaseEntities, - logger - }) - } + const initializedInstance: FlowiseMemory = await newNodeInstance.init(memoryNode.data, '', { + appDataSource, + databaseEntities, + logger + }) - return '' + return (await initializedInstance.getChatMessages()) as IMessage[] +} + +/** + * Method that find memory that is connected within chatflow + * In a chatflow, there should only be 1 memory node + * @param {IReactFlowNode[]} nodes + * @param {IReactFlowEdge[]} edges + * @returns {string | undefined} + */ +export const findMemoryNode = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined => { + const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') + const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) + + for (const edge of edges) { + if (memoryNodeIds.includes(edge.source)) { + const memoryNode = nodes.find((node) => node.data.id === edge.source) + return memoryNode + } + } + return undefined } /** diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 617d1066..a673d6b7 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -280,6 +280,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA style={{ display: 'flex', flexDirection: 'row', + alignItems: 'center', borderRadius: 10, background: 'rgb(254,252,191)', padding: 10, @@ -287,7 +288,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA marginBottom: 10 }} > - + {inputParam.warning} )} From 4364537595a9b1e914eabe834337451bc0053382 Mon Sep 17 00:00:00 2001 From: YISH Date: Wed, 10 Jan 2024 17:32:46 +0800 Subject: [PATCH 214/268] Add milvusTextField configuration for Milvus langchain python use `text` https://github.com/langchain-ai/langchain/blob/master/libs/community/langchain_community/vectorstores/milvus.py#L119 while langchian js use `langchain` https://github.com/langchain-ai/langchainjs/blob/main/libs/langchain-community/src/vectorstores/milvus.ts#L61 so it is necessary to add milvusTextField configuration for Milvus. --- .../components/nodes/vectorstores/Milvus/Milvus.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/vectorstores/Milvus/Milvus.ts b/packages/components/nodes/vectorstores/Milvus/Milvus.ts index 090f35f7..7566f8a8 100644 --- a/packages/components/nodes/vectorstores/Milvus/Milvus.ts +++ b/packages/components/nodes/vectorstores/Milvus/Milvus.ts @@ -65,6 +65,14 @@ class Milvus_VectorStores implements INode { name: 'milvusCollection', type: 'string' }, + { + label: 'Milvus Text Field', + name: 'milvusTextField', + type: 'string', + placeholder: 'langchain_text', + optional: true, + additionalParams: true + }, { label: 'Milvus Filter', name: 'milvusFilter', @@ -150,6 +158,7 @@ class Milvus_VectorStores implements INode { const address = nodeData.inputs?.milvusServerUrl as string const collectionName = nodeData.inputs?.milvusCollection as string const milvusFilter = nodeData.inputs?.milvusFilter as string + const textField = nodeData.inputs?.milvusTextField as string // embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings @@ -169,7 +178,8 @@ class Milvus_VectorStores implements INode { // init MilvusLibArgs const milVusArgs: MilvusLibArgs = { url: address, - collectionName: collectionName + collectionName: collectionName, + textField: textField } if (milvusUser) milVusArgs.username = milvusUser From 4b9b30bf7a0eaf3840211995e99bea79a0c38fbb Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 00:31:21 +0500 Subject: [PATCH 215/268] feature: Integrate Astra Vectorstore --- .../credentials/AstraApi.credential.ts | 34 ++++ .../nodes/vectorstores/Astra/Astra.ts | 190 ++++++++++++++++++ .../nodes/vectorstores/Astra/astra.svg | 1 + packages/components/package.json | 2 + 4 files changed, 227 insertions(+) create mode 100644 packages/components/credentials/AstraApi.credential.ts create mode 100644 packages/components/nodes/vectorstores/Astra/Astra.ts create mode 100644 packages/components/nodes/vectorstores/Astra/astra.svg diff --git a/packages/components/credentials/AstraApi.credential.ts b/packages/components/credentials/AstraApi.credential.ts new file mode 100644 index 00000000..ad4c65a8 --- /dev/null +++ b/packages/components/credentials/AstraApi.credential.ts @@ -0,0 +1,34 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class AstraApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Astra API' + this.name = 'AstraApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Colection Name', + name: 'collectionName', + type: 'string' + }, + { + label: 'Astra DB Application Token', + name: 'applicationToken', + type: 'password' + }, + { + label: 'Astra DB Api Endpoint', + name: 'dbEndPoint', + type: 'string' + } + ] + } +} + +module.exports = { credClass: AstraApi } diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts new file mode 100644 index 00000000..648a8b49 --- /dev/null +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -0,0 +1,190 @@ +import { flatten } from 'lodash' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData } from '../../../src/utils' +import { AstraDBVectorStore, AstraLibArgs } from '@langchain/community/vectorstores/astradb' + +class Astra_VectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + badge: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Astra' + this.name = 'Astra' + this.version = 1.0 + this.type = 'Astra' + this.icon = 'astra.svg' + this.category = 'Vector Stores' + this.description = `Upsert embedded data and perform similarity search upon query using DataStax Astra DB, a serverless vector database that’s perfect for managing mission-critical AI workloads` + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.badge = 'NEW' + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['AstraApi'] + } + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true, + optional: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Vector Dimension', + name: 'vectorDimension', + type: 'number', + placeholder: '1536', + optional: true, + description: 'Dimension used for storing vector embedding' + }, + { + label: 'Similarity Metric', + name: 'similarityMetric', + type: 'string', + placeholder: 'cosine', + optional: true, + description: 'cosine | euclidean | dot_product' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Astra Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Astra Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(AstraDBVectorStore)] + } + ] + } + + //@ts-ignore + vectorStoreMethods = { + async upsert(nodeData: INodeData, options: ICommonObject): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const vectorDimension = nodeData.inputs?.vectorDimension as number + const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + + const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product'] + if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) { + throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`) + } + + const clientConfig = { + token: credentialData?.applicationToken ?? 'dummy', + endpoint: credentialData?.dbEndPoint ?? 'dummy' + } + + const astraConfig: AstraLibArgs = { + ...clientConfig, + collection: credentialData.collectionName ?? 'flowise_test', + collectionOptions: { + vector: { + dimension: vectorDimension ?? 1536, + metric: similarityMetric ?? 'cosine' + } + } + } + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + + try { + await AstraDBVectorStore.fromDocuments(finalDocs, embeddings, astraConfig) + } catch (e) { + throw new Error(e) + } + } + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const vectorDimension = nodeData.inputs?.vectorDimension as number + const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + + const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product'] + if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) { + throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`) + } + + const clientConfig = { + token: credentialData?.applicationToken ?? 'dummy', + endpoint: credentialData?.dbEndPoint ?? 'dummy' + } + + const astraConfig: AstraLibArgs = { + ...clientConfig, + collection: credentialData.collectionName ?? 'flowise_test', + collectionOptions: { + vector: { + dimension: vectorDimension ?? 1536, + metric: similarityMetric ?? 'cosine' + } + } + } + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + + const vectorStore = await AstraDBVectorStore.fromExistingIndex(embeddings, astraConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Astra_VectorStores } diff --git a/packages/components/nodes/vectorstores/Astra/astra.svg b/packages/components/nodes/vectorstores/Astra/astra.svg new file mode 100644 index 00000000..59c2fc3f --- /dev/null +++ b/packages/components/nodes/vectorstores/Astra/astra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index a77d91e4..ff0b687b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -19,6 +19,7 @@ "@aws-sdk/client-bedrock-runtime": "3.422.0", "@aws-sdk/client-dynamodb": "^3.360.0", "@aws-sdk/client-s3": "^3.427.0", + "@datastax/astra-db-ts": "^0.1.2", "@dqbd/tiktoken": "^1.0.7", "@elastic/elasticsearch": "^8.9.0", "@getzep/zep-js": "^0.9.0", @@ -26,6 +27,7 @@ "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", + "@langchain/community": "^0.0.16", "@langchain/google-genai": "^0.0.6", "@langchain/mistralai": "^0.0.6", "@notionhq/client": "^2.2.8", From 4b39b4115b01b4d294af8d3375c9ff45a9df957a Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 01:36:57 +0500 Subject: [PATCH 216/268] chore: refactoring (naming convention) --- packages/components/credentials/AstraApi.credential.ts | 10 +++++----- packages/components/nodes/vectorstores/Astra/Astra.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/credentials/AstraApi.credential.ts b/packages/components/credentials/AstraApi.credential.ts index ad4c65a8..3bec1a49 100644 --- a/packages/components/credentials/AstraApi.credential.ts +++ b/packages/components/credentials/AstraApi.credential.ts @@ -1,6 +1,6 @@ import { INodeParams, INodeCredential } from '../src/Interface' -class AstraApi implements INodeCredential { +class AstraDBApi implements INodeCredential { label: string name: string version: number @@ -8,12 +8,12 @@ class AstraApi implements INodeCredential { inputs: INodeParams[] constructor() { - this.label = 'Astra API' - this.name = 'AstraApi' + this.label = 'Astra DB API' + this.name = 'AstraDBApi' this.version = 1.0 this.inputs = [ { - label: 'Colection Name', + label: 'Collection Name', name: 'collectionName', type: 'string' }, @@ -31,4 +31,4 @@ class AstraApi implements INodeCredential { } } -module.exports = { credClass: AstraApi } +module.exports = { credClass: AstraDBApi } diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts index 648a8b49..e3377cb5 100644 --- a/packages/components/nodes/vectorstores/Astra/Astra.ts +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -33,7 +33,7 @@ class Astra_VectorStores implements INode { label: 'Connect Credential', name: 'credential', type: 'credential', - credentialNames: ['AstraApi'] + credentialNames: ['AstraDBApi'] } this.inputs = [ { From b3c9c32a4869ec51219ca30eac8ec105b56fcb64 Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 01:43:22 +0500 Subject: [PATCH 217/268] Update AstraApi.credential.ts --- packages/components/credentials/AstraApi.credential.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/credentials/AstraApi.credential.ts b/packages/components/credentials/AstraApi.credential.ts index 3bec1a49..a89a259f 100644 --- a/packages/components/credentials/AstraApi.credential.ts +++ b/packages/components/credentials/AstraApi.credential.ts @@ -13,7 +13,7 @@ class AstraDBApi implements INodeCredential { this.version = 1.0 this.inputs = [ { - label: 'Collection Name', + label: 'Astra DB Collection Name', name: 'collectionName', type: 'string' }, From adfeb37e8bcf2a4db044460a80a7b2c927e9f3d9 Mon Sep 17 00:00:00 2001 From: hakeemsyd Date: Fri, 12 Jan 2024 02:43:43 +0500 Subject: [PATCH 218/268] svg added and refactored again --- .../components/nodes/vectorstores/Astra/Astra.ts | 8 ++++---- .../components/nodes/vectorstores/Astra/astra.svg | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts index e3377cb5..865f1044 100644 --- a/packages/components/nodes/vectorstores/Astra/Astra.ts +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -103,8 +103,8 @@ class Astra_VectorStores implements INode { } const clientConfig = { - token: credentialData?.applicationToken ?? 'dummy', - endpoint: credentialData?.dbEndPoint ?? 'dummy' + token: credentialData?.applicationToken, + endpoint: credentialData?.dbEndPoint } const astraConfig: AstraLibArgs = { @@ -151,8 +151,8 @@ class Astra_VectorStores implements INode { } const clientConfig = { - token: credentialData?.applicationToken ?? 'dummy', - endpoint: credentialData?.dbEndPoint ?? 'dummy' + token: credentialData?.applicationToken, + endpoint: credentialData?.dbEndPoint } const astraConfig: AstraLibArgs = { diff --git a/packages/components/nodes/vectorstores/Astra/astra.svg b/packages/components/nodes/vectorstores/Astra/astra.svg index 59c2fc3f..de58397d 100644 --- a/packages/components/nodes/vectorstores/Astra/astra.svg +++ b/packages/components/nodes/vectorstores/Astra/astra.svg @@ -1 +1,12 @@ - \ No newline at end of file + + + + + + + + + + + + From b1b9b9fcffe3d45abdd022a5ac2496c9044185fe Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 18:36:23 -0800 Subject: [PATCH 219/268] added support for MMR --- .../nodes/chains/VectaraChain/VectaraChain.ts | 38 ++++++++++++++----- .../nodes/vectorstores/Vectara/Vectara.ts | 31 +++++++++++++-- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index 3799d062..c80b354f 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -69,22 +69,23 @@ class VectaraChain_Chains implements INode { options: [ { label: 'vectara-summary-ext-v1.2.0 (gpt-3.5-turbo)', - name: 'vectara-summary-ext-v1.2.0' + name: 'vectara-summary-ext-v1.2.0', + description: 'base summarizer, available to all Vectara users' }, { label: 'vectara-experimental-summary-ext-2023-10-23-small (gpt-3.5-turbo)', name: 'vectara-experimental-summary-ext-2023-10-23-small', - description: 'In beta, available to both Growth and Scale Vectara users' + description: `In beta, available to both Growth and Scale Vectara users` }, { label: 'vectara-summary-ext-v1.3.0 (gpt-4.0)', name: 'vectara-summary-ext-v1.3.0', - description: 'Only available to paying Scale Vectara users' + description: 'Only available to Scale Vectara users' }, { label: 'vectara-experimental-summary-ext-2023-10-23-med (gpt-4.0)', name: 'vectara-experimental-summary-ext-2023-10-23-med', - description: 'In beta, only available to paying Scale Vectara users' + description: `In beta, only available to Scale Vectara users` } ], default: 'vectara-summary-ext-v1.2.0' @@ -228,7 +229,7 @@ class VectaraChain_Chains implements INode { async run(nodeData: INodeData, input: string): Promise { const vectorStore = nodeData.inputs?.vectaraStore as VectaraStore - const responseLang = (nodeData.inputs?.responseLang as string) ?? 'auto' + const responseLang = (nodeData.inputs?.responseLang as string) ?? 'eng' const summarizerPromptName = nodeData.inputs?.summarizerPromptName as string const maxSummarizedResultsStr = nodeData.inputs?.maxSummarizedResults as string const maxSummarizedResults = maxSummarizedResultsStr ? parseInt(maxSummarizedResultsStr, 10) : 7 @@ -247,17 +248,28 @@ class VectaraChain_Chains implements INode { lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 } })) + const mmrRerankerId = 272725718 // Vectara reranker ID for MMR const data = { query: [ { query: input, start: 0, - numResults: topK, + numResults: vectaraFilter?.mmrConfig?.mmrK > 0 ? vectaraFilter?.mmrK : topK, + corpusKey: corpusKeys, contextConfig: { sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2 }, - corpusKey: corpusKeys, + ...(vectaraFilter?.mmrConfig?.mmrK > 0 + ? { + rerankingConfig: { + rerankerId: mmrRerankerId, + mmrConfig: { + diversityBias: vectaraFilter?.mmrConfig.diversityBias + } + } + } + : {}), summary: [ { summarizerPromptName, @@ -285,6 +297,14 @@ class VectaraChain_Chains implements INode { const documents = result.responseSet[0].document let rawSummarizedText = '' + // remove responses that are not in the topK (in case of MMR) + // Note that this does not really matter functionally due to the reorder citations, but it is more efficient + const maxResponses = vectaraFilter?.mmrConfig?.mmrK > 0 ? Math.min(responses.length, topK) : responses.length + if (responses.length > maxResponses) { + responses.splice(0, maxResponses) + } + + // Add metadata to each text response given its corresponding document metadata for (let i = 0; i < responses.length; i += 1) { const responseMetadata = responses[i].metadata const documentMetadata = documents[responses[i].documentIndex].metadata @@ -301,13 +321,13 @@ class VectaraChain_Chains implements INode { responses[i].metadata = combinedMetadata } + // Create the summarization response const summaryStatus = result.responseSet[0].summary[0].status if (summaryStatus.length > 0 && summaryStatus[0].code === 'BAD_REQUEST') { throw new Error( `BAD REQUEST: Too much text for the summarizer to summarize. Please try reducing the number of search results to summarize, or the context of each result by adjusting the 'summary_num_sentences', and 'summary_num_results' parameters respectively.` ) } - if ( summaryStatus.length > 0 && summaryStatus[0].code === 'NOT_FOUND' && @@ -316,8 +336,8 @@ class VectaraChain_Chains implements INode { throw new Error(`BAD REQUEST: summarizer ${summarizerPromptName} is invalid for this account.`) } + // Reorder citations in summary and create the list of returned source documents rawSummarizedText = result.responseSet[0].summary[0]?.text - let summarizedText = reorderCitations(rawSummarizedText) let summaryResponses = applyCitationOrder(responses, rawSummarizedText) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 7460c586..98acf00c 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -82,7 +82,9 @@ class Vectara_VectorStores implements INode { label: 'Lambda', name: 'lambda', description: - 'Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.', + 'Enable hybrid search to improve retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.' + + 'A value of 0.0 means that only neural search is used, while a value of 1.0 means that only keyword-based search is used. Defaults to 0.0 (neural only).', + default: 0.0, type: 'number', additionalParams: true, optional: true @@ -90,8 +92,26 @@ class Vectara_VectorStores implements INode { { label: 'Top K', name: 'topK', - description: 'Number of top results to fetch. Defaults to 4', - placeholder: '4', + description: 'Number of top results to fetch. Defaults to 5', + placeholder: '5', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'MMR K', + name: 'mmrK', + description: 'Number of top results to fetch for MMR. Defaults to 50', + placeholder: '50', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'MMR diversity bias', + name: 'mmrDiversityBias', + description: 'The diversity bias to use for MMR. Defaults to 0.3', + placeholder: '0.3', type: 'number', additionalParams: true, optional: true @@ -191,7 +211,9 @@ class Vectara_VectorStores implements INode { const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 + const k = topK ? parseFloat(topK) : 5 + const mmrK = nodeData.inputs?.mmrK as number + const mmrDiversityBias = nodeData.inputs?.mmrDiversityBias as number const vectaraArgs: VectaraLibArgs = { apiKey: apiKey, @@ -208,6 +230,7 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig + if (mmrK) vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } const vectorStore = new VectaraStore(vectaraArgs) From 78bc93cc9e68392b3a9e10fcb196c01fe9da42c4 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 18:59:49 -0800 Subject: [PATCH 220/268] bug fix --- .../components/nodes/chains/VectaraChain/VectaraChain.ts | 8 +++++--- packages/components/nodes/vectorstores/Vectara/Vectara.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index c80b354f..16257b69 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -249,18 +249,20 @@ class VectaraChain_Chains implements INode { })) const mmrRerankerId = 272725718 // Vectara reranker ID for MMR + const mmrEnabled = vectaraFilter?.mmrConfig?.mmrDiversityBias > 0 + const data = { query: [ { query: input, start: 0, - numResults: vectaraFilter?.mmrConfig?.mmrK > 0 ? vectaraFilter?.mmrK : topK, + numResults: mmrEnabled ? vectaraFilter?.mmrK : topK, corpusKey: corpusKeys, contextConfig: { sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2 }, - ...(vectaraFilter?.mmrConfig?.mmrK > 0 + ...(mmrEnabled ? { rerankingConfig: { rerankerId: mmrRerankerId, @@ -299,7 +301,7 @@ class VectaraChain_Chains implements INode { // remove responses that are not in the topK (in case of MMR) // Note that this does not really matter functionally due to the reorder citations, but it is more efficient - const maxResponses = vectaraFilter?.mmrConfig?.mmrK > 0 ? Math.min(responses.length, topK) : responses.length + const maxResponses = mmrEnabled ? Math.min(responses.length, topK) : responses.length if (responses.length > maxResponses) { responses.splice(0, maxResponses) } diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 98acf00c..488a8803 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -110,8 +110,8 @@ class Vectara_VectorStores implements INode { { label: 'MMR diversity bias', name: 'mmrDiversityBias', - description: 'The diversity bias to use for MMR. Defaults to 0.3', - placeholder: '0.3', + description: 'The diversity bias to use for MMR. Defaults to 0 (MMR disabled)', + placeholder: '0.0', type: 'number', additionalParams: true, optional: true @@ -230,7 +230,7 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig - if (mmrK) vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } + vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } const vectorStore = new VectaraStore(vectaraArgs) From 356137b88bb2a95dc328354fec06b4a91edb6c97 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 19:18:33 -0800 Subject: [PATCH 221/268] bug fix 2 --- .../nodes/vectorstores/Vectara/Vectara.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 488a8803..d83f6cb9 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -1,5 +1,12 @@ import { flatten } from 'lodash' -import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile } from 'langchain/vectorstores/vectara' +import { + VectaraStore, + VectaraLibArgs, + VectaraFilter, + VectaraContextConfig, + VectaraFile, + VectaraMMRConfig +} from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { Embeddings } from 'langchain/embeddings/base' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' @@ -230,7 +237,10 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig - vectaraFilter.mmrConfig = { mmrK: mmrK, diversityBias: mmrDiversityBias } + const mmrConfig: VectaraMMRConfig = {} + mmrConfig.mmrK = mmrK + mmrConfig.diversityBias = mmrDiversityBias + vectaraFilter.mmrConfig = mmrConfig const vectorStore = new VectaraStore(vectaraArgs) From 0c2252c642eb5cb9b7d373ecfd10d714f058a93d Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 21:53:48 -0800 Subject: [PATCH 222/268] bugfix --- .../nodes/chains/VectaraChain/VectaraChain.ts | 4 ++-- .../nodes/vectorstores/Vectara/Vectara.ts | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index 16257b69..986d587a 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -249,14 +249,14 @@ class VectaraChain_Chains implements INode { })) const mmrRerankerId = 272725718 // Vectara reranker ID for MMR - const mmrEnabled = vectaraFilter?.mmrConfig?.mmrDiversityBias > 0 + const mmrEnabled = vectaraFilter?.mmrConfig?.enabled const data = { query: [ { query: input, start: 0, - numResults: mmrEnabled ? vectaraFilter?.mmrK : topK, + numResults: mmrEnabled ? vectaraFilter?.mmrTopK : topK, corpusKey: corpusKeys, contextConfig: { sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index d83f6cb9..be63d582 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -1,12 +1,5 @@ import { flatten } from 'lodash' -import { - VectaraStore, - VectaraLibArgs, - VectaraFilter, - VectaraContextConfig, - VectaraFile, - VectaraMMRConfig -} from 'langchain/vectorstores/vectara' +import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile, MMRConfig } from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { Embeddings } from 'langchain/embeddings/base' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' @@ -237,8 +230,9 @@ class Vectara_VectorStores implements INode { if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter vectaraFilter.contextConfig = vectaraContextConfig - const mmrConfig: VectaraMMRConfig = {} - mmrConfig.mmrK = mmrK + const mmrConfig: MMRConfig = {} + mmrConfig.enabled = mmrDiversityBias > 0 + mmrConfig.mmrTopK = mmrK mmrConfig.diversityBias = mmrDiversityBias vectaraFilter.mmrConfig = mmrConfig From 2ae8a60ec103029756c3d7a73bc32939b32cd4bb Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 23:40:04 -0800 Subject: [PATCH 223/268] na --- package.json | 2 +- packages/components/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5a9bfcbf..19a79da7 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,5 @@ }, "engines": { "node": ">=18.15.0" - } + }, } diff --git a/packages/components/package.json b/packages/components/package.json index ff0b687b..57ab7b3d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -50,7 +50,7 @@ "faiss-node": "^0.2.2", "fast-json-patch": "^3.1.1", "form-data": "^4.0.0", - "google-auth-library": "^9.0.0", + "google-auth-library": "^9.4.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", "husky": "^8.0.3", From 9a63d30333d6262b12bed0a30e8a55a81289d06b Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 23:41:19 -0800 Subject: [PATCH 224/268] extra comma --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19a79da7..5a9bfcbf 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,5 @@ }, "engines": { "node": ">=18.15.0" - }, + } } From c93b01e8212bdb5b40189b18c17785f69501e5aa Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 07:25:54 -0800 Subject: [PATCH 225/268] updates per PR comments --- .../components/nodes/chains/VectaraChain/VectaraChain.ts | 3 ++- packages/components/nodes/vectorstores/Vectara/Vectara.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts index 986d587a..7d65c9cd 100644 --- a/packages/components/nodes/chains/VectaraChain/VectaraChain.ts +++ b/packages/components/nodes/chains/VectaraChain/VectaraChain.ts @@ -248,7 +248,8 @@ class VectaraChain_Chains implements INode { lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 } })) - const mmrRerankerId = 272725718 // Vectara reranker ID for MMR + // Vectara reranker ID for MMR (https://docs.vectara.com/docs/api-reference/search-apis/reranking#maximal-marginal-relevance-mmr-reranker) + const mmrRerankerId = 272725718 const mmrEnabled = vectaraFilter?.mmrConfig?.enabled const data = { diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index be63d582..df709e0b 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -110,7 +110,10 @@ class Vectara_VectorStores implements INode { { label: 'MMR diversity bias', name: 'mmrDiversityBias', - description: 'The diversity bias to use for MMR. Defaults to 0 (MMR disabled)', + description: + 'The diversity bias to use for MMR. This is a value between 0.0 and 1.0' + + 'Values closer to 1.0 optimize for the most diverse results.' + + 'Defaults to 0 (MMR disabled)', placeholder: '0.0', type: 'number', additionalParams: true, From 2484d2a6766f8a076e6632f9c47f6c156bc968fd Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 10:26:17 -0800 Subject: [PATCH 226/268] added step to diversityBias --- packages/components/nodes/vectorstores/Vectara/Vectara.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index df709e0b..45825b4f 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -110,6 +110,7 @@ class Vectara_VectorStores implements INode { { label: 'MMR diversity bias', name: 'mmrDiversityBias', + step: 0.1, description: 'The diversity bias to use for MMR. This is a value between 0.0 and 1.0' + 'Values closer to 1.0 optimize for the most diverse results.' + From 7acbc0b0684ec447343fada8bd38269bb1e3b713 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 16:09:50 -0800 Subject: [PATCH 227/268] updated component to V2.0 Updated marketplace "Chain Upload" JSON file --- .../nodes/vectorstores/Vectara/Vectara.ts | 2 +- .../chatflows/Vectara LLM Chain Upload.json | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara.ts b/packages/components/nodes/vectorstores/Vectara/Vectara.ts index 45825b4f..939a4ac3 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara.ts @@ -22,7 +22,7 @@ class Vectara_VectorStores implements INode { constructor() { this.label = 'Vectara' this.name = 'vectara' - this.version = 1.0 + this.version = 2.0 this.type = 'Vectara' this.icon = 'vectara.png' this.category = 'Vector Stores' diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 6f0edeea..e544486e 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -350,13 +350,36 @@ { "label": "Top K", "name": "topK", - "description": "Number of top results to fetch. Defaults to 4", - "placeholder": "4", + "description": "Number of top results to fetch. Defaults to 5", + "placeholder": "5", "type": "number", "additionalParams": true, "optional": true, "id": "vectara_0-input-topK-number" + }, + { + "label": "MMR K", + "name": "mmrK", + "description": "The number of results to rerank if MMR is enabled.", + "placeholder": "50", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectara_0-input-mmrK-number" + }, + { + "label": "MMR Diversity Bias", + "name": "mmrDiversityBias", + "step": 0.1, + "description": "Diversity Bias parameter for MMR, if enabled. 0.0 means no diversiry bias, 1.0 means maximum diversity bias. Defaults to 0.0 (MMR disabled).", + "placeholder": "0.0", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectara_0-input-mmrDiversityBias-number" } + + ], "inputAnchors": [ { @@ -374,7 +397,9 @@ "sentencesBefore": "", "sentencesAfter": "", "lambda": "", - "topK": "" + "topK": "", + "mmrK": "", + "mmrDiversityBias": "" }, "outputAnchors": [ { From a3ea487ecb0d108fec12b3fca31d7d8950841e6f Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Mon, 8 Jan 2024 16:46:36 -0800 Subject: [PATCH 228/268] after yarn lint-fix --- .../marketplaces/chatflows/Vectara LLM Chain Upload.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index e544486e..1a440be7 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -366,7 +366,7 @@ "additionalParams": true, "optional": true, "id": "vectara_0-input-mmrK-number" - }, + }, { "label": "MMR Diversity Bias", "name": "mmrDiversityBias", @@ -378,8 +378,6 @@ "optional": true, "id": "vectara_0-input-mmrDiversityBias-number" } - - ], "inputAnchors": [ { From c8f624de9c108ef3856bcb8f9c7336dd4e1e20f2 Mon Sep 17 00:00:00 2001 From: YISH Date: Wed, 10 Jan 2024 17:41:53 +0800 Subject: [PATCH 229/268] Fix OpenAIFunctionAgent that function not return string result refer to https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/agents/format_scratchpad/openai_functions.py#L29 and fix the role of systemMessage from `ai` to `system`. --- .../nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 135121d2..c21c887a 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -112,7 +112,7 @@ const prepareAgent = ( const inputKey = memory.inputKey ? memory.inputKey : 'input' const prompt = ChatPromptTemplate.fromMessages([ - ['ai', systemMessage ? systemMessage : `You are a helpful AI assistant.`], + ['system', systemMessage ? systemMessage : `You are a helpful AI assistant.`], new MessagesPlaceholder(memoryKey), ['human', `{${inputKey}}`], new MessagesPlaceholder('agent_scratchpad') From 79e988be09dea9560aada68f1fd601af19c12666 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 14 Jan 2024 11:57:53 +0000 Subject: [PATCH 230/268] delete message API --- packages/server/src/index.ts | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b446445e..94a3b538 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -522,7 +522,7 @@ export class App { res.status(404).send(`Chatflow ${chatflowid} not found`) return } - const chatId = (req.query?.chatId as string) ?? (await getChatId(chatflowid)) + const chatId = req.query?.chatId as string const memoryType = req.query?.memoryType as string | undefined const sessionId = req.query?.sessionId as string | undefined const chatType = req.query?.chatType as string | undefined @@ -546,7 +546,8 @@ export class App { return res.status(500).send('Error clearing chat messages') } - const deleteOptions: FindOptionsWhere = { chatflowid, chatId } + const deleteOptions: FindOptionsWhere = { chatflowid } + if (chatId) deleteOptions.chatId = chatId if (memoryType) deleteOptions.memoryType = memoryType if (sessionId) deleteOptions.sessionId = sessionId if (chatType) deleteOptions.chatType = chatType @@ -634,7 +635,7 @@ export class App { return res.json(result) }) - // Delete all chatmessages from chatflowid + // Delete all credentials from chatflowid this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id }) return res.json(results) @@ -1791,23 +1792,6 @@ export class App { } } -/** - * Get first chat message id - * @param {string} chatflowid - * @returns {string} - */ -export async function getChatId(chatflowid: string): Promise { - // first chatmessage id as the unique chat id - const firstChatMessage = await getDataSource() - .getRepository(ChatMessage) - .createQueryBuilder('cm') - .select('cm.id') - .where('chatflowid = :chatflowid', { chatflowid }) - .orderBy('cm.createdDate', 'ASC') - .getOne() - return firstChatMessage ? firstChatMessage.id : '' -} - let serverApp: App | undefined export async function getAllChatFlow(): Promise { From 58f9e88b1f2c6a08b8be96964afe07ebd25cc890 Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Sat, 6 Jan 2024 17:16:06 -0700 Subject: [PATCH 231/268] Introduce new credential for LocalAI, Pass optional auth to LocalAI, New env var --- .../credentials/LcoalAIApi.credential.ts | 23 +++++++++++++++ .../chatmodels/ChatLocalAI/ChatLocalAI.ts | 29 +++++++++++++++---- packages/server/.env.example | 2 ++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 packages/components/credentials/LcoalAIApi.credential.ts diff --git a/packages/components/credentials/LcoalAIApi.credential.ts b/packages/components/credentials/LcoalAIApi.credential.ts new file mode 100644 index 00000000..624e07fa --- /dev/null +++ b/packages/components/credentials/LcoalAIApi.credential.ts @@ -0,0 +1,23 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class LocalAIApi implements INodeCredential { + label: string + name: string + version: number + inputs: INodeParams[] + + constructor() { + this.label = 'LocalAI API' + this.name = 'LocalAIApi' + this.version = 1.0 + this.inputs = [ + { + label: 'LocalAI Api Key', + name: 'LocalAIApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: LocalAIApi } diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index 18ed409b..258db1f8 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIChat } from 'langchain/llms/openai' import { OpenAIChatInput } from 'langchain/chat_models/openai' import { BaseCache } from 'langchain/schema' @@ -14,6 +14,7 @@ class ChatLocalAI_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -25,6 +26,16 @@ class ChatLocalAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Use local LLMs like llama.cpp, gpt4all using LocalAI' this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(OpenAIChat)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['LocalAIApi'], + optional: true + } + + const modelOptions = JSON.parse(process.env.LOCALAI_CHAT_MODELS || '[]'); + this.inputs = [ { label: 'Cache', @@ -41,8 +52,10 @@ class ChatLocalAI_ChatModels implements INode { { label: 'Model Name', name: 'modelName', - type: 'string', - placeholder: 'gpt4all-lora-quantized.bin' + type: 'options', + options: modelOptions, + default: modelOptions.length > 0 ? modelOptions[0].name : '', + optional: true }, { label: 'Temperature', @@ -79,19 +92,23 @@ class ChatLocalAI_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) + const cache = nodeData.inputs?.cache as BaseCache const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, - openAIApiKey: 'sk-' + openAIApiKey } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/server/.env.example b/packages/server/.env.example index 6e746a4d..9b7be0ff 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -26,3 +26,5 @@ PORT=3000 # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_PROJECT=your_project + +# LOCALAI_CHAT_MODELS='[{"label": "model1", "name": "model1"}, {"label": "model2", "name": "model2"}]' From d9b75cdf8e81721cd2c713084cfb930230ca8010 Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Sat, 6 Jan 2024 17:33:41 -0700 Subject: [PATCH 232/268] Updating docs --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04cb80b4..2c91906c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,6 +141,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | +| LOCALAI_CHAT_MODELS | JSON-encoded string representing an array of chat models for LocalAI. Each object in the array should have a 'label' and 'name' property. | String | '[]' (Empty Array) | You can also specify the env variables when using `npx`. For example: From 06201e7cf09dc5e206f7902611cc9716c021e0c8 Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Mon, 8 Jan 2024 17:53:18 -0700 Subject: [PATCH 233/268] Revert model var to string, refactor for case without a key and just override if so --- CONTRIBUTING.md | 1 - .../nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts | 17 ++++++----------- packages/server/.env.example | 2 -- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c91906c..04cb80b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,7 +141,6 @@ Flowise support different environment variables to configure your instance. You | DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | -| LOCALAI_CHAT_MODELS | JSON-encoded string representing an array of chat models for LocalAI. Each object in the array should have a 'label' and 'name' property. | String | '[]' (Empty Array) | You can also specify the env variables when using `npx`. For example: diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index 258db1f8..c44f03ce 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -33,9 +33,6 @@ class ChatLocalAI_ChatModels implements INode { credentialNames: ['LocalAIApi'], optional: true } - - const modelOptions = JSON.parse(process.env.LOCALAI_CHAT_MODELS || '[]'); - this.inputs = [ { label: 'Cache', @@ -52,10 +49,8 @@ class ChatLocalAI_ChatModels implements INode { { label: 'Model Name', name: 'modelName', - type: 'options', - options: modelOptions, - default: modelOptions.length > 0 ? modelOptions[0].name : '', - optional: true + type: 'string', + placeholder: 'gpt4all-lora-quantized.bin' }, { label: 'Temperature', @@ -99,22 +94,22 @@ class ChatLocalAI_ChatModels implements INode { const topP = nodeData.inputs?.topP as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string - const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const openAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) + const localAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) const cache = nodeData.inputs?.cache as BaseCache - const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { + const obj: Partial & BaseLLMParams & { localAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, - openAIApiKey + openAIApiKey: 'sk-' } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseFloat(topP) if (timeout) obj.timeout = parseInt(timeout, 10) if (cache) obj.cache = cache + if (localAIApiKey) obj.openAIApiKey = localAIApiKey const model = new OpenAIChat(obj, { basePath }) diff --git a/packages/server/.env.example b/packages/server/.env.example index 9b7be0ff..6e746a4d 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -26,5 +26,3 @@ PORT=3000 # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_PROJECT=your_project - -# LOCALAI_CHAT_MODELS='[{"label": "model1", "name": "model1"}, {"label": "model2", "name": "model2"}]' From b9d1d75d6a043770cf3ca2f3db7781746423ea8f Mon Sep 17 00:00:00 2001 From: Keith Kacsh Date: Sat, 13 Jan 2024 19:14:45 -0700 Subject: [PATCH 234/268] Fixing naming, handling embeddings for LocalAI also --- ....credential.ts => LocalAIApi.credential.ts} | 4 ++-- .../chatmodels/ChatLocalAI/ChatLocalAI.ts | 6 +++--- .../LocalAIEmbedding/LocalAIEmbedding.ts | 18 ++++++++++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) rename packages/components/credentials/{LcoalAIApi.credential.ts => LocalAIApi.credential.ts} (86%) diff --git a/packages/components/credentials/LcoalAIApi.credential.ts b/packages/components/credentials/LocalAIApi.credential.ts similarity index 86% rename from packages/components/credentials/LcoalAIApi.credential.ts rename to packages/components/credentials/LocalAIApi.credential.ts index 624e07fa..4aafe040 100644 --- a/packages/components/credentials/LcoalAIApi.credential.ts +++ b/packages/components/credentials/LocalAIApi.credential.ts @@ -8,12 +8,12 @@ class LocalAIApi implements INodeCredential { constructor() { this.label = 'LocalAI API' - this.name = 'LocalAIApi' + this.name = 'localAIApi' this.version = 1.0 this.inputs = [ { label: 'LocalAI Api Key', - name: 'LocalAIApiKey', + name: 'localAIApiKey', type: 'password' } ] diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index c44f03ce..f2825d0d 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -30,7 +30,7 @@ class ChatLocalAI_ChatModels implements INode { label: 'Connect Credential', name: 'credential', type: 'credential', - credentialNames: ['LocalAIApi'], + credentialNames: ['localAIApi'], optional: true } this.inputs = [ @@ -95,11 +95,11 @@ class ChatLocalAI_ChatModels implements INode { const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const localAIApiKey = getCredentialParam('LocalAIApiKey', credentialData, nodeData) + const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData) const cache = nodeData.inputs?.cache as BaseCache - const obj: Partial & BaseLLMParams & { localAIApiKey?: string } = { + const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, openAIApiKey: 'sk-' diff --git a/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts b/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts index 557e35d6..24efaf8c 100644 --- a/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts +++ b/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts @@ -1,4 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' class LocalAIEmbedding_Embeddings implements INode { @@ -10,6 +11,7 @@ class LocalAIEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,6 +23,13 @@ class LocalAIEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Use local embeddings models like llama.cpp' this.baseClasses = [this.type, 'Embeddings'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['localAIApi'], + optional: true + } this.inputs = [ { label: 'Base Path', @@ -37,15 +46,20 @@ class LocalAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string const basePath = nodeData.inputs?.basePath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { modelName, openAIApiKey: 'sk-' } + if (localAIApiKey) obj.openAIApiKey = localAIApiKey + const model = new OpenAIEmbeddings(obj, { basePath }) return model From 8f92bba145261c391574e900aafb1b63fef97b9d Mon Sep 17 00:00:00 2001 From: Carson Yang Date: Tue, 9 Jan 2024 13:58:40 +0800 Subject: [PATCH 235/268] Update README-ZH.md --- README-ZH.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README-ZH.md b/README-ZH.md index 2805ef9b..208eee92 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -153,6 +153,10 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package [![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) +### [Sealos](https://docs.flowiseai.com/configuration/deployment/sealos) + +[![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://template.cloud.sealos.io/deploy?templateName=flowise) + ### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) HuggingFace Spaces From 3d96169b7584a35764d3d724a5eecd674a0c9d1f Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Jan 2024 02:46:21 +0000 Subject: [PATCH 236/268] update README-ZH md --- README-ZH.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/README-ZH.md b/README-ZH.md index 208eee92..8750ebc7 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -145,29 +145,40 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package ## 🌐 自托管 -### [Railway](https://docs.flowiseai.com/deployment/railway) +在您现有的基础设施中部署自托管的 Flowise,我们支持各种[部署](https://docs.flowiseai.com/configuration/deployment) -[![在 Railway 上部署](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) +- [AWS](https://docs.flowiseai.com/deployment/aws) +- [Azure](https://docs.flowiseai.com/deployment/azure) +- [Digital Ocean](https://docs.flowiseai.com/deployment/digital-ocean) +- [GCP](https://docs.flowiseai.com/deployment/gcp) +-
    + 其他 -### [Render](https://docs.flowiseai.com/deployment/render) + - [Railway](https://docs.flowiseai.com/deployment/railway) -[![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) + [![在 Railway 上部署](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) -### [Sealos](https://docs.flowiseai.com/configuration/deployment/sealos) + - [Render](https://docs.flowiseai.com/deployment/render) -[![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://template.cloud.sealos.io/deploy?templateName=flowise) + [![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) -### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) + - [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) -HuggingFace Spaces + HuggingFace Spaces -### [AWS](https://docs.flowiseai.com/deployment/aws) + - [Elestio](https://elest.io/open-source/flowiseai) -### [Azure](https://docs.flowiseai.com/deployment/azure) + [![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai) -### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) + - [Sealos](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) -### [GCP](https://docs.flowiseai.com/deployment/gcp) + [![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise) + + - [RepoCloud](https://repocloud.io/details/?app_id=29) + + [![部署到 RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=29) + +
    ## 💻 云托管 From 853992949dc3810081d3a9d11bd95c08b605c055 Mon Sep 17 00:00:00 2001 From: Joshua Carter Date: Wed, 10 Jan 2024 12:30:01 -0800 Subject: [PATCH 237/268] Correct DockerHub link in docs from private to public repo page --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index d3ad1c19..11b29cf3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,6 +1,6 @@ # Flowise Docker Hub Image -Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/flowiseai/flowise/general) +Starts Flowise from [DockerHub Image](https://hub.docker.com/r/flowiseai/flowise) ## Usage From 8bf72493b434f482b0a0e2d443b6201609ef5354 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Jan 2024 18:28:46 +0000 Subject: [PATCH 238/268] add sessionId tracking --- packages/components/package.json | 2 +- packages/components/src/handler.ts | 42 +++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 57ab7b3d..c90ea5cc 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -57,7 +57,7 @@ "ioredis": "^5.3.2", "langchain": "^0.0.214", "langfuse": "2.0.2", - "langfuse-langchain": "2.0.2", + "langfuse-langchain": "2.3.3", "langsmith": "0.0.53", "linkifyjs": "^4.1.1", "llmonitor": "^0.5.5", diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index 1eb05a51..df72a685 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -1,13 +1,13 @@ -import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks' +import { BaseTracer, Run, BaseCallbackHandler, LangChainTracer } from 'langchain/callbacks' import { AgentAction, ChainValues } from 'langchain/schema' import { Logger } from 'winston' import { Server } from 'socket.io' import { Client } from 'langsmith' -import { LangChainTracer } from 'langchain/callbacks' -import { LLMonitorHandler } from 'langchain/callbacks/handlers/llmonitor' +import { LLMonitorHandler, LLMonitorHandlerFields } from 'langchain/callbacks/handlers/llmonitor' import { getCredentialData, getCredentialParam } from './utils' import { ICommonObject, INodeData } from './Interface' import CallbackHandler from 'langfuse-langchain' +import { LangChainTracerFields } from '@langchain/core/tracers/tracer_langchain' import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith' import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse' import monitor from 'llmonitor' @@ -235,11 +235,16 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO apiKey: langSmithApiKey }) - const tracer = new LangChainTracer({ + let langSmithField: LangChainTracerFields = { projectName: langSmithProject ?? 'default', - //@ts-ignore client - }) + } + + if (nodeData?.inputs?.analytics?.langSmith) { + langSmithField = { ...langSmithField, ...nodeData?.inputs?.analytics?.langSmith } + } + + const tracer = new LangChainTracer(langSmithField) callbacks.push(tracer) } else if (provider === 'langFuse') { const release = analytic[provider].release as string @@ -248,13 +253,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData) const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData) - const langFuseOptions: any = { + let langFuseOptions: any = { secretKey: langFuseSecretKey, publicKey: langFusePublicKey, baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com' } if (release) langFuseOptions.release = release - if (options.chatId) langFuseOptions.userId = options.chatId + if (options.chatId) langFuseOptions.sessionId = options.chatId + + if (nodeData?.inputs?.analytics?.langFuse) { + langFuseOptions = { ...langFuseOptions, ...nodeData?.inputs?.analytics?.langFuse } + } const handler = new CallbackHandler(langFuseOptions) callbacks.push(handler) @@ -262,11 +271,15 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, nodeData) const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, nodeData) - const llmonitorFields: ICommonObject = { + let llmonitorFields: LLMonitorHandlerFields = { appId: llmonitorAppId, apiUrl: llmonitorEndpoint ?? 'https://app.llmonitor.com' } + if (nodeData?.inputs?.analytics?.llmonitor) { + llmonitorFields = { ...llmonitorFields, ...nodeData?.inputs?.analytics?.llmonitor } + } + const handler = new LLMonitorHandler(llmonitorFields) callbacks.push(handler) } @@ -360,7 +373,8 @@ export class AnalyticHandler { }, serialized: {}, project_name: this.handlers['langSmith'].langSmithProject, - client: this.handlers['langSmith'].client + client: this.handlers['langSmith'].client, + ...this.nodeData?.inputs?.analytics?.langSmith } const parentRun = new RunTree(parentRunConfig) await parentRun.postRun() @@ -390,8 +404,9 @@ export class AnalyticHandler { const langfuse: Langfuse = this.handlers['langFuse'].client langfuseTraceClient = langfuse.trace({ name, - userId: this.options.chatId, - metadata: { tags: ['openai-assistant'] } + sessionId: this.options.chatId, + metadata: { tags: ['openai-assistant'] }, + ...this.nodeData?.inputs?.analytics?.langFuse }) } else { langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']] @@ -420,7 +435,8 @@ export class AnalyticHandler { runId, name, userId: this.options.chatId, - input + input, + ...this.nodeData?.inputs?.analytics?.llmonitor }) this.handlers['llmonitor'].chainEvent = { [runId]: runId } returnIds['llmonitor'].chainEvent = runId From 779e036c2331a1e336664b2ec8f36150402e7b58 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 15 Jan 2024 18:36:31 +0000 Subject: [PATCH 239/268] add ts-ignore --- packages/components/src/handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index df72a685..5d2b53f6 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -237,6 +237,7 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO let langSmithField: LangChainTracerFields = { projectName: langSmithProject ?? 'default', + //@ts-ignore client } From 7fd339982e550deb62aba25c8c4542640ccd7a03 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sun, 31 Dec 2023 23:40:04 -0800 Subject: [PATCH 240/268] na --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a9bfcbf..19a79da7 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,5 @@ }, "engines": { "node": ">=18.15.0" - } + }, } From f9d6089245e6702b1dd4b4170cb9ef2d2dd7f966 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 16 Jan 2024 11:20:51 +0530 Subject: [PATCH 241/268] Fix package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19a79da7..5a9bfcbf 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,5 @@ }, "engines": { "node": ">=18.15.0" - }, + } } From c77e23b0d15989f78f310145ae8614e53049d887 Mon Sep 17 00:00:00 2001 From: YISH Date: Tue, 16 Jan 2024 15:32:51 +0800 Subject: [PATCH 242/268] Fix CustomFunction receiving excaped inputs --- .../nodes/utilities/CustomFunction/CustomFunction.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index 37511e47..749c3a86 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -73,7 +73,11 @@ class CustomFunction_Utilities implements INode { if (Object.keys(inputVars).length) { for (const item in inputVars) { - sandbox[`$${item}`] = inputVars[item] + let value = inputVars[item] + if (typeof value === 'string') { + value = handleEscapeCharacters(value, true) + } + sandbox[`$${item}`] = value } } From f3a244a93c497ab5c84baa475a673ea48896a0de Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 16 Jan 2024 16:19:21 +0800 Subject: [PATCH 243/268] modify google gemini based on request changes --- .../ChatGoogleGenerativeAI.ts | 59 +++---------------- packages/components/src/utils.ts | 9 +-- 2 files changed, 11 insertions(+), 57 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index bd660b47..9a4b8891 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -78,10 +78,11 @@ class GoogleGenerativeAI_ChatModels implements INode { additionalParams: true }, { - label: 'topK', + label: 'Top Next Highest Probability Tokens', name: 'topK', type: 'number', - step: 0.1, + description: `Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive`, + step: 1, optional: true, additionalParams: true }, @@ -90,7 +91,7 @@ class GoogleGenerativeAI_ChatModels implements INode { name: 'harmCategory', type: 'multiOptions', description: - 'Refer to official guide on how to use Harm Category', + 'Refer to official guide on how to use Harm Category', options: [ { label: 'Dangerous', @@ -117,7 +118,7 @@ class GoogleGenerativeAI_ChatModels implements INode { name: 'harmBlockThreshold', type: 'multiOptions', description: - 'Refer to official guide on how to use Harm Block Threshold', + 'Refer to official guide on how to use Harm Block Threshold', options: [ { label: 'Low and Above', @@ -169,7 +170,7 @@ class GoogleGenerativeAI_ChatModels implements INode { const model = new ChatGoogleGenerativeAI(obj) if (topP) model.topP = parseFloat(topP) - if (topK) model.topP = parseFloat(topK) + if (topK) model.topK = parseFloat(topK) if (cache) model.cache = cache if (temperature) model.temperature = parseFloat(temperature) @@ -178,10 +179,10 @@ class GoogleGenerativeAI_ChatModels implements INode { let harmBlockThresholds: string[] = convertMultiOptionsToStringArray(harmBlockThreshold) if (harmCategories.length != harmBlockThresholds.length) throw new Error(`Harm Category & Harm Block Threshold are not the same length`) - const safetySettings: SafetySetting[] = harmCategories.map((value, index) => { + const safetySettings: SafetySetting[] = harmCategories.map((harmCategory, index) => { return { - category: categoryInput(value), - threshold: thresholdInput(harmBlockThresholds[index]) + category: harmCategory as HarmCategory, + threshold: harmBlockThresholds[index] as HarmBlockThreshold } }) if (safetySettings.length > 0) model.safetySettings = safetySettings @@ -190,46 +191,4 @@ class GoogleGenerativeAI_ChatModels implements INode { } } -const categoryInput = (categoryInput: string): HarmCategory => { - let categoryOutput: HarmCategory - switch (categoryInput) { - case HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: - categoryOutput = HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT - break - case HarmCategory.HARM_CATEGORY_HATE_SPEECH: - categoryOutput = HarmCategory.HARM_CATEGORY_HATE_SPEECH - break - case HarmCategory.HARM_CATEGORY_HARASSMENT: - categoryOutput = HarmCategory.HARM_CATEGORY_HARASSMENT - break - case HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: - categoryOutput = HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT - break - default: - categoryOutput = HarmCategory.HARM_CATEGORY_UNSPECIFIED - } - return categoryOutput -} - -const thresholdInput = (thresholdInput: string): HarmBlockThreshold => { - let thresholdOutput: HarmBlockThreshold - switch (thresholdInput) { - case HarmBlockThreshold.BLOCK_LOW_AND_ABOVE: - thresholdOutput = HarmBlockThreshold.BLOCK_LOW_AND_ABOVE - break - case HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: - thresholdOutput = HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE - break - case HarmBlockThreshold.BLOCK_NONE: - thresholdOutput = HarmBlockThreshold.BLOCK_NONE - break - case HarmBlockThreshold.BLOCK_ONLY_HIGH: - thresholdOutput = HarmBlockThreshold.BLOCK_ONLY_HIGH - break - default: - thresholdOutput = HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED - } - return thresholdOutput -} - module.exports = { nodeClass: GoogleGenerativeAI_ChatModels } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 2d983562..eacfa4a0 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -681,12 +681,7 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[ */ export const convertMultiOptionsToStringArray = (inputString: string): string[] => { let ArrayString: string[] = [] - if (inputString) { - try { - ArrayString = JSON.parse(inputString) - } catch (e) { - ArrayString = [] - } - } + if (inputString) ArrayString = JSON.parse(inputString) + return ArrayString } From 74602484b29b93fb2b63089be874d6390a3fb3cd Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 16 Jan 2024 14:25:18 +0530 Subject: [PATCH 244/268] Fix sticky note in marketplace chatflows --- .../cards/NodeCardWrapper.js} | 6 +++--- .../tooltip/NodeTooltip.js} | 4 ++-- packages/ui/src/views/canvas/CanvasNode.js | 12 ++++++------ packages/ui/src/views/canvas/StickyNote.js | 12 ++++++------ .../ui/src/views/marketplaces/MarketplaceCanvas.js | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) rename packages/ui/src/{views/canvas/CardWrapper.js => ui-component/cards/NodeCardWrapper.js} (75%) rename packages/ui/src/{views/canvas/LightTooltip.js => ui-component/tooltip/NodeTooltip.js} (66%) diff --git a/packages/ui/src/views/canvas/CardWrapper.js b/packages/ui/src/ui-component/cards/NodeCardWrapper.js similarity index 75% rename from packages/ui/src/views/canvas/CardWrapper.js rename to packages/ui/src/ui-component/cards/NodeCardWrapper.js index 3e010899..7d7cafe5 100644 --- a/packages/ui/src/views/canvas/CardWrapper.js +++ b/packages/ui/src/ui-component/cards/NodeCardWrapper.js @@ -2,9 +2,9 @@ import { styled } from '@mui/material/styles' // project imports -import MainCard from '../../ui-component/cards/MainCard' +import MainCard from './MainCard' -const CardWrapper = styled(MainCard)(({ theme }) => ({ +const NodeCardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, border: 'solid 1px', @@ -18,4 +18,4 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ } })) -export default CardWrapper +export default NodeCardWrapper diff --git a/packages/ui/src/views/canvas/LightTooltip.js b/packages/ui/src/ui-component/tooltip/NodeTooltip.js similarity index 66% rename from packages/ui/src/views/canvas/LightTooltip.js rename to packages/ui/src/ui-component/tooltip/NodeTooltip.js index 32e34aae..30bd7cf8 100644 --- a/packages/ui/src/views/canvas/LightTooltip.js +++ b/packages/ui/src/ui-component/tooltip/NodeTooltip.js @@ -1,7 +1,7 @@ import { styled } from '@mui/material/styles' import Tooltip, { tooltipClasses } from '@mui/material/Tooltip' -const LightTooltip = styled(({ className, ...props }) => )(({ theme }) => ({ +const NodeTooltip = styled(({ className, ...props }) => )(({ theme }) => ({ [`& .${tooltipClasses.tooltip}`]: { backgroundColor: theme.palette.nodeToolTip.background, color: theme.palette.nodeToolTip.color, @@ -9,4 +9,4 @@ const LightTooltip = styled(({ className, ...props }) => { return ( <> - { }} border={false} > - { ))} - - + + { return ( <> - { }} border={false} > - { nodeId={data.id} /> - - + + ) } diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvas.js b/packages/ui/src/views/marketplaces/MarketplaceCanvas.js index 7ce29451..613f3cdb 100644 --- a/packages/ui/src/views/marketplaces/MarketplaceCanvas.js +++ b/packages/ui/src/views/marketplaces/MarketplaceCanvas.js @@ -11,10 +11,10 @@ import { useTheme } from '@mui/material/styles' // project imports import MarketplaceCanvasNode from './MarketplaceCanvasNode' - import MarketplaceCanvasHeader from './MarketplaceCanvasHeader' +import StickyNote from '../canvas/StickyNote' -const nodeTypes = { customNode: MarketplaceCanvasNode } +const nodeTypes = { customNode: MarketplaceCanvasNode, stickyNote: StickyNote } const edgeTypes = { buttonedge: '' } // ==============================|| CANVAS ||============================== // From 9b114212c0cc1f0a719b20ad56d6be0685ac8eb4 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 16 Jan 2024 14:25:36 +0530 Subject: [PATCH 245/268] Apply prettier --- packages/server/src/database/entities/Variable.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/server/src/database/entities/Variable.ts b/packages/server/src/database/entities/Variable.ts index 88e0587d..6af7a237 100644 --- a/packages/server/src/database/entities/Variable.ts +++ b/packages/server/src/database/entities/Variable.ts @@ -1,9 +1,9 @@ /* eslint-disable */ import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' -import { IVariable } from "../../Interface"; +import { IVariable } from '../../Interface' @Entity() -export class Variable implements IVariable{ +export class Variable implements IVariable { @PrimaryGeneratedColumn('uuid') id: string @@ -13,10 +13,9 @@ export class Variable implements IVariable{ @Column({ nullable: true, type: 'text' }) value: string - @Column({default: 'string', type: 'text'}) + @Column({ default: 'string', type: 'text' }) type: string - @CreateDateColumn() createdDate: Date From 2661a42a136b5b546e26f39c53e6a381a5c1bf85 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 16 Jan 2024 22:13:43 +0800 Subject: [PATCH 246/268] modify google gemini based on requested changes --- packages/components/src/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index eacfa4a0..2215eb41 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -681,7 +681,10 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[ */ export const convertMultiOptionsToStringArray = (inputString: string): string[] => { let ArrayString: string[] = [] - if (inputString) ArrayString = JSON.parse(inputString) - + try { + ArrayString = JSON.parse(inputString) + } catch (e) { + ArrayString = [] + } return ArrayString } From 0e7df3a5b51d2cc6f30c3faadcb0b711ee863f62 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 16 Jan 2024 20:52:25 +0530 Subject: [PATCH 247/268] Lock upstash redis version which will fix the credentials and deployment issues --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index c90ea5cc..c35419bc 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -37,7 +37,7 @@ "@supabase/supabase-js": "^2.29.0", "@types/js-yaml": "^4.0.5", "@types/jsdom": "^21.1.1", - "@upstash/redis": "^1.22.1", + "@upstash/redis": "1.22.1", "@zilliz/milvus2-sdk-node": "^2.2.24", "apify-client": "^2.7.1", "axios": "1.6.2", From 019e7caac36dbc85fcdbf17db5b8f5a3664d53bc Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 Jan 2024 16:07:28 +0000 Subject: [PATCH 248/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.5.1?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index c35419bc..ddf09399 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.5.0", + "version": "1.5.1", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From f16b29503d58568f00301abdecdf8d046f70f4f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 Jan 2024 16:08:25 +0000 Subject: [PATCH 249/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.4.7=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index c5549b23..fef08851 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.4.6", + "version": "1.4.7", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 8c1e62be425810b0d7cc7725e707a8415a670a4d Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 Jan 2024 16:09:13 +0000 Subject: [PATCH 250/268] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.4.10=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 12fbe20f..561694d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.9", + "version": "1.4.10", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index f1c0b7f7..79ff4961 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.9", + "version": "1.4.10", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 3407fa92f4e39f8d4804257224639063deced46a Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Wed, 17 Jan 2024 18:29:37 +0530 Subject: [PATCH 251/268] Compression Retriever - Cohere Rerank - Add max chunks per document as optional parameter --- .../CohereRerankRetriever/CohereRerank.ts | 6 ++++-- .../CohereRerankRetriever/CohereRerankRetriever.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts index 55f3c4aa..f74b8365 100644 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts @@ -7,11 +7,13 @@ export class CohereRerank extends BaseDocumentCompressor { private COHERE_API_URL = 'https://api.cohere.ai/v1/rerank' private readonly model: string private readonly k: number - constructor(cohereAPIKey: string, model: string, k: number) { + private readonly maxChunksPerDoc: number + constructor(cohereAPIKey: string, model: string, k: number, maxChunksPerDoc: number) { super() this.cohereAPIKey = cohereAPIKey this.model = model this.k = k + this.maxChunksPerDoc = maxChunksPerDoc } async compressDocuments( documents: Document>[], @@ -32,7 +34,7 @@ export class CohereRerank extends BaseDocumentCompressor { const data = { model: this.model, topN: this.k, - max_chunks_per_doc: 10, + max_chunks_per_doc: this.maxChunksPerDoc, query: query, return_documents: false, documents: documents.map((doc) => doc.pageContent) diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts index 3c1872b3..ca89ca77 100644 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts @@ -67,6 +67,15 @@ class CohereRerankRetriever_Retrievers implements INode { default: 0, additionalParams: true, optional: true + }, + { + label: 'Max Chunks Per Document', + name: 'maxChunksPerDoc', + placeholder: '10', + type: 'number', + default: 10, + additionalParams: true, + optional: true } ] } @@ -78,12 +87,14 @@ class CohereRerankRetriever_Retrievers implements INode { const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) const topK = nodeData.inputs?.topK as string let k = topK ? parseFloat(topK) : 4 + const maxChunks = nodeData.inputs?.maxChunksPerDoc as string + let max = maxChunks ? parseInt(maxChunks) : 10 if (k <= 0) { k = (baseRetriever as VectorStoreRetriever).k } - const cohereCompressor = new CohereRerank(cohereApiKey, model, k) + const cohereCompressor = new CohereRerank(cohereApiKey, model, k, max) return new ContextualCompressionRetriever({ baseCompressor: cohereCompressor, baseRetriever: baseRetriever From 1bf7944776c83ceb3903fe9d33dba82f2acb1fdf Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 17 Jan 2024 15:55:56 +0000 Subject: [PATCH 252/268] update retrievers and add mmr to other vector stores --- .../CohereRerankRetriever/Cohere.svg | 1 + .../CohereRerankRetriever/CohereRerank.ts | 7 +- .../CohereRerankRetriever.ts | 74 ++++++++++++--- .../compressionRetriever.svg | 7 -- .../EmbeddingsFilterRetriever.ts | 62 ++++++++++--- .../retrievers/HydeRetriever/HydeRetriever.ts | 50 ++++++++++- .../LLMFilterCompressionRetriever.ts | 62 ++++++++++--- .../compressionRetriever.svg | 7 -- .../LLMFilterRetriever/llmFilterRetriever.svg | 1 + .../retrievers/RRFRetriever/RRFRetriever.ts | 64 ++++++++++--- .../RRFRetriever/compressionRetriever.svg | 7 -- .../retrievers/RRFRetriever/rrfRetriever.svg | 1 + .../SimilarityThresholdRetriever.ts | 21 +++-- .../nodes/vectorstores/Astra/Astra.ts | 16 +--- .../vectorstores/MongoDBAtlas/MongoDBAtlas.ts | 15 +--- .../nodes/vectorstores/Pinecone/Pinecone.ts | 2 +- .../marketplaces/chatflows/AutoGPT.json | 46 +++++++++- .../marketplaces/chatflows/BabyAGI.json | 46 +++++++++- .../Conversational Retrieval Agent.json | 46 +++++++++- .../Conversational Retrieval QA Chain.json | 46 +++++++++- .../chatflows/Metadata Filter.json | 46 +++++++++- .../chatflows/Multi Retrieval QA Chain.json | 90 ++++++++++++++++++- .../marketplaces/chatflows/WebPage QnA.json | 46 +++++++++- 23 files changed, 642 insertions(+), 121 deletions(-) create mode 100644 packages/components/nodes/retrievers/CohereRerankRetriever/Cohere.svg delete mode 100644 packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg delete mode 100644 packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg create mode 100644 packages/components/nodes/retrievers/LLMFilterRetriever/llmFilterRetriever.svg delete mode 100644 packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg create mode 100644 packages/components/nodes/retrievers/RRFRetriever/rrfRetriever.svg diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/Cohere.svg b/packages/components/nodes/retrievers/CohereRerankRetriever/Cohere.svg new file mode 100644 index 00000000..88bcabe3 --- /dev/null +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/Cohere.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts index 55f3c4aa..e70c044f 100644 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerank.ts @@ -7,11 +7,14 @@ export class CohereRerank extends BaseDocumentCompressor { private COHERE_API_URL = 'https://api.cohere.ai/v1/rerank' private readonly model: string private readonly k: number - constructor(cohereAPIKey: string, model: string, k: number) { + private readonly max_chunks_per_doc: number + + constructor(cohereAPIKey: string, model: string, k: number, max_chunks_per_doc: number) { super() this.cohereAPIKey = cohereAPIKey this.model = model this.k = k + this.max_chunks_per_doc = max_chunks_per_doc } async compressDocuments( documents: Document>[], @@ -32,8 +35,8 @@ export class CohereRerank extends BaseDocumentCompressor { const data = { model: this.model, topN: this.k, - max_chunks_per_doc: 10, query: query, + max_chunks_per_doc: this.max_chunks_per_doc, return_documents: false, documents: documents.map((doc) => doc.pageContent) } diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts index 3c1872b3..442fdc7a 100644 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts +++ b/packages/components/nodes/retrievers/CohereRerankRetriever/CohereRerankRetriever.ts @@ -1,7 +1,7 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { BaseRetriever } from 'langchain/schema/retriever' import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' -import { getCredentialData, getCredentialParam } from '../../../src' +import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src' import { CohereRerank } from './CohereRerank' import { VectorStoreRetriever } from 'langchain/vectorstores/base' @@ -15,16 +15,16 @@ class CohereRerankRetriever_Retrievers implements INode { category: string baseClasses: string[] inputs: INodeParams[] - outputs: INodeOutputsValue[] credential: INodeParams badge: string + outputs: INodeOutputsValue[] constructor() { this.label = 'Cohere Rerank Retriever' this.name = 'cohereRerankRetriever' this.version = 1.0 this.type = 'Cohere Rerank Retriever' - this.icon = 'compressionRetriever.svg' + this.icon = 'Cohere.svg' this.category = 'Retrievers' this.badge = 'NEW' this.description = 'Cohere Rerank indexes the documents from most to least semantically relevant to the query.' @@ -37,7 +37,7 @@ class CohereRerankRetriever_Retrievers implements INode { } this.inputs = [ { - label: 'Base Retriever', + label: 'Vector Store Retriever', name: 'baseRetriever', type: 'VectorStoreRetriever' }, @@ -58,36 +58,84 @@ class CohereRerankRetriever_Retrievers implements INode { default: 'rerank-english-v2.0', optional: true }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true + }, { label: 'Top K', name: 'topK', description: 'Number of top results to fetch. Default to the TopK of the Base Retriever', - placeholder: '0', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'Max Chunks Per Doc', + name: 'maxChunksPerDoc', + description: 'The maximum number of chunks to produce internally from a document. Default to 10', + placeholder: '10', type: 'number', - default: 0, additionalParams: true, optional: true } ] + this.outputs = [ + { + label: 'Cohere Rerank Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Document', + name: 'document', + baseClasses: ['Document'] + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] } - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever const model = nodeData.inputs?.model as string + const query = nodeData.inputs?.query as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) const topK = nodeData.inputs?.topK as string - let k = topK ? parseFloat(topK) : 4 + const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4 + const maxChunksPerDoc = nodeData.inputs?.maxChunksPerDoc as string + const max_chunks_per_doc = maxChunksPerDoc ? parseFloat(maxChunksPerDoc) : 10 + const output = nodeData.outputs?.output as string - if (k <= 0) { - k = (baseRetriever as VectorStoreRetriever).k - } + const cohereCompressor = new CohereRerank(cohereApiKey, model, k, max_chunks_per_doc) - const cohereCompressor = new CohereRerank(cohereApiKey, model, k) - return new ContextualCompressionRetriever({ + const retriever = new ContextualCompressionRetriever({ baseCompressor: cohereCompressor, baseRetriever: baseRetriever }) + + if (output === 'retriever') return retriever + else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input) + else if (output === 'text') { + let finaltext = '' + + const docs = await retriever.getRelevantDocuments(query ? query : input) + + for (const doc of docs) finaltext += `${doc.pageContent}\n` + + return handleEscapeCharacters(finaltext, false) + } + + return retriever } } diff --git a/packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg deleted file mode 100644 index 23c52d25..00000000 --- a/packages/components/nodes/retrievers/CohereRerankRetriever/compressionRetriever.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts b/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts index d373704c..d1049fa4 100644 --- a/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts +++ b/packages/components/nodes/retrievers/EmbeddingsFilterRetriever/EmbeddingsFilterRetriever.ts @@ -3,6 +3,7 @@ import { BaseRetriever } from 'langchain/schema/retriever' import { Embeddings } from 'langchain/embeddings/base' import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' import { EmbeddingsFilter } from 'langchain/retrievers/document_compressors/embeddings_filter' +import { handleEscapeCharacters } from '../../../src/utils' class EmbeddingsFilterRetriever_Retrievers implements INode { label: string @@ -29,15 +30,22 @@ class EmbeddingsFilterRetriever_Retrievers implements INode { this.baseClasses = [this.type, 'BaseRetriever'] this.inputs = [ { - label: 'Base Retriever', + label: 'Vector Store Retriever', name: 'baseRetriever', type: 'VectorStoreRetriever' }, { label: 'Embeddings', name: 'embeddings', - type: 'Embeddings', - optional: false + type: 'Embeddings' + }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true }, { label: 'Similarity Threshold', @@ -61,36 +69,64 @@ class EmbeddingsFilterRetriever_Retrievers implements INode { additionalParams: true } ] + this.outputs = [ + { + label: 'Embeddings Filter Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Document', + name: 'document', + baseClasses: ['Document'] + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, input: string): Promise { const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever const embeddings = nodeData.inputs?.embeddings as Embeddings + const query = nodeData.inputs?.query as string const similarityThreshold = nodeData.inputs?.similarityThreshold as string const k = nodeData.inputs?.k as string + const output = nodeData.outputs?.output as string if (k === undefined && similarityThreshold === undefined) { throw new Error(`Must specify one of "k" or "similarity_threshold".`) } - let similarityThresholdNumber = 0.8 - if (similarityThreshold) { - similarityThresholdNumber = parseFloat(similarityThreshold) - } - let kNumber = 0.8 - if (k) { - kNumber = parseFloat(k) - } + const similarityThresholdNumber = similarityThreshold ? parseFloat(similarityThreshold) : 0.8 + const kNumber = k ? parseFloat(k) : undefined + const baseCompressor = new EmbeddingsFilter({ embeddings: embeddings, similarityThreshold: similarityThresholdNumber, k: kNumber }) - return new ContextualCompressionRetriever({ + const retriever = new ContextualCompressionRetriever({ baseCompressor, baseRetriever: baseRetriever }) + + if (output === 'retriever') return retriever + else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input) + else if (output === 'text') { + let finaltext = '' + + const docs = await retriever.getRelevantDocuments(query ? query : input) + + for (const doc of docs) finaltext += `${doc.pageContent}\n` + + return handleEscapeCharacters(finaltext, false) + } + + return retriever } } diff --git a/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts b/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts index 10d9a6e7..10fff764 100644 --- a/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts +++ b/packages/components/nodes/retrievers/HydeRetriever/HydeRetriever.ts @@ -1,8 +1,9 @@ import { VectorStore } from 'langchain/vectorstores/base' -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { HydeRetriever, HydeRetrieverOptions, PromptKey } from 'langchain/retrievers/hyde' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' +import { handleEscapeCharacters } from '../../../src/utils' class HydeRetriever_Retrievers implements INode { label: string @@ -14,11 +15,12 @@ class HydeRetriever_Retrievers implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { - this.label = 'Hyde Retriever' + this.label = 'HyDE Retriever' this.name = 'HydeRetriever' - this.version = 2.0 + this.version = 3.0 this.type = 'HydeRetriever' this.icon = 'hyderetriever.svg' this.category = 'Retrievers' @@ -35,6 +37,14 @@ class HydeRetriever_Retrievers implements INode { name: 'vectorStore', type: 'VectorStore' }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true + }, { label: 'Select Defined Prompt', name: 'promptKey', @@ -121,15 +131,34 @@ Passage:` optional: true } ] + this.outputs = [ + { + label: 'HyDE Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Document', + name: 'document', + baseClasses: ['Document'] + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, input: string): Promise { const llm = nodeData.inputs?.model as BaseLanguageModel const vectorStore = nodeData.inputs?.vectorStore as VectorStore const promptKey = nodeData.inputs?.promptKey as PromptKey const customPrompt = nodeData.inputs?.customPrompt as string + const query = nodeData.inputs?.query as string const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const output = nodeData.outputs?.output as string const obj: HydeRetrieverOptions = { llm, @@ -141,6 +170,19 @@ Passage:` else if (promptKey) obj.promptTemplate = promptKey const retriever = new HydeRetriever(obj) + + if (output === 'retriever') return retriever + else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input) + else if (output === 'text') { + let finaltext = '' + + const docs = await retriever.getRelevantDocuments(query ? query : input) + + for (const doc of docs) finaltext += `${doc.pageContent}\n` + + return handleEscapeCharacters(finaltext, false) + } + return retriever } } diff --git a/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts b/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts index e044468f..6b710cf3 100644 --- a/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts +++ b/packages/components/nodes/retrievers/LLMFilterRetriever/LLMFilterCompressionRetriever.ts @@ -3,6 +3,7 @@ import { BaseRetriever } from 'langchain/schema/retriever' import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' import { BaseLanguageModel } from 'langchain/base_language' import { LLMChainExtractor } from 'langchain/retrievers/document_compressors/chain_extract' +import { handleEscapeCharacters } from '../../../src/utils' class LLMFilterCompressionRetriever_Retrievers implements INode { label: string @@ -22,7 +23,7 @@ class LLMFilterCompressionRetriever_Retrievers implements INode { this.name = 'llmFilterRetriever' this.version = 1.0 this.type = 'LLMFilterRetriever' - this.icon = 'compressionRetriever.svg' + this.icon = 'llmFilterRetriever.svg' this.category = 'Retrievers' this.badge = 'NEW' this.description = @@ -30,30 +31,69 @@ class LLMFilterCompressionRetriever_Retrievers implements INode { this.baseClasses = [this.type, 'BaseRetriever'] this.inputs = [ { - label: 'Base Retriever', + label: 'Vector Store Retriever', name: 'baseRetriever', type: 'VectorStoreRetriever' }, { label: 'Language Model', name: 'model', - type: 'BaseLanguageModel', - optional: true + type: 'BaseLanguageModel' + }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true + } + ] + this.outputs = [ + { + label: 'LLM Filter Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Document', + name: 'document', + baseClasses: ['Document'] + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] } ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, input: string): Promise { const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever const model = nodeData.inputs?.model as BaseLanguageModel + const query = nodeData.inputs?.query as string + const output = nodeData.outputs?.output as string - if (model) { - return new ContextualCompressionRetriever({ - baseCompressor: LLMChainExtractor.fromLLM(model), - baseRetriever: baseRetriever - }) + if (!model) throw new Error('There must be a LLM model connected to LLM Filter Retriever') + + const retriever = new ContextualCompressionRetriever({ + baseCompressor: LLMChainExtractor.fromLLM(model), + baseRetriever: baseRetriever + }) + + if (output === 'retriever') return retriever + else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input) + else if (output === 'text') { + let finaltext = '' + + const docs = await retriever.getRelevantDocuments(query ? query : input) + + for (const doc of docs) finaltext += `${doc.pageContent}\n` + + return handleEscapeCharacters(finaltext, false) } - return {} + + return retriever } } diff --git a/packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg deleted file mode 100644 index 23c52d25..00000000 --- a/packages/components/nodes/retrievers/LLMFilterRetriever/compressionRetriever.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/retrievers/LLMFilterRetriever/llmFilterRetriever.svg b/packages/components/nodes/retrievers/LLMFilterRetriever/llmFilterRetriever.svg new file mode 100644 index 00000000..d3f4d15f --- /dev/null +++ b/packages/components/nodes/retrievers/LLMFilterRetriever/llmFilterRetriever.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts b/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts index 3229b3a8..ed15ed24 100644 --- a/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts +++ b/packages/components/nodes/retrievers/RRFRetriever/RRFRetriever.ts @@ -1,9 +1,10 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { BaseLanguageModel } from 'langchain/base_language' import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression' import { BaseRetriever } from 'langchain/schema/retriever' import { ReciprocalRankFusion } from './ReciprocalRankFusion' import { VectorStoreRetriever } from 'langchain/vectorstores/base' +import { handleEscapeCharacters } from '../../../src/utils' class RRFRetriever_Retrievers implements INode { label: string @@ -16,20 +17,21 @@ class RRFRetriever_Retrievers implements INode { baseClasses: string[] inputs: INodeParams[] badge: string + outputs: INodeOutputsValue[] constructor() { this.label = 'Reciprocal Rank Fusion Retriever' this.name = 'RRFRetriever' - this.version = 2.0 + this.version = 1.0 this.type = 'RRFRetriever' this.badge = 'NEW' - this.icon = 'compressionRetriever.svg' + this.icon = 'rrfRetriever.svg' this.category = 'Retrievers' this.description = 'Reciprocal Rank Fusion to re-rank search results by multiple query generation.' this.baseClasses = [this.type, 'BaseRetriever'] this.inputs = [ { - label: 'Base Retriever', + label: 'Vector Store Retriever', name: 'baseRetriever', type: 'VectorStoreRetriever' }, @@ -38,6 +40,14 @@ class RRFRetriever_Retrievers implements INode { name: 'model', type: 'BaseLanguageModel' }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true + }, { label: 'Query Count', name: 'queryCount', @@ -54,7 +64,6 @@ class RRFRetriever_Retrievers implements INode { description: 'Number of top results to fetch. Default to the TopK of the Base Retriever', placeholder: '0', type: 'number', - default: 0, additionalParams: true, optional: true }, @@ -71,27 +80,56 @@ class RRFRetriever_Retrievers implements INode { optional: true } ] + this.outputs = [ + { + label: 'Reciprocal Rank Fusion Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Document', + name: 'document', + baseClasses: ['Document'] + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, input: string): Promise { const llm = nodeData.inputs?.model as BaseLanguageModel const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever + const query = nodeData.inputs?.query as string const queryCount = nodeData.inputs?.queryCount as string const q = queryCount ? parseFloat(queryCount) : 4 const topK = nodeData.inputs?.topK as string - let k = topK ? parseFloat(topK) : 4 + const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4 const constantC = nodeData.inputs?.c as string - let c = topK ? parseFloat(constantC) : 60 - - if (k <= 0) { - k = (baseRetriever as VectorStoreRetriever).k - } + const c = topK ? parseFloat(constantC) : 60 + const output = nodeData.outputs?.output as string const ragFusion = new ReciprocalRankFusion(llm, baseRetriever as VectorStoreRetriever, q, k, c) - return new ContextualCompressionRetriever({ + const retriever = new ContextualCompressionRetriever({ baseCompressor: ragFusion, baseRetriever: baseRetriever }) + + if (output === 'retriever') return retriever + else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input) + else if (output === 'text') { + let finaltext = '' + + const docs = await retriever.getRelevantDocuments(query ? query : input) + + for (const doc of docs) finaltext += `${doc.pageContent}\n` + + return handleEscapeCharacters(finaltext, false) + } + + return retriever } } diff --git a/packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg b/packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg deleted file mode 100644 index 23c52d25..00000000 --- a/packages/components/nodes/retrievers/RRFRetriever/compressionRetriever.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/retrievers/RRFRetriever/rrfRetriever.svg b/packages/components/nodes/retrievers/RRFRetriever/rrfRetriever.svg new file mode 100644 index 00000000..56fbcc5a --- /dev/null +++ b/packages/components/nodes/retrievers/RRFRetriever/rrfRetriever.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/retrievers/SimilarityThresholdRetriever/SimilarityThresholdRetriever.ts b/packages/components/nodes/retrievers/SimilarityThresholdRetriever/SimilarityThresholdRetriever.ts index a9f4b3d8..5f5a9ed0 100644 --- a/packages/components/nodes/retrievers/SimilarityThresholdRetriever/SimilarityThresholdRetriever.ts +++ b/packages/components/nodes/retrievers/SimilarityThresholdRetriever/SimilarityThresholdRetriever.ts @@ -18,7 +18,7 @@ class SimilarityThresholdRetriever_Retrievers implements INode { constructor() { this.label = 'Similarity Score Threshold Retriever' this.name = 'similarityThresholdRetriever' - this.version = 1.0 + this.version = 2.0 this.type = 'SimilarityThresholdRetriever' this.icon = 'similaritythreshold.svg' this.category = 'Retrievers' @@ -30,6 +30,14 @@ class SimilarityThresholdRetriever_Retrievers implements INode { name: 'vectorStore', type: 'VectorStore' }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true + }, { label: 'Minimum Similarity Score (%)', name: 'minSimilarityScore', @@ -44,7 +52,8 @@ class SimilarityThresholdRetriever_Retrievers implements INode { description: `The maximum number of results to fetch`, type: 'number', default: 20, - step: 1 + step: 1, + additionalParams: true }, { label: 'K Increment', @@ -52,7 +61,8 @@ class SimilarityThresholdRetriever_Retrievers implements INode { description: `How much to increase K by each time. It'll fetch N results, then N + kIncrement, then N + kIncrement * 2, etc.`, type: 'number', default: 2, - step: 1 + step: 1, + additionalParams: true } ] this.outputs = [ @@ -77,6 +87,7 @@ class SimilarityThresholdRetriever_Retrievers implements INode { async init(nodeData: INodeData, input: string): Promise { const vectorStore = nodeData.inputs?.vectorStore as VectorStore const minSimilarityScore = nodeData.inputs?.minSimilarityScore as number + const query = nodeData.inputs?.query as string const maxK = nodeData.inputs?.maxK as string const kIncrement = nodeData.inputs?.kIncrement as string @@ -89,11 +100,11 @@ class SimilarityThresholdRetriever_Retrievers implements INode { }) if (output === 'retriever') return retriever - else if (output === 'document') return await retriever.getRelevantDocuments(input) + else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input) else if (output === 'text') { let finaltext = '' - const docs = await retriever.getRelevantDocuments(input) + const docs = await retriever.getRelevantDocuments(query ? query : input) for (const doc of docs) finaltext += `${doc.pageContent}\n` diff --git a/packages/components/nodes/vectorstores/Astra/Astra.ts b/packages/components/nodes/vectorstores/Astra/Astra.ts index 865f1044..edaadc9c 100644 --- a/packages/components/nodes/vectorstores/Astra/Astra.ts +++ b/packages/components/nodes/vectorstores/Astra/Astra.ts @@ -4,6 +4,7 @@ import { Document } from 'langchain/document' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData } from '../../../src/utils' import { AstraDBVectorStore, AstraLibArgs } from '@langchain/community/vectorstores/astradb' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' class Astra_VectorStores implements INode { label: string @@ -26,7 +27,7 @@ class Astra_VectorStores implements INode { this.type = 'Astra' this.icon = 'astra.svg' this.category = 'Vector Stores' - this.description = `Upsert embedded data and perform similarity search upon query using DataStax Astra DB, a serverless vector database that’s perfect for managing mission-critical AI workloads` + this.description = `Upsert embedded data and perform similarity or mmr search upon query using DataStax Astra DB, a serverless vector database that’s perfect for managing mission-critical AI workloads` this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.badge = 'NEW' this.credential = { @@ -74,6 +75,7 @@ class Astra_VectorStores implements INode { optional: true } ] + addMMRInputParams(this.inputs) this.outputs = [ { label: 'Astra Retriever', @@ -139,9 +141,6 @@ class Astra_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const vectorDimension = nodeData.inputs?.vectorDimension as number const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined - const output = nodeData.outputs?.output as string - const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 const credentialData = await getCredentialData(nodeData.credential ?? '', options) @@ -176,14 +175,7 @@ class Astra_VectorStores implements INode { const vectorStore = await AstraDBVectorStore.fromExistingIndex(embeddings, astraConfig) - if (output === 'retriever') { - const retriever = vectorStore.asRetriever(k) - return retriever - } else if (output === 'vectorStore') { - ;(vectorStore as any).k = k - return vectorStore - } - return vectorStore + return resolveVectorStoreOrRetriever(nodeData, vectorStore) } } diff --git a/packages/components/nodes/vectorstores/MongoDBAtlas/MongoDBAtlas.ts b/packages/components/nodes/vectorstores/MongoDBAtlas/MongoDBAtlas.ts index 9bc23f10..6ba7199f 100644 --- a/packages/components/nodes/vectorstores/MongoDBAtlas/MongoDBAtlas.ts +++ b/packages/components/nodes/vectorstores/MongoDBAtlas/MongoDBAtlas.ts @@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' class MongoDBAtlas_VectorStores implements INode { label: string @@ -24,7 +25,7 @@ class MongoDBAtlas_VectorStores implements INode { this.label = 'MongoDB Atlas' this.name = 'mongoDBAtlas' this.version = 1.0 - this.description = `Upsert embedded data and perform similarity search upon query using MongoDB Atlas, a managed cloud mongodb database` + this.description = `Upsert embedded data and perform similarity or mmr search upon query using MongoDB Atlas, a managed cloud mongodb database` this.type = 'MongoDB Atlas' this.icon = 'mongodb.svg' this.category = 'Vector Stores' @@ -95,6 +96,7 @@ class MongoDBAtlas_VectorStores implements INode { optional: true } ] + addMMRInputParams(this.inputs) this.outputs = [ { label: 'MongoDB Retriever', @@ -162,9 +164,6 @@ class MongoDBAtlas_VectorStores implements INode { let textKey = nodeData.inputs?.textKey as string let embeddingKey = nodeData.inputs?.embeddingKey as string const embeddings = nodeData.inputs?.embeddings as Embeddings - const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 - const output = nodeData.outputs?.output as string let mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) @@ -181,13 +180,7 @@ class MongoDBAtlas_VectorStores implements INode { embeddingKey }) - if (output === 'retriever') { - return vectorStore.asRetriever(k) - } else if (output === 'vectorStore') { - ;(vectorStore as any).k = k - return vectorStore - } - return vectorStore + return resolveVectorStoreOrRetriever(nodeData, vectorStore) } } diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts index 4b91a9b5..6623b1a2 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts @@ -24,7 +24,7 @@ class Pinecone_VectorStores implements INode { constructor() { this.label = 'Pinecone' this.name = 'pinecone' - this.version = 3.0 + this.version = 2.0 this.type = 'Pinecone' this.icon = 'pinecone.svg' this.category = 'Vector Stores' diff --git a/packages/server/marketplaces/chatflows/AutoGPT.json b/packages/server/marketplaces/chatflows/AutoGPT.json index 150fe17e..0062cd43 100644 --- a/packages/server/marketplaces/chatflows/AutoGPT.json +++ b/packages/server/marketplaces/chatflows/AutoGPT.json @@ -511,7 +511,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -552,6 +552,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -576,7 +615,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/BabyAGI.json b/packages/server/marketplaces/chatflows/BabyAGI.json index ab387205..81e3f230 100644 --- a/packages/server/marketplaces/chatflows/BabyAGI.json +++ b/packages/server/marketplaces/chatflows/BabyAGI.json @@ -166,7 +166,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -207,6 +207,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -231,7 +270,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json index 0e9e41bd..4378a47d 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -301,7 +301,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -342,6 +342,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -366,7 +405,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index e2fd6421..253a1dfc 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -541,7 +541,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -582,6 +582,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -606,7 +645,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Metadata Filter.json b/packages/server/marketplaces/chatflows/Metadata Filter.json index abd85d36..f7b2fbfb 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter.json @@ -625,7 +625,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -666,6 +666,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -690,7 +729,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "{\"id\":{\"$in\":[\"doc1\",\"doc2\"]}}", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json index 5388d965..e86b28c9 100644 --- a/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json @@ -560,7 +560,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -601,6 +601,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -625,7 +664,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { @@ -840,6 +882,45 @@ "additionalParams": true, "optional": true, "id": "supabase_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -865,7 +946,10 @@ "tableName": "", "queryName": "", "supabaseMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index 1b1d8de6..df05feef 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -643,7 +643,7 @@ "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", - "description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", + "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database", "inputParams": [ { "label": "Connect Credential", @@ -684,6 +684,45 @@ "additionalParams": true, "optional": true, "id": "pinecone_0-input-topK-number" + }, + { + "label": "Search Type", + "name": "searchType", + "type": "options", + "default": "similarity", + "options": [ + { + "label": "Similarity", + "name": "similarity" + }, + { + "label": "Max Marginal Relevance", + "name": "mmr" + } + ], + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-searchType-options" + }, + { + "label": "Fetch K (for MMR Search)", + "name": "fetchK", + "description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR", + "placeholder": "20", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-fetchK-number" + }, + { + "label": "Lambda (for MMR Search)", + "name": "lambda", + "description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR", + "placeholder": "0.5", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pinecone_0-input-lambda-number" } ], "inputAnchors": [ @@ -708,7 +747,10 @@ "pineconeIndex": "", "pineconeNamespace": "", "pineconeMetadataFilter": "", - "topK": "" + "topK": "", + "searchType": "similarity", + "fetchK": "", + "lambda": "" }, "outputAnchors": [ { From f26a99ade246a11d8cead064807cdf6752e83fbc Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 17 Jan 2024 22:08:04 +0000 Subject: [PATCH 253/268] update figma loader --- .../nodes/documentloaders/Figma/Figma.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/documentloaders/Figma/Figma.ts b/packages/components/nodes/documentloaders/Figma/Figma.ts index 3d313044..6d7aa530 100644 --- a/packages/components/nodes/documentloaders/Figma/Figma.ts +++ b/packages/components/nodes/documentloaders/Figma/Figma.ts @@ -1,6 +1,7 @@ import { getCredentialData, getCredentialParam } from '../../../src' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma' +import { TextSplitter } from 'langchain/text_splitter' class Figma_DocumentLoaders implements INode { label: string @@ -71,6 +72,8 @@ class Figma_DocumentLoaders implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const nodeIds = (nodeData.inputs?.nodeIds as string)?.trim().split(',') || [] const fileKey = nodeData.inputs?.fileKey as string + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const metadata = nodeData.inputs?.metadata const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessToken = getCredentialParam('accessToken', credentialData, nodeData) @@ -82,7 +85,21 @@ class Figma_DocumentLoaders implements INode { } const loader = new FigmaFileLoader(figmaOptions) - const docs = await loader.load() + + const docs = textSplitter ? await loader.loadAndSplit() : await loader.load() + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + return docs.map((doc) => { + return { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + }) + } return docs } From 4256655c7bace928ff4bc8e12ebe81696c9304f1 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 17 Jan 2024 23:47:11 +0000 Subject: [PATCH 254/268] add $vars and $flow to custom function --- .../nodes/tools/CustomTool/CustomTool.ts | 20 +----- .../components/nodes/tools/CustomTool/core.ts | 39 +--------- .../CustomFunction/CustomFunction.ts | 35 +++++---- .../IfElseFunction/IfElseFunction.ts | 35 +++++---- packages/components/src/Interface.ts | 6 ++ packages/components/src/utils.ts | 72 ++++++++++++++++++- packages/server/src/index.ts | 27 +++++-- packages/server/src/utils/index.ts | 3 + 8 files changed, 136 insertions(+), 101 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index a983d0d9..6ba5bc26 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -1,5 +1,5 @@ import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface' -import { convertSchemaToZod, getBaseClasses } from '../../../src/utils' +import { convertSchemaToZod, getBaseClasses, getVars } from '../../../src/utils' import { DynamicStructuredTool } from './core' import { z } from 'zod' import { DataSource } from 'typeorm' @@ -81,23 +81,7 @@ class CustomTool_Tools implements INode { } if (customToolFunc) obj.code = customToolFunc - const variables = await appDataSource.getRepository(databaseEntities['Variable']).find() - - // override variables defined in overrideConfig - // nodeData.inputs.variables is an Object, check each property and override the variable - if (nodeData?.inputs?.vars) { - for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) { - const foundVar = variables.find((v) => v.name === propertyName) - if (foundVar) { - // even if the variable was defined as runtime, we override it with static value - foundVar.type = 'static' - foundVar.value = nodeData.inputs.vars[propertyName] - } else { - // add it the variables, if not found locally in the db - variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] }) - } - } - } + const variables = await getVars(appDataSource, databaseEntities, nodeData) const flow = { chatflowId: options.chatflowid } diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index b543aefa..19be88f1 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { NodeVM } from 'vm2' -import { availableDependencies } from '../../../src/utils' +import { availableDependencies, defaultAllowBuiltInDep, prepareSandboxVars } from '../../../src/utils' import { RunnableConfig } from '@langchain/core/runnables' import { StructuredTool, ToolParams } from '@langchain/core/tools' import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager' @@ -112,48 +112,13 @@ export class DynamicStructuredTool< } } - // inject variables - let vars = {} - if (this.variables) { - for (const item of this.variables) { - let value = item.value - - // read from .env file - if (item.type === 'runtime') { - value = process.env[item.name] - } - - Object.defineProperty(vars, item.name, { - enumerable: true, - configurable: true, - writable: true, - value: value - }) - } - } - sandbox['$vars'] = vars + sandbox['$vars'] = prepareSandboxVars(this.variables) // inject flow properties if (this.flowObj) { sandbox['$flow'] = { ...this.flowObj, ...flowConfig } } - const defaultAllowBuiltInDep = [ - 'assert', - 'buffer', - 'crypto', - 'events', - 'http', - 'https', - 'net', - 'path', - 'querystring', - 'timers', - 'tls', - 'url', - 'zlib' - ] - const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) : defaultAllowBuiltInDep diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index 749c3a86..ff29d589 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -1,6 +1,7 @@ -import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { NodeVM } from 'vm2' -import { availableDependencies, handleEscapeCharacters } from '../../../src/utils' +import { DataSource } from 'typeorm' +import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils' class CustomFunction_Utilities implements INode { label: string @@ -55,9 +56,19 @@ class CustomFunction_Utilities implements INode { ] } - async init(nodeData: INodeData, input: string): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const javascriptFunction = nodeData.inputs?.javascriptFunction as string const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + const variables = await getVars(appDataSource, databaseEntities, nodeData) + const flow = { + chatflowId: options.chatflowid, + sessionId: options.sessionId, + chatId: options.chatId, + input + } let inputVars: ICommonObject = {} if (functionInputVariablesRaw) { @@ -70,6 +81,8 @@ class CustomFunction_Utilities implements INode { } let sandbox: any = { $input: input } + sandbox['$vars'] = prepareSandboxVars(variables) + sandbox['$flow'] = flow if (Object.keys(inputVars).length) { for (const item in inputVars) { @@ -81,22 +94,6 @@ class CustomFunction_Utilities implements INode { } } - const defaultAllowBuiltInDep = [ - 'assert', - 'buffer', - 'crypto', - 'events', - 'http', - 'https', - 'net', - 'path', - 'querystring', - 'timers', - 'tls', - 'url', - 'zlib' - ] - const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) : defaultAllowBuiltInDep diff --git a/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts b/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts index 862521eb..55339e1a 100644 --- a/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts +++ b/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts @@ -1,6 +1,7 @@ -import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { NodeVM } from 'vm2' -import { availableDependencies } from '../../../src/utils' +import { DataSource } from 'typeorm' +import { availableDependencies, defaultAllowBuiltInDep, getVars, prepareSandboxVars } from '../../../src/utils' class IfElseFunction_Utilities implements INode { label: string @@ -73,10 +74,20 @@ class IfElseFunction_Utilities implements INode { ] } - async init(nodeData: INodeData, input: string): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const ifFunction = nodeData.inputs?.ifFunction as string const elseFunction = nodeData.inputs?.elseFunction as string const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + const variables = await getVars(appDataSource, databaseEntities, nodeData) + const flow = { + chatflowId: options.chatflowid, + sessionId: options.sessionId, + chatId: options.chatId, + input + } let inputVars: ICommonObject = {} if (functionInputVariablesRaw) { @@ -89,6 +100,8 @@ class IfElseFunction_Utilities implements INode { } let sandbox: any = { $input: input } + sandbox['$vars'] = prepareSandboxVars(variables) + sandbox['$flow'] = flow if (Object.keys(inputVars).length) { for (const item in inputVars) { @@ -96,22 +109,6 @@ class IfElseFunction_Utilities implements INode { } } - const defaultAllowBuiltInDep = [ - 'assert', - 'buffer', - 'crypto', - 'events', - 'http', - 'https', - 'net', - 'path', - 'querystring', - 'timers', - 'tls', - 'url', - 'zlib' - ] - const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) : defaultAllowBuiltInDep diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d74ba1b4..fe08f070 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -29,6 +29,12 @@ export interface ICommonObject { [key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[] } +export interface IVariable { + name: string + value: string + type: string +} + export type IDatabaseEntity = { [key: string]: any } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 2215eb41..7e9a68eb 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -5,7 +5,7 @@ import * as path from 'path' import { JSDOM } from 'jsdom' import { z } from 'zod' import { DataSource } from 'typeorm' -import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface' +import { ICommonObject, IDatabaseEntity, IMessage, INodeData, IVariable } from './Interface' import { AES, enc } from 'crypto-js' import { ChatMessageHistory } from 'langchain/memory' import { AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' @@ -70,6 +70,22 @@ export const availableDependencies = [ 'weaviate-ts-client' ] +export const defaultAllowBuiltInDep = [ + 'assert', + 'buffer', + 'crypto', + 'events', + 'http', + 'https', + 'net', + 'path', + 'querystring', + 'timers', + 'tls', + 'url', + 'zlib' +] + /** * Get base classes of components * @@ -688,3 +704,57 @@ export const convertMultiOptionsToStringArray = (inputString: string): string[] } return ArrayString } + +/** + * Get variables + * @param {DataSource} appDataSource + * @param {IDatabaseEntity} databaseEntities + * @param {INodeData} nodeData + */ +export const getVars = async (appDataSource: DataSource, databaseEntities: IDatabaseEntity, nodeData: INodeData) => { + const variables = ((await appDataSource.getRepository(databaseEntities['Variable']).find()) as IVariable[]) ?? [] + + // override variables defined in overrideConfig + // nodeData.inputs.variables is an Object, check each property and override the variable + if (nodeData?.inputs?.vars) { + for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) { + const foundVar = variables.find((v) => v.name === propertyName) + if (foundVar) { + // even if the variable was defined as runtime, we override it with static value + foundVar.type = 'static' + foundVar.value = nodeData.inputs.vars[propertyName] + } else { + // add it the variables, if not found locally in the db + variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] }) + } + } + } + + return variables +} + +/** + * Prepare sandbox variables + * @param {IVariable[]} variables + */ +export const prepareSandboxVars = (variables: IVariable[]) => { + let vars = {} + if (variables) { + for (const item of variables) { + let value = item.value + + // read from .env file + if (item.type === 'runtime') { + value = process.env[item.name] ?? '' + } + + Object.defineProperty(vars, item.name, { + enumerable: true, + configurable: true, + writable: true, + value: value + }) + } + } + return vars +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 94a3b538..1986d207 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -294,7 +294,13 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() - const returnData = await newNodeInstance.init(nodeData) + const options: ICommonObject = { + appDataSource: this.AppDataSource, + databaseEntities, + logger + } + + const returnData = await newNodeInstance.init(nodeData, '', options) const result = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData return res.json(result) @@ -1448,6 +1454,11 @@ export class App { let chatId = incomingInput.chatId ?? '' let isUpsert = true + // Get session ID + const memoryNode = findMemoryNode(nodes, edges) + let sessionId = undefined + if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) + const vsNodes = nodes.filter( (node) => node.data.category === 'Vector Stores' && @@ -1485,6 +1496,7 @@ export class App { incomingInput.question, chatHistory, chatId, + sessionId ?? '', chatflowid, this.AppDataSource, incomingInput?.overrideConfig, @@ -1562,6 +1574,12 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges + // Get session ID + const memoryNode = findMemoryNode(nodes, edges) + const memoryType = memoryNode?.data.label + let sessionId = undefined + if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) + /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met: * - Node Data already exists in pool * - Still in sync (i.e the flow has not been modified since) @@ -1671,6 +1689,7 @@ export class App { incomingInput.question, chatHistory, chatId, + sessionId ?? '', chatflowid, this.AppDataSource, incomingInput?.overrideConfig, @@ -1700,12 +1719,6 @@ export class App { logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - const memoryNode = findMemoryNode(nodes, edges) - const memoryType = memoryNode?.data.label - - let sessionId = undefined - if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) - const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string const nodeModule = await import(nodeInstanceFilePath) const nodeInstance = new nodeModule.nodeClass({ sessionId }) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index dafe612c..2d6acf58 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -273,6 +273,7 @@ export const buildLangchain = async ( question: string, chatHistory: IMessage[], chatId: string, + sessionId: string, chatflowid: string, appDataSource: DataSource, overrideConfig?: ICommonObject, @@ -317,6 +318,7 @@ export const buildLangchain = async ( logger.debug(`[server]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) await newNodeInstance.vectorStoreMethods!['upsert']!.call(newNodeInstance, reactFlowNodeData, { chatId, + sessionId, chatflowid, chatHistory, logger, @@ -331,6 +333,7 @@ export const buildLangchain = async ( logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) let outputResult = await newNodeInstance.init(reactFlowNodeData, question, { chatId, + sessionId, chatflowid, chatHistory, logger, From d02b5a89885495cbf28997da3e0f4ed3335e7372 Mon Sep 17 00:00:00 2001 From: Octavian Cioaca <132788754+domselardi@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:47:33 +0100 Subject: [PATCH 255/268] Add autoSync github workflows --- .../workflows/autoSyncMergedPullRequest.yml | 24 ++++++++++++++ .github/workflows/autoSyncSingleCommit.yml | 31 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .github/workflows/autoSyncMergedPullRequest.yml create mode 100644 .github/workflows/autoSyncSingleCommit.yml diff --git a/.github/workflows/autoSyncMergedPullRequest.yml b/.github/workflows/autoSyncMergedPullRequest.yml new file mode 100644 index 00000000..5b1991d7 --- /dev/null +++ b/.github/workflows/autoSyncMergedPullRequest.yml @@ -0,0 +1,24 @@ +name: autoSyncMergedPullRequest +on: + pull_request: + types: + - closed + branches: [ "main" ] +jobs: + build: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Show PR info + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + run: | + echo The PR #${{ github.event.pull_request.number }} was merged on main branch! + - name: Repository Dispatch + uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.AUTOSYNC_TOKEN }} + repository: ${{ secrets.AUTOSYNC_CH_URL }} + event-type: ${{ secrets.AUTOSYNC_PR_EVENT_TYPE }} + client-payload: '{"ref": "${{ github.ref }}", "prNumber": "${{ github.event.pull_request.number }}", "sha": "${{ github.sha }}"}' diff --git a/.github/workflows/autoSyncSingleCommit.yml b/.github/workflows/autoSyncSingleCommit.yml new file mode 100644 index 00000000..ce429a60 --- /dev/null +++ b/.github/workflows/autoSyncSingleCommit.yml @@ -0,0 +1,31 @@ +name: autoSyncSingleCommit +on: + push: + branches: + - main +jobs: + doNotAutoSyncSingleCommit: + if: github.event.commits[1] != null + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: IGNORE autoSyncSingleCommit + run: | + echo This single commit has came from a merged commit. We will ignore it. This case is handled in autoSyncMergedPullRequest workflow for merge commits comming from merged pull requests only! Beware, the regular merge commits are not handled by any workflow for the moment. + autoSyncSingleCommit: + if: github.event.commits[1] == null + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: autoSyncSingleCommit + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + run: | + echo Autosync a single commit with id: ${{ github.sha }} from openSource main branch towards cloud hosted version. + - name: Repository Dispatch + uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.AUTOSYNC_TOKEN }} + repository: ${{ secrets.AUTOSYNC_CH_URL }} + event-type: ${{ secrets.AUTOSYNC_SC_EVENT_TYPE }} + client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' From db7915f426c1016f352de11d00bfdfe1ec14a31d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Thu, 18 Jan 2024 15:21:01 +0000 Subject: [PATCH 256/268] Update artillery-load-test.yml --- artillery-load-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artillery-load-test.yml b/artillery-load-test.yml index 6b1c8140..809a2a8e 100644 --- a/artillery-load-test.yml +++ b/artillery-load-test.yml @@ -33,4 +33,4 @@ scenarios: # Seconds # Total Users = 2 + 3 + 3 = 8 # Each making 1 HTTP call -# Over a duration of 3 seconds +# Over a durations of 3 seconds From 0214ed1a7333d5ecc108a442393bf41f70026377 Mon Sep 17 00:00:00 2001 From: niztal Date: Thu, 18 Jan 2024 19:28:38 +0200 Subject: [PATCH 257/268] support pinecone serverless indexes --- packages/components/credentials/PineconeApi.credential.ts | 5 ----- packages/components/nodes/vectorstores/Pinecone/Pinecone.ts | 6 ++---- .../nodes/vectorstores/Pinecone/Pinecone_Existing.ts | 4 +--- .../nodes/vectorstores/Pinecone/Pinecone_Upsert.ts | 4 +--- packages/components/package.json | 2 +- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/components/credentials/PineconeApi.credential.ts b/packages/components/credentials/PineconeApi.credential.ts index 4c5f62fe..486c9834 100644 --- a/packages/components/credentials/PineconeApi.credential.ts +++ b/packages/components/credentials/PineconeApi.credential.ts @@ -16,11 +16,6 @@ class PineconeApi implements INodeCredential { label: 'Pinecone Api Key', name: 'pineconeApiKey', type: 'password' - }, - { - label: 'Pinecone Environment', - name: 'pineconeEnv', - type: 'string' } ] } diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts index 6623b1a2..dd0c76f7 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone.ts @@ -108,8 +108,7 @@ class Pinecone_VectorStores implements INode { const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) const client = new Pinecone({ - apiKey: pineconeApiKey, - environment: pineconeEnv + apiKey: pineconeApiKey }) const pineconeIndex = client.Index(index) @@ -148,8 +147,7 @@ class Pinecone_VectorStores implements INode { const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) const client = new Pinecone({ - apiKey: pineconeApiKey, - environment: pineconeEnv + apiKey: pineconeApiKey }) const pineconeIndex = client.Index(index) diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts index 9eea70de..f805fc33 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts @@ -95,11 +95,9 @@ class Pinecone_Existing_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) - const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) const client = new Pinecone({ - apiKey: pineconeApiKey, - environment: pineconeEnv + apiKey: pineconeApiKey }) const pineconeIndex = client.Index(index) diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts index cb54e6e9..14dbf663 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts @@ -96,11 +96,9 @@ class PineconeUpsert_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) - const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) const client = new Pinecone({ - apiKey: pineconeApiKey, - environment: pineconeEnv + apiKey: pineconeApiKey }) const pineconeIndex = client.Index(index) diff --git a/packages/components/package.json b/packages/components/package.json index 894014d4..0d4cd274 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -33,7 +33,7 @@ "@langchain/mistralai": "^0.0.6", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", - "@pinecone-database/pinecone": "^1.1.1", + "@pinecone-database/pinecone": "^2.0.1", "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.29.0", "@types/js-yaml": "^4.0.5", From 953e1468bbfb2e51d4e958c4a4e8389fc6174a09 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 19 Jan 2024 00:02:31 +0000 Subject: [PATCH 258/268] add telemetry --- packages/server/package.json | 1 + packages/server/src/index.ts | 44 +++++++++++++++++++- packages/server/src/utils/index.ts | 56 ++++++++++++++++++++++++++ packages/server/src/utils/telemetry.ts | 50 +++++++++++++++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/utils/telemetry.ts diff --git a/packages/server/package.json b/packages/server/package.json index 79ff4961..fe27f8d6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -60,6 +60,7 @@ "multer": "^1.4.5-lts.1", "mysql": "^2.18.1", "pg": "^8.11.1", + "posthog-node": "^3.5.0", "reflect-metadata": "^0.1.13", "sanitize-html": "^2.11.0", "socket.io": "^4.6.1", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1986d207..c64333dd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -45,7 +45,9 @@ import { getSessionChatHistory, getAllConnectedNodes, clearSessionMemory, - findMemoryNode + findMemoryNode, + getTelemetryFlowObj, + getAppVersion } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -64,6 +66,7 @@ import { sanitizeMiddleware } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' +import { Telemetry } from './utils/telemetry' import { Variable } from './database/entities/Variable' export class App { @@ -71,6 +74,7 @@ export class App { nodesPool: NodesPool chatflowPool: ChatflowPool cachePool: CachePool + telemetry: Telemetry AppDataSource = getDataSource() constructor() { @@ -105,6 +109,9 @@ export class App { // Initialize cache pool this.cachePool = new CachePool() + + // Initialize telemetry + this.telemetry = new Telemetry() }) .catch((err) => { logger.error('❌ [server]: Error during Data Source initialization:', err) @@ -388,6 +395,12 @@ export class App { const chatflow = this.AppDataSource.getRepository(ChatFlow).create(newChatFlow) const results = await this.AppDataSource.getRepository(ChatFlow).save(chatflow) + await this.telemetry.sendTelemetry('chatflow_created', { + version: await getAppVersion(), + chatlowId: results.id, + flowGraph: getTelemetryFlowObj(JSON.parse(results.flowData)?.nodes, JSON.parse(results.flowData)?.edges) + }) + return res.json(results) }) @@ -674,6 +687,12 @@ export class App { const tool = this.AppDataSource.getRepository(Tool).create(newTool) const results = await this.AppDataSource.getRepository(Tool).save(tool) + await this.telemetry.sendTelemetry('tool_created', { + version: await getAppVersion(), + toolId: results.id, + toolName: results.name + }) + return res.json(results) }) @@ -880,6 +899,11 @@ export class App { const assistant = this.AppDataSource.getRepository(Assistant).create(newAssistant) const results = await this.AppDataSource.getRepository(Assistant).save(assistant) + await this.telemetry.sendTelemetry('assistant_created', { + version: await getAppVersion(), + assistantId: results.id + }) + return res.json(results) }) @@ -1508,6 +1532,15 @@ export class App { const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.data.id)) this.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig) + + await this.telemetry.sendTelemetry('vector_upserted', { + version: await getAppVersion(), + chatlowId: chatflowid, + type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL, + flowGraph: getTelemetryFlowObj(nodes, edges), + stopNodeId + }) + return res.status(201).send('Successfully Upserted') } catch (e: any) { logger.error('[server]: Error:', e) @@ -1784,6 +1817,14 @@ export class App { await this.addChatMessage(apiMessage) logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + await this.telemetry.sendTelemetry('prediction_sent', { + version: await getAppVersion(), + chatlowId: chatflowid, + chatId, + sessionId, + type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL, + flowGraph: getTelemetryFlowObj(nodes, edges) + }) // Only return ChatId when its Internal OR incoming input has ChatId, to avoid confusion when calling API if (incomingInput.chatId || isInternal) result.chatId = chatId @@ -1798,6 +1839,7 @@ export class App { async stopApp() { try { const removePromises: any[] = [] + removePromises.push(this.telemetry.flush()) await Promise.all(removePromises) } catch (e) { logger.error(`❌[server]: Flowise Server shut down error: ${e}`) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2d6acf58..a232094e 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1082,3 +1082,59 @@ export const getAllValuesFromJson = (obj: any): any[] => { extractValues(obj) return values } + +/** + * Get only essential flow data items for telemetry + * @param {IReactFlowNode[]} nodes + * @param {IReactFlowEdge[]} edges + */ +export const getTelemetryFlowObj = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]) => { + const nodeData = nodes.map((node) => node.id) + const edgeData = edges.map((edge) => ({ source: edge.source, target: edge.target })) + return { nodes: nodeData, edges: edgeData } +} + +/** + * Get user settings file + * TODO: move env variables to settings json file, easier configuration + */ +export const getUserSettingsFilePath = () => { + const checkPaths = [path.join(getUserHome(), '.flowise', 'settings.json')] + for (const checkPath of checkPaths) { + if (fs.existsSync(checkPath)) { + return checkPath + } + } + return '' +} + +/** + * Get app current version + */ +export const getAppVersion = async () => { + const getPackageJsonPath = (): string => { + const checkPaths = [ + path.join(__dirname, '..', 'package.json'), + path.join(__dirname, '..', '..', 'package.json'), + path.join(__dirname, '..', '..', '..', 'package.json'), + path.join(__dirname, '..', '..', '..', '..', 'package.json'), + path.join(__dirname, '..', '..', '..', '..', '..', 'package.json') + ] + for (const checkPath of checkPaths) { + if (fs.existsSync(checkPath)) { + return checkPath + } + } + return '' + } + + const packagejsonPath = getPackageJsonPath() + if (!packagejsonPath) return '' + try { + const content = await fs.promises.readFile(packagejsonPath, 'utf8') + const parsedContent = JSON.parse(content) + return parsedContent.version + } catch (error) { + return '' + } +} diff --git a/packages/server/src/utils/telemetry.ts b/packages/server/src/utils/telemetry.ts new file mode 100644 index 00000000..4254ea76 --- /dev/null +++ b/packages/server/src/utils/telemetry.ts @@ -0,0 +1,50 @@ +import { v4 as uuidv4 } from 'uuid' +import { PostHog } from 'posthog-node' +import path from 'path' +import fs from 'fs' +import { getUserHome, getUserSettingsFilePath } from '.' + +export class Telemetry { + postHog?: PostHog + + constructor() { + if (process.env.DISABLE_FLOWISE_TELEMETRY !== 'true') { + this.postHog = new PostHog('phc_jEDuFYnOnuXsws986TLWzuisbRjwFqTl9JL8tDMgqme') + } else { + this.postHog = undefined + } + } + + async id(): Promise { + try { + const settingsContent = await fs.promises.readFile(getUserSettingsFilePath(), 'utf8') + const settings = JSON.parse(settingsContent) + return settings.instanceId + } catch (error) { + const instanceId = uuidv4() + const settings = { + instanceId + } + const defaultLocation = path.join(getUserHome(), '.flowise', 'settings.json') + await fs.promises.writeFile(defaultLocation, JSON.stringify(settings, null, 2)) + return instanceId + } + } + + async sendTelemetry(event: string, properties = {}): Promise { + if (this.postHog) { + const distinctId = await this.id() + this.postHog.capture({ + event, + distinctId, + properties + }) + } + } + + async flush(): Promise { + if (this.postHog) { + await this.postHog.shutdownAsync() + } + } +} From b5de88784bd718989b0a8102ef59c36a020b4c5b Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 19 Jan 2024 00:38:08 +0000 Subject: [PATCH 259/268] add flag --- CONTRIBUTING-ZH.md | 1 + CONTRIBUTING.md | 1 + docker/.env.example | 4 +++- docker/docker-compose.yml | 1 + packages/server/.env.example | 2 ++ packages/server/src/commands/start.ts | 6 +++++- 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING-ZH.md b/CONTRIBUTING-ZH.md index 5a752280..7e35d194 100644 --- a/CONTRIBUTING-ZH.md +++ b/CONTRIBUTING-ZH.md @@ -138,6 +138,7 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package | DATABASE_NAME | 数据库名称(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | | SECRETKEY_PATH | 保存加密密钥(用于加密/解密凭据)的位置 | 字符串 | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | 加密密钥用于替代存储在 SECRETKEY_PATH 中的密钥 | 字符串 | +| DISABLE_FLOWISE_TELEMETRY | 关闭遥测 | 字符串 | 您也可以在使用 `npx` 时指定环境变量。例如: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04cb80b4..88d1aaac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,6 +141,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | +| DISABLE_FLOWISE_TELEMETRY | Turn off telemetry | Boolean | You can also specify the env variables when using `npx`. For example: diff --git a/docker/.env.example b/docker/.env.example index 7d4f1699..0fe69dd1 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -25,4 +25,6 @@ LOG_PATH=/root/.flowise/logs # LANGCHAIN_TRACING_V2=true # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key -# LANGCHAIN_PROJECT=your_project \ No newline at end of file +# LANGCHAIN_PROJECT=your_project + +# DISABLE_FLOWISE_TELEMETRY=true \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 92688469..c8c88bf3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -22,6 +22,7 @@ services: - FLOWISE_SECRETKEY_OVERWRITE=${FLOWISE_SECRETKEY_OVERWRITE} - LOG_LEVEL=${LOG_LEVEL} - LOG_PATH=${LOG_PATH} + - DISABLE_FLOWISE_TELEMETRY=${DISABLE_FLOWISE_TELEMETRY} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/server/.env.example b/packages/server/.env.example index 6e746a4d..ed54ac66 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -26,3 +26,5 @@ PORT=3000 # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_PROJECT=your_project + +# DISABLE_FLOWISE_TELEMETRY=true \ No newline at end of file diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index d4e8cfdb..08cd8298 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -39,7 +39,8 @@ export default class Start extends Command { LANGCHAIN_TRACING_V2: Flags.string(), LANGCHAIN_ENDPOINT: Flags.string(), LANGCHAIN_API_KEY: Flags.string(), - LANGCHAIN_PROJECT: Flags.string() + LANGCHAIN_PROJECT: Flags.string(), + DISABLE_FLOWISE_TELEMETRY: Flags.string() } async stopProcess() { @@ -113,6 +114,9 @@ export default class Start extends Command { if (flags.LANGCHAIN_API_KEY) process.env.LANGCHAIN_API_KEY = flags.LANGCHAIN_API_KEY if (flags.LANGCHAIN_PROJECT) process.env.LANGCHAIN_PROJECT = flags.LANGCHAIN_PROJECT + // Telemetry + if (flags.DISABLE_FLOWISE_TELEMETRY) process.env.DISABLE_FLOWISE_TELEMETRY = flags.DISABLE_FLOWISE_TELEMETRY + await (async () => { try { logger.info('Starting Flowise...') From 2640f6b1dc1d86a29f7d70a436a87f26ae55b239 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 19 Jan 2024 00:49:52 +0000 Subject: [PATCH 260/268] update prediction_sent metadata --- packages/server/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index c64333dd..3c5766fd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1821,7 +1821,6 @@ export class App { version: await getAppVersion(), chatlowId: chatflowid, chatId, - sessionId, type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL, flowGraph: getTelemetryFlowObj(nodes, edges) }) From 31a4769079c73601596130cf2244899da79c7eee Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 19 Jan 2024 14:27:40 +0000 Subject: [PATCH 261/268] fix output prediction output parser --- .../nodes/chains/LLMChain/LLMChain.ts | 11 ++++++++-- .../CustomFunction/CustomFunction.ts | 11 ++++++++++ .../IfElseFunction/IfElseFunction.ts | 21 ++++++++++++++++--- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index b7c055e4..f83fc36a 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -82,7 +82,7 @@ class LLMChain_Chains implements INode { const model = nodeData.inputs?.model as BaseLanguageModel const prompt = nodeData.inputs?.prompt const output = nodeData.outputs?.output as string - const promptValues = prompt.promptValues as ICommonObject + let promptValues: ICommonObject | undefined = nodeData.inputs?.prompt.promptValues as ICommonObject const llmOutputParser = nodeData.inputs?.outputParser as BaseOutputParser this.outputParser = llmOutputParser if (llmOutputParser) { @@ -107,17 +107,24 @@ class LLMChain_Chains implements INode { verbose: process.env.DEBUG === 'true' }) const inputVariables = chain.prompt.inputVariables as string[] // ["product"] + promptValues = injectOutputParser(this.outputParser, chain, promptValues) const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData) // eslint-disable-next-line no-console console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console console.log(res) + + let finalRes = res + if (this.outputParser && typeof res === 'object' && Object.prototype.hasOwnProperty.call(res, 'json')) { + finalRes = (res as ICommonObject).json + } + /** * Apply string transformation to convert special chars: * FROM: hello i am ben\n\n\thow are you? * TO: hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you? */ - return handleEscapeCharacters(res, false) + return handleEscapeCharacters(finalRes, false) } } diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index ff29d589..3dbb7c7d 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -80,6 +80,17 @@ class CustomFunction_Utilities implements INode { } } + // Some values might be a stringified JSON, parse it + for (const key in inputVars) { + if (typeof inputVars[key] === 'string' && inputVars[key].startsWith('{') && inputVars[key].endsWith('}')) { + try { + inputVars[key] = JSON.parse(inputVars[key]) + } catch (e) { + continue + } + } + } + let sandbox: any = { $input: input } sandbox['$vars'] = prepareSandboxVars(variables) sandbox['$flow'] = flow diff --git a/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts b/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts index 55339e1a..1c5c0b7d 100644 --- a/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts +++ b/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts @@ -1,7 +1,7 @@ import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { NodeVM } from 'vm2' import { DataSource } from 'typeorm' -import { availableDependencies, defaultAllowBuiltInDep, getVars, prepareSandboxVars } from '../../../src/utils' +import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils' class IfElseFunction_Utilities implements INode { label: string @@ -95,7 +95,18 @@ class IfElseFunction_Utilities implements INode { inputVars = typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) } catch (exception) { - throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) + throw new Error("Invalid JSON in the IfElse's Input Variables: " + exception) + } + } + + // Some values might be a stringified JSON, parse it + for (const key in inputVars) { + if (typeof inputVars[key] === 'string' && inputVars[key].startsWith('{') && inputVars[key].endsWith('}')) { + try { + inputVars[key] = JSON.parse(inputVars[key]) + } catch (e) { + continue + } } } @@ -105,7 +116,11 @@ class IfElseFunction_Utilities implements INode { if (Object.keys(inputVars).length) { for (const item in inputVars) { - sandbox[`$${item}`] = inputVars[item] + let value = inputVars[item] + if (typeof value === 'string') { + value = handleEscapeCharacters(value, true) + } + sandbox[`$${item}`] = value } } From 2b2a5b90289286464207bf1eed8f14a549ad02a1 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 19 Jan 2024 18:36:49 +0000 Subject: [PATCH 262/268] add console call back to chains --- .../ConversationChain/ConversationChain.ts | 86 ++-- .../ConversationalRetrievalQAChain.ts | 11 +- .../marketplaces/chatflows/Claude LLM.json | 261 +++++++---- .../chatflows/Simple Conversation Chain.json | 418 +++++++++--------- 4 files changed, 446 insertions(+), 330 deletions(-) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index fcd9921e..764f4f0e 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,13 +1,12 @@ import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, handleEscapeCharacters } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BaseChatModel } from 'langchain/chat_models/base' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' -import { flatten } from 'lodash' -import { Document } from 'langchain/document' import { RunnableSequence } from 'langchain/schema/runnable' import { StringOutputParser } from 'langchain/schema/output_parser' +import { ConsoleCallbackHandler as LCConsoleCallbackHandler } from '@langchain/core/tracers/console' let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` const inputKey = 'input' @@ -27,7 +26,7 @@ class ConversationChain_Chains implements INode { constructor(fields?: { sessionId?: string }) { this.label = 'Conversation Chain' this.name = 'conversationChain' - this.version = 1.0 + this.version = 2.0 this.type = 'ConversationChain' this.icon = 'conv.svg' this.category = 'Chains' @@ -44,6 +43,14 @@ class ConversationChain_Chains implements INode { name: 'memory', type: 'BaseMemory' }, + { + label: 'Chat Prompt Template', + name: 'chatPromptTemplate', + type: 'ChatPromptTemplate', + description: 'Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable', + optional: true + }, + /* Deprecated { label: 'Document', name: 'document', @@ -52,15 +59,17 @@ class ConversationChain_Chains implements INode { 'Include whole document into the context window, if you get maximum context length error, please use model with higher context window like Claude 100k, or gpt4 32k', optional: true, list: true - }, + },*/ { label: 'System Message', name: 'systemMessagePrompt', type: 'string', rows: 4, + description: 'If Chat Prompt Template is provided, this will be ignored', additionalParams: true, optional: true, - placeholder: 'You are a helpful assistant that write codes' + default: systemMessage, + placeholder: systemMessage } ] this.sessionId = fields?.sessionId @@ -76,15 +85,21 @@ class ConversationChain_Chains implements INode { const chain = prepareChain(nodeData, this.sessionId, options.chatHistory) const loggerHandler = new ConsoleCallbackHandler(options.logger) - const callbacks = await additionalCallbacks(nodeData, options) + const additionalCallback = await additionalCallbacks(nodeData, options) let res = '' + let callbacks = [loggerHandler, ...additionalCallback] + + if (process.env.DEBUG === 'true') { + callbacks.push(new LCConsoleCallbackHandler()) + } if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - res = await chain.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) + callbacks.push(handler) + res = await chain.invoke({ input }, { callbacks }) } else { - res = await chain.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) + res = await chain.invoke({ input }, { callbacks }) } await memory.addChatMessages( @@ -108,28 +123,27 @@ class ConversationChain_Chains implements INode { const prepareChatPrompt = (nodeData: INodeData) => { const memory = nodeData.inputs?.memory as FlowiseMemory const prompt = nodeData.inputs?.systemMessagePrompt as string - const docs = nodeData.inputs?.document as Document[] + const chatPromptTemplate = nodeData.inputs?.chatPromptTemplate as ChatPromptTemplate - const flattenDocs = docs && docs.length ? flatten(docs) : [] - const finalDocs = [] - for (let i = 0; i < flattenDocs.length; i += 1) { - if (flattenDocs[i] && flattenDocs[i].pageContent) { - finalDocs.push(new Document(flattenDocs[i])) + if (chatPromptTemplate && chatPromptTemplate.promptMessages.length) { + const sysPrompt = chatPromptTemplate.promptMessages[0] + const humanPrompt = chatPromptTemplate.promptMessages[chatPromptTemplate.promptMessages.length - 1] + const chatPrompt = ChatPromptTemplate.fromMessages([ + sysPrompt, + new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), + humanPrompt + ]) + + if ((chatPromptTemplate as any).promptValues) { + // @ts-ignore + chatPrompt.promptValues = (chatPromptTemplate as any).promptValues } + + return chatPrompt } - let finalText = '' - for (let i = 0; i < finalDocs.length; i += 1) { - finalText += finalDocs[i].pageContent - } - - const replaceChar: string[] = ['{', '}'] - for (const char of replaceChar) finalText = finalText.replaceAll(char, '') - - if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` - const chatPrompt = ChatPromptTemplate.fromMessages([ - SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage), + SystemMessagePromptTemplate.fromTemplate(prompt ? prompt : systemMessage), new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`) ]) @@ -142,15 +156,31 @@ const prepareChain = (nodeData: INodeData, sessionId?: string, chatHistory: IMes const memory = nodeData.inputs?.memory as FlowiseMemory const memoryKey = memory.memoryKey ?? 'chat_history' + const chatPrompt = prepareChatPrompt(nodeData) + let promptVariables = {} + const promptValuesRaw = (chatPrompt as any).promptValues + if (promptValuesRaw) { + const promptValues = handleEscapeCharacters(promptValuesRaw, true) + for (const val in promptValues) { + promptVariables = { + ...promptVariables, + [val]: () => { + return promptValues[val] + } + } + } + } + const conversationChain = RunnableSequence.from([ { [inputKey]: (input: { input: string }) => input.input, [memoryKey]: async () => { const history = await memory.getChatMessages(sessionId, true, chatHistory) return history - } + }, + ...promptVariables }, - prepareChatPrompt(nodeData), + chatPrompt, model, new StringOutputParser() ]) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 5f98cba1..964543de 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -13,6 +13,7 @@ import { applyPatch } from 'fast-json-patch' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { ConsoleCallbackHandler, additionalCallbacks } from '../../../src/handler' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' +import { ConsoleCallbackHandler as LCConsoleCallbackHandler } from '@langchain/core/tracers/console' type RetrievalChainInput = { chat_history: string @@ -176,11 +177,17 @@ class ConversationalRetrievalQAChain_Chains implements INode { const history = ((await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[]) ?? [] const loggerHandler = new ConsoleCallbackHandler(options.logger) - const callbacks = await additionalCallbacks(nodeData, options) + const additionalCallback = await additionalCallbacks(nodeData, options) + + let callbacks = [loggerHandler, ...additionalCallback] + + if (process.env.DEBUG === 'true') { + callbacks.push(new LCConsoleCallbackHandler()) + } const stream = answerChain.streamLog( { question: input, chat_history: history }, - { callbacks: [loggerHandler, ...callbacks] }, + { callbacks }, { includeNames: [sourceRunnableName] } diff --git a/packages/server/marketplaces/chatflows/Claude LLM.json b/packages/server/marketplaces/chatflows/Claude LLM.json index 39d4d400..5d632ff1 100644 --- a/packages/server/marketplaces/chatflows/Claude LLM.json +++ b/packages/server/marketplaces/chatflows/Claude LLM.json @@ -6,15 +6,15 @@ "height": 376, "id": "bufferMemory_0", "position": { - "x": 451.4449437285705, - "y": 118.30026803362762 + "x": 240.5161028076149, + "y": 165.35849026339048 }, "type": "customNode", "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -53,8 +53,8 @@ }, "selected": false, "positionAbsolute": { - "x": 451.4449437285705, - "y": 118.30026803362762 + "x": 240.5161028076149, + "y": 165.35849026339048 }, "dragging": false }, @@ -63,17 +63,17 @@ "height": 383, "id": "conversationChain_0", "position": { - "x": 1176.1569322079652, - "y": 303.56879146735974 + "x": 958.9887390513221, + "y": 318.8734467468765 }, "type": "customNode", "data": { "id": "conversationChain_0", "label": "Conversation Chain", + "version": 2, "name": "conversationChain", - "version": 1, "type": "ConversationChain", - "baseClasses": ["ConversationChain", "LLMChain", "BaseChain"], + "baseClasses": ["ConversationChain", "LLMChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Chat models specific conversational chain with memory", "inputParams": [ @@ -82,9 +82,11 @@ "name": "systemMessagePrompt", "type": "string", "rows": 4, + "description": "If Chat Prompt Template is provided, this will be ignored", "additionalParams": true, "optional": true, - "placeholder": "You are a helpful assistant that write codes", + "default": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.", + "placeholder": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.", "id": "conversationChain_0-input-systemMessagePrompt-string" } ], @@ -102,27 +104,26 @@ "id": "conversationChain_0-input-memory-BaseMemory" }, { - "label": "Document", - "name": "document", - "type": "Document", - "description": "Include whole document into the context window", + "label": "Chat Prompt Template", + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "description": "Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable", "optional": true, - "list": true, - "id": "conversationChain_0-input-document-Document" + "id": "conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate" } ], "inputs": { "model": "{{chatAnthropic_0.data.instance}}", "memory": "{{bufferMemory_0.data.instance}}", - "document": ["{{pdfFile_0.data.instance}}"], - "systemMessagePrompt": "" + "chatPromptTemplate": "{{chatPromptTemplate_0.data.instance}}", + "systemMessagePrompt": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know." }, "outputAnchors": [ { - "id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain", + "id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain|Runnable", "name": "conversationChain", "label": "ConversationChain", - "type": "ConversationChain | LLMChain | BaseChain" + "type": "ConversationChain | LLMChain | BaseChain | Runnable" } ], "outputs": {}, @@ -130,27 +131,27 @@ }, "selected": false, "positionAbsolute": { - "x": 1176.1569322079652, - "y": 303.56879146735974 + "x": 958.9887390513221, + "y": 318.8734467468765 }, "dragging": false }, { "width": 300, - "height": 523, + "height": 574, "id": "chatAnthropic_0", "position": { - "x": 800.5525382783799, - "y": -130.7988221837009 + "x": 585.3308245972187, + "y": -116.32789506560908 }, "type": "customNode", "data": { "id": "chatAnthropic_0", "label": "ChatAnthropic", - "name": "chatAnthropic", "version": 3, + "name": "chatAnthropic", "type": "ChatAnthropic", - "baseClasses": ["ChatAnthropic", "BaseChatModel", "BaseLanguageModel"], + "baseClasses": ["ChatAnthropic", "BaseChatModel", "BaseLanguageModel", "Runnable"], "category": "Chat Models", "description": "Wrapper around ChatAnthropic large language models that use the Chat endpoint", "inputParams": [ @@ -226,7 +227,7 @@ "name": "claude-instant-v1.1-100k" } ], - "default": "claude-v1", + "default": "claude-2", "optional": true, "id": "chatAnthropic_0-input-modelName-options" }, @@ -234,6 +235,7 @@ "label": "Temperature", "name": "temperature", "type": "number", + "step": 0.1, "default": 0.9, "optional": true, "id": "chatAnthropic_0-input-temperature-number" @@ -242,6 +244,7 @@ "label": "Max Tokens", "name": "maxTokensToSample", "type": "number", + "step": 1, "optional": true, "additionalParams": true, "id": "chatAnthropic_0-input-maxTokensToSample-number" @@ -250,6 +253,7 @@ "label": "Top P", "name": "topP", "type": "number", + "step": 0.1, "optional": true, "additionalParams": true, "id": "chatAnthropic_0-input-topP-number" @@ -258,6 +262,7 @@ "label": "Top K", "name": "topK", "type": "number", + "step": 0.1, "optional": true, "additionalParams": true, "id": "chatAnthropic_0-input-topK-number" @@ -273,6 +278,7 @@ } ], "inputs": { + "cache": "", "modelName": "claude-2.1", "temperature": 0.9, "maxTokensToSample": "", @@ -281,10 +287,10 @@ }, "outputAnchors": [ { - "id": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel", + "id": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel|Runnable", "name": "chatAnthropic", "label": "ChatAnthropic", - "type": "ChatAnthropic | BaseChatModel | BaseLanguageModel" + "type": "ChatAnthropic | BaseChatModel | BaseLanguageModel | Runnable" } ], "outputs": {}, @@ -292,61 +298,106 @@ }, "selected": false, "positionAbsolute": { - "x": 800.5525382783799, - "y": -130.7988221837009 + "x": 585.3308245972187, + "y": -116.32789506560908 }, "dragging": false }, { "width": 300, - "height": 507, - "id": "pdfFile_0", + "height": 688, + "id": "chatPromptTemplate_0", "position": { - "x": 94.16886576108482, - "y": 37.12056504707391 + "x": -106.44189698270114, + "y": 20.133956087516538 }, "type": "customNode", "data": { - "id": "pdfFile_0", - "label": "Pdf File", - "name": "pdfFile", + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", "version": 1, + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a chat prompt", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}.", + "id": "chatPromptTemplate_0-input-systemMessagePrompt-string" + }, + { + "label": "Human Message", + "name": "humanMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "{text}", + "id": "chatPromptTemplate_0-input-humanMessagePrompt-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "chatPromptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "systemMessagePrompt": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\nThe AI has the following context:\n{context}", + "humanMessagePrompt": "{input}", + "promptValues": "{\"context\":\"{{plainText_0.data.instance}}\",\"input\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable", + "name": "chatPromptTemplate", + "label": "ChatPromptTemplate", + "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": -106.44189698270114, + "y": 20.133956087516538 + }, + "dragging": false + }, + { + "width": 300, + "height": 485, + "id": "plainText_0", + "position": { + "x": -487.7511991135089, + "y": 77.83838996645807 + }, + "type": "customNode", + "data": { + "id": "plainText_0", + "label": "Plain Text", + "version": 2, + "name": "plainText", "type": "Document", "baseClasses": ["Document"], "category": "Document Loaders", - "description": "Load data from PDF files", + "description": "Load data from plain text", "inputParams": [ { - "label": "Pdf File", - "name": "pdfFile", - "type": "file", - "fileType": ".pdf", - "id": "pdfFile_0-input-pdfFile-file" - }, - { - "label": "Usage", - "name": "usage", - "type": "options", - "options": [ - { - "label": "One document per page", - "name": "perPage" - }, - { - "label": "One document per file", - "name": "perFile" - } - ], - "default": "perPage", - "id": "pdfFile_0-input-usage-options" - }, - { - "label": "Use Legacy Build", - "name": "legacyBuild", - "type": "boolean", - "optional": true, - "additionalParams": true, - "id": "pdfFile_0-input-legacyBuild-boolean" + "label": "Text", + "name": "text", + "type": "string", + "rows": 4, + "placeholder": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...", + "id": "plainText_0-input-text-string" }, { "label": "Metadata", @@ -354,7 +405,7 @@ "type": "json", "optional": true, "additionalParams": true, - "id": "pdfFile_0-input-metadata-json" + "id": "plainText_0-input-metadata-json" } ], "inputAnchors": [ @@ -363,30 +414,45 @@ "name": "textSplitter", "type": "TextSplitter", "optional": true, - "id": "pdfFile_0-input-textSplitter-TextSplitter" + "id": "plainText_0-input-textSplitter-TextSplitter" } ], "inputs": { + "text": "Welcome to Skyworld Hotel, where your dreams take flight and your stay soars to new heights. Nestled amidst breathtaking cityscape views, our upscale establishment offers an unparalleled blend of luxury and comfort. Our rooms are elegantly appointed, featuring modern amenities and plush furnishings to ensure your relaxation.\n\nIndulge in culinary delights at our rooftop restaurant, offering a gastronomic journey with panoramic vistas. Skyworld Hotel boasts state-of-the-art conference facilities, perfect for business travelers, and an inviting spa for relaxation seekers. Our attentive staff is dedicated to ensuring your every need is met, making your stay memorable.\n\nCentrally located, we offer easy access to local attractions, making us an ideal choice for both leisure and business travelers. Experience the world of hospitality like never before at Skyworld Hotel.", "textSplitter": "", - "usage": "perPage", - "legacyBuild": "", "metadata": "" }, "outputAnchors": [ { - "id": "pdfFile_0-output-pdfFile-Document", - "name": "pdfFile", - "label": "Document", - "type": "Document" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "plainText_0-output-document-Document", + "name": "document", + "label": "Document", + "type": "Document" + }, + { + "id": "plainText_0-output-text-string|json", + "name": "text", + "label": "Text", + "type": "string | json" + } + ], + "default": "document" } ], - "outputs": {}, + "outputs": { + "output": "text" + }, "selected": false }, "selected": false, "positionAbsolute": { - "x": 94.16886576108482, - "y": 37.12056504707391 + "x": -487.7511991135089, + "y": 77.83838996645807 }, "dragging": false } @@ -398,32 +464,31 @@ "target": "conversationChain_0", "targetHandle": "conversationChain_0-input-memory-BaseMemory", "type": "buttonedge", - "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory", - "data": { - "label": "" - } + "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory" }, { "source": "chatAnthropic_0", - "sourceHandle": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel", + "sourceHandle": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationChain_0", "targetHandle": "conversationChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatAnthropic_0-chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel-conversationChain_0-conversationChain_0-input-model-BaseChatModel", - "data": { - "label": "" - } + "id": "chatAnthropic_0-chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel|Runnable-conversationChain_0-conversationChain_0-input-model-BaseChatModel" }, { - "source": "pdfFile_0", - "sourceHandle": "pdfFile_0-output-pdfFile-Document", - "target": "conversationChain_0", - "targetHandle": "conversationChain_0-input-document-Document", + "source": "plainText_0", + "sourceHandle": "plainText_0-output-text-string|json", + "target": "chatPromptTemplate_0", + "targetHandle": "chatPromptTemplate_0-input-promptValues-json", "type": "buttonedge", - "id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-conversationChain_0-conversationChain_0-input-document-Document", - "data": { - "label": "" - } + "id": "plainText_0-plainText_0-output-text-string|json-chatPromptTemplate_0-chatPromptTemplate_0-input-promptValues-json" + }, + { + "source": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable", + "target": "conversationChain_0", + "targetHandle": "conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate", + "type": "buttonedge", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable-conversationChain_0-conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate" } ] } diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index 2322136c..9689bf8c 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -2,20 +2,210 @@ "description": "Basic example of Conversation Chain with built-in memory - works exactly like ChatGPT", "badge": "POPULAR", "nodes": [ + { + "width": 300, + "height": 574, + "id": "chatOpenAI_0", + "position": { + "x": 579.0877964395976, + "y": -138.68792413227874 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 2, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-1106-preview", + "name": "gpt-4-1106-preview" + }, + { + "label": "gpt-4-vision-preview", + "name": "gpt-4-vision-preview" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-1106", + "name": "gpt-3.5-turbo-1106" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo-16k", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 579.0877964395976, + "y": -138.68792413227874 + }, + "dragging": false + }, { "width": 300, "height": 376, "id": "bufferMemory_0", "position": { - "x": 753.4300788823234, - "y": 479.5336426526603 + "x": 220.30240896145915, + "y": 351.61324070296877 }, "type": "customNode", "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -54,179 +244,8 @@ }, "selected": false, "positionAbsolute": { - "x": 753.4300788823234, - "y": 479.5336426526603 - }, - "dragging": false - }, - { - "width": 300, - "height": 523, - "id": "chatOpenAI_0", - "position": { - "x": 754.8942497823595, - "y": -140 - }, - "type": "customNode", - "data": { - "id": "chatOpenAI_0", - "label": "ChatOpenAI", - "name": "chatOpenAI", - "version": 2, - "type": "ChatOpenAI", - "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], - "category": "Chat Models", - "description": "Wrapper around OpenAI large language models that use the Chat endpoint", - "inputParams": [ - { - "label": "Connect Credential", - "name": "credential", - "type": "credential", - "credentialNames": ["openAIApi"], - "id": "chatOpenAI_0-input-credential-credential" - }, - { - "label": "Model Name", - "name": "modelName", - "type": "options", - "options": [ - { - "label": "gpt-4", - "name": "gpt-4" - }, - { - "label": "gpt-4-0613", - "name": "gpt-4-0613" - }, - { - "label": "gpt-4-32k", - "name": "gpt-4-32k" - }, - { - "label": "gpt-4-32k-0613", - "name": "gpt-4-32k-0613" - }, - { - "label": "gpt-3.5-turbo", - "name": "gpt-3.5-turbo" - }, - { - "label": "gpt-3.5-turbo-0613", - "name": "gpt-3.5-turbo-0613" - }, - { - "label": "gpt-3.5-turbo-16k", - "name": "gpt-3.5-turbo-16k" - }, - { - "label": "gpt-3.5-turbo-16k-0613", - "name": "gpt-3.5-turbo-16k-0613" - } - ], - "default": "gpt-3.5-turbo", - "optional": true, - "id": "chatOpenAI_0-input-modelName-options" - }, - { - "label": "Temperature", - "name": "temperature", - "type": "number", - "default": 0.9, - "optional": true, - "id": "chatOpenAI_0-input-temperature-number" - }, - { - "label": "Max Tokens", - "name": "maxTokens", - "type": "number", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-maxTokens-number" - }, - { - "label": "Top Probability", - "name": "topP", - "type": "number", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-topP-number" - }, - { - "label": "Frequency Penalty", - "name": "frequencyPenalty", - "type": "number", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-frequencyPenalty-number" - }, - { - "label": "Presence Penalty", - "name": "presencePenalty", - "type": "number", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-presencePenalty-number" - }, - { - "label": "Timeout", - "name": "timeout", - "type": "number", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-timeout-number" - }, - { - "label": "BasePath", - "name": "basepath", - "type": "string", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-basepath-string" - }, - { - "label": "BaseOptions", - "name": "baseOptions", - "type": "json", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-baseOptions-json" - } - ], - "inputAnchors": [ - { - "label": "Cache", - "name": "cache", - "type": "BaseCache", - "optional": true, - "id": "chatOpenAI_0-input-cache-BaseCache" - } - ], - "inputs": { - "modelName": "gpt-3.5-turbo", - "temperature": 0.9, - "maxTokens": "", - "topP": "", - "frequencyPenalty": "", - "presencePenalty": "", - "timeout": "", - "basepath": "", - "baseOptions": "" - }, - "outputAnchors": [ - { - "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "name": "chatOpenAI", - "label": "ChatOpenAI", - "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 754.8942497823595, - "y": -140 + "x": 220.30240896145915, + "y": 351.61324070296877 }, "dragging": false }, @@ -235,17 +254,17 @@ "height": 383, "id": "conversationChain_0", "position": { - "x": 1174.6496397666272, - "y": 311.1052536740497 + "x": 958.9887390513221, + "y": 318.8734467468765 }, "type": "customNode", "data": { "id": "conversationChain_0", "label": "Conversation Chain", + "version": 2, "name": "conversationChain", - "version": 1, "type": "ConversationChain", - "baseClasses": ["ConversationChain", "LLMChain", "BaseChain"], + "baseClasses": ["ConversationChain", "LLMChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Chat models specific conversational chain with memory", "inputParams": [ @@ -254,9 +273,11 @@ "name": "systemMessagePrompt", "type": "string", "rows": 4, + "description": "If Chat Prompt Template is provided, this will be ignored", "additionalParams": true, "optional": true, - "placeholder": "You are a helpful assistant that write codes", + "default": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.", + "placeholder": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.", "id": "conversationChain_0-input-systemMessagePrompt-string" } ], @@ -274,27 +295,26 @@ "id": "conversationChain_0-input-memory-BaseMemory" }, { - "label": "Document", - "name": "document", - "type": "Document", - "description": "Include whole document into the context window", + "label": "Chat Prompt Template", + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "description": "Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable", "optional": true, - "list": true, - "id": "conversationChain_0-input-document-Document" + "id": "conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate" } ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", "memory": "{{bufferMemory_0.data.instance}}", - "document": "", - "systemMessagePrompt": "" + "chatPromptTemplate": "", + "systemMessagePrompt": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know." }, "outputAnchors": [ { - "id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain", + "id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain|Runnable", "name": "conversationChain", "label": "ConversationChain", - "type": "ConversationChain | LLMChain | BaseChain" + "type": "ConversationChain | LLMChain | BaseChain | Runnable" } ], "outputs": {}, @@ -302,8 +322,8 @@ }, "selected": false, "positionAbsolute": { - "x": 1174.6496397666272, - "y": 311.1052536740497 + "x": 958.9887390513221, + "y": 318.8734467468765 }, "dragging": false } @@ -311,14 +331,11 @@ "edges": [ { "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationChain_0", "targetHandle": "conversationChain_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationChain_0-conversationChain_0-input-model-BaseChatModel", - "data": { - "label": "" - } + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationChain_0-conversationChain_0-input-model-BaseChatModel" }, { "source": "bufferMemory_0", @@ -326,10 +343,7 @@ "target": "conversationChain_0", "targetHandle": "conversationChain_0-input-memory-BaseMemory", "type": "buttonedge", - "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory", - "data": { - "label": "" - } + "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory" } ] } From d2e6b094f62b1781749a3635ec3a1a00f52bdf16 Mon Sep 17 00:00:00 2001 From: Octavian FlowiseAI <154992625+ocflowiseai@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:20:50 +0100 Subject: [PATCH 263/268] Update autoSyncMergedPullRequest workflow flags --- .github/workflows/autoSyncMergedPullRequest.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/autoSyncMergedPullRequest.yml b/.github/workflows/autoSyncMergedPullRequest.yml index 5b1991d7..288d0971 100644 --- a/.github/workflows/autoSyncMergedPullRequest.yml +++ b/.github/workflows/autoSyncMergedPullRequest.yml @@ -5,14 +5,16 @@ on: - closed branches: [ "main" ] jobs: - build: + autoSyncMergedPullRequest: if: github.event.pull_request.merged == true runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v3 - name: Show PR info env: - GITHUB_CONTEXT: ${{ toJSON(github) }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo The PR #${{ github.event.pull_request.number }} was merged on main branch! - name: Repository Dispatch From 5d25c35a1ac4250bb79b21bc22243576a18b2e36 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 21 Jan 2024 18:24:54 +0000 Subject: [PATCH 264/268] add env path for settings json --- packages/server/src/utils/index.ts | 1 + packages/server/src/utils/telemetry.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index a232094e..aa3e9ef6 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1099,6 +1099,7 @@ export const getTelemetryFlowObj = (nodes: IReactFlowNode[], edges: IReactFlowEd * TODO: move env variables to settings json file, easier configuration */ export const getUserSettingsFilePath = () => { + if (process.env.SECRETKEY_PATH) return path.join(process.env.SECRETKEY_PATH, 'settings.json') const checkPaths = [path.join(getUserHome(), '.flowise', 'settings.json')] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { diff --git a/packages/server/src/utils/telemetry.ts b/packages/server/src/utils/telemetry.ts index 4254ea76..4b033f20 100644 --- a/packages/server/src/utils/telemetry.ts +++ b/packages/server/src/utils/telemetry.ts @@ -25,7 +25,9 @@ export class Telemetry { const settings = { instanceId } - const defaultLocation = path.join(getUserHome(), '.flowise', 'settings.json') + const defaultLocation = process.env.SECRETKEY_PATH + ? path.join(process.env.SECRETKEY_PATH, 'settings.json') + : path.join(getUserHome(), '.flowise', 'settings.json') await fs.promises.writeFile(defaultLocation, JSON.stringify(settings, null, 2)) return instanceId } From 2dd1c71cf26d2cb627da2a97be32aa5c312df01b Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 21 Jan 2024 22:26:58 +0000 Subject: [PATCH 265/268] return response --- packages/server/src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3c5766fd..b11653ee 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1825,8 +1825,10 @@ export class App { flowGraph: getTelemetryFlowObj(nodes, edges) }) - // Only return ChatId when its Internal OR incoming input has ChatId, to avoid confusion when calling API - if (incomingInput.chatId || isInternal) result.chatId = chatId + // Prepare response + result.chatId = chatId + if (sessionId) result.sessionId = sessionId + if (memoryType) result.memoryType = memoryType return res.json(result) } catch (e: any) { From ddab853cfd2266d95f2f379917359a2e9ca210f1 Mon Sep 17 00:00:00 2001 From: Octavian FlowiseAI <154992625+ocflowiseai@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:57:01 +0100 Subject: [PATCH 266/268] Update autoSyncMergedPullRequest.yml - send pr title - send pr description --- .github/workflows/autoSyncMergedPullRequest.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/autoSyncMergedPullRequest.yml b/.github/workflows/autoSyncMergedPullRequest.yml index 288d0971..518db0bc 100644 --- a/.github/workflows/autoSyncMergedPullRequest.yml +++ b/.github/workflows/autoSyncMergedPullRequest.yml @@ -23,4 +23,11 @@ jobs: token: ${{ secrets.AUTOSYNC_TOKEN }} repository: ${{ secrets.AUTOSYNC_CH_URL }} event-type: ${{ secrets.AUTOSYNC_PR_EVENT_TYPE }} - client-payload: '{"ref": "${{ github.ref }}", "prNumber": "${{ github.event.pull_request.number }}", "sha": "${{ github.sha }}"}' + client-payload: >- + { + "ref": "${{ github.ref }}", + "prNumber": "${{ github.event.pull_request.number }}", + "prTitle": "${{ github.event.pull_request.title }}", + "prDescription": "${{ github.event.pull_request.description }}", + "sha": "${{ github.sha }}" + } From fb2e6a0e08cb62284ca11647e294d4dcf30136f7 Mon Sep 17 00:00:00 2001 From: Octavian FlowiseAI <154992625+ocflowiseai@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:08:29 +0100 Subject: [PATCH 267/268] Update autoSyncSingleCommit.yml - send single commit message --- .github/workflows/autoSyncSingleCommit.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/autoSyncSingleCommit.yml b/.github/workflows/autoSyncSingleCommit.yml index ce429a60..738e271a 100644 --- a/.github/workflows/autoSyncSingleCommit.yml +++ b/.github/workflows/autoSyncSingleCommit.yml @@ -28,4 +28,9 @@ jobs: token: ${{ secrets.AUTOSYNC_TOKEN }} repository: ${{ secrets.AUTOSYNC_CH_URL }} event-type: ${{ secrets.AUTOSYNC_SC_EVENT_TYPE }} - client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' + client-payload: >- + { + "ref": "${{ github.ref }}", + "sha": "${{ github.sha }}", + "commitMessage": "${{ github.event.commits[0].message }}", + } From 764efcccb738e1683b7e0cf623044ce3286d2f39 Mon Sep 17 00:00:00 2001 From: Octavian FlowiseAI <154992625+ocflowiseai@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:09:19 +0100 Subject: [PATCH 268/268] Update autoSyncSingleCommit.yml --- .github/workflows/autoSyncSingleCommit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autoSyncSingleCommit.yml b/.github/workflows/autoSyncSingleCommit.yml index 738e271a..8700f232 100644 --- a/.github/workflows/autoSyncSingleCommit.yml +++ b/.github/workflows/autoSyncSingleCommit.yml @@ -32,5 +32,5 @@ jobs: { "ref": "${{ github.ref }}", "sha": "${{ github.sha }}", - "commitMessage": "${{ github.event.commits[0].message }}", + "commitMessage": "${{ github.event.commits[0].message }}" }