Chore/Update issue templates and add new tools (#4687)

* Enhancement: Update issue templates and add new tools

- Updated bug report template to include a default label of 'bug'.
- Updated feature request template to include a default label of 'enhancement'.
- Added new credential class for Agentflow API.
- Enhanced Agent and HTTP nodes to improve tool management and error handling.
- Added deprecation badges to several agent and chain classes.
- Introduced new tools for handling requests (GET, POST, DELETE, PUT) with improved error handling.
- Added new chatflows and agentflows for various use cases, including document QnA and translation.
- Updated UI components for better handling of agent flows and marketplace interactions.
- Refactored utility functions for improved functionality and clarity.

* Refactor: Remove beta badge and streamline template title assignment

- Removed the 'BETA' badge from the ExtractMetadataRetriever class.
- Simplified the title assignment in the agentflowv2 generator by using a variable instead of inline string manipulation.
This commit is contained in:
Henry Heng
2025-06-19 18:11:24 +01:00
committed by GitHub
parent 15dd28356b
commit a107aa7a77
86 changed files with 9942 additions and 12634 deletions
@@ -116,8 +116,9 @@ const getAllAgentflowv2Marketplaces = async () => {
}
})
const title = file.split('.json')[0]
const template = {
title: file.split('.json')[0],
title,
description: fileDataObj.description || `Template from ${file}`,
usecases: fileDataObj.usecases || [],
nodes: filteredNodes,
@@ -126,7 +127,11 @@ const getAllAgentflowv2Marketplaces = async () => {
// Validate template against schema
const validatedTemplate = AgentFlowV2Type.parse(template)
templates.push(validatedTemplate)
templates.push({
...validatedTemplate,
// @ts-ignore
title: title
})
} catch (error) {
console.error(`Error processing template file ${file}:`, error)
// Continue with next file instead of failing completely
@@ -1613,8 +1613,12 @@ const upsertDocStore = async (
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, `Error: Invalid metadata`)
}
}
const replaceExisting = data.replaceExisting ?? false
const createNewDocStore = data.createNewDocStore ?? false
const replaceExisting =
typeof data.replaceExisting === 'string' ? (data.replaceExisting as string).toLowerCase() === 'true' : data.replaceExisting ?? false
const createNewDocStore =
typeof data.createNewDocStore === 'string'
? (data.createNewDocStore as string).toLowerCase() === 'true'
: data.createNewDocStore ?? false
const newLoader = typeof data.loader === 'string' ? JSON.parse(data.loader) : data.loader
const newSplitter = typeof data.splitter === 'string' ? JSON.parse(data.splitter) : data.splitter
const newVectorStore = typeof data.vectorStore === 'string' ? JSON.parse(data.vectorStore) : data.vectorStore
@@ -69,6 +69,8 @@ const getAllTemplates = async () => {
templates.push(template)
})
/*
* Agentflow is deprecated
marketplaceDir = path.join(__dirname, '..', '..', '..', 'marketplaces', 'agentflows')
jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
jsonsInDir.forEach((file) => {
@@ -87,7 +89,7 @@ const getAllTemplates = async () => {
description: fileDataObj?.description || ''
}
templates.push(template)
})
})*/
marketplaceDir = path.join(__dirname, '..', '..', '..', 'marketplaces', 'agentflowsv2')
jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
@@ -108,11 +110,24 @@ const getAllTemplates = async () => {
}
templates.push(template)
})
const sortedTemplates = templates.sort((a, b) => a.templateName.localeCompare(b.templateName))
const FlowiseDocsQnAIndex = sortedTemplates.findIndex((tmp) => tmp.templateName === 'Flowise Docs QnA')
if (FlowiseDocsQnAIndex > 0) {
sortedTemplates.unshift(sortedTemplates.splice(FlowiseDocsQnAIndex, 1)[0])
}
const sortedTemplates = templates.sort((a, b) => {
// Prioritize AgentflowV2 templates first
if (a.type === 'AgentflowV2' && b.type !== 'AgentflowV2') {
return -1
}
if (b.type === 'AgentflowV2' && a.type !== 'AgentflowV2') {
return 1
}
// Put Tool templates last
if (a.type === 'Tool' && b.type !== 'Tool') {
return 1
}
if (b.type === 'Tool' && a.type !== 'Tool') {
return -1
}
// For same types, sort alphabetically by templateName
return a.templateName.localeCompare(b.templateName)
})
const dbResponse = sortedTemplates
return dbResponse
} catch (error) {
+34 -3
View File
@@ -40,7 +40,8 @@ import {
getGlobalVariable,
getStartingNode,
getTelemetryFlowObj,
QUESTION_VAR_PREFIX
QUESTION_VAR_PREFIX,
CURRENT_DATE_TIME_VAR_PREFIX
} from '.'
import { ChatFlow } from '../database/entities/ChatFlow'
import { Variable } from '../database/entities/Variable'
@@ -294,9 +295,18 @@ export const resolveVariables = async (
resolvedValue = resolvedValue.replace(match, flowConfig?.runtimeChatHistoryLength ?? 0)
}
if (variableFullPath === CURRENT_DATE_TIME_VAR_PREFIX) {
resolvedValue = resolvedValue.replace(match, new Date().toISOString())
}
if (variableFullPath.startsWith('$iteration')) {
if (iterationContext && iterationContext.value) {
if (typeof iterationContext.value === 'string') {
if (variableFullPath === '$iteration') {
// If it's exactly $iteration, stringify the entire value
const formattedValue =
typeof iterationContext.value === 'object' ? JSON.stringify(iterationContext.value) : iterationContext.value
resolvedValue = resolvedValue.replace(match, formattedValue)
} else if (typeof iterationContext.value === 'string') {
resolvedValue = resolvedValue.replace(match, iterationContext?.value)
} else if (typeof iterationContext.value === 'object') {
const iterationValue = get(iterationContext.value, variableFullPath.replace('$iteration.', ''))
@@ -342,8 +352,10 @@ export const resolveVariables = async (
const [, nodeIdPart, outputPath] = outputMatch
// Clean nodeId (handle escaped underscores)
const cleanNodeId = nodeIdPart.replace('\\', '')
// Find the last (most recent) matching node data instead of the first one
const nodeData = [...agentFlowExecutedData].reverse().find((d) => d.nodeId === cleanNodeId)
if (nodeData?.data?.output && outputPath.trim()) {
const variableValue = get(nodeData.data.output, outputPath)
if (variableValue !== undefined) {
@@ -1234,6 +1246,20 @@ const checkForMultipleStartNodes = (startingNodeIds: string[], isRecursive: bool
}
}
const parseFormStringToJson = (formString: string): Record<string, string> => {
const result: Record<string, string> = {}
const lines = formString.split('\n')
for (const line of lines) {
const [key, value] = line.split(': ').map((part) => part.trim())
if (key && value) {
result[key] = value
}
}
return result
}
/*
* Function to traverse the flow graph and execute the nodes
*/
@@ -1376,7 +1402,12 @@ export const executeAgentFlow = async ({
if (previousStartAgent) {
const previousStartAgentOutput = previousStartAgent.data.output
if (previousStartAgentOutput && typeof previousStartAgentOutput === 'object' && 'form' in previousStartAgentOutput) {
agentflowRuntime.form = previousStartAgentOutput.form
const formValues = previousStartAgentOutput.form
if (typeof formValues === 'string') {
agentflowRuntime.form = parseFormStringToJson(formValues)
} else {
agentflowRuntime.form = formValues
}
}
}
}
+1
View File
@@ -71,6 +71,7 @@ export const QUESTION_VAR_PREFIX = 'question'
export const FILE_ATTACHMENT_PREFIX = 'file_attachment'
export const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
export const RUNTIME_MESSAGES_LENGTH_VAR_PREFIX = 'runtime_messages_length'
export const CURRENT_DATE_TIME_VAR_PREFIX = 'current_date_time'
export const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
let secretsManagerClient: SecretsManagerClient | null = null
+7 -3
View File
@@ -28,17 +28,21 @@ if (process.env.STORAGE_TYPE === 's3') {
const customURL = process.env.S3_ENDPOINT_URL
const forcePathStyle = process.env.S3_FORCE_PATH_STYLE === 'true'
if (!region || !s3Bucket) {
if (!region || region.trim() === '' || !s3Bucket || s3Bucket.trim() === '') {
throw new Error('S3 storage configuration is missing')
}
const s3Config: S3ClientConfig = {
region: region,
endpoint: customURL,
forcePathStyle: forcePathStyle
}
if (accessKeyId && secretAccessKey) {
// Only include endpoint if customURL is not empty
if (customURL && customURL.trim() !== '') {
s3Config.endpoint = customURL
}
if (accessKeyId && accessKeyId.trim() !== '' && secretAccessKey && secretAccessKey.trim() !== '') {
s3Config.credentials = {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey