Feature/lang graph (#2319)
* add langgraph * datasource: initial commit * datasource: datasource details and chunks * datasource: Document Store Node * more changes * Document Store - Base functionality * Document Store Loader Component * Document Store Loader Component * before merging the modularity PR * after merging the modularity PR * preview mode * initial draft PR * fixes * minor updates and fixes * preview with loader and splitter * preview with credential * show stored chunks * preview update... * edit config * save, preview and other changes * save, preview and other changes * save, process and other changes * save, process and other changes * alpha1 - for internal testing * rerouting urls * bug fix on new leader create * pagination support for chunks * delete document store * Update pnpm-lock.yaml * doc store card view * Update store files to use updated storage functions, Document Store Table View and other changes * ui changes * add expanded chunk dialog, improve ui * change throw Error to InternalError * Bug Fixes and removal of subFolder, adding of view chunks for store * lint fixes * merge changes * DocumentStoreStatus component * ui changes for doc store * add remove metadata key field, add custom document loader * add chatflows used doc store chips * add types/interfaces to DocumentStore Services * document loader list dialog title bar color change * update interfaces * Whereused Chatflow Name and Added chunkNo to retain order of created chunks. * use typeorm order chunkNo, ui changes * update tabler icons react * cleanup agents * add pysandbox tool * add abort functionality, loading next agent * add empty view svg * update chatflow tool with chatId * rename to agentflows * update worker for prompt input values * update dashboard to agentflows, agentcanvas * fix marketplace use template * add agentflow templates * resolve merge conflict * update baseURL --------- Co-authored-by: vinodkiran <vinodkiran@usa.net> Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in>
@@ -54,7 +54,7 @@ class ToolAgent_Agents implements INode {
|
||||
name: 'model',
|
||||
type: 'BaseChatModel',
|
||||
description:
|
||||
'Only compatible with models that are capable of function calling. ChatOpenAI, ChatMistral, ChatAnthropic, ChatVertexAI'
|
||||
'Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat'
|
||||
},
|
||||
{
|
||||
label: 'System Message',
|
||||
|
||||
@@ -206,7 +206,8 @@ class LangchainChatGoogleGenerativeAI extends BaseChatModel implements GoogleGen
|
||||
options: this['ParsedCallOptions'],
|
||||
runManager?: CallbackManagerForLLMRun
|
||||
): Promise<ChatResult> {
|
||||
const prompt = convertBaseMessagesToContent(messages, this._isMultimodalModel)
|
||||
let prompt = convertBaseMessagesToContent(messages, this._isMultimodalModel)
|
||||
prompt = checkIfEmptyContentAndSameRole(prompt)
|
||||
|
||||
// Handle streaming
|
||||
if (this.streaming) {
|
||||
@@ -235,7 +236,9 @@ class LangchainChatGoogleGenerativeAI extends BaseChatModel implements GoogleGen
|
||||
options: this['ParsedCallOptions'],
|
||||
runManager?: CallbackManagerForLLMRun
|
||||
): AsyncGenerator<ChatGenerationChunk> {
|
||||
const prompt = convertBaseMessagesToContent(messages, this._isMultimodalModel)
|
||||
let prompt = convertBaseMessagesToContent(messages, this._isMultimodalModel)
|
||||
prompt = checkIfEmptyContentAndSameRole(prompt)
|
||||
|
||||
//@ts-ignore
|
||||
if (options.tools !== undefined && options.tools.length > 0) {
|
||||
const result = await this._generateNonStreaming(prompt, options, runManager)
|
||||
@@ -333,7 +336,9 @@ function convertAuthorToRole(author: string) {
|
||||
case 'tool':
|
||||
return 'function'
|
||||
default:
|
||||
throw new Error(`Unknown / unsupported author: ${author}`)
|
||||
// Instead of throwing, we return model
|
||||
// throw new Error(`Unknown / unsupported author: ${author}`)
|
||||
return 'model'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,6 +401,25 @@ function convertMessageContentToParts(content: MessageContent, isMultimodalModel
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a dedicated logic for Multi Agent Supervisor to handle the case where the content is empty, and the role is the same
|
||||
*/
|
||||
|
||||
function checkIfEmptyContentAndSameRole(contents: Content[]) {
|
||||
let prevRole = ''
|
||||
const removedContents: Content[] = []
|
||||
for (const content of contents) {
|
||||
const role = content.role
|
||||
if (content.parts.length && content.parts[0].text === '' && role === prevRole) {
|
||||
removedContents.push(content)
|
||||
}
|
||||
|
||||
prevRole = role
|
||||
}
|
||||
|
||||
return contents.filter((content) => !removedContents.includes(content))
|
||||
}
|
||||
|
||||
function convertBaseMessagesToContent(messages: BaseMessage[], isMultimodalModel: boolean) {
|
||||
return messages.reduce<{
|
||||
content: Content[]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { omit } from 'lodash'
|
||||
import { ICommonObject, IDocument, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { ICommonObject, IDocument, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { TextSplitter } from 'langchain/text_splitter'
|
||||
import { CSVLoader } from 'langchain/document_loaders/fs/csv'
|
||||
import { getFileFromStorage } from '../../../src'
|
||||
import { getFileFromStorage, handleEscapeCharacters } from '../../../src'
|
||||
|
||||
class Csv_DocumentLoaders implements INode {
|
||||
label: string
|
||||
@@ -14,11 +14,12 @@ class Csv_DocumentLoaders implements INode {
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
outputs: INodeOutputsValue[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Csv File'
|
||||
this.name = 'csvFile'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'Document'
|
||||
this.icon = 'csv.svg'
|
||||
this.category = 'Document Loaders'
|
||||
@@ -65,6 +66,20 @@ class Csv_DocumentLoaders implements INode {
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
description: 'Array of document objects containing metadata and pageContent',
|
||||
baseClasses: [...this.baseClasses, 'json']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
description: 'Concatenated string from pageContent of documents',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
@@ -72,6 +87,7 @@ class Csv_DocumentLoaders implements INode {
|
||||
const csvFileBase64 = nodeData.inputs?.csvFile as string
|
||||
const columnName = nodeData.inputs?.columnName as string
|
||||
const metadata = nodeData.inputs?.metadata
|
||||
const output = nodeData.outputs?.output as string
|
||||
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||
|
||||
let omitMetadataKeys: string[] = []
|
||||
@@ -156,7 +172,15 @@ class Csv_DocumentLoaders implements INode {
|
||||
}))
|
||||
}
|
||||
|
||||
return docs
|
||||
if (output === 'document') {
|
||||
return docs
|
||||
} else {
|
||||
let finaltext = ''
|
||||
for (const doc of docs) {
|
||||
finaltext += `${doc.pageContent}\n`
|
||||
}
|
||||
return handleEscapeCharacters(finaltext, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
import { flatten } from 'lodash'
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
||||
import { Runnable, RunnableConfig } from '@langchain/core/runnables'
|
||||
import { ChatPromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate } from '@langchain/core/prompts'
|
||||
import {
|
||||
ICommonObject,
|
||||
IMultiAgentNode,
|
||||
INode,
|
||||
INodeData,
|
||||
INodeParams,
|
||||
ITeamState,
|
||||
IVisionChatModal,
|
||||
MessageContentImageUrl
|
||||
} from '../../../src/Interface'
|
||||
import { Moderation } from '../../moderation/Moderation'
|
||||
import { z } from 'zod'
|
||||
import { StructuredTool } from '@langchain/core/tools'
|
||||
import { AgentExecutor, JsonOutputToolsParser, ToolCallingAgentOutputParser } from '../../../src/agents'
|
||||
import { ChatMistralAI } from '@langchain/mistralai'
|
||||
import { ChatOpenAI } from '../../chatmodels/ChatOpenAI/FlowiseChatOpenAI'
|
||||
import { ChatAnthropic } from '../../chatmodels/ChatAnthropic/FlowiseChatAnthropic'
|
||||
import { ChatGoogleGenerativeAI } from '../../chatmodels/ChatGoogleGenerativeAI/FlowiseChatGoogleGenerativeAI'
|
||||
import { addImagesToMessages, llmSupportsVision } from '../../../src/multiModalUtils'
|
||||
|
||||
const sysPrompt = `You are a supervisor tasked with managing a conversation between the following workers: {team_members}.
|
||||
Given the following user request, respond with the worker to act next.
|
||||
Each worker will perform a task and respond with their results and status.
|
||||
When finished, respond with FINISH.
|
||||
Select strategically to minimize the number of steps taken.`
|
||||
|
||||
const routerToolName = 'route'
|
||||
|
||||
class Supervisor_MultiAgents implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs?: INodeParams[]
|
||||
badge?: string
|
||||
|
||||
constructor() {
|
||||
this.label = 'Supervisor'
|
||||
this.name = 'supervisor'
|
||||
this.version = 1.0
|
||||
this.type = 'Supervisor'
|
||||
this.icon = 'supervisor.svg'
|
||||
this.category = 'Multi Agents'
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Supervisor Name',
|
||||
name: 'supervisorName',
|
||||
type: 'string',
|
||||
placeholder: 'Supervisor',
|
||||
default: 'Supervisor'
|
||||
},
|
||||
{
|
||||
label: 'Supervisor Prompt',
|
||||
name: 'supervisorPrompt',
|
||||
type: 'string',
|
||||
description: 'Prompt must contains {team_members}',
|
||||
rows: 4,
|
||||
default: sysPrompt,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'Tool Calling Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseChatModel',
|
||||
description: `Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, GroqChat. Best result with GPT-4 model`
|
||||
},
|
||||
{
|
||||
label: 'Recursion Limit',
|
||||
name: 'recursionLimit',
|
||||
type: 'number',
|
||||
description: 'Maximum number of times a call can recurse. If not provided, defaults to 100.',
|
||||
default: 100,
|
||||
additionalParams: 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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const llm = nodeData.inputs?.model as BaseChatModel
|
||||
const supervisorPrompt = nodeData.inputs?.supervisorPrompt as string
|
||||
const supervisorLabel = nodeData.inputs?.supervisorName as string
|
||||
const _recursionLimit = nodeData.inputs?.recursionLimit as string
|
||||
const recursionLimit = _recursionLimit ? parseFloat(_recursionLimit) : 100
|
||||
const moderations = (nodeData.inputs?.inputModeration as Moderation[]) ?? []
|
||||
|
||||
const abortControllerSignal = options.signal as AbortController
|
||||
|
||||
const workersNodes: IMultiAgentNode[] =
|
||||
nodeData.inputs?.workerNodes && nodeData.inputs?.workerNodes.length ? flatten(nodeData.inputs?.workerNodes) : []
|
||||
const workersNodeNames = workersNodes.map((node: IMultiAgentNode) => node.name)
|
||||
|
||||
if (!supervisorLabel) throw new Error('Supervisor name is required!')
|
||||
|
||||
const supervisorName = supervisorLabel.toLowerCase().replace(/\s/g, '_').trim()
|
||||
|
||||
let multiModalMessageContent: MessageContentImageUrl[] = []
|
||||
|
||||
async function createTeamSupervisor(llm: BaseChatModel, systemPrompt: string, members: string[]): Promise<Runnable> {
|
||||
const memberOptions = ['FINISH', ...members]
|
||||
|
||||
systemPrompt = systemPrompt.replaceAll('{team_members}', members.join(', '))
|
||||
|
||||
let userPrompt = `Given the conversation above, who should act next? Or should we FINISH? Select one of: ${memberOptions.join(
|
||||
', '
|
||||
)}`
|
||||
|
||||
const tool = new RouteTool({
|
||||
schema: z.object({
|
||||
reasoning: z.string(),
|
||||
next: z.enum(['FINISH', ...members]),
|
||||
instructions: z.string().describe('The specific instructions of the sub-task the next role should accomplish.')
|
||||
})
|
||||
})
|
||||
|
||||
let supervisor
|
||||
|
||||
if (llm instanceof ChatMistralAI) {
|
||||
let prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', systemPrompt],
|
||||
new MessagesPlaceholder('messages'),
|
||||
['human', userPrompt]
|
||||
])
|
||||
|
||||
const messages = await processImageMessage(1, llm, prompt, nodeData, options)
|
||||
prompt = messages.prompt
|
||||
multiModalMessageContent = messages.multiModalMessageContent
|
||||
|
||||
// Force Mistral to use tool
|
||||
const modelWithTool = llm.bind({
|
||||
tools: [tool],
|
||||
tool_choice: 'any',
|
||||
signal: abortControllerSignal ? abortControllerSignal.signal : undefined
|
||||
})
|
||||
|
||||
const outputParser = new JsonOutputToolsParser()
|
||||
|
||||
supervisor = prompt
|
||||
.pipe(modelWithTool)
|
||||
.pipe(outputParser)
|
||||
.pipe((x) => {
|
||||
if (Array.isArray(x) && x.length) {
|
||||
const toolAgentAction = x[0]
|
||||
return {
|
||||
next: Object.keys(toolAgentAction.args).length ? toolAgentAction.args.next : 'FINISH',
|
||||
instructions: Object.keys(toolAgentAction.args).length
|
||||
? toolAgentAction.args.instructions
|
||||
: 'Conversation finished',
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: 'Conversation finished',
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (llm instanceof ChatAnthropic) {
|
||||
// Force Anthropic to use tool : https://docs.anthropic.com/claude/docs/tool-use#forcing-tool-use
|
||||
userPrompt = `Given the conversation above, who should act next? Or should we FINISH? Select one of: ${memberOptions.join(
|
||||
', '
|
||||
)}. Use the ${routerToolName} tool in your response.`
|
||||
|
||||
let prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', systemPrompt],
|
||||
new MessagesPlaceholder('messages'),
|
||||
['human', userPrompt]
|
||||
])
|
||||
|
||||
const messages = await processImageMessage(1, llm, prompt, nodeData, options)
|
||||
prompt = messages.prompt
|
||||
multiModalMessageContent = messages.multiModalMessageContent
|
||||
|
||||
if (llm.bindTools === undefined) {
|
||||
throw new Error(`This agent only compatible with function calling models.`)
|
||||
}
|
||||
|
||||
const modelWithTool = llm.bindTools([tool])
|
||||
|
||||
const outputParser = new ToolCallingAgentOutputParser()
|
||||
|
||||
supervisor = prompt
|
||||
.pipe(modelWithTool)
|
||||
.pipe(outputParser)
|
||||
.pipe((x) => {
|
||||
if (Array.isArray(x) && x.length) {
|
||||
const toolAgentAction = x[0] as any
|
||||
return {
|
||||
next: toolAgentAction.toolInput.next,
|
||||
instructions: toolAgentAction.toolInput.instructions,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else if (typeof x === 'object' && 'returnValues' in x) {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: x.returnValues?.output,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: 'Conversation finished',
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (llm instanceof ChatOpenAI) {
|
||||
let prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', systemPrompt],
|
||||
new MessagesPlaceholder('messages'),
|
||||
['human', userPrompt]
|
||||
])
|
||||
|
||||
const messages = await processImageMessage(1, llm, prompt, nodeData, options)
|
||||
prompt = messages.prompt
|
||||
multiModalMessageContent = messages.multiModalMessageContent
|
||||
|
||||
// Force OpenAI to use tool
|
||||
const modelWithTool = llm.bind({
|
||||
tools: [tool],
|
||||
tool_choice: { type: 'function', function: { name: routerToolName } },
|
||||
signal: abortControllerSignal ? abortControllerSignal.signal : undefined
|
||||
})
|
||||
|
||||
const outputParser = new ToolCallingAgentOutputParser()
|
||||
|
||||
supervisor = prompt
|
||||
.pipe(modelWithTool)
|
||||
.pipe(outputParser)
|
||||
.pipe((x) => {
|
||||
if (Array.isArray(x) && x.length) {
|
||||
const toolAgentAction = x[0] as any
|
||||
return {
|
||||
next: toolAgentAction.toolInput.next,
|
||||
instructions: toolAgentAction.toolInput.instructions,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else if (typeof x === 'object' && 'returnValues' in x) {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: x.returnValues?.output,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: 'Conversation finished',
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (llm instanceof ChatGoogleGenerativeAI) {
|
||||
/*
|
||||
* Gemini doesn't have system message and messages have to be alternate between model and user
|
||||
* So we have to place the system + human prompt at last
|
||||
*/
|
||||
let prompt = ChatPromptTemplate.fromMessages([
|
||||
['human', systemPrompt],
|
||||
['ai', ''],
|
||||
new MessagesPlaceholder('messages'),
|
||||
['ai', ''],
|
||||
['human', userPrompt]
|
||||
])
|
||||
|
||||
const messages = await processImageMessage(2, llm, prompt, nodeData, options)
|
||||
prompt = messages.prompt
|
||||
multiModalMessageContent = messages.multiModalMessageContent
|
||||
|
||||
if (llm.bindTools === undefined) {
|
||||
throw new Error(`This agent only compatible with function calling models.`)
|
||||
}
|
||||
const modelWithTool = llm.bindTools([tool])
|
||||
|
||||
const outputParser = new ToolCallingAgentOutputParser()
|
||||
|
||||
supervisor = prompt
|
||||
.pipe(modelWithTool)
|
||||
.pipe(outputParser)
|
||||
.pipe((x) => {
|
||||
if (Array.isArray(x) && x.length) {
|
||||
const toolAgentAction = x[0] as any
|
||||
return {
|
||||
next: toolAgentAction.toolInput.next,
|
||||
instructions: toolAgentAction.toolInput.instructions,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else if (typeof x === 'object' && 'returnValues' in x) {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: x.returnValues?.output,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: 'Conversation finished',
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', systemPrompt],
|
||||
new MessagesPlaceholder('messages'),
|
||||
['human', userPrompt]
|
||||
])
|
||||
|
||||
const messages = await processImageMessage(1, llm, prompt, nodeData, options)
|
||||
prompt = messages.prompt
|
||||
multiModalMessageContent = messages.multiModalMessageContent
|
||||
|
||||
if (llm.bindTools === undefined) {
|
||||
throw new Error(`This agent only compatible with function calling models.`)
|
||||
}
|
||||
const modelWithTool = llm.bindTools([tool])
|
||||
|
||||
const outputParser = new ToolCallingAgentOutputParser()
|
||||
|
||||
supervisor = prompt
|
||||
.pipe(modelWithTool)
|
||||
.pipe(outputParser)
|
||||
.pipe((x) => {
|
||||
if (Array.isArray(x) && x.length) {
|
||||
const toolAgentAction = x[0] as any
|
||||
return {
|
||||
next: toolAgentAction.toolInput.next,
|
||||
instructions: toolAgentAction.toolInput.instructions,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else if (typeof x === 'object' && 'returnValues' in x) {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: x.returnValues?.output,
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
next: 'FINISH',
|
||||
instructions: 'Conversation finished',
|
||||
team_members: members.join(', ')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return supervisor
|
||||
}
|
||||
|
||||
const supervisorAgent = await createTeamSupervisor(llm, supervisorPrompt ? supervisorPrompt : sysPrompt, workersNodeNames)
|
||||
|
||||
const supervisorNode = async (state: ITeamState, config: RunnableConfig) =>
|
||||
await agentNode(
|
||||
{
|
||||
state,
|
||||
agent: supervisorAgent,
|
||||
abortControllerSignal
|
||||
},
|
||||
config
|
||||
)
|
||||
|
||||
const returnOutput: IMultiAgentNode = {
|
||||
node: supervisorNode,
|
||||
name: supervisorName ?? 'supervisor',
|
||||
label: supervisorLabel ?? 'Supervisor',
|
||||
type: 'supervisor',
|
||||
workers: workersNodeNames,
|
||||
recursionLimit,
|
||||
llm,
|
||||
moderations,
|
||||
multiModalMessageContent
|
||||
}
|
||||
|
||||
return returnOutput
|
||||
}
|
||||
}
|
||||
|
||||
async function agentNode(
|
||||
{ state, agent, abortControllerSignal }: { state: ITeamState; agent: AgentExecutor | Runnable; abortControllerSignal: AbortController },
|
||||
config: RunnableConfig
|
||||
) {
|
||||
try {
|
||||
if (abortControllerSignal.signal.aborted) {
|
||||
throw new Error('Aborted!')
|
||||
}
|
||||
const result = await agent.invoke({ ...state, signal: abortControllerSignal.signal }, config)
|
||||
return result
|
||||
} catch (error) {
|
||||
throw new Error('Aborted!')
|
||||
}
|
||||
}
|
||||
|
||||
const processImageMessage = async (
|
||||
index: number,
|
||||
llm: BaseChatModel,
|
||||
prompt: ChatPromptTemplate,
|
||||
nodeData: INodeData,
|
||||
options: ICommonObject
|
||||
) => {
|
||||
let multiModalMessageContent: MessageContentImageUrl[] = []
|
||||
|
||||
if (llmSupportsVision(llm)) {
|
||||
const visionChatModel = llm as IVisionChatModal
|
||||
multiModalMessageContent = await addImagesToMessages(nodeData, options, llm.multiModalOption)
|
||||
|
||||
if (multiModalMessageContent?.length) {
|
||||
visionChatModel.setVisionModel()
|
||||
|
||||
const msg = HumanMessagePromptTemplate.fromTemplate([...multiModalMessageContent])
|
||||
|
||||
prompt.promptMessages.splice(index, 0, msg)
|
||||
} else {
|
||||
visionChatModel.revertToOriginalModel()
|
||||
}
|
||||
}
|
||||
|
||||
return { prompt, multiModalMessageContent }
|
||||
}
|
||||
|
||||
class RouteTool extends StructuredTool {
|
||||
name = routerToolName
|
||||
|
||||
description = 'Select the worker to act next'
|
||||
|
||||
schema
|
||||
|
||||
constructor(fields: ICommonObject) {
|
||||
super()
|
||||
this.schema = fields.schema
|
||||
}
|
||||
|
||||
async _call(input: any) {
|
||||
return JSON.stringify(input)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Supervisor_MultiAgents }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" /><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M17 10h2a2 2 0 0 1 2 2v1" /><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M3 13v-1a2 2 0 0 1 2 -2h2" /></svg>
|
||||
|
After Width: | Height: | Size: 559 B |
@@ -0,0 +1,291 @@
|
||||
import { flatten } from 'lodash'
|
||||
import { RunnableSequence, RunnablePassthrough, RunnableConfig } from '@langchain/core/runnables'
|
||||
import { ChatPromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate } from '@langchain/core/prompts'
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
||||
import { HumanMessage } from '@langchain/core/messages'
|
||||
import { formatToOpenAIToolMessages } from 'langchain/agents/format_scratchpad/openai_tools'
|
||||
import { type ToolsAgentStep } from 'langchain/agents/openai/output_parser'
|
||||
import { INode, INodeData, INodeParams, IMultiAgentNode, ITeamState, ICommonObject, MessageContentImageUrl } from '../../../src/Interface'
|
||||
import { ToolCallingAgentOutputParser, AgentExecutor } from '../../../src/agents'
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers'
|
||||
import { getInputVariables, handleEscapeCharacters } from '../../../src/utils'
|
||||
|
||||
const examplePrompt = 'You are a research assistant who can search for up-to-date info using search engine.'
|
||||
|
||||
class Worker_MultiAgents 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 = 'Worker'
|
||||
this.name = 'worker'
|
||||
this.version = 1.0
|
||||
this.type = 'Worker'
|
||||
this.icon = 'worker.svg'
|
||||
this.category = 'Multi Agents'
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Worker Name',
|
||||
name: 'workerName',
|
||||
type: 'string',
|
||||
placeholder: 'Worker'
|
||||
},
|
||||
{
|
||||
label: 'Worker Prompt',
|
||||
name: 'workerPrompt',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
default: examplePrompt
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
name: 'tools',
|
||||
type: 'Tool',
|
||||
list: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Supervisor',
|
||||
name: 'supervisor',
|
||||
type: 'Supervisor'
|
||||
},
|
||||
{
|
||||
label: 'Tool Calling Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseChatModel',
|
||||
optional: true,
|
||||
description: `Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat. If not specified, supervisor's model will be used`
|
||||
},
|
||||
{
|
||||
label: 'Format Prompt Values',
|
||||
name: 'promptValues',
|
||||
type: 'json',
|
||||
optional: true,
|
||||
acceptVariable: true,
|
||||
list: true
|
||||
},
|
||||
{
|
||||
label: 'Max Iterations',
|
||||
name: 'maxIterations',
|
||||
type: 'number',
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
let tools = nodeData.inputs?.tools
|
||||
tools = flatten(tools)
|
||||
let workerPrompt = nodeData.inputs?.workerPrompt as string
|
||||
const workerLabel = nodeData.inputs?.workerName as string
|
||||
const supervisor = nodeData.inputs?.supervisor as IMultiAgentNode
|
||||
const maxIterations = nodeData.inputs?.maxIterations as string
|
||||
const model = nodeData.inputs?.model as BaseChatModel
|
||||
const promptValuesStr = nodeData.inputs?.promptValues
|
||||
|
||||
if (!workerLabel) throw new Error('Worker name is required!')
|
||||
const workerName = workerLabel.toLowerCase().replace(/\s/g, '_').trim()
|
||||
|
||||
if (!workerPrompt) throw new Error('Worker prompt is required!')
|
||||
|
||||
let workerInputVariablesValues: ICommonObject = {}
|
||||
if (promptValuesStr) {
|
||||
try {
|
||||
workerInputVariablesValues = typeof promptValuesStr === 'object' ? promptValuesStr : JSON.parse(promptValuesStr)
|
||||
} catch (exception) {
|
||||
throw new Error("Invalid JSON in the Worker's Prompt Input Values: " + exception)
|
||||
}
|
||||
}
|
||||
workerInputVariablesValues = handleEscapeCharacters(workerInputVariablesValues, true)
|
||||
|
||||
const llm = model || (supervisor.llm as BaseChatModel)
|
||||
const multiModalMessageContent = supervisor?.multiModalMessageContent || []
|
||||
|
||||
const abortControllerSignal = options.signal as AbortController
|
||||
const workerInputVariables = getInputVariables(workerPrompt)
|
||||
|
||||
if (!workerInputVariables.every((element) => Object.keys(workerInputVariablesValues).includes(element))) {
|
||||
throw new Error('Worker input variables values are not provided!')
|
||||
}
|
||||
|
||||
const agent = await createAgent(
|
||||
llm,
|
||||
[...tools],
|
||||
workerPrompt,
|
||||
multiModalMessageContent,
|
||||
workerInputVariablesValues,
|
||||
maxIterations,
|
||||
{
|
||||
sessionId: options.sessionId,
|
||||
chatId: options.chatId,
|
||||
input
|
||||
}
|
||||
)
|
||||
|
||||
const workerNode = async (state: ITeamState, config: RunnableConfig) =>
|
||||
await agentNode(
|
||||
{
|
||||
state,
|
||||
agent: agent,
|
||||
name: workerName,
|
||||
abortControllerSignal
|
||||
},
|
||||
config
|
||||
)
|
||||
|
||||
const returnOutput: IMultiAgentNode = {
|
||||
node: workerNode,
|
||||
name: workerName,
|
||||
label: workerLabel,
|
||||
type: 'worker',
|
||||
workerPrompt,
|
||||
workerInputVariables,
|
||||
parentSupervisorName: supervisor.name ?? 'supervisor'
|
||||
}
|
||||
|
||||
return returnOutput
|
||||
}
|
||||
}
|
||||
|
||||
async function createAgent(
|
||||
llm: BaseChatModel,
|
||||
tools: any[],
|
||||
systemPrompt: string,
|
||||
multiModalMessageContent: MessageContentImageUrl[],
|
||||
workerInputVariablesValues: ICommonObject,
|
||||
maxIterations?: string,
|
||||
flowObj?: { sessionId?: string; chatId?: string; input?: string }
|
||||
): Promise<AgentExecutor | RunnableSequence> {
|
||||
if (tools.length) {
|
||||
const combinedPrompt =
|
||||
systemPrompt +
|
||||
'\nWork autonomously according to your specialty, using the tools available to you.' +
|
||||
' Do not ask for clarification.' +
|
||||
' Your other team members (and other teams) will collaborate with you with their own specialties.' +
|
||||
' You are chosen for a reason! You are one of the following team members: {team_members}.'
|
||||
|
||||
//const toolNames = tools.length ? tools.map((t) => t.name).join(', ') : ''
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', combinedPrompt],
|
||||
new MessagesPlaceholder('messages'),
|
||||
new MessagesPlaceholder('agent_scratchpad')
|
||||
/* Gettind rid of this for now because other LLMs dont support system message at later stage
|
||||
[
|
||||
'system',
|
||||
[
|
||||
'Supervisor instructions: {instructions}\n' + tools.length
|
||||
? `Remember, you individually can only use these tools: ${toolNames}`
|
||||
: '' + '\n\nEnd if you have already completed the requested task. Communicate the work completed.'
|
||||
].join('\n')
|
||||
]*/
|
||||
])
|
||||
|
||||
if (multiModalMessageContent.length) {
|
||||
const msg = HumanMessagePromptTemplate.fromTemplate([...multiModalMessageContent])
|
||||
prompt.promptMessages.splice(1, 0, msg)
|
||||
}
|
||||
|
||||
if (llm.bindTools === undefined) {
|
||||
throw new Error(`This agent only compatible with function calling models.`)
|
||||
}
|
||||
const modelWithTools = llm.bindTools(tools)
|
||||
|
||||
const agent = RunnableSequence.from([
|
||||
RunnablePassthrough.assign({
|
||||
//@ts-ignore
|
||||
agent_scratchpad: (input: { steps: ToolsAgentStep[] }) => formatToOpenAIToolMessages(input.steps)
|
||||
}),
|
||||
RunnablePassthrough.assign(transformObjectPropertyToFunction(workerInputVariablesValues)),
|
||||
prompt,
|
||||
modelWithTools,
|
||||
new ToolCallingAgentOutputParser()
|
||||
])
|
||||
|
||||
const executor = AgentExecutor.fromAgentAndTools({
|
||||
agent: agent,
|
||||
tools,
|
||||
sessionId: flowObj?.sessionId,
|
||||
chatId: flowObj?.chatId,
|
||||
input: flowObj?.input,
|
||||
verbose: process.env.DEBUG === 'true' ? true : false,
|
||||
maxIterations: maxIterations ? parseFloat(maxIterations) : undefined
|
||||
})
|
||||
return executor
|
||||
} else {
|
||||
const combinedPrompt =
|
||||
systemPrompt +
|
||||
'\nWork autonomously according to your specialty, using the tools available to you.' +
|
||||
' Do not ask for clarification.' +
|
||||
' Your other team members (and other teams) will collaborate with you with their own specialties.' +
|
||||
' You are chosen for a reason! You are one of the following team members: {team_members}.'
|
||||
|
||||
const prompt = ChatPromptTemplate.fromMessages([['system', combinedPrompt], new MessagesPlaceholder('messages')])
|
||||
if (multiModalMessageContent.length) {
|
||||
const msg = HumanMessagePromptTemplate.fromTemplate([...multiModalMessageContent])
|
||||
prompt.promptMessages.splice(1, 0, msg)
|
||||
}
|
||||
const conversationChain = RunnableSequence.from([
|
||||
RunnablePassthrough.assign(transformObjectPropertyToFunction(workerInputVariablesValues)),
|
||||
prompt,
|
||||
llm,
|
||||
new StringOutputParser()
|
||||
])
|
||||
return conversationChain
|
||||
}
|
||||
}
|
||||
|
||||
async function agentNode(
|
||||
{
|
||||
state,
|
||||
agent,
|
||||
name,
|
||||
abortControllerSignal
|
||||
}: { state: ITeamState; agent: AgentExecutor | RunnableSequence; name: string; abortControllerSignal: AbortController },
|
||||
config: RunnableConfig
|
||||
) {
|
||||
try {
|
||||
if (abortControllerSignal.signal.aborted) {
|
||||
throw new Error('Aborted!')
|
||||
}
|
||||
const result = await agent.invoke({ ...state, signal: abortControllerSignal.signal }, config)
|
||||
const additional_kwargs: ICommonObject = {}
|
||||
if (result.usedTools) {
|
||||
additional_kwargs.usedTools = result.usedTools
|
||||
}
|
||||
if (result.sourceDocuments) {
|
||||
additional_kwargs.sourceDocuments = result.sourceDocuments
|
||||
}
|
||||
return {
|
||||
messages: [
|
||||
new HumanMessage({
|
||||
content: typeof result === 'string' ? result : result.output,
|
||||
name,
|
||||
additional_kwargs: Object.keys(additional_kwargs).length ? additional_kwargs : undefined
|
||||
})
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Aborted!')
|
||||
}
|
||||
}
|
||||
|
||||
const transformObjectPropertyToFunction = (obj: ICommonObject) => {
|
||||
const transformedObject: ICommonObject = {}
|
||||
|
||||
for (const key in obj) {
|
||||
transformedObject[key] = () => obj[key]
|
||||
}
|
||||
|
||||
return transformedObject
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Worker_MultiAgents }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>
|
||||
|
After Width: | Height: | Size: 382 B |
@@ -0,0 +1,259 @@
|
||||
import { DataSource } from 'typeorm'
|
||||
import { z } from 'zod'
|
||||
import fetch from 'node-fetch'
|
||||
import { RunnableConfig } from '@langchain/core/runnables'
|
||||
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
|
||||
import { StructuredTool } from '@langchain/core/tools'
|
||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
|
||||
class ChatflowTool_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Chatflow Tool'
|
||||
this.name = 'ChatflowTool'
|
||||
this.version = 1.0
|
||||
this.type = 'ChatflowTool'
|
||||
this.icon = 'chatflowTool.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Use as a tool to execute another chatflow'
|
||||
this.baseClasses = [this.type, 'Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['chatflowApi'],
|
||||
optional: true
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Select Chatflow',
|
||||
name: 'selectedChatflow',
|
||||
type: 'asyncOptions',
|
||||
loadMethod: 'listChatflows'
|
||||
},
|
||||
{
|
||||
label: 'Tool Name',
|
||||
name: 'name',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
label: 'Tool Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
description: 'Description of what the tool does. This is for LLM to determine when to use this tool.',
|
||||
rows: 3,
|
||||
placeholder:
|
||||
'State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.'
|
||||
},
|
||||
{
|
||||
label: 'Use Question from Chat',
|
||||
name: 'useQuestionFromChat',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether to use the question from the chat as input to the chatflow. If turned on, this will override the custom input.',
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'Custom Input',
|
||||
name: 'customInput',
|
||||
type: 'string',
|
||||
description: 'Custom input to be passed to the chatflow. Leave empty to let LLM decides the input.',
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
loadMethods = {
|
||||
async listChatflows(_: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> {
|
||||
const returnData: INodeOptionsValue[] = []
|
||||
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
if (appDataSource === undefined || !appDataSource) {
|
||||
return returnData
|
||||
}
|
||||
|
||||
const chatflows = await appDataSource.getRepository(databaseEntities['ChatFlow']).find()
|
||||
|
||||
for (let i = 0; i < chatflows.length; i += 1) {
|
||||
const data = {
|
||||
label: chatflows[i].name,
|
||||
name: chatflows[i].id
|
||||
} as INodeOptionsValue
|
||||
returnData.push(data)
|
||||
}
|
||||
return returnData
|
||||
}
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
const selectedChatflowId = nodeData.inputs?.selectedChatflow as string
|
||||
const _name = nodeData.inputs?.name as string
|
||||
const description = nodeData.inputs?.description as string
|
||||
const useQuestionFromChat = nodeData.inputs?.useQuestionFromChat as boolean
|
||||
const customInput = nodeData.inputs?.customInput as string
|
||||
|
||||
const baseURL = options.baseURL as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const chatflowApiKey = getCredentialParam('chatflowApiKey', credentialData, nodeData)
|
||||
|
||||
let headers = {}
|
||||
if (chatflowApiKey) headers = { Authorization: `Bearer ${chatflowApiKey}` }
|
||||
|
||||
let toolInput = ''
|
||||
if (useQuestionFromChat) {
|
||||
toolInput = input
|
||||
} else if (!customInput) {
|
||||
toolInput = customInput
|
||||
}
|
||||
|
||||
let name = _name || 'chatflow_tool'
|
||||
|
||||
return new ChatflowTool({ name, baseURL, description, chatflowid: selectedChatflowId, headers, input: toolInput })
|
||||
}
|
||||
}
|
||||
|
||||
class ChatflowTool extends StructuredTool {
|
||||
static lc_name() {
|
||||
return 'ChatflowTool'
|
||||
}
|
||||
|
||||
name = 'chatflow_tool'
|
||||
|
||||
description = 'Execute another chatflow'
|
||||
|
||||
input = ''
|
||||
|
||||
chatflowid = ''
|
||||
|
||||
baseURL = 'http://localhost:3000'
|
||||
|
||||
headers = {}
|
||||
|
||||
schema = z.object({
|
||||
input: z.string().describe('input question')
|
||||
})
|
||||
|
||||
constructor({
|
||||
name,
|
||||
description,
|
||||
input,
|
||||
chatflowid,
|
||||
baseURL,
|
||||
headers
|
||||
}: {
|
||||
name: string
|
||||
description: string
|
||||
input: string
|
||||
chatflowid: string
|
||||
baseURL: string
|
||||
headers: ICommonObject
|
||||
}) {
|
||||
super()
|
||||
this.name = name
|
||||
this.description = description
|
||||
this.input = input
|
||||
this.baseURL = baseURL
|
||||
this.headers = headers
|
||||
this.chatflowid = chatflowid
|
||||
}
|
||||
|
||||
async call(
|
||||
arg: z.infer<typeof this.schema>,
|
||||
configArg?: RunnableConfig | Callbacks,
|
||||
tags?: string[],
|
||||
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
|
||||
): Promise<string> {
|
||||
const config = parseCallbackConfigArg(configArg)
|
||||
if (config.runName === undefined) {
|
||||
config.runName = this.name
|
||||
}
|
||||
let parsed
|
||||
try {
|
||||
parsed = await this.schema.parseAsync(arg)
|
||||
} catch (e) {
|
||||
throw new Error(`Received tool input did not match expected schema: ${JSON.stringify(arg)}`)
|
||||
}
|
||||
const callbackManager_ = await CallbackManager.configure(
|
||||
config.callbacks,
|
||||
this.callbacks,
|
||||
config.tags || tags,
|
||||
this.tags,
|
||||
config.metadata,
|
||||
this.metadata,
|
||||
{ verbose: this.verbose }
|
||||
)
|
||||
const runManager = await callbackManager_?.handleToolStart(
|
||||
this.toJSON(),
|
||||
typeof parsed === 'string' ? parsed : JSON.stringify(parsed),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
config.runName
|
||||
)
|
||||
let result
|
||||
try {
|
||||
result = await this._call(parsed, runManager, flowConfig)
|
||||
} catch (e) {
|
||||
await runManager?.handleToolError(e)
|
||||
throw e
|
||||
}
|
||||
await runManager?.handleToolEnd(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
protected async _call(
|
||||
arg: z.infer<typeof this.schema>,
|
||||
_?: CallbackManagerForToolRun,
|
||||
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
|
||||
): Promise<string> {
|
||||
const inputQuestion = this.input || arg.input
|
||||
|
||||
const url = `${this.baseURL}/api/v1/prediction/${this.chatflowid}`
|
||||
|
||||
const body = {
|
||||
question: inputQuestion,
|
||||
chatId: flowConfig?.chatId,
|
||||
overrideConfig: {
|
||||
sessionId: flowConfig?.sessionId
|
||||
}
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...this.headers
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, options)
|
||||
const resp = await response.json()
|
||||
return resp.text || ''
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: ChatflowTool_Tools }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-hierarchy"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M5 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M19 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M6.5 17.5l5.5 -4.5l5.5 4.5" /><path d="M12 7l0 6" /></svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* TODO: Implement codeInterpreter column to chat_message table
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { StructuredTool, ToolParams } from '@langchain/core/tools'
|
||||
import { CodeInterpreter } from '@e2b/code-interpreter'
|
||||
import { z } from 'zod'
|
||||
|
||||
const DESC = `Evaluates python code in a sandbox environment. \
|
||||
The environment is long running and exists across multiple executions. \
|
||||
You must send the whole script every time and print your outputs. \
|
||||
Script should be pure python code that can be evaluated. \
|
||||
It should be in python format NOT markdown. \
|
||||
The code should NOT be wrapped in backticks. \
|
||||
All python packages including requests, matplotlib, scipy, numpy, pandas, \
|
||||
etc are available. Create and display chart using "plt.show()".`
|
||||
const NAME = 'code_interpreter'
|
||||
|
||||
class E2B_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
badge: string
|
||||
credential: INodeParams
|
||||
|
||||
constructor() {
|
||||
this.label = 'E2B'
|
||||
this.name = 'e2b'
|
||||
this.version = 1.0
|
||||
this.type = 'E2B'
|
||||
this.icon = 'e2b.png'
|
||||
this.category = 'Tools'
|
||||
this.badge = 'NEW'
|
||||
this.description = 'Execute code in E2B Code Intepreter'
|
||||
this.baseClasses = [this.type, 'Tool', ...getBaseClasses(E2BTool)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['E2BApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Tool Name',
|
||||
name: 'toolName',
|
||||
type: 'string',
|
||||
description: 'Specify the name of the tool',
|
||||
default: 'code_interpreter'
|
||||
},
|
||||
{
|
||||
label: 'Tool Description',
|
||||
name: 'toolDesc',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
description: 'Specify the description of the tool',
|
||||
default: DESC
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const toolDesc = nodeData.inputs?.toolDesc as string
|
||||
const toolName = nodeData.inputs?.toolName as string
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const e2bApiKey = getCredentialParam('e2bApiKey', credentialData, nodeData)
|
||||
const socketIO = options.socketIO
|
||||
const socketIOClientId = options.socketIOClientId
|
||||
|
||||
return await E2BTool.initialize({
|
||||
description: toolDesc ?? DESC,
|
||||
name: toolName ?? NAME,
|
||||
apiKey: e2bApiKey,
|
||||
schema: z.object({
|
||||
input: z.string().describe('Python code to be executed in the sandbox environment')
|
||||
}),
|
||||
socketIO,
|
||||
socketIOClientId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type E2BToolParams = ToolParams & { instance: CodeInterpreter }
|
||||
|
||||
export class E2BTool extends StructuredTool {
|
||||
static lc_name() {
|
||||
return 'E2BTool'
|
||||
}
|
||||
|
||||
name = NAME
|
||||
|
||||
description = DESC
|
||||
|
||||
instance: CodeInterpreter
|
||||
|
||||
apiKey: string
|
||||
|
||||
schema
|
||||
|
||||
socketIO
|
||||
|
||||
socketIOClientId = ''
|
||||
|
||||
constructor(options: E2BToolParams & { name: string; description: string, apiKey: string, schema: any, socketIO: any, socketIOClientId: string}) {
|
||||
super(options)
|
||||
this.instance = options.instance
|
||||
this.description = options.description
|
||||
this.name = options.name
|
||||
this.apiKey = options.apiKey
|
||||
this.schema = options.schema
|
||||
this.returnDirect = true
|
||||
this.socketIO = options.socketIO
|
||||
this.socketIOClientId = options.socketIOClientId
|
||||
}
|
||||
|
||||
static async initialize(options: Partial<E2BToolParams> & { name: string; description: string, apiKey: string, schema: any, socketIO: any, socketIOClientId: string }) {
|
||||
const instance = await CodeInterpreter.create({ apiKey: options.apiKey })
|
||||
return new this({ instance, name: options.name, description: options.description, apiKey: options.apiKey, schema: options.schema, socketIO: options.socketIO, socketIOClientId: options.socketIOClientId})
|
||||
}
|
||||
|
||||
async _call(args: any) {
|
||||
try {
|
||||
if ('input' in args) {
|
||||
const execution = await this.instance.notebook.execCell(args?.input)
|
||||
let imgHTML = ''
|
||||
for (const result of execution.results) {
|
||||
if (result.png) {
|
||||
imgHTML += `\n\n<img src="data:image/png;base64,${result.png}" width="100%" height="max-content" alt="image" /><br/>`
|
||||
}
|
||||
if (result.jpeg) {
|
||||
imgHTML += `\n\n<img src="data:image/jpeg;base64,${result.jpeg}" width="100%" height="max-content" alt="image" /><br/>`
|
||||
}
|
||||
}
|
||||
const output = execution.text ? execution.text + imgHTML : imgHTML
|
||||
if (this.socketIO && this.socketIOClientId) this.socketIO.to(this.socketIOClientId).emit('token', output)
|
||||
return output
|
||||
} else {
|
||||
return 'No input provided'
|
||||
}
|
||||
} catch (e) {
|
||||
return typeof e === 'string' ? e : JSON.stringify(e, null, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: E2B_Tools }
|
||||
*/
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,128 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { loadPyodide, type PyodideInterface } from 'pyodide'
|
||||
import { Tool, ToolParams } from '@langchain/core/tools'
|
||||
import * as path from 'path'
|
||||
import { getUserHome } from '../../../src/utils'
|
||||
|
||||
let pyodideInstance: PyodideInterface | undefined
|
||||
const DESC = `Evaluates python code in a sandbox environment. The environment resets on every execution. You must send the whole script every time and print your outputs. Script should be pure python code that can be evaluated. Use only packages available in Pyodide.`
|
||||
const NAME = 'python_interpreter'
|
||||
|
||||
async function LoadPyodide(): Promise<PyodideInterface> {
|
||||
if (pyodideInstance === undefined) {
|
||||
const obj = { packageCacheDir: path.join(getUserHome(), '.flowise', 'pyodideCacheDir') }
|
||||
pyodideInstance = await loadPyodide(obj)
|
||||
}
|
||||
return pyodideInstance
|
||||
}
|
||||
|
||||
class PythonInterpreter_Tools 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 = 'Python Interpreter'
|
||||
this.name = 'pythonInterpreter'
|
||||
this.version = 1.0
|
||||
this.type = 'PythonInterpreter'
|
||||
this.icon = 'python.svg'
|
||||
this.category = 'Tools'
|
||||
this.badge = 'NEW'
|
||||
this.description = 'Execute python code in Pyodide sandbox environment'
|
||||
this.baseClasses = [this.type, 'Tool', ...getBaseClasses(PythonInterpreterTool)]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Tool Name',
|
||||
name: 'toolName',
|
||||
type: 'string',
|
||||
description: 'Specify the name of the tool',
|
||||
default: 'python_interpreter'
|
||||
},
|
||||
{
|
||||
label: 'Tool Description',
|
||||
name: 'toolDesc',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
description: 'Specify the description of the tool',
|
||||
default: DESC
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const toolDesc = nodeData.inputs?.toolDesc as string
|
||||
const toolName = nodeData.inputs?.toolName as string
|
||||
|
||||
return await PythonInterpreterTool.initialize({
|
||||
description: toolDesc ?? DESC,
|
||||
name: toolName ?? NAME
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type PythonInterpreterToolParams = Parameters<typeof loadPyodide>[0] &
|
||||
ToolParams & {
|
||||
instance: PyodideInterface
|
||||
}
|
||||
|
||||
export class PythonInterpreterTool extends Tool {
|
||||
static lc_name() {
|
||||
return 'PythonInterpreterTool'
|
||||
}
|
||||
|
||||
name = NAME
|
||||
|
||||
description = DESC
|
||||
|
||||
pyodideInstance: PyodideInterface
|
||||
|
||||
stdout = ''
|
||||
|
||||
stderr = ''
|
||||
|
||||
constructor(options: PythonInterpreterToolParams & { name: string; description: string }) {
|
||||
super(options)
|
||||
this.description = options.description
|
||||
this.name = options.name
|
||||
this.pyodideInstance = options.instance
|
||||
this.pyodideInstance.setStderr({
|
||||
batched: (text: string) => {
|
||||
this.stderr += text
|
||||
}
|
||||
})
|
||||
this.pyodideInstance.setStdout({
|
||||
batched: (text: string) => {
|
||||
this.stdout += text
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static async initialize(options: Partial<PythonInterpreterToolParams> & { name: string; description: string }) {
|
||||
const instance = await LoadPyodide()
|
||||
return new this({ instance, name: options.name, description: options.description })
|
||||
}
|
||||
|
||||
async _call(script: string) {
|
||||
this.stdout = ''
|
||||
this.stderr = ''
|
||||
|
||||
try {
|
||||
await this.pyodideInstance.loadPackagesFromImports(script)
|
||||
await this.pyodideInstance.runPythonAsync(script)
|
||||
return JSON.stringify({ stdout: this.stdout, stderr: this.stderr }, null, 2)
|
||||
} catch (e) {
|
||||
return typeof e === 'string' ? e : JSON.stringify(e, null, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: PythonInterpreter_Tools }
|
||||
@@ -0,0 +1 @@
|
||||
<svg class="mr-1.5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M15.84.5a16.4,16.4,0,0,0-3.57.32C9.1,1.39,8.53,2.53,8.53,4.64V7.48H16v1H5.77a4.73,4.73,0,0,0-4.7,3.74,14.82,14.82,0,0,0,0,7.54c.57,2.28,1.86,3.82,4,3.82h2.6V20.14a4.73,4.73,0,0,1,4.63-4.63h7.38a3.72,3.72,0,0,0,3.73-3.73V4.64A4.16,4.16,0,0,0,19.65.82,20.49,20.49,0,0,0,15.84.5ZM11.78,2.77a1.39,1.39,0,0,1,1.38,1.46,1.37,1.37,0,0,1-1.38,1.38A1.42,1.42,0,0,1,10.4,4.23,1.44,1.44,0,0,1,11.78,2.77Z" fill="#5a9fd4"></path><path d="M16.16,31.5a16.4,16.4,0,0,0,3.57-.32c3.17-.57,3.74-1.71,3.74-3.82V24.52H16v-1H26.23a4.73,4.73,0,0,0,4.7-3.74,14.82,14.82,0,0,0,0-7.54c-.57-2.28-1.86-3.82-4-3.82h-2.6v3.41a4.73,4.73,0,0,1-4.63,4.63H12.35a3.72,3.72,0,0,0-3.73,3.73v7.14a4.16,4.16,0,0,0,3.73,3.82A20.49,20.49,0,0,0,16.16,31.5Zm4.06-2.27a1.39,1.39,0,0,1-1.38-1.46,1.37,1.37,0,0,1,1.38-1.38,1.42,1.42,0,0,1,1.38,1.38A1.44,1.44,0,0,1,20.22,29.23Z" fill="#ffd43b"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,72 +0,0 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { desc, RequestParameters, RequestsGetTool } from './core'
|
||||
|
||||
class RequestsGet_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Requests Get'
|
||||
this.name = 'requestsGet'
|
||||
this.version = 1.0
|
||||
this.type = 'RequestsGet'
|
||||
this.icon = 'requestsget.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Execute HTTP GET requests'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(RequestsGetTool)]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'URL',
|
||||
name: 'url',
|
||||
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',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
default: desc,
|
||||
description: 'Acts like a prompt to tell agent when it should use this tool',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Headers',
|
||||
name: 'headers',
|
||||
type: 'json',
|
||||
additionalParams: true,
|
||||
optional: 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 obj: RequestParameters = {}
|
||||
if (url) obj.url = url
|
||||
if (description) obj.description = description
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON.parse(headers)
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
|
||||
return new RequestsGetTool(obj)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: RequestsGet_Tools }
|
||||
@@ -1,46 +0,0 @@
|
||||
import fetch from 'node-fetch'
|
||||
import { Tool } from '@langchain/core/tools'
|
||||
|
||||
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 interface Headers {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface RequestParameters {
|
||||
headers?: Headers
|
||||
url?: string
|
||||
description?: string
|
||||
maxOutputLength?: number
|
||||
}
|
||||
|
||||
export class RequestsGetTool extends Tool {
|
||||
name = 'requests_get'
|
||||
url = ''
|
||||
description = desc
|
||||
maxOutputLength = 2000
|
||||
headers = {}
|
||||
|
||||
constructor(args?: RequestParameters) {
|
||||
super()
|
||||
this.url = args?.url ?? this.url
|
||||
this.headers = args?.headers ?? this.headers
|
||||
this.description = args?.description ?? this.description
|
||||
this.maxOutputLength = args?.maxOutputLength ?? this.maxOutputLength
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
async _call(input: string) {
|
||||
const inputUrl = !this.url ? input : this.url
|
||||
|
||||
if (process.env.DEBUG === 'true') console.info(`Making GET API call to ${inputUrl}`)
|
||||
|
||||
const res = await fetch(inputUrl, {
|
||||
headers: this.headers
|
||||
})
|
||||
|
||||
const text = await res.text()
|
||||
return text.slice(0, this.maxOutputLength)
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -1,86 +0,0 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { RequestParameters, desc, RequestsPostTool } from './core'
|
||||
|
||||
class RequestsPost_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Requests Post'
|
||||
this.name = 'requestsPost'
|
||||
this.version = 1.0
|
||||
this.type = 'RequestsPost'
|
||||
this.icon = 'requestspost.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Execute HTTP POST requests'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(RequestsPostTool)]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'URL',
|
||||
name: 'url',
|
||||
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',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body',
|
||||
name: 'body',
|
||||
type: 'json',
|
||||
description:
|
||||
'JSON body for the POST request. If not specified, agent will try to figure out itself from AIPlugin if provided',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
default: desc,
|
||||
description: 'Acts like a prompt to tell agent when it should use this tool',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Headers',
|
||||
name: 'headers',
|
||||
type: 'json',
|
||||
additionalParams: true,
|
||||
optional: 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 body = nodeData.inputs?.body as string
|
||||
|
||||
const obj: RequestParameters = {}
|
||||
if (url) obj.url = url
|
||||
if (description) obj.description = description
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON.parse(headers)
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
if (body) {
|
||||
const parsedBody = typeof body === 'object' ? body : JSON.parse(body)
|
||||
obj.body = parsedBody
|
||||
}
|
||||
|
||||
return new RequestsPostTool(obj)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: RequestsPost_Tools }
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Tool } from '@langchain/core/tools'
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
export const desc = `Use this when you want to POST to a website.
|
||||
Input should be a json string with two keys: "url" and "data".
|
||||
The value of "url" should be a string, and the value of "data" should be a dictionary of
|
||||
key-value pairs you want to POST to the url as a JSON body.
|
||||
Be careful to always use double quotes for strings in the json string
|
||||
The output will be the text response of the POST request.`
|
||||
|
||||
export interface Headers {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface Body {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface RequestParameters {
|
||||
headers?: Headers
|
||||
body?: Body
|
||||
url?: string
|
||||
description?: string
|
||||
maxOutputLength?: number
|
||||
}
|
||||
|
||||
export class RequestsPostTool extends Tool {
|
||||
name = 'requests_post'
|
||||
url = ''
|
||||
description = desc
|
||||
maxOutputLength = Infinity
|
||||
headers = {}
|
||||
body = {}
|
||||
|
||||
constructor(args?: RequestParameters) {
|
||||
super()
|
||||
this.url = args?.url ?? this.url
|
||||
this.headers = args?.headers ?? this.headers
|
||||
this.body = args?.body ?? this.body
|
||||
this.description = args?.description ?? this.description
|
||||
this.maxOutputLength = args?.maxOutputLength ?? this.maxOutputLength
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
async _call(input: string) {
|
||||
try {
|
||||
let inputUrl = ''
|
||||
let inputBody = {}
|
||||
if (Object.keys(this.body).length || this.url) {
|
||||
if (this.url) inputUrl = this.url
|
||||
if (Object.keys(this.body).length) inputBody = this.body
|
||||
} else {
|
||||
const { url, data } = JSON.parse(input)
|
||||
inputUrl = url
|
||||
inputBody = data
|
||||
}
|
||||
|
||||
if (process.env.DEBUG === 'true') console.info(`Making POST API call to ${inputUrl} with body ${JSON.stringify(inputBody)}`)
|
||||
|
||||
const res = await fetch(inputUrl, {
|
||||
method: 'POST',
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(inputBody)
|
||||
})
|
||||
|
||||
const text = await res.text()
|
||||
return text.slice(0, this.maxOutputLength)
|
||||
} catch (error) {
|
||||
return `${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 27V20H6.5C7.60457 20 8.5 20.8954 8.5 22C8.5 23.1046 7.60457 24 6.5 24H4" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 27V20M25 20H29" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.5644 20.4399C21.6769 19.7608 19 19.6332 19 21.7961C19 24.1915 23 22.5657 23 25.0902C23 26.9875 20.33 27.5912 19 26.3537" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 23.5C11 20.7 12.6667 20 13.5 20C14.3333 20 16 20.7 16 23.5C16 26.3 14.3333 27 13.5 27C12.6667 27 11 26.3 11 23.5Z" 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.3 KiB |