Feature/seq agents (#2798)

* update build functions

* sequential agents

* update langchain to 0.2, added sequential agent nodes

* add marketplace templates

* update howto wordings

* Merge branch 'main' into feature/Seq-Agents

# Conflicts:
#	pnpm-lock.yaml

* update deprecated functions and add new sequential nodes

* add marketplace templates

* update marketplace templates, add structured output to llm node

* add multi agents template

* update llm node with bindmodels

* update cypress version

* update templates sticky note wordings

* update tool node to include human in loop action

* update structured outputs error from models

* update cohere package to resolve google genai pipeThrough bug

* update mistral package version, added message reconstruction before invoke seq agent

* add HITL to agent

* update state messages restructuring

* update load and split methods for s3 directory
This commit is contained in:
Henry Heng
2024-07-22 17:46:14 +01:00
committed by GitHub
parent 34d0e4302c
commit bca4de0c63
152 changed files with 55307 additions and 35236 deletions
@@ -0,0 +1,95 @@
import { createPortal } from 'react-dom'
import { useState, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import PropTypes from 'prop-types'
// MUI
import { Button, Dialog, DialogActions, DialogContent } from '@mui/material'
import { Tabs } from '@mui/base/Tabs'
// Project Import
import { StyledButton } from '@/ui-component/button/StyledButton'
import { TabPanel } from '@/ui-component/tabs/TabPanel'
import { TabsList } from '@/ui-component/tabs/TabsList'
import { Tab } from '@/ui-component/tabs/Tab'
import NodeInputHandler from '@/views/canvas/NodeInputHandler'
// Store
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
const ConditionDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
const portalElement = document.getElementById('portal')
const dispatch = useDispatch()
const [inputParam, setInputParam] = useState(null)
const [tabValue, setTabValue] = useState(0)
const [data, setData] = useState({})
useEffect(() => {
if (dialogProps.inputParam) {
setInputParam(dialogProps.inputParam)
}
if (dialogProps.data) setData(dialogProps.data)
return () => {
setInputParam(null)
setData({})
}
}, [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 ? (
<Dialog open={show} fullWidth maxWidth='md' aria-labelledby='alert-dialog-title' aria-describedby='alert-dialog-description'>
<DialogContent>
<>
{inputParam && inputParam.type.includes('conditionFunction') && (
<>
<Tabs value={tabValue} onChange={(event, val) => setTabValue(val)} aria-label='tabs' variant='fullWidth'>
<TabsList>
{inputParam.tabs.map((inputChildParam, index) => (
<Tab key={index}>{inputChildParam.label}</Tab>
))}
</TabsList>
</Tabs>
{inputParam.tabs.map((inputChildParam, index) => (
<TabPanel key={index} value={tabValue} index={index}>
<NodeInputHandler
disabled={inputChildParam.disabled}
inputParam={inputChildParam}
data={data}
isAdditionalParams={true}
disablePadding={true}
/>
</TabPanel>
))}
</>
)}
</>
</DialogContent>
<DialogActions>
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
<StyledButton disabled={dialogProps.disabled} variant='contained' onClick={() => onConfirm(data, inputParam, tabValue)}>
{dialogProps.confirmButtonName}
</StyledButton>
</DialogActions>
</Dialog>
) : null
return createPortal(component, portalElement)
}
ConditionDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func,
onConfirm: PropTypes.func
}
export default ConditionDialog
@@ -22,7 +22,7 @@ import useApi from '@/hooks/useApi'
import './ExpandTextDialog.css'
const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
const ExpandTextDialog = ({ show, dialogProps, onCancel, onInputHintDialogClicked, onConfirm }) => {
const portalElement = document.getElementById('portal')
const theme = useTheme()
@@ -38,13 +38,18 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
const executeCustomFunctionNodeApi = useApi(nodesApi.executeCustomFunctionNode)
useEffect(() => {
if (dialogProps.value) setInputValue(dialogProps.value)
if (dialogProps.value) {
setInputValue(dialogProps.value)
}
if (dialogProps.inputParam) {
setInputParam(dialogProps.inputParam)
if (dialogProps.inputParam.type === 'code') {
setLanguageType('js')
}
}
if (dialogProps.languageType) {
setLanguageType(dialogProps.languageType)
}
return () => {
setInputValue('')
@@ -91,9 +96,22 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
<div style={{ display: 'flex', flexDirection: 'row' }}>
{inputParam && (inputParam.type === 'string' || inputParam.type === 'code') && (
<div style={{ flex: 70 }}>
<Typography sx={{ mb: 2, ml: 1 }} variant='h4'>
{inputParam.label}
</Typography>
<div style={{ marginBottom: '10px', display: 'flex', flexDirection: 'row' }}>
<Typography variant='h4'>{inputParam.label}</Typography>
<div style={{ flex: 1 }} />
{inputParam.hint && (
<Button
sx={{ p: 0, px: 2 }}
color='secondary'
variant='text'
onClick={() => {
onInputHintDialogClicked(inputParam.hint)
}}
>
{inputParam.hint.label}
</Button>
)}
</div>
<PerfectScrollbar
style={{
border: '1px solid',
@@ -114,7 +132,12 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
placeholder={inputParam.placeholder}
basicSetup={
languageType !== 'js'
? { lineNumbers: false, foldGutter: false, autocompletion: false, highlightActiveLine: false }
? {
lineNumbers: false,
foldGutter: false,
autocompletion: false,
highlightActiveLine: false
}
: {}
}
onValueChange={(code) => setInputValue(code)}
@@ -123,7 +146,7 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
</div>
)}
</div>
{languageType === 'js' && (
{languageType === 'js' && !inputParam.hideCodeExecute && (
<LoadingButton
sx={{
mt: 2,
@@ -177,7 +200,8 @@ ExpandTextDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func,
onConfirm: PropTypes.func
onConfirm: PropTypes.func,
onInputHintDialogClicked: PropTypes.func
}
export default ExpandTextDialog
@@ -46,6 +46,7 @@ const FormatPromptValuesDialog = ({ show, dialogProps, onChange, onCancel }) =>
nodes={dialogProps.nodes}
edges={dialogProps.edges}
nodeId={dialogProps.nodeId}
isSequentialAgent={dialogProps.data.category === 'Sequential Agents'}
/>
</PerfectScrollbar>
</DialogContent>
@@ -0,0 +1,63 @@
import { createPortal } from 'react-dom'
import PropTypes from 'prop-types'
import rehypeMathjax from 'rehype-mathjax'
import rehypeRaw from 'rehype-raw'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
import { CodeBlock } from '@/ui-component/markdown/CodeBlock'
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
const InputHintDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
const component = show ? (
<Dialog
onClose={onCancel}
open={show}
fullWidth
maxWidth='sm'
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
{dialogProps.label}
</DialogTitle>
<DialogContent>
<MemoizedReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax, rehypeRaw]}
components={{
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<CodeBlock
isDialog={true}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}
>
{dialogProps?.value}
</MemoizedReactMarkdown>
</DialogContent>
</Dialog>
) : null
return createPortal(component, portalElement)
}
InputHintDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func
}
export default InputHintDialog
@@ -34,7 +34,7 @@ import userPNG from '@/assets/images/account.png'
import msgEmptySVG from '@/assets/images/message_empty.svg'
import multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
import { IconFileExport, IconEraser, IconX, IconDownload } from '@tabler/icons-react'
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload } from '@tabler/icons-react'
// Project import
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
@@ -846,6 +846,64 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
</Box>
<div>{agent.agentName}</div>
</Stack>
{agent.usedTools && agent.usedTools.length > 0 && (
<div
style={{
display: 'block',
flexDirection: 'row',
width: '100%'
}}
>
{agent.usedTools.map((tool, index) => {
return tool !== null ? (
<Chip
size='small'
key={index}
label={tool.tool}
component='a'
sx={{ mr: 1, mt: 1 }}
variant='outlined'
clickable
icon={<IconTool size={15} />}
onClick={() =>
onSourceDialogClick(
tool,
'Used Tools'
)
}
/>
) : null
})}
</div>
)}
{agent.state &&
Object.keys(agent.state).length > 0 && (
<div
style={{
display: 'block',
flexDirection: 'row',
width: '100%'
}}
>
<Chip
size='small'
label={'State'}
component='a'
sx={{ mr: 1, mt: 1 }}
variant='outlined'
clickable
icon={
<IconDeviceSdCard size={15} />
}
onClick={() =>
onSourceDialogClick(
agent.state,
'State'
)
}
/>
</div>
)}
{agent.messages.length > 0 && (
<MemoizedReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
@@ -893,6 +951,67 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
{agent.instructions && <p>{agent.instructions}</p>}
{agent.messages.length === 0 &&
!agent.instructions && <p>Finished</p>}
{agent.sourceDocuments &&
agent.sourceDocuments.length > 0 && (
<div
style={{
display: 'block',
flexDirection: 'row',
width: '100%'
}}
>
{removeDuplicateURL(agent).map(
(source, index) => {
const URL =
source &&
source.metadata &&
source.metadata.source
? isValidURL(
source.metadata
.source
)
: undefined
return (
<Chip
size='small'
key={index}
label={
URL
? URL.pathname.substring(
0,
15
) === '/'
? URL.host
: `${URL.pathname.substring(
0,
15
)}...`
: `${source.pageContent.substring(
0,
15
)}...`
}
component='a'
sx={{ mr: 1, mb: 1 }}
variant='outlined'
clickable
onClick={() =>
URL
? onURLClick(
source
.metadata
.source
)
: onSourceDialogClick(
source
)
}
/>
)
}
)}
</div>
)}
</CardContent>
</Card>
)
+27 -1
View File
@@ -6,6 +6,32 @@ import { Button } from '@mui/material'
import DeleteIcon from '@mui/icons-material/Delete'
import { cloneDeep } from 'lodash'
import { formatDataGridRows } from '@/utils/genericHelper'
import { styled } from '@mui/material/styles'
const StyledDataGrid = styled(MUIDataGrid)(({ theme }) => ({
border: `1px solid ${theme.palette.mode === 'light' ? '#b4b4b4' : '#303030'}`,
letterSpacing: 'normal',
'& .MuiDataGrid-columnsContainer': {
backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d'
},
'& .MuiDataGrid-iconSeparator': {
display: 'none'
},
'& .MuiDataGrid-columnHeader, .MuiDataGrid-cell': {
borderRight: `1px solid ${theme.palette.mode === 'light' ? '#f0f0f0' : '#303030'}`
},
'& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': {
borderBottom: `1px solid ${theme.palette.mode === 'light' ? '#f0f0f0' : '#303030'}`
},
'& .MuiPaginationItem-root': {
borderRadius: 0
},
'& .MuiDataGrid-columnHeader:last-child, .MuiDataGrid-cell:last-child': {
borderRight: 'none'
}
}))
export const DataGrid = ({ columns, rows, style, disabled = false, hideFooter = false, onChange }) => {
const [rowValues, setRowValues] = useState(formatDataGridRows(rows) ?? [])
@@ -80,7 +106,7 @@ export const DataGrid = ({ columns, rows, style, disabled = false, hideFooter =
<>
{rowValues && colValues && (
<div style={{ marginTop: 10, height: 210, width: '100%', ...style }}>
<MUIDataGrid
<StyledDataGrid
processRowUpdate={handleProcessRowUpdate}
isCellEditable={() => {
return !disabled
@@ -6,7 +6,17 @@ import SelectVariable from './SelectVariable'
import { cloneDeep } from 'lodash'
import { getAvailableNodesForVariable } from '@/utils/genericHelper'
export const JsonEditorInput = ({ value, onChange, inputParam, nodes, edges, nodeId, disabled = false, isDarkMode = false }) => {
export const JsonEditorInput = ({
value,
onChange,
inputParam,
nodes,
edges,
nodeId,
disabled = false,
isDarkMode = false,
isSequentialAgent = false
}) => {
const [myValue, setMyValue] = useState(value ? JSON.parse(value) : {})
const [availableNodesForVariable, setAvailableNodesForVariable] = useState([])
const [mouseUpKey, setMouseUpKey] = useState('')
@@ -110,6 +120,7 @@ export const JsonEditorInput = ({ value, onChange, inputParam, nodes, edges, nod
setNewVal(val)
handleClosePopOver()
}}
isSequentialAgent={isSequentialAgent}
/>
</Popover>
)}
@@ -125,5 +136,6 @@ JsonEditorInput.propTypes = {
inputParam: PropTypes.object,
nodes: PropTypes.array,
edges: PropTypes.array,
nodeId: PropTypes.string
nodeId: PropTypes.string,
isSequentialAgent: PropTypes.bool
}
@@ -4,9 +4,29 @@ import { Box, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, Typo
import PerfectScrollbar from 'react-perfect-scrollbar'
import robotPNG from '@/assets/images/robot.png'
import chatPNG from '@/assets/images/chathistory.png'
import diskPNG from '@/assets/images/floppy-disc.png'
import { baseURL } from '@/store/constant'
const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal }) => {
const sequentialStateMessagesSelection = [
{
primary: '$flow.state.messages',
secondary: `All messages from the start of the conversation till now`
},
{
primary: '$flow.state.<replace-with-key>',
secondary: `Current value of the state variable with specified key`
},
{
primary: '$flow.state.messages[0].content',
secondary: `First message content`
},
{
primary: '$flow.state.messages[-1].content',
secondary: `Last message content`
}
]
const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal, isSequentialAgent }) => {
const customization = useSelector((state) => state.customization)
const onSelectOutputResponseClick = (node, prefix) => {
@@ -102,9 +122,10 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA
{availableNodesForVariable &&
availableNodesForVariable.length > 0 &&
availableNodesForVariable.map((node, index) => {
const selectedOutputAnchor = node.data.outputAnchors[0].options.find(
(ancr) => ancr.name === node.data.outputs['output']
)
const selectedOutputAnchor =
node.data.outputAnchors.length &&
node.data.outputAnchors[0].options &&
node.data.outputAnchors[0].options.find((ancr) => ancr.name === node.data.outputs['output'])
return (
<ListItemButton
key={index}
@@ -157,6 +178,45 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA
</ListItemButton>
)
})}
{isSequentialAgent &&
(sequentialStateMessagesSelection || []).map((item, index) => (
<ListItemButton
key={index}
sx={{
p: 0,
borderRadius: `${customization.borderRadius}px`,
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
mb: 1
}}
disabled={disabled}
onClick={() => onSelectAndReturnVal(item.primary)}
>
<ListItem alignItems='center'>
<ListItemAvatar>
<div
style={{
width: 50,
height: 50,
borderRadius: '50%',
backgroundColor: 'white'
}}
>
<img
style={{
width: '100%',
height: '100%',
padding: 10,
objectFit: 'contain'
}}
alt='state'
src={diskPNG}
/>
</div>
</ListItemAvatar>
<ListItemText sx={{ ml: 1 }} primary={item.primary} secondary={item.secondary} />
</ListItem>
</ListItemButton>
))}
</List>
</Box>
</PerfectScrollbar>
@@ -169,7 +229,8 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA
SelectVariable.propTypes = {
availableNodesForVariable: PropTypes.array,
disabled: PropTypes.bool,
onSelectAndReturnVal: PropTypes.func
onSelectAndReturnVal: PropTypes.func,
isSequentialAgent: PropTypes.bool
}
export default SelectVariable
@@ -0,0 +1,21 @@
.react-markdown table {
border-spacing: 0 !important;
border-collapse: collapse !important;
border-color: inherit !important;
display: block !important;
width: max-content !important;
max-width: 100% !important;
overflow: auto !important;
}
.react-markdown tbody,
.react-markdown td,
.react-markdown tfoot,
.react-markdown th,
.react-markdown thead,
.react-markdown tr {
border-color: inherit !important;
border-style: solid !important;
border-width: 1px !important;
padding: 10px !important;
}
@@ -1,4 +1,19 @@
import { memo } from 'react'
import PropTypes from 'prop-types'
import ReactMarkdown from 'react-markdown'
import './Markdown.css'
export const MemoizedReactMarkdown = memo(ReactMarkdown, (prevProps, nextProps) => prevProps.children === nextProps.children)
export const MemoizedReactMarkdown = memo(
({ children, ...props }) => (
<div className='react-markdown'>
<ReactMarkdown {...props}>{children}</ReactMarkdown>
</div>
),
(prevProps, nextProps) => prevProps.children === nextProps.children
)
MemoizedReactMarkdown.displayName = 'MemoizedReactMarkdown'
MemoizedReactMarkdown.propTypes = {
children: PropTypes.any
}
+42
View File
@@ -0,0 +1,42 @@
import { styled } from '@mui/system'
import { buttonClasses } from '@mui/base/Button'
import { Tab as BaseTab, tabClasses } from '@mui/base/Tab'
import { blue } from './tabColors'
export const Tab = styled(BaseTab)(
({ ...props }) => `
font-family: 'IBM Plex Sans', sans-serif;
color: white;
cursor: pointer;
font-size: 0.75rem;
font-weight: bold;
background-color: transparent;
width: 100%;
line-height: 1.5;
padding: 8px 12px;
margin: 6px;
border: none;
border-radius: 25px;
display: flex;
justify-content: center;
&:hover {
background-color: ${props.sx?.backgroundColor || blue[400]};
}
&:focus {
color: #fff;
outline: 3px solid ${props.sx?.backgroundColor || blue[200]};
}
&.${tabClasses.selected} {
background-color: #fff;
color: ${blue[600]};
}
&.${buttonClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
}
`
)
@@ -0,0 +1,17 @@
import PropTypes from 'prop-types'
import { Box } from '@mui/material'
export const TabPanel = (props) => {
const { children, value, index, ...other } = props
return (
<div role='tabpanel' hidden={value !== index} id={`tabpanel-${index}`} aria-labelledby={`tab-${index}`} {...other}>
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
</div>
)
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
}
@@ -0,0 +1,18 @@
import { styled } from '@mui/system'
import { TabsList as BaseTabsList } from '@mui/base/TabsList'
import { blue } from './tabColors'
export const TabsList = styled(BaseTabsList)(
({ theme, ...props }) => `
min-width: 400px;
background-color: ${props.sx?.backgroundColor || blue[500]};
border-radius: 20px;
margin-top: 16px;
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: center;
align-content: space-between;
box-shadow: 0px 4px 6px ${theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.4)' : 'rgba(0,0,0, 0.2)'};
`
)
@@ -0,0 +1,12 @@
export const blue = {
50: '#F0F7FF',
100: '#C2E0FF',
200: '#80BFFF',
300: '#66B2FF',
400: '#3399FF',
500: '#007FFF',
600: '#0072E5',
700: '#0059B2',
800: '#004C99',
900: '#003A75'
}