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
@@ -1,7 +1,21 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
import { desc, RequestParameters, RequestsGetTool } from './core'
const codeExample = `{
"id": {
"type": "string",
"required": true,
"in": "path",
"description": "ID of the item to get. /:id"
},
"limit": {
"type": "string",
"in": "query",
"description": "Limit the number of items to get. ?limit=10"
}
}`
class RequestsGet_Tools implements INode {
label: string
name: string
@@ -16,52 +30,107 @@ class RequestsGet_Tools implements INode {
constructor() {
this.label = 'Requests Get'
this.name = 'requestsGet'
this.version = 1.0
this.version = 2.0
this.type = 'RequestsGet'
this.icon = 'requestsget.svg'
this.icon = 'get.png'
this.category = 'Tools'
this.description = 'Execute HTTP GET requests'
this.baseClasses = [this.type, ...getBaseClasses(RequestsGetTool)]
this.inputs = [
{
label: 'URL',
name: 'url',
name: 'requestsGetUrl',
type: 'string',
description:
'Agent will make call to this exact URL. If not specified, agent will try to figure out itself from AIPlugin if provided',
acceptVariable: true
},
{
label: 'Name',
name: 'requestsGetName',
type: 'string',
default: 'requests_get',
description: 'Name of the tool',
additionalParams: true,
optional: true
},
{
label: 'Description',
name: 'description',
name: 'requestsGetDescription',
type: 'string',
rows: 4,
default: desc,
description: 'Acts like a prompt to tell agent when it should use this tool',
description: 'Describe to LLM when it should use this tool',
additionalParams: true,
optional: true
},
{
label: 'Headers',
name: 'headers',
type: 'json',
name: 'requestsGetHeaders',
type: 'string',
rows: 4,
acceptVariable: true,
additionalParams: true,
optional: true
optional: true,
placeholder: `{
"Authorization": "Bearer <token>"
}`
},
{
label: 'Query Params Schema',
name: 'requestsGetQueryParamsSchema',
type: 'code',
description: 'Description of the available query params to enable LLM to figure out which query params to use',
placeholder: `{
"id": {
"type": "string",
"required": true,
"in": "path",
"description": "ID of the item to get. /:id"
},
"limit": {
"type": "string",
"in": "query",
"description": "Limit the number of items to get. ?limit=10"
}
}`,
optional: true,
hideCodeExecute: true,
additionalParams: true,
codeExample: codeExample
},
{
label: 'Max Output Length',
name: 'requestsGetMaxOutputLength',
type: 'number',
description: 'Max length of the output. Remove this if you want to return the entire response',
default: '2000',
step: 1,
optional: true,
additionalParams: true
}
]
}
async init(nodeData: INodeData): Promise<any> {
const headers = nodeData.inputs?.headers as string
const url = nodeData.inputs?.url as string
const description = nodeData.inputs?.description as string
const headers = (nodeData.inputs?.headers as string) || (nodeData.inputs?.requestsGetHeaders as string)
const url = (nodeData.inputs?.url as string) || (nodeData.inputs?.requestsGetUrl as string)
const description = (nodeData.inputs?.description as string) || (nodeData.inputs?.requestsGetDescription as string)
const name = (nodeData.inputs?.name as string) || (nodeData.inputs?.requestsGetName as string)
const queryParamsSchema =
(nodeData.inputs?.queryParamsSchema as string) || (nodeData.inputs?.requestsGetQueryParamsSchema as string)
const maxOutputLength = nodeData.inputs?.requestsGetMaxOutputLength as string
const obj: RequestParameters = {}
if (url) obj.url = url
if (url) obj.url = stripHTMLFromToolInput(url)
if (description) obj.description = description
if (name)
obj.name = name
.toLowerCase()
.replace(/ /g, '_')
.replace(/[^a-z0-9_-]/g, '')
if (queryParamsSchema) obj.queryParamsSchema = queryParamsSchema
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
if (headers) {
const parsedHeaders = typeof headers === 'object' ? headers : JSON.parse(headers)
const parsedHeaders = typeof headers === 'object' ? headers : JSON.parse(stripHTMLFromToolInput(headers))
obj.headers = parsedHeaders
}
@@ -1,8 +1,8 @@
import { z } from 'zod'
import fetch from 'node-fetch'
import { Tool } from '@langchain/core/tools'
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
export const desc = `A portal to the internet. Use this when you need to get specific content from a website.
Input should be a url (i.e. https://www.google.com). The output will be the text response of the GET request.`
export const desc = `Use this when you need to execute a GET request to get data from a website.`
export interface Headers {
[key: string]: string
@@ -11,36 +11,173 @@ export interface Headers {
export interface RequestParameters {
headers?: Headers
url?: string
name?: string
queryParamsSchema?: string
description?: string
maxOutputLength?: number
}
export class RequestsGetTool extends Tool {
name = 'requests_get'
// Base schema for GET request
const createRequestsGetSchema = (queryParamsSchema?: string) => {
// If queryParamsSchema is provided, parse it and add dynamic query params
if (queryParamsSchema) {
try {
const parsedSchema = JSON.parse(queryParamsSchema)
const queryParamsObject: Record<string, z.ZodTypeAny> = {}
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
let zodType: z.ZodTypeAny = z.string()
// Handle different types
if (config.type === 'number') {
zodType = z.string().transform((val) => Number(val))
} else if (config.type === 'boolean') {
zodType = z.string().transform((val) => val === 'true')
}
// Add description
if (config.description) {
zodType = zodType.describe(config.description)
}
// Make optional if not required
if (!config.required) {
zodType = zodType.optional()
}
queryParamsObject[key] = zodType
})
if (Object.keys(queryParamsObject).length > 0) {
return z.object({
queryParams: z.object(queryParamsObject).optional().describe('Query parameters for the request')
})
}
} catch (error) {
console.warn('Failed to parse queryParamsSchema:', error)
}
}
// Fallback to generic query params
return z.object({
queryParams: z.record(z.string()).optional().describe('Optional query parameters to include in the request')
})
}
export class RequestsGetTool extends DynamicStructuredTool {
url = ''
description = desc
maxOutputLength = 2000
maxOutputLength = Infinity
headers = {}
queryParamsSchema?: string
constructor(args?: RequestParameters) {
super()
const schema = createRequestsGetSchema(args?.queryParamsSchema)
const toolInput = {
name: args?.name || 'requests_get',
description: args?.description || desc,
schema: schema,
baseUrl: '',
method: 'GET',
headers: args?.headers || {}
}
super(toolInput)
this.url = args?.url ?? this.url
this.headers = args?.headers ?? this.headers
this.description = args?.description ?? this.description
this.maxOutputLength = args?.maxOutputLength ?? this.maxOutputLength
this.queryParamsSchema = args?.queryParamsSchema
}
/** @ignore */
async _call(input: string) {
const inputUrl = !this.url ? input : this.url
async _call(arg: any): Promise<string> {
const params = { ...arg }
if (process.env.DEBUG === 'true') console.info(`Making GET API call to ${inputUrl}`)
const inputUrl = this.url
if (!inputUrl) {
throw new Error('URL is required for GET request')
}
const res = await fetch(inputUrl, {
headers: this.headers
})
const requestHeaders = {
...(params.headers || {}),
...this.headers
}
const text = await res.text()
return text.slice(0, this.maxOutputLength)
// Process URL and query parameters based on schema
let finalUrl = inputUrl
const queryParams: Record<string, string> = {}
if (this.queryParamsSchema && params.queryParams && Object.keys(params.queryParams).length > 0) {
try {
const parsedSchema = JSON.parse(this.queryParamsSchema)
const pathParams: Array<{ key: string; value: string }> = []
Object.entries(params.queryParams).forEach(([key, value]) => {
const paramConfig = parsedSchema[key]
if (paramConfig && value !== undefined && value !== null) {
if (paramConfig.in === 'path') {
// Check if URL contains path parameter placeholder
const pathPattern = new RegExp(`:${key}\\b`, 'g')
if (finalUrl.includes(`:${key}`)) {
// Replace path parameters in URL (e.g., /:id -> /123)
finalUrl = finalUrl.replace(pathPattern, encodeURIComponent(String(value)))
} else {
// Collect path parameters to append to URL
pathParams.push({ key, value: String(value) })
}
} else if (paramConfig.in === 'query') {
// Add to query parameters
queryParams[key] = String(value)
}
}
})
// Append path parameters to URL if any exist
if (pathParams.length > 0) {
let urlPath = finalUrl
// Remove trailing slash if present
if (urlPath.endsWith('/')) {
urlPath = urlPath.slice(0, -1)
}
// Append each path parameter
pathParams.forEach(({ value }) => {
urlPath += `/${encodeURIComponent(value)}`
})
finalUrl = urlPath
}
// Add query parameters to URL if any exist
if (Object.keys(queryParams).length > 0) {
const url = new URL(finalUrl)
Object.entries(queryParams).forEach(([key, value]) => {
url.searchParams.append(key, value)
})
finalUrl = url.toString()
}
} catch (error) {
console.warn('Failed to process queryParamsSchema:', error)
}
} else if (params.queryParams && Object.keys(params.queryParams).length > 0) {
// Fallback: treat all parameters as query parameters if no schema is defined
const url = new URL(finalUrl)
Object.entries(params.queryParams).forEach(([key, value]) => {
url.searchParams.append(key, String(value))
})
finalUrl = url.toString()
}
try {
const res = await fetch(finalUrl, {
headers: requestHeaders
})
if (!res.ok) {
throw new Error(`HTTP Error ${res.status}: ${res.statusText}`)
}
const text = await res.text()
return text.slice(0, this.maxOutputLength)
} catch (error) {
throw new Error(`Failed to make GET request: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

@@ -1,6 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 20.5C10.5 20.5 10 20 9 20C7.067 20 6 21.567 6 23.5C6 25.433 7.067 27 9 27C10 27 10.7037 26.4812 11 26V24H10" stroke="#110000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.5 20H14V27H18.5M14 23.5H17.5" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M23.5 27V20M21 20H26" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.1112 15.2076L17.482 13.3556C15.4506 14.3228 13.0464 14.0464 11.477 12.477C10.1962 11.1962 9.77656 9.35939 10.1913 7.62299C10.3492 6.9619 11.1601 6.82676 11.6407 7.30737L13.5196 9.18628C14.1962 9.86283 15.3416 9.81433 16.078 9.07795C16.8143 8.34157 16.8628 7.19616 16.1863 6.51961L14.3074 4.64071C13.8268 4.16009 13.9619 3.34916 14.623 3.19127C16.3594 2.77656 18.1962 3.19622 19.477 4.477C21.0464 6.04639 21.3228 8.45065 20.3556 10.482L22.2076 12.1112" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB