diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 26190de0..3641f0bd 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutor, AgentExecutor } from 'langchain/agents' +import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' import { Tool } from 'langchain/tools' import { BaseChatModel } from 'langchain/chat_models/base' import { BaseChatMemory } from 'langchain/memory' @@ -48,7 +48,10 @@ class ConversationalAgent_Agents implements INode { const tools = nodeData.inputs?.tools as Tool[] const memory = nodeData.inputs?.memory as BaseChatMemory - const executor = await initializeAgentExecutor(tools, model, 'chat-conversational-react-description', true) + const executor = await initializeAgentExecutorWithOptions(tools, model, { + agentType: 'chat-conversational-react-description', + verbose: true + }) executor.memory = memory return executor } diff --git a/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts b/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts index a60ccf7b..1a619e6a 100644 --- a/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts +++ b/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts @@ -1,8 +1,8 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutor, AgentExecutor } from 'langchain/agents' -import { Tool } from 'langchain/tools' +import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' import { BaseChatModel } from 'langchain/chat_models/base' import { getBaseClasses } from '../../../src/utils' +import { Tool } from 'langchain/tools' class MRKLAgentChat_Agents implements INode { label: string @@ -40,8 +40,10 @@ class MRKLAgentChat_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseChatModel const tools = nodeData.inputs?.tools as Tool[] - - const executor = await initializeAgentExecutor(tools, model, 'chat-zero-shot-react-description', true) + const executor = await initializeAgentExecutorWithOptions(tools, model, { + agentType: 'chat-zero-shot-react-description', + verbose: true + }) return executor } diff --git a/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts b/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts index 4df6ea41..499b9383 100644 --- a/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts +++ b/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutor, AgentExecutor } from 'langchain/agents' +import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' import { Tool } from 'langchain/tools' import { BaseLLM } from 'langchain/llms/base' import { getBaseClasses } from '../../../src/utils' @@ -41,7 +41,10 @@ class MRKLAgentLLM_Agents implements INode { const model = nodeData.inputs?.model as BaseLLM const tools = nodeData.inputs?.tools as Tool[] - const executor = await initializeAgentExecutor(tools, model, 'zero-shot-react-description', true) + const executor = await initializeAgentExecutorWithOptions(tools, model, { + agentType: 'zero-shot-react-description', + verbose: true + }) return executor } diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 0200b316..1e45cc44 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -1,8 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' -import { BasePromptTemplate } from 'langchain/prompts' class LLMChain_Chains implements INode { label: string @@ -13,6 +12,7 @@ class LLMChain_Chains implements INode { baseClasses: string[] description: string inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'LLM Chain' @@ -34,65 +34,99 @@ class LLMChain_Chains implements INode { type: 'BasePromptTemplate' }, { - label: 'Format Prompt Values', - name: 'promptValues', + label: 'Chain Name', + name: 'chainName', type: 'string', - rows: 5, - placeholder: `{ - "input_language": "English", - "output_language": "French" -}`, + placeholder: 'Name Your Chain', optional: true } ] + this.outputs = [ + { + label: 'LLM Chain', + name: 'llmChain', + baseClasses: [this.type, ...getBaseClasses(LLMChain)] + }, + { + label: 'Output Prediction', + name: 'outputPrediction', + baseClasses: ['string'] + } + ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, input: string): Promise { const model = nodeData.inputs?.model as BaseLanguageModel - const prompt = nodeData.inputs?.prompt as BasePromptTemplate + const prompt = nodeData.inputs?.prompt + const output = nodeData.outputs?.output as string + const promptValues = prompt.promptValues as ICommonObject - const chain = new LLMChain({ llm: model, prompt }) - return chain + if (output === this.name) { + const chain = new LLMChain({ llm: model, prompt }) + return chain + } else if (output === 'outputPrediction') { + const chain = new LLMChain({ llm: model, prompt }) + const inputVariables = chain.prompt.inputVariables as string[] // ["product"] + const res = await runPrediction(inputVariables, chain, input, promptValues) + // eslint-disable-next-line no-console + console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m') + // eslint-disable-next-line no-console + console.log(res) + return res + } } async run(nodeData: INodeData, input: string): Promise { const inputVariables = nodeData.instance.prompt.inputVariables as string[] // ["product"] const chain = nodeData.instance as LLMChain + const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject - if (inputVariables.length === 1) { - const res = await chain.run(input) - return res - } else if (inputVariables.length > 1) { - const promptValuesStr = nodeData.inputs?.promptValues as string - if (!promptValuesStr) throw new Error('Please provide Prompt Values') + const res = await runPrediction(inputVariables, chain, input, promptValues) + // eslint-disable-next-line no-console + console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') + // eslint-disable-next-line no-console + console.log(res) + return res + } +} - const promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) +const runPrediction = async (inputVariables: string[], chain: LLMChain, input: string, promptValues: ICommonObject) => { + if (inputVariables.length === 1) { + const res = await chain.run(input) + return res + } else if (inputVariables.length > 1) { + let seen: string[] = [] - let seen: string[] = [] - - for (const variable of inputVariables) { - seen.push(variable) - if (promptValues[variable]) { - seen.pop() - } + for (const variable of inputVariables) { + seen.push(variable) + if (promptValues[variable]) { + seen.pop() } - - if (seen.length === 1) { - const lastValue = seen.pop() - if (!lastValue) throw new Error('Please provide Prompt Values') - const options = { - ...promptValues, - [lastValue]: input - } - const res = await chain.call(options) - return res?.text - } else { - throw new Error('Please provide Prompt Values') - } - } else { - const res = await chain.run(input) - return res } + + if (seen.length === 0) { + // All inputVariables have fixed values specified + const options = { + ...promptValues + } + const res = await chain.call(options) + return res?.text + } else if (seen.length === 1) { + // If one inputVariable is not specify, use input (user's question) as value + const lastValue = seen.pop() + if (!lastValue) throw new Error('Please provide Prompt Values') + const options = { + ...promptValues, + [lastValue]: input + } + const res = await chain.call(options) + return res?.text + } else { + throw new Error(`Please provide Prompt Values for: ${seen.join(', ')}`) + } + } else { + const res = await chain.run(input) + return res } } diff --git a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts new file mode 100644 index 00000000..91b3fed1 --- /dev/null +++ b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts @@ -0,0 +1,57 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { VectorDBQAChain } from 'langchain/chains' +import { BaseLanguageModel } from 'langchain/base_language' +import { VectorStore } from 'langchain/vectorstores' + +class VectorDBQAChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'VectorDB QA Chain' + this.name = 'vectorDBQAChain' + this.type = 'VectorDBQAChain' + this.icon = 'chain.svg' + this.category = 'Chains' + this.description = 'QA chain for vector databases' + this.baseClasses = [this.type, ...getBaseClasses(VectorDBQAChain)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'Vector Store', + name: 'vectorStore', + type: 'VectorStore' + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const vectorStore = nodeData.inputs?.vectorStore as VectorStore + + const chain = VectorDBQAChain.fromLLM(model, vectorStore) + return chain + } + + async run(nodeData: INodeData, input: string): Promise { + const chain = nodeData.instance as VectorDBQAChain + const obj = { + query: input + } + const res = await chain.call(obj) + return res?.text + } +} + +module.exports = { nodeClass: VectorDBQAChain_Chains } diff --git a/packages/components/nodes/chains/VectorDBQAChain/chain.svg b/packages/components/nodes/chains/VectorDBQAChain/chain.svg new file mode 100644 index 00000000..a5b32f90 --- /dev/null +++ b/packages/components/nodes/chains/VectorDBQAChain/chain.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts index 5bec3ac0..c3c4d77f 100644 --- a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts +++ b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts @@ -1,4 +1,4 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate } from 'langchain/prompts' @@ -25,15 +25,28 @@ class ChatPromptTemplate_Prompts implements INode { label: 'System Message', name: 'systemMessagePrompt', type: 'string', - rows: 3, + rows: 4, placeholder: `You are a helpful assistant that translates {input_language} to {output_language}.` }, { label: 'Human Message', name: 'humanMessagePrompt', type: 'string', - rows: 3, + rows: 4, placeholder: `{text}` + }, + { + label: 'Format Prompt Values', + name: 'promptValues', + type: 'string', + rows: 4, + placeholder: `{ + "input_language": "English", + "output_language": "French" +}`, + optional: true, + acceptVariable: true, + list: true } ] } @@ -41,11 +54,20 @@ class ChatPromptTemplate_Prompts implements INode { async init(nodeData: INodeData): Promise { const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const humanMessagePrompt = nodeData.inputs?.humanMessagePrompt as string + const promptValuesStr = nodeData.inputs?.promptValues as string const prompt = ChatPromptTemplate.fromPromptMessages([ SystemMessagePromptTemplate.fromTemplate(systemMessagePrompt), HumanMessagePromptTemplate.fromTemplate(humanMessagePrompt) ]) + + let promptValues: ICommonObject = {} + if (promptValuesStr) { + promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) + } + // @ts-ignore + prompt.promptValues = promptValues + return prompt } } diff --git a/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts b/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts index f137eedf..a42a1d08 100644 --- a/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts +++ b/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts @@ -27,7 +27,7 @@ class FewShotPromptTemplate_Prompts implements INode { label: 'Examples', name: 'examples', type: 'string', - rows: 5, + rows: 4, placeholder: `[ { "word": "happy", "antonym": "sad" }, { "word": "tall", "antonym": "short" }, @@ -42,14 +42,14 @@ class FewShotPromptTemplate_Prompts implements INode { label: 'Prefix', name: 'prefix', type: 'string', - rows: 3, + rows: 4, placeholder: `Give the antonym of every input` }, { label: 'Suffix', name: 'suffix', type: 'string', - rows: 3, + rows: 4, placeholder: `Word: {input}\nAntonym:` }, { diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index 50e79255..f976d64c 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -1,6 +1,6 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams, PromptTemplate } from '../../../src/Interface' import { getBaseClasses, getInputVariables } from '../../../src/utils' -import { PromptTemplate, PromptTemplateInput } from 'langchain/prompts' +import { PromptTemplateInput } from 'langchain/prompts' class PromptTemplate_Prompts implements INode { label: string @@ -19,20 +19,40 @@ class PromptTemplate_Prompts implements INode { this.icon = 'prompt.svg' this.category = 'Prompts' this.description = 'Schema to represent a basic prompt for an LLM' - this.baseClasses = [this.type, ...getBaseClasses(PromptTemplate)] + this.baseClasses = [...getBaseClasses(PromptTemplate)] this.inputs = [ { label: 'Template', name: 'template', type: 'string', - rows: 5, + rows: 4, placeholder: `What is a good name for a company that makes {product}?` + }, + { + label: 'Format Prompt Values', + name: 'promptValues', + type: 'string', + rows: 4, + placeholder: `{ + "input_language": "English", + "output_language": "French" +}`, + optional: true, + acceptVariable: true, + list: true } ] } async init(nodeData: INodeData): Promise { const template = nodeData.inputs?.template as string + const promptValuesStr = nodeData.inputs?.promptValues as string + + let promptValues: ICommonObject = {} + if (promptValuesStr) { + promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) + } + const inputVariables = getInputVariables(template) try { @@ -41,6 +61,7 @@ class PromptTemplate_Prompts implements INode { inputVariables } const prompt = new PromptTemplate(options) + prompt.promptValues = promptValues return prompt } catch (e) { throw new Error(e) diff --git a/packages/components/nodes/tools/AIPlugin/AIPlugin.ts b/packages/components/nodes/tools/AIPlugin/AIPlugin.ts new file mode 100644 index 00000000..ad21f8db --- /dev/null +++ b/packages/components/nodes/tools/AIPlugin/AIPlugin.ts @@ -0,0 +1,41 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { AIPluginTool } from 'langchain/tools' +import { getBaseClasses } from '../../../src/utils' + +class AIPlugin implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs?: INodeParams[] + + constructor() { + this.label = 'AI Plugin' + this.name = 'aiPlugin' + this.type = 'AIPlugin' + this.icon = 'aiplugin.svg' + this.category = 'Tools' + this.description = 'Execute actions using ChatGPT Plugin Url' + this.baseClasses = [this.type, ...getBaseClasses(AIPluginTool)] + this.inputs = [ + { + label: 'Plugin Url', + name: 'pluginUrl', + type: 'string', + placeholder: 'https://www.klarna.com/.well-known/ai-plugin.json' + } + ] + } + + async init(nodeData: INodeData): Promise { + const pluginUrl = nodeData.inputs?.pluginUrl as string + const aiplugin = await AIPluginTool.fromPluginUrl(pluginUrl) + + return aiplugin + } +} + +module.exports = { nodeClass: AIPlugin } diff --git a/packages/components/nodes/tools/AIPlugin/aiplugin.svg b/packages/components/nodes/tools/AIPlugin/aiplugin.svg new file mode 100644 index 00000000..e617e45c --- /dev/null +++ b/packages/components/nodes/tools/AIPlugin/aiplugin.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/tools/ChainTool/ChainTool.ts b/packages/components/nodes/tools/ChainTool/ChainTool.ts new file mode 100644 index 00000000..32e414af --- /dev/null +++ b/packages/components/nodes/tools/ChainTool/ChainTool.ts @@ -0,0 +1,73 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ChainTool } from 'langchain/tools' +import { BaseChain } from 'langchain/chains' + +class ChainTool_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Chain Tool' + this.name = 'chainTool' + this.type = 'ChainTool' + this.icon = 'chaintool.svg' + this.category = 'Tools' + this.description = 'Use a chain as allowed tool for agent' + this.baseClasses = [this.type, ...getBaseClasses(ChainTool)] + this.inputs = [ + { + label: 'Chain Name', + name: 'name', + type: 'string', + placeholder: 'state-of-union-qa' + }, + { + label: 'Chain Description', + name: 'description', + type: 'string', + 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: 'Return Direct', + name: 'returnDirect', + type: 'boolean', + optional: true + }, + { + label: 'Base Chain', + name: 'baseChain', + type: 'BaseChain' + } + ] + } + + async init(nodeData: INodeData): Promise { + const name = nodeData.inputs?.name as string + const description = nodeData.inputs?.description as string + const baseChain = nodeData.inputs?.baseChain as BaseChain + const returnDirect = nodeData.inputs?.returnDirect as boolean + + const obj = { + name, + description, + chain: baseChain + } as any + + if (returnDirect) obj.returnDirect = returnDirect + + const tool = new ChainTool(obj) + + return tool + } +} + +module.exports = { nodeClass: ChainTool_Tools } diff --git a/packages/components/nodes/tools/ChainTool/chaintool.svg b/packages/components/nodes/tools/ChainTool/chaintool.svg new file mode 100644 index 00000000..c5bd0fbc --- /dev/null +++ b/packages/components/nodes/tools/ChainTool/chaintool.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts b/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts index 1e16140f..904c44ed 100644 --- a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts +++ b/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Chroma } from 'langchain/vectorstores/chroma' import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' class Chroma_Existing_VectorStores implements INode { label: string @@ -11,6 +12,7 @@ class Chroma_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'Chroma Load Existing Index' @@ -32,17 +34,36 @@ class Chroma_Existing_VectorStores implements INode { type: 'string' } ] + this.outputs = [ + { + label: 'Chroma Retriever', + name: 'retriever', + baseClasses: [this.type, 'BaseRetriever'] + }, + { + label: 'Chroma Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(Chroma)] + } + ] } async init(nodeData: INodeData): Promise { const collectionName = nodeData.inputs?.collectionName as string const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string const vectorStore = await Chroma.fromExistingCollection(embeddings, { collectionName }) - const retriever = vectorStore.asRetriever() - return retriever + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore } } diff --git a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts b/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts index 71d3edf5..d71f96ec 100644 --- a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts +++ b/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts @@ -1,7 +1,8 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Chroma } from 'langchain/vectorstores/chroma' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' class ChromaUpsert_VectorStores implements INode { label: string @@ -12,6 +13,7 @@ class ChromaUpsert_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'Chroma Upsert Document' @@ -38,12 +40,25 @@ class ChromaUpsert_VectorStores implements INode { type: 'string' } ] + this.outputs = [ + { + label: 'Chroma Retriever', + name: 'retriever', + baseClasses: [this.type, 'BaseRetriever'] + }, + { + label: 'Chroma Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(Chroma)] + } + ] } async init(nodeData: INodeData): Promise { const collectionName = nodeData.inputs?.collectionName as string const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string const finalDocs = [] for (let i = 0; i < docs.length; i += 1) { @@ -53,8 +68,14 @@ class ChromaUpsert_VectorStores implements INode { const vectorStore = await Chroma.fromDocuments(finalDocs, embeddings, { collectionName }) - const retriever = vectorStore.asRetriever() - return retriever + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore } } diff --git a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts index bdc5ba87..fd55de48 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts @@ -1,7 +1,8 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { PineconeClient } from '@pinecone-database/pinecone' import { PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' class Pinecone_Existing_VectorStores implements INode { label: string @@ -12,6 +13,7 @@ class Pinecone_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'Pinecone Load Existing Index' @@ -43,6 +45,18 @@ class Pinecone_Existing_VectorStores implements INode { type: 'string' } ] + this.outputs = [ + { + label: 'Pinecone Retriever', + name: 'retriever', + baseClasses: [this.type, 'BaseRetriever'] + }, + { + label: 'Pinecone Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(PineconeStore)] + } + ] } async init(nodeData: INodeData): Promise { @@ -50,6 +64,7 @@ class Pinecone_Existing_VectorStores implements INode { const pineconeEnv = nodeData.inputs?.pineconeEnv as string const index = nodeData.inputs?.pineconeIndex as string const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string const client = new PineconeClient() await client.init({ @@ -62,8 +77,14 @@ class Pinecone_Existing_VectorStores implements INode { const vectorStore = await PineconeStore.fromExistingIndex(embeddings, { pineconeIndex }) - const retriever = vectorStore.asRetriever() - return retriever + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore } } diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts index d498e9ca..cbce7b4b 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts @@ -1,8 +1,9 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { PineconeClient } from '@pinecone-database/pinecone' import { PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' class PineconeUpsert_VectorStores implements INode { label: string @@ -13,6 +14,7 @@ class PineconeUpsert_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'Pinecone Upsert Document' @@ -49,6 +51,18 @@ class PineconeUpsert_VectorStores implements INode { type: 'string' } ] + this.outputs = [ + { + label: 'Pinecone Retriever', + name: 'retriever', + baseClasses: [this.type, 'BaseRetriever'] + }, + { + label: 'Pinecone Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(PineconeStore)] + } + ] } async init(nodeData: INodeData): Promise { @@ -57,6 +71,7 @@ class PineconeUpsert_VectorStores implements INode { const index = nodeData.inputs?.pineconeIndex as string const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string const client = new PineconeClient() await client.init({ @@ -74,8 +89,14 @@ class PineconeUpsert_VectorStores implements INode { const vectorStore = await PineconeStore.fromDocuments(finalDocs, embeddings, { pineconeIndex }) - const retriever = vectorStore.asRetriever() - return retriever + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore } } diff --git a/packages/components/package.json b/packages/components/package.json index b18040e2..214ec332 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -27,7 +27,7 @@ "dotenv": "^16.0.0", "express": "^4.17.3", "form-data": "^4.0.0", - "langchain": "^0.0.53", + "langchain": "^0.0.59", "moment": "^2.29.3", "node-fetch": "2", "pdf-parse": "^1.1.1", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 2de18d5c..44c87d74 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -2,18 +2,7 @@ * Types */ -export type NodeParamsType = - | 'asyncOptions' - | 'options' - | 'string' - | 'number' - | 'boolean' - | 'password' - | 'json' - | 'code' - | 'date' - | 'file' - | 'folder' +export type NodeParamsType = 'options' | 'string' | 'number' | 'boolean' | 'password' | 'json' | 'code' | 'date' | 'file' | 'folder' export type CommonType = string | number | boolean | undefined | null @@ -40,6 +29,13 @@ export interface INodeOptionsValue { description?: string } +export interface INodeOutputsValue { + label: string + name: string + baseClasses: string[] + description?: string +} + export interface INodeParams { label: string name: string @@ -50,6 +46,7 @@ export interface INodeParams { optional?: boolean | INodeDisplay rows?: number list?: boolean + acceptVariable?: boolean placeholder?: string fileType?: string } @@ -75,12 +72,15 @@ export interface INodeProperties { export interface INode extends INodeProperties { inputs?: INodeParams[] - getInstance?(nodeData: INodeData): Promise + output?: INodeOutputsValue[] + init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { + id: string inputs?: ICommonObject + outputs?: ICommonObject instance?: any } @@ -88,3 +88,17 @@ export interface IMessage { message: string type: MessageType } + +/** + * Classes + */ + +import { PromptTemplate as LangchainPromptTemplate, PromptTemplateInput } from 'langchain/prompts' + +export class PromptTemplate extends LangchainPromptTemplate { + promptValues: ICommonObject + + constructor(input: PromptTemplateInput) { + super(input) + } +} diff --git a/packages/server/.env.example b/packages/server/.env.example new file mode 100644 index 00000000..2fc80e3a --- /dev/null +++ b/packages/server/.env.example @@ -0,0 +1 @@ +PORT=3000 diff --git a/packages/server/bin/dev b/packages/server/bin/dev old mode 100644 new mode 100755 diff --git a/packages/server/bin/dev.cmd b/packages/server/bin/dev.cmd old mode 100644 new mode 100755 diff --git a/packages/server/bin/run b/packages/server/bin/run old mode 100644 new mode 100755 diff --git a/packages/server/bin/run.cmd b/packages/server/bin/run.cmd old mode 100644 new mode 100755 diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/Antonym.json index c91161fc..a56f5b58 100644 --- a/packages/server/marketplaces/Antonym.json +++ b/packages/server/marketplaces/Antonym.json @@ -3,11 +3,11 @@ "nodes": [ { "width": 300, - "height": 360, + "height": 533, "id": "promptTemplate_0", "position": { - "x": 294.38456937448433, - "y": 66.5400435451831 + "x": 567, + "y": 85 }, "type": "customNode", "data": { @@ -23,13 +23,26 @@ "label": "Template", "name": "template", "type": "string", - "rows": 5, - "placeholder": "What is a good name for a company that makes {product}?" + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "string", + "rows": 4, + "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-string" } ], "inputAnchors": [], "inputs": { - "template": "Word: {word}\\nAntonym: {antonym}\\n" + "template": "Word: {word}\\nAntonym: {antonym}\\n", + "promptValues": "" }, "outputAnchors": [ { @@ -39,22 +52,23 @@ "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" } ], + "outputs": {}, "selected": false }, "selected": false, + "dragging": false, "positionAbsolute": { - "x": 294.38456937448433, - "y": 66.5400435451831 - }, - "dragging": false + "x": 567, + "y": 85 + } }, { "width": 300, - "height": 886, + "height": 955, "id": "fewShotPromptTemplate_0", "position": { - "x": 719.2200337843097, - "y": 67.20405755860693 + "x": 942.9569947740308, + "y": 82.93222833361332 }, "type": "customNode", "data": { @@ -70,28 +84,32 @@ "label": "Examples", "name": "examples", "type": "string", - "rows": 5, - "placeholder": "[\n { \"word\": \"happy\", \"antonym\": \"sad\" },\n { \"word\": \"tall\", \"antonym\": \"short\" },\n]" + "rows": 4, + "placeholder": "[\n { \"word\": \"happy\", \"antonym\": \"sad\" },\n { \"word\": \"tall\", \"antonym\": \"short\" },\n]", + "id": "fewShotPromptTemplate_0-input-examples-string" }, { "label": "Prefix", "name": "prefix", "type": "string", - "rows": 3, - "placeholder": "Give the antonym of every input" + "rows": 4, + "placeholder": "Give the antonym of every input", + "id": "fewShotPromptTemplate_0-input-prefix-string" }, { "label": "Suffix", "name": "suffix", "type": "string", - "rows": 3, - "placeholder": "Word: {input}\nAntonym:" + "rows": 4, + "placeholder": "Word: {input}\nAntonym:", + "id": "fewShotPromptTemplate_0-input-suffix-string" }, { "label": "Example Seperator", "name": "exampleSeparator", "type": "string", - "placeholder": "\n\n" + "placeholder": "\n\n", + "id": "fewShotPromptTemplate_0-input-exampleSeparator-string" }, { "label": "Template Format", @@ -107,7 +125,8 @@ "name": "jinja-2" } ], - "default": "f-string" + "default": "f-string", + "id": "fewShotPromptTemplate_0-input-templateFormat-options" } ], "inputAnchors": [ @@ -134,12 +153,13 @@ "type": "FewShotPromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" } ], + "outputs": {}, "selected": false }, "selected": false, "positionAbsolute": { - "x": 719.2200337843097, - "y": 67.20405755860693 + "x": 942.9569947740308, + "y": 82.93222833361332 }, "dragging": false }, @@ -148,8 +168,8 @@ "height": 472, "id": "openAI_0", "position": { - "x": 1089.6434062122398, - "y": 27.515288538129425 + "x": 1304.9299247555505, + "y": 8.707397857674266 }, "type": "customNode", "data": { @@ -164,7 +184,8 @@ { "label": "OpenAI Api Key", "name": "openAIApiKey", - "type": "password" + "type": "password", + "id": "openAI_0-input-openAIApiKey-password" }, { "label": "Model Name", @@ -189,20 +210,22 @@ } ], "default": "text-davinci-003", - "optional": true + "optional": true, + "id": "openAI_0-input-modelName-options" }, { "label": "Temperature", "name": "temperature", "type": "number", "default": 0.7, - "optional": true + "optional": true, + "id": "openAI_0-input-temperature-number" } ], "inputAnchors": [], "inputs": { "modelName": "text-davinci-003", - "temperature": 0.7 + "temperature": "0" }, "outputAnchors": [ { @@ -212,22 +235,23 @@ "type": "OpenAI | BaseLLM | BaseLanguageModel" } ], + "outputs": {}, "selected": false }, "selected": false, "positionAbsolute": { - "x": 1089.6434062122398, - "y": 27.515288538129425 + "x": 1304.9299247555505, + "y": 8.707397857674266 }, "dragging": false }, { "width": 300, - "height": 461, + "height": 405, "id": "llmChain_0", "position": { - "x": 1499.2654451385026, - "y": 356.3275374721362 + "x": 1669.2177402155296, + "y": 338.65158088371567 }, "type": "customNode", "data": { @@ -240,12 +264,12 @@ "description": "Chain to run queries against LLMs", "inputParams": [ { - "label": "Format Prompt Values", - "name": "promptValues", + "label": "Chain Name", + "name": "chainName", "type": "string", - "rows": 5, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" } ], "inputAnchors": [ @@ -265,38 +289,44 @@ "inputs": { "model": "{{openAI_0.data.instance}}", "prompt": "{{fewShotPromptTemplate_0.data.instance}}", - "promptValues": "" + "chainName": "" }, "outputAnchors": [ { - "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", - "name": "llmChain", - "label": "LLMChain", - "type": "LLMChain | BaseChain" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string" + } + ], + "default": "llmChain" } ], + "outputs": { + "output": "llmChain" + }, "selected": false }, "selected": false, "positionAbsolute": { - "x": 1499.2654451385026, - "y": 356.3275374721362 + "x": 1669.2177402155296, + "y": 338.65158088371567 }, "dragging": false } ], "edges": [ - { - "source": "promptTemplate_0", - "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "fewShotPromptTemplate_0", - "targetHandle": "fewShotPromptTemplate_0-input-examplePrompt-PromptTemplate", - "type": "buttonedge", - "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-fewShotPromptTemplate_0-fewShotPromptTemplate_0-input-examplePrompt-PromptTemplate", - "data": { - "label": "" - } - }, { "source": "openAI_0", "sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", @@ -318,6 +348,17 @@ "data": { "label": "" } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "fewShotPromptTemplate_0", + "targetHandle": "fewShotPromptTemplate_0-input-examplePrompt-PromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-fewShotPromptTemplate_0-fewShotPromptTemplate_0-input-examplePrompt-PromptTemplate", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/ChatGPTPlugin.json new file mode 100644 index 00000000..7a65a665 --- /dev/null +++ b/packages/server/marketplaces/ChatGPTPlugin.json @@ -0,0 +1,307 @@ +{ + "description": "Use ChatGPT Plugins within LangChain abstractions with GET and POST Tools", + "nodes": [ + { + "width": 300, + "height": 278, + "id": "aiPlugin_0", + "position": { + "x": 992.9213747553727, + "y": 115.80946637479596 + }, + "type": "customNode", + "data": { + "id": "aiPlugin_0", + "label": "AI Plugin", + "name": "aiPlugin", + "type": "AIPlugin", + "baseClasses": ["AIPlugin", "Tool"], + "category": "Tools", + "description": "Execute actions using ChatGPT Plugin Url", + "inputParams": [ + { + "label": "Plugin Url", + "name": "pluginUrl", + "type": "string", + "placeholder": "https://www.klarna.com/.well-known/ai-plugin.json" + } + ], + "inputAnchors": [], + "inputs": { + "pluginUrl": "https://www.klarna.com/.well-known/ai-plugin.json" + }, + "outputAnchors": [ + { + "id": "aiPlugin_0-output-aiPlugin-AIPlugin|Tool", + "name": "aiPlugin", + "label": "AIPlugin", + "type": "AIPlugin | Tool" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 992.9213747553727, + "y": 115.80946637479596 + }, + "dragging": false + }, + { + "width": 300, + "height": 143, + "id": "requestsPost_0", + "position": { + "x": 638.2831241951309, + "y": 294.0784991300699 + }, + "type": "customNode", + "data": { + "id": "requestsPost_0", + "label": "Requests Post", + "name": "requestsPost", + "type": "RequestsPost", + "baseClasses": ["RequestsPost", "Tool"], + "category": "Tools", + "description": "Execute HTTP POST requests", + "inputParams": [], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "requestsPost_0-output-requestsPost-RequestsPost|Tool", + "name": "requestsPost", + "label": "RequestsPost", + "type": "RequestsPost | Tool" + } + ], + "selected": false + }, + "positionAbsolute": { + "x": 638.2831241951309, + "y": 294.0784991300699 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 143, + "id": "requestsGet_0", + "position": { + "x": 703.0477667387721, + "y": 476.8955204497346 + }, + "type": "customNode", + "data": { + "id": "requestsGet_0", + "label": "Requests Get", + "name": "requestsGet", + "type": "RequestsGet", + "baseClasses": ["RequestsGet", "Tool"], + "category": "Tools", + "description": "Execute HTTP GET requests", + "inputParams": [], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "requestsGet_0-output-requestsGet-RequestsGet|Tool", + "name": "requestsGet", + "label": "RequestsGet", + "type": "RequestsGet | Tool" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 703.0477667387721, + "y": 476.8955204497346 + }, + "dragging": false + }, + { + "width": 300, + "height": 280, + "id": "mrklAgentChat_0", + "position": { + "x": 1363.057715565282, + "y": 479.27393467974 + }, + "type": "customNode", + "data": { + "id": "mrklAgentChat_0", + "label": "MRKL Agent for Chat Models", + "name": "mrklAgentChat", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain"], + "category": "Agents", + "description": "Agent that uses the ReAct Framework to decide what action to take, optimized to be used with Chat Models", + "inputParams": [], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "mrklAgentChat_0-input-tools-Tool" + }, + { + "label": "Chat Model", + "name": "model", + "type": "BaseChatModel", + "id": "mrklAgentChat_0-input-model-BaseChatModel" + } + ], + "inputs": { + "tools": ["{{requestsGet_0.data.instance}}", "{{requestsPost_0.data.instance}}", "{{aiPlugin_0.data.instance}}"], + "model": "{{chatOpenAI_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "mrklAgentChat_0-output-mrklAgentChat-AgentExecutor|BaseChain", + "name": "mrklAgentChat", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1363.057715565282, + "y": 479.27393467974 + }, + "dragging": false + }, + { + "width": 300, + "height": 472, + "id": "chatOpenAI_0", + "position": { + "x": 724.4534948088211, + "y": 668.3578659651726 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + } + ], + "default": "gpt-3.5-turbo", + "optional": true + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": "0" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 724.4534948088211, + "y": 668.3578659651726 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "aiPlugin_0", + "sourceHandle": "aiPlugin_0-output-aiPlugin-AIPlugin|Tool", + "target": "mrklAgentChat_0", + "targetHandle": "mrklAgentChat_0-input-tools-Tool", + "type": "buttonedge", + "id": "aiPlugin_0-aiPlugin_0-output-aiPlugin-AIPlugin|Tool-mrklAgentChat_0-mrklAgentChat_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "requestsGet_0", + "sourceHandle": "requestsGet_0-output-requestsGet-RequestsGet|Tool", + "target": "mrklAgentChat_0", + "targetHandle": "mrklAgentChat_0-input-tools-Tool", + "type": "buttonedge", + "id": "requestsGet_0-requestsGet_0-output-requestsGet-RequestsGet|Tool-mrklAgentChat_0-mrklAgentChat_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "requestsPost_0", + "sourceHandle": "requestsPost_0-output-requestsPost-RequestsPost|Tool", + "target": "mrklAgentChat_0", + "targetHandle": "mrklAgentChat_0-input-tools-Tool", + "type": "buttonedge", + "id": "requestsPost_0-requestsPost_0-output-requestsPost-RequestsPost|Tool-mrklAgentChat_0-mrklAgentChat_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "mrklAgentChat_0", + "targetHandle": "mrklAgentChat_0-input-model-BaseChatModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-mrklAgentChat_0-mrklAgentChat_0-input-model-BaseChatModel", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/Multiple VectorDB.json b/packages/server/marketplaces/Multiple VectorDB.json new file mode 100644 index 00000000..65ead35c --- /dev/null +++ b/packages/server/marketplaces/Multiple VectorDB.json @@ -0,0 +1,1020 @@ +{ + "description": "Use the agent to choose between multiple different vector databases", + "nodes": [ + { + "width": 300, + "height": 280, + "id": "mrklAgentLLM_0", + "position": { + "x": 2364.5095620121215, + "y": -46.710940607495274 + }, + "type": "customNode", + "data": { + "id": "mrklAgentLLM_0", + "label": "MRKL Agent for LLMs", + "name": "mrklAgentLLM", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain"], + "category": "Agents", + "description": "Agent that uses the ReAct Framework to decide what action to take, optimized to be used with LLMs", + "inputParams": [], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "mrklAgentLLM_0-input-tools-Tool" + }, + { + "label": "LLM Model", + "name": "model", + "type": "BaseLLM", + "id": "mrklAgentLLM_0-input-model-BaseLLM" + } + ], + "inputs": { + "tools": [ + "{{serpAPI_0.data.instance}}", + "{{calculator_0.data.instance}}", + "{{chainTool_0.data.instance}}", + "{{chainTool_1.data.instance}}" + ], + "model": "{{openAI_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "mrklAgentLLM_0-output-mrklAgentLLM-AgentExecutor|BaseChain", + "name": "mrklAgentLLM", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2364.5095620121215, + "y": -46.710940607495274 + }, + "dragging": false + }, + { + "width": 300, + "height": 278, + "id": "serpAPI_0", + "position": { + "x": 1895.6273348203852, + "y": -424.5095222426859 + }, + "type": "customNode", + "data": { + "id": "serpAPI_0", + "label": "Serp API", + "name": "serpAPI", + "type": "SerpAPI", + "baseClasses": ["SerpAPI", "Tool"], + "category": "Tools", + "description": "Wrapper around SerpAPI - a real-time API to access Google search results", + "inputParams": [ + { + "label": "Serp Api Key", + "name": "apiKey", + "type": "password" + } + ], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "serpAPI_0-output-serpAPI-SerpAPI|Tool", + "name": "serpAPI", + "label": "SerpAPI", + "type": "SerpAPI | Tool" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1895.6273348203852, + "y": -424.5095222426859 + }, + "dragging": false + }, + { + "width": 300, + "height": 143, + "id": "calculator_0", + "position": { + "x": 1895.7396501948126, + "y": -601.6479551033939 + }, + "type": "customNode", + "data": { + "id": "calculator_0", + "label": "Calculator", + "name": "calculator", + "type": "Calculator", + "baseClasses": ["Calculator", "Tool"], + "category": "Tools", + "description": "Perform calculations on response", + "inputParams": [], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "calculator_0-output-calculator-Calculator|Tool", + "name": "calculator", + "label": "Calculator", + "type": "Calculator | Tool" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1895.7396501948126, + "y": -601.6479551033939 + }, + "dragging": false + }, + { + "width": 300, + "height": 280, + "id": "vectorDBQAChain_0", + "position": { + "x": 1034.1209991774203, + "y": 312.59111975871537 + }, + "type": "customNode", + "data": { + "id": "vectorDBQAChain_0", + "label": "VectorDB QA Chain", + "name": "vectorDBQAChain", + "type": "VectorDBQAChain", + "baseClasses": ["VectorDBQAChain", "BaseChain"], + "category": "Chains", + "description": "QA chain for vector databases", + "inputParams": [], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "vectorDBQAChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store", + "name": "vectorStore", + "type": "VectorStore", + "id": "vectorDBQAChain_0-input-vectorStore-VectorStore" + } + ], + "inputs": { + "model": "{{openAI_1.data.instance}}", + "vectorStore": "{{pineconeExistingIndex_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "vectorDBQAChain_0-output-vectorDBQAChain-VectorDBQAChain|BaseChain", + "name": "vectorDBQAChain", + "label": "VectorDBQAChain", + "type": "VectorDBQAChain | BaseChain" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1034.1209991774203, + "y": 312.59111975871537 + }, + "dragging": false + }, + { + "width": 300, + "height": 472, + "id": "openAI_0", + "position": { + "x": 2024.2530157999474, + "y": 579.0657481214333 + }, + "type": "customNode", + "data": { + "id": "openAI_0", + "label": "OpenAI", + "name": "openAI", + "type": "OpenAI", + "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], + "category": "LLMs", + "description": "Wrapper around OpenAI large language models", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-davinci-003", + "name": "text-davinci-003" + }, + { + "label": "text-davinci-002", + "name": "text-davinci-002" + }, + { + "label": "text-curie-001", + "name": "text-curie-001" + }, + { + "label": "text-babbage-001", + "name": "text-babbage-001" + } + ], + "default": "text-davinci-003", + "optional": true + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.7, + "optional": true + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "text-davinci-003", + "temperature": "0" + }, + "outputAnchors": [ + { + "id": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "name": "openAI", + "label": "OpenAI", + "type": "OpenAI | BaseLLM | BaseLanguageModel" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2024.2530157999474, + "y": 579.0657481214333 + }, + "dragging": false + }, + { + "width": 300, + "height": 472, + "id": "openAI_1", + "position": { + "x": 649.0214134160765, + "y": -74.86211370060312 + }, + "type": "customNode", + "data": { + "id": "openAI_1", + "label": "OpenAI", + "name": "openAI", + "type": "OpenAI", + "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], + "category": "LLMs", + "description": "Wrapper around OpenAI large language models", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-davinci-003", + "name": "text-davinci-003" + }, + { + "label": "text-davinci-002", + "name": "text-davinci-002" + }, + { + "label": "text-curie-001", + "name": "text-curie-001" + }, + { + "label": "text-babbage-001", + "name": "text-babbage-001" + } + ], + "default": "text-davinci-003", + "optional": true + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.7, + "optional": true + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "text-davinci-003", + "temperature": "0" + }, + "outputAnchors": [ + { + "id": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "name": "openAI", + "label": "OpenAI", + "type": "OpenAI | BaseLLM | BaseLanguageModel" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 649.0214134160765, + "y": -74.86211370060312 + }, + "dragging": false + }, + { + "width": 300, + "height": 602, + "id": "chainTool_0", + "position": { + "x": 1385.7876556310375, + "y": 301.1018172104151 + }, + "type": "customNode", + "data": { + "id": "chainTool_0", + "label": "Chain Tool", + "name": "chainTool", + "type": "ChainTool", + "baseClasses": ["ChainTool", "Tool"], + "category": "Tools", + "description": "Use a chain as allowed tool for agent", + "inputParams": [ + { + "label": "Chain Name", + "name": "name", + "type": "string", + "placeholder": "state-of-union-qa", + "id": "chainTool_0-input-name-string" + }, + { + "label": "Chain Description", + "name": "description", + "type": "string", + "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.", + "id": "chainTool_0-input-description-string" + }, + { + "label": "Return Direct", + "name": "returnDirect", + "type": "boolean", + "optional": true, + "id": "chainTool_0-input-returnDirect-boolean" + } + ], + "inputAnchors": [ + { + "label": "Base Chain", + "name": "baseChain", + "type": "BaseChain", + "id": "chainTool_0-input-baseChain-BaseChain" + } + ], + "inputs": { + "name": "state-of-union-qa", + "description": "State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.", + "returnDirect": false, + "baseChain": "{{vectorDBQAChain_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "chainTool_0-output-chainTool-ChainTool|Tool", + "name": "chainTool", + "label": "ChainTool", + "type": "ChainTool | Tool" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1385.7876556310375, + "y": 301.1018172104151 + }, + "dragging": false + }, + { + "width": 300, + "height": 552, + "id": "pineconeExistingIndex_0", + "position": { + "x": 651.5547843299339, + "y": 424.6009806171603 + }, + "type": "customNode", + "data": { + "id": "pineconeExistingIndex_0", + "label": "Pinecone Load Existing Index", + "name": "pineconeExistingIndex", + "type": "Pinecone", + "baseClasses": ["Pinecone", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Pinecone (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Pinecone Api Key", + "name": "pineconeApiKey", + "type": "password", + "id": "pineconeExistingIndex_0-input-pineconeApiKey-password" + }, + { + "label": "Pinecone Environment", + "name": "pineconeEnv", + "type": "string", + "id": "pineconeExistingIndex_0-input-pineconeEnv-string" + }, + { + "label": "Pinecone Index", + "name": "pineconeIndex", + "type": "string", + "id": "pineconeExistingIndex_0-input-pineconeIndex-string" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "pineconeExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "pineconeEnv": "us-west4-gcp", + "pineconeIndex": "test" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeExistingIndex_0-output-retriever-Pinecone|BaseRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | BaseRetriever" + }, + { + "id": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "name": "vectorStore", + "label": "Pinecone Vector Store", + "type": "Pinecone | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "vectorStore" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 651.5547843299339, + "y": 424.6009806171603 + }, + "dragging": false + }, + { + "width": 300, + "height": 278, + "id": "openAIEmbeddings_0", + "position": { + "x": 291.9693257708366, + "y": 298.91918778665286 + }, + "type": "customNode", + "data": { + "id": "openAIEmbeddings_0", + "label": "OpenAI Embeddings", + "name": "openAIEmbeddings", + "type": "OpenAIEmbeddings", + "baseClasses": ["OpenAIEmbeddings", "Embeddings"], + "category": "Embeddings", + "description": "OpenAI API to generate embeddings for a given text", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "openAIEmbeddings_0-input-openAIApiKey-password" + } + ], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "name": "openAIEmbeddings", + "label": "OpenAIEmbeddings", + "type": "OpenAIEmbeddings | Embeddings" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 291.9693257708366, + "y": 298.91918778665286 + }, + "dragging": false + }, + { + "width": 300, + "height": 602, + "id": "chainTool_1", + "position": { + "x": 1389.4337532996956, + "y": -747.2926394544418 + }, + "type": "customNode", + "data": { + "id": "chainTool_1", + "label": "Chain Tool", + "name": "chainTool", + "type": "ChainTool", + "baseClasses": ["ChainTool", "Tool"], + "category": "Tools", + "description": "Use a chain as allowed tool for agent", + "inputParams": [ + { + "label": "Chain Name", + "name": "name", + "type": "string", + "placeholder": "state-of-union-qa", + "id": "chainTool_1-input-name-string" + }, + { + "label": "Chain Description", + "name": "description", + "type": "string", + "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.", + "id": "chainTool_1-input-description-string" + }, + { + "label": "Return Direct", + "name": "returnDirect", + "type": "boolean", + "optional": true, + "id": "chainTool_1-input-returnDirect-boolean" + } + ], + "inputAnchors": [ + { + "label": "Base Chain", + "name": "baseChain", + "type": "BaseChain", + "id": "chainTool_1-input-baseChain-BaseChain" + } + ], + "inputs": { + "name": "ai-paper-qa", + "description": "AI Paper QA - useful for when you need to ask questions about the AI-Generated Content paper.", + "returnDirect": false, + "baseChain": "{{vectorDBQAChain_1.data.instance}}" + }, + "outputAnchors": [ + { + "id": "chainTool_1-output-chainTool-ChainTool|Tool", + "name": "chainTool", + "label": "ChainTool", + "type": "ChainTool | Tool" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1389.4337532996956, + "y": -747.2926394544418 + }, + "dragging": false + }, + { + "width": 300, + "height": 472, + "id": "openAI_2", + "position": { + "x": 643.0214134160765, + "y": -1105.2621137006015 + }, + "type": "customNode", + "data": { + "id": "openAI_2", + "label": "OpenAI", + "name": "openAI", + "type": "OpenAI", + "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], + "category": "LLMs", + "description": "Wrapper around OpenAI large language models", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-davinci-003", + "name": "text-davinci-003" + }, + { + "label": "text-davinci-002", + "name": "text-davinci-002" + }, + { + "label": "text-curie-001", + "name": "text-curie-001" + }, + { + "label": "text-babbage-001", + "name": "text-babbage-001" + } + ], + "default": "text-davinci-003", + "optional": true + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.7, + "optional": true + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "text-davinci-003", + "temperature": "0" + }, + "outputAnchors": [ + { + "id": "openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "name": "openAI", + "label": "OpenAI", + "type": "OpenAI | BaseLLM | BaseLanguageModel" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 643.0214134160765, + "y": -1105.2621137006015 + }, + "dragging": false + }, + { + "width": 300, + "height": 278, + "id": "openAIEmbeddings_1", + "position": { + "x": 271.9693257708366, + "y": -739.0808122133471 + }, + "type": "customNode", + "data": { + "id": "openAIEmbeddings_1", + "label": "OpenAI Embeddings", + "name": "openAIEmbeddings", + "type": "OpenAIEmbeddings", + "baseClasses": ["OpenAIEmbeddings", "Embeddings"], + "category": "Embeddings", + "description": "OpenAI API to generate embeddings for a given text", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "openAIEmbeddings_1-input-openAIApiKey-password" + } + ], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "name": "openAIEmbeddings", + "label": "OpenAIEmbeddings", + "type": "OpenAIEmbeddings | Embeddings" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 271.9693257708366, + "y": -739.0808122133471 + }, + "dragging": false + }, + { + "width": 300, + "height": 280, + "id": "vectorDBQAChain_1", + "position": { + "x": 1034.1209991774203, + "y": -747.4088802412846 + }, + "type": "customNode", + "data": { + "id": "vectorDBQAChain_1", + "label": "VectorDB QA Chain", + "name": "vectorDBQAChain", + "type": "VectorDBQAChain", + "baseClasses": ["VectorDBQAChain", "BaseChain"], + "category": "Chains", + "description": "QA chain for vector databases", + "inputParams": [], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "vectorDBQAChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store", + "name": "vectorStore", + "type": "VectorStore", + "id": "vectorDBQAChain_1-input-vectorStore-VectorStore" + } + ], + "inputs": { + "model": "{{openAI_2.data.instance}}", + "vectorStore": "{{chromaExistingIndex_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "vectorDBQAChain_1-output-vectorDBQAChain-VectorDBQAChain|BaseChain", + "name": "vectorDBQAChain", + "label": "VectorDBQAChain", + "type": "VectorDBQAChain | BaseChain" + } + ], + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1034.1209991774203, + "y": -747.4088802412846 + }, + "dragging": false + }, + { + "width": 300, + "height": 355, + "id": "chromaExistingIndex_0", + "position": { + "x": 643.7296055499842, + "y": -602.9508117559833 + }, + "type": "customNode", + "data": { + "id": "chromaExistingIndex_0", + "label": "Chroma Load Existing Index", + "name": "chromaExistingIndex", + "type": "Chroma", + "baseClasses": ["Chroma", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Chroma (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Collection Name", + "name": "collectionName", + "type": "string", + "id": "chromaExistingIndex_0-input-collectionName-string" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "chromaExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_1.data.instance}}", + "collectionName": "ai-paper" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "chromaExistingIndex_0-output-retriever-Chroma|BaseRetriever", + "name": "retriever", + "label": "Chroma Retriever", + "type": "Chroma | BaseRetriever" + }, + { + "id": "chromaExistingIndex_0-output-vectorStore-Chroma|VectorStore", + "name": "vectorStore", + "label": "Chroma Vector Store", + "type": "Chroma | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "vectorStore" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 643.7296055499842, + "y": -602.9508117559833 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "serpAPI_0", + "sourceHandle": "serpAPI_0-output-serpAPI-SerpAPI|Tool", + "target": "mrklAgentLLM_0", + "targetHandle": "mrklAgentLLM_0-input-tools-Tool", + "type": "buttonedge", + "id": "serpAPI_0-serpAPI_0-output-serpAPI-SerpAPI|Tool-mrklAgentLLM_0-mrklAgentLLM_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "calculator_0", + "sourceHandle": "calculator_0-output-calculator-Calculator|Tool", + "target": "mrklAgentLLM_0", + "targetHandle": "mrklAgentLLM_0-input-tools-Tool", + "type": "buttonedge", + "id": "calculator_0-calculator_0-output-calculator-Calculator|Tool-mrklAgentLLM_0-mrklAgentLLM_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "openAI_0", + "sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "mrklAgentLLM_0", + "targetHandle": "mrklAgentLLM_0-input-model-BaseLLM", + "type": "buttonedge", + "id": "openAI_0-openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-mrklAgentLLM_0-mrklAgentLLM_0-input-model-BaseLLM", + "data": { + "label": "" + } + }, + { + "source": "openAI_1", + "sourceHandle": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "vectorDBQAChain_0", + "targetHandle": "vectorDBQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_1-openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-vectorDBQAChain_0-vectorDBQAChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "vectorDBQAChain_0", + "sourceHandle": "vectorDBQAChain_0-output-vectorDBQAChain-VectorDBQAChain|BaseChain", + "target": "chainTool_0", + "targetHandle": "chainTool_0-input-baseChain-BaseChain", + "type": "buttonedge", + "id": "vectorDBQAChain_0-vectorDBQAChain_0-output-vectorDBQAChain-VectorDBQAChain|BaseChain-chainTool_0-chainTool_0-input-baseChain-BaseChain", + "data": { + "label": "" + } + }, + { + "source": "chainTool_0", + "sourceHandle": "chainTool_0-output-chainTool-ChainTool|Tool", + "target": "mrklAgentLLM_0", + "targetHandle": "mrklAgentLLM_0-input-tools-Tool", + "type": "buttonedge", + "id": "chainTool_0-chainTool_0-output-chainTool-ChainTool|Tool-mrklAgentLLM_0-mrklAgentLLM_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "pineconeExistingIndex_0", + "sourceHandle": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "target": "vectorDBQAChain_0", + "targetHandle": "vectorDBQAChain_0-input-vectorStore-VectorStore", + "type": "buttonedge", + "id": "pineconeExistingIndex_0-pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore-vectorDBQAChain_0-vectorDBQAChain_0-input-vectorStore-VectorStore", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "pineconeExistingIndex_0", + "targetHandle": "pineconeExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeExistingIndex_0-pineconeExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "vectorDBQAChain_1", + "sourceHandle": "vectorDBQAChain_1-output-vectorDBQAChain-VectorDBQAChain|BaseChain", + "target": "chainTool_1", + "targetHandle": "chainTool_1-input-baseChain-BaseChain", + "type": "buttonedge", + "id": "vectorDBQAChain_1-vectorDBQAChain_1-output-vectorDBQAChain-VectorDBQAChain|BaseChain-chainTool_1-chainTool_1-input-baseChain-BaseChain", + "data": { + "label": "" + } + }, + { + "source": "openAI_2", + "sourceHandle": "openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "vectorDBQAChain_1", + "targetHandle": "vectorDBQAChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_2-openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-vectorDBQAChain_1-vectorDBQAChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "chromaExistingIndex_0", + "sourceHandle": "chromaExistingIndex_0-output-vectorStore-Chroma|VectorStore", + "target": "vectorDBQAChain_1", + "targetHandle": "vectorDBQAChain_1-input-vectorStore-VectorStore", + "type": "buttonedge", + "id": "chromaExistingIndex_0-chromaExistingIndex_0-output-vectorStore-Chroma|VectorStore-vectorDBQAChain_1-vectorDBQAChain_1-input-vectorStore-VectorStore", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_1", + "sourceHandle": "openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "chromaExistingIndex_0", + "targetHandle": "chromaExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_1-openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-chromaExistingIndex_0-chromaExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "chainTool_1", + "sourceHandle": "chainTool_1-output-chainTool-ChainTool|Tool", + "target": "mrklAgentLLM_0", + "targetHandle": "mrklAgentLLM_0-input-tools-Tool", + "type": "buttonedge", + "id": "chainTool_1-chainTool_1-output-chainTool-ChainTool|Tool-mrklAgentLLM_0-mrklAgentLLM_0-input-tools-Tool", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/Prompt Chaining.json new file mode 100644 index 00000000..356c0b81 --- /dev/null +++ b/packages/server/marketplaces/Prompt Chaining.json @@ -0,0 +1,508 @@ +{ + "description": "Use output from a chain as prompt for another chain", + "nodes": [ + { + "width": 300, + "height": 533, + "id": "promptTemplate_0", + "position": { + "x": 796.6293062501211, + "y": 523.6130142453178 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "string", + "rows": 4, + "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-string" + } + ], + "inputAnchors": [], + "inputs": { + "template": "You are an AI who performs one task based on the following objective: {objective}.\nRespond with how you would complete this task:", + "promptValues": "{\n \"objective\": \"{{question}}\"\n}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 796.6293062501211, + "y": 523.6130142453178 + }, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_0", + "position": { + "x": 1239.1590462985343, + "y": 477.999065568104 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_0-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{openAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "chainName": "FirstChain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1239.1590462985343, + "y": 477.999065568104 + }, + "dragging": false + }, + { + "width": 300, + "height": 472, + "id": "openAI_0", + "position": { + "x": 801.1835381596817, + "y": 21.196316952440355 + }, + "type": "customNode", + "data": { + "id": "openAI_0", + "label": "OpenAI", + "name": "openAI", + "type": "OpenAI", + "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], + "category": "LLMs", + "description": "Wrapper around OpenAI large language models", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "openAI_0-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-davinci-003", + "name": "text-davinci-003" + }, + { + "label": "text-davinci-002", + "name": "text-davinci-002" + }, + { + "label": "text-curie-001", + "name": "text-curie-001" + }, + { + "label": "text-babbage-001", + "name": "text-babbage-001" + } + ], + "default": "text-davinci-003", + "optional": true, + "id": "openAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.7, + "optional": true, + "id": "openAI_0-input-temperature-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "text-davinci-003", + "temperature": "0" + }, + "outputAnchors": [ + { + "id": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "name": "openAI", + "label": "OpenAI", + "type": "OpenAI | BaseLLM | BaseLanguageModel" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 801.1835381596817, + "y": 21.196316952440355 + }, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_1", + "position": { + "x": 2078.2072357874076, + "y": 476.5404337093371 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{openAI_1.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", + "chainName": "LastChain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2078.2072357874076, + "y": 476.5404337093371 + }, + "dragging": false + }, + { + "width": 300, + "height": 533, + "id": "promptTemplate_1", + "position": { + "x": 1686.7296107958396, + "y": 520.6957505277837 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "string", + "rows": 4, + "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-string" + } + ], + "inputAnchors": [], + "inputs": { + "template": "You are a task creation AI that uses the result of an execution agent to create new tasks with the following objective: {objective}.\nThe last completed task has the result: {result}.\nBased on the result, create new tasks to be completed by the AI system that do not overlap with result.\nReturn the tasks as an array.", + "promptValues": "{\n \"objective\": \"{{question}}\",\n \"result\": \"{{llmChain_0.data.instance}}\"\n}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1686.7296107958396, + "y": 520.6957505277837 + }, + "dragging": false + }, + { + "width": 300, + "height": 472, + "id": "openAI_1", + "position": { + "x": 1688.3665789878662, + "y": 16.528695004385895 + }, + "type": "customNode", + "data": { + "id": "openAI_1", + "label": "OpenAI", + "name": "openAI", + "type": "OpenAI", + "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], + "category": "LLMs", + "description": "Wrapper around OpenAI large language models", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "openAI_1-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-davinci-003", + "name": "text-davinci-003" + }, + { + "label": "text-davinci-002", + "name": "text-davinci-002" + }, + { + "label": "text-curie-001", + "name": "text-curie-001" + }, + { + "label": "text-babbage-001", + "name": "text-babbage-001" + } + ], + "default": "text-davinci-003", + "optional": true, + "id": "openAI_1-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.7, + "optional": true, + "id": "openAI_1-input-temperature-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "text-davinci-003", + "temperature": "0" + }, + "outputAnchors": [ + { + "id": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "name": "openAI", + "label": "OpenAI", + "type": "OpenAI | BaseLLM | BaseLanguageModel" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1688.3665789878662, + "y": 16.528695004385895 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "openAI_0", + "sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_0-openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-llmChain_0-llmChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "openAI_1", + "sourceHandle": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_1-openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "llmChain_0", + "sourceHandle": "llmChain_0-output-outputPrediction-string", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-string", + "type": "buttonedge", + "id": "llmChain_0-llmChain_0-output-outputPrediction-string-promptTemplate_1-promptTemplate_1-input-promptValues-string", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/Simple LLM Chain.json index 1e7529a0..91867d24 100644 --- a/packages/server/marketplaces/Simple LLM Chain.json +++ b/packages/server/marketplaces/Simple LLM Chain.json @@ -6,8 +6,8 @@ "height": 472, "id": "openAI_0", "position": { - "x": 968.1753795547951, - "y": -8.62176310944858 + "x": 618, + "y": 97 }, "type": "customNode", "data": { @@ -22,7 +22,8 @@ { "label": "OpenAI Api Key", "name": "openAIApiKey", - "type": "password" + "type": "password", + "id": "openAI_0-input-openAIApiKey-password" }, { "label": "Model Name", @@ -47,14 +48,16 @@ } ], "default": "text-davinci-003", - "optional": true + "optional": true, + "id": "openAI_0-input-modelName-options" }, { "label": "Temperature", "name": "temperature", "type": "number", "default": 0.7, - "optional": true + "optional": true, + "id": "openAI_0-input-temperature-number" } ], "inputAnchors": [], @@ -70,69 +73,23 @@ "type": "OpenAI | BaseLLM | BaseLanguageModel" } ], + "outputs": {}, "selected": false }, "selected": false, + "dragging": false, "positionAbsolute": { - "x": 968.1753795547951, - "y": -8.62176310944858 - }, - "dragging": false + "x": 618, + "y": 97 + } }, { "width": 300, - "height": 360, - "id": "promptTemplate_0", - "position": { - "x": 970.576876549135, - "y": 502.493937944275 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_0", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 5, - "placeholder": "What is a good name for a company that makes {product}?" - } - ], - "inputAnchors": [], - "inputs": { - "template": "What is a good name for a company that makes {product}?" - }, - "outputAnchors": [ - { - "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 970.576876549135, - "y": 502.493937944275 - }, - "dragging": false - }, - { - "width": 300, - "height": 461, + "height": 405, "id": "llmChain_0", "position": { - "x": 1414.1175742139496, - "y": 340.4040954840462 + "x": 998.3768292410252, + "y": 426.849642225371 }, "type": "customNode", "data": { @@ -145,12 +102,12 @@ "description": "Chain to run queries against LLMs", "inputParams": [ { - "label": "Format Prompt Values", - "name": "promptValues", + "label": "Chain Name", + "name": "chainName", "type": "string", - "rows": 5, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" } ], "inputAnchors": [ @@ -170,38 +127,105 @@ "inputs": { "model": "{{openAI_0.data.instance}}", "prompt": "{{promptTemplate_0.data.instance}}", - "promptValues": "" + "chainName": "" }, "outputAnchors": [ { - "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", - "name": "llmChain", - "label": "LLMChain", - "type": "LLMChain | BaseChain" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string" + } + ], + "default": "llmChain" } ], + "outputs": { + "output": "llmChain" + }, "selected": false }, "selected": false, "positionAbsolute": { - "x": 1414.1175742139496, - "y": 340.4040954840462 + "x": 998.3768292410252, + "y": 426.849642225371 + }, + "dragging": false + }, + { + "width": 300, + "height": 533, + "id": "promptTemplate_0", + "position": { + "x": 618.658978699234, + "y": 589.2586352262571 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "string", + "rows": 4, + "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-string" + } + ], + "inputAnchors": [], + "inputs": { + "template": "What is a good name for a company that makes {product}?", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 618.658978699234, + "y": 589.2586352262571 }, "dragging": false } ], "edges": [ - { - "source": "promptTemplate_0", - "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_0", - "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", - "type": "buttonedge", - "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", - "data": { - "label": "" - } - }, { "source": "openAI_0", "sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", @@ -212,6 +236,17 @@ "data": { "label": "" } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/Translator.json index f9179228..12269d73 100644 --- a/packages/server/marketplaces/Translator.json +++ b/packages/server/marketplaces/Translator.json @@ -3,66 +3,91 @@ "nodes": [ { "width": 300, - "height": 460, - "id": "chatPromptTemplate_0", + "height": 405, + "id": "llmChain_0", "position": { - "x": 524, - "y": 237 + "x": 1136.5578350285277, + "y": 619.2492937692573 }, "type": "customNode", "data": { - "id": "chatPromptTemplate_0", - "label": "Chat Prompt Template", - "name": "chatPromptTemplate", - "type": "ChatPromptTemplate", - "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a chat prompt", + "id": "llmChain_0", + "label": "LLM Chain", + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain"], + "category": "Chains", + "description": "Chain to run queries against LLMs", "inputParams": [ { - "label": "System Message", - "name": "systemMessagePrompt", + "label": "Chain Name", + "name": "chainName", "type": "string", - "rows": 3, - "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}." - }, - { - "label": "Human Message", - "name": "humanMessagePrompt", - "type": "string", - "rows": 3, - "placeholder": "{text}" + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_0-input-prompt-BasePromptTemplate" } ], - "inputAnchors": [], "inputs": { - "systemMessagePrompt": "You are a helpful assistant that translates {input_language} to {output_language}.", - "humanMessagePrompt": "{input}" + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", + "chainName": "Language Translation" }, "outputAnchors": [ { - "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", - "name": "chatPromptTemplate", - "label": "ChatPromptTemplate", - "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string" + } + ], + "default": "llmChain" } ], + "outputs": { + "output": "llmChain" + }, "selected": false }, "selected": false, - "dragging": false, "positionAbsolute": { - "x": 524, - "y": 237 - } + "x": 1136.5578350285277, + "y": 619.2492937692573 + }, + "dragging": false }, { "width": 300, "height": 472, "id": "chatOpenAI_0", "position": { - "x": 855.1997276913991, - "y": 24.090553068402556 + "x": 776.3729862229602, + "y": 290.4580650723551 }, "type": "customNode", "data": { @@ -77,7 +102,8 @@ { "label": "OpenAI Api Key", "name": "openAIApiKey", - "type": "password" + "type": "password", + "id": "chatOpenAI_0-input-openAIApiKey-password" }, { "label": "Model Name", @@ -106,20 +132,22 @@ } ], "default": "gpt-3.5-turbo", - "optional": true + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" }, { "label": "Temperature", "name": "temperature", "type": "number", "default": 0.9, - "optional": true + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" } ], "inputAnchors": [], "inputs": { "modelName": "gpt-3.5-turbo", - "temperature": 0.9 + "temperature": "0" }, "outputAnchors": [ { @@ -129,75 +157,83 @@ "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" } ], + "outputs": {}, "selected": false }, "selected": false, "positionAbsolute": { - "x": 855.1997276913991, - "y": 24.090553068402556 + "x": 776.3729862229602, + "y": 290.4580650723551 }, "dragging": false }, { "width": 300, - "height": 461, - "id": "llmChain_0", + "height": 710, + "id": "chatPromptTemplate_0", "position": { - "x": 1192.2235692202612, - "y": 361.71736677076257 + "x": 428.40848918154023, + "y": 291.77611240963313 }, "type": "customNode", "data": { - "id": "llmChain_0", - "label": "LLM Chain", - "name": "llmChain", - "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain"], - "category": "Chains", - "description": "Chain to run queries against LLMs", + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a chat prompt", "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}.", + "id": "chatPromptTemplate_0-input-systemMessagePrompt-string" + }, + { + "label": "Human Message", + "name": "humanMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "{text}", + "id": "chatPromptTemplate_0-input-humanMessagePrompt-string" + }, { "label": "Format Prompt Values", "name": "promptValues", "type": "string", - "rows": 5, + "rows": 4, "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true - } - ], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "llmChain_0-input-model-BaseLanguageModel" - }, - { - "label": "Prompt", - "name": "prompt", - "type": "BasePromptTemplate", - "id": "llmChain_0-input-prompt-BasePromptTemplate" + "optional": true, + "acceptVariable": true, + "list": true, + "id": "chatPromptTemplate_0-input-promptValues-string" } ], + "inputAnchors": [], "inputs": { - "model": "{{chatOpenAI_0.data.instance}}", - "prompt": "{{chatPromptTemplate_0.data.instance}}", + "systemMessagePrompt": "You are a helpful assistant that translates {input_language} to {output_language}.", + "humanMessagePrompt": "{input}", "promptValues": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}" }, "outputAnchors": [ { - "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", - "name": "llmChain", - "label": "LLMChain", - "type": "LLMChain | BaseChain" + "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", + "name": "chatPromptTemplate", + "label": "ChatPromptTemplate", + "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate" } ], + "outputs": {}, "selected": false }, "selected": false, "positionAbsolute": { - "x": 1192.2235692202612, - "y": 361.71736677076257 + "x": 428.40848918154023, + "y": 291.77611240963313 }, "dragging": false } diff --git a/packages/server/src/ChatflowPool.ts b/packages/server/src/ChatflowPool.ts index cc738e03..3b363a8b 100644 --- a/packages/server/src/ChatflowPool.ts +++ b/packages/server/src/ChatflowPool.ts @@ -1,9 +1,8 @@ -import { INodeData } from 'flowise-components' -import { IActiveChatflows } from './Interface' +import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface' /** - * This pool is to keep track of active test triggers (event listeners), - * so we can clear the event listeners whenever user refresh or exit page + * This pool is to keep track of active chatflow pools + * so we can prevent building langchain flow all over again */ export class ChatflowPool { activeChatflows: IActiveChatflows = {} @@ -12,9 +11,11 @@ export class ChatflowPool { * Add to the pool * @param {string} chatflowid * @param {INodeData} endingNodeData + * @param {IReactFlowNode[]} startingNodes */ - add(chatflowid: string, endingNodeData: INodeData) { + add(chatflowid: string, endingNodeData: INodeData, startingNodes: IReactFlowNode[]) { this.activeChatflows[chatflowid] = { + startingNodes, endingNodeData, inSync: true } diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index d228d937..822b6343 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -1,4 +1,4 @@ -import { INode, INodeData } from 'flowise-components' +import { INode, INodeData as INodeDataFromComponent, INodeParams } from 'flowise-components' export type MessageType = 'apiMessage' | 'userMessage' @@ -38,6 +38,12 @@ export interface INodeDirectedGraph { [key: string]: string[] } +export interface INodeData extends INodeDataFromComponent { + inputAnchors: INodeParams[] + inputParams: INodeParams[] + outputAnchors: INodeParams[] +} + export interface IReactFlowNode { id: string position: { @@ -111,6 +117,7 @@ export interface IncomingInput { export interface IActiveChatflows { [key: string]: { + startingNodes: IReactFlowNode[] endingNodeData: INodeData inSync: boolean } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index cec71051..7246e7c8 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -4,15 +4,22 @@ import cors from 'cors' import http from 'http' import * as fs from 'fs' -import { IChatFlow, IncomingInput, IReactFlowNode, IReactFlowObject } from './Interface' -import { getNodeModulesPackagePath, getStartingNodes, buildLangchain, getEndingNode, constructGraphs } from './utils' +import { IChatFlow, IncomingInput, IReactFlowNode, IReactFlowObject, INodeData } from './Interface' +import { + getNodeModulesPackagePath, + getStartingNodes, + buildLangchain, + getEndingNode, + constructGraphs, + resolveVariables, + isStartNodeDependOnInput +} from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' import { NodesPool } from './NodesPool' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { ChatflowPool } from './ChatflowPool' -import { INodeData } from 'flowise-components' export class App { app: express.Application @@ -196,12 +203,19 @@ export class App { let nodeToExecuteData: INodeData + /* Check if: + * - Node Data already exists in pool + * - Still in sync (i.e the flow has not been modified since) + * - Flow doesn't start with nodes that depend on incomingInput.question + ***/ if ( Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) && - this.chatflowPool.activeChatflows[chatflowid].inSync + this.chatflowPool.activeChatflows[chatflowid].inSync && + !isStartNodeDependOnInput(this.chatflowPool.activeChatflows[chatflowid].startingNodes) ) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData } else { + /*** Get chatflows and prepare data ***/ const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ id: chatflowid }) @@ -209,33 +223,53 @@ export class App { const flowData = chatflow.flowData const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges /*** Get Ending Node with Directed Graph ***/ - const { graph, nodeDependencies } = constructGraphs(parsedFlowData.nodes, parsedFlowData.edges) + const { graph, nodeDependencies } = constructGraphs(nodes, edges) const directedGraph = graph const endingNodeId = getEndingNode(nodeDependencies, directedGraph) if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data + if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + + if ( + endingNodeData.outputs && + Object.keys(endingNodeData.outputs).length && + !Object.values(endingNodeData.outputs).includes(endingNodeData.name) + ) { + return res + .status(500) + .send( + `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + ) + } + /*** Get Starting Nodes with Non-Directed Graph ***/ - const constructedObj = constructGraphs(parsedFlowData.nodes, parsedFlowData.edges, true) + const constructedObj = constructGraphs(nodes, edges, true) const nonDirectedGraph = constructedObj.graph const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) /*** BFS to traverse from Starting Nodes to Ending Node ***/ const reactFlowNodes = await buildLangchain( startingNodeIds, - parsedFlowData.nodes, + nodes, graph, depthQueue, - this.nodesPool.componentNodes + this.nodesPool.componentNodes, + incomingInput.question ) const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`) - nodeToExecuteData = nodeToExecute.data + const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) + nodeToExecuteData = reactFlowNodeData - this.chatflowPool.add(chatflowid, nodeToExecuteData) + const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) + this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes) } const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index fce1e02e..1e2a91e9 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -8,10 +8,14 @@ import { INodeDirectedGraph, INodeQueue, IReactFlowEdge, - IReactFlowNode + IReactFlowNode, + IVariableDict, + INodeData } from '../Interface' import { cloneDeep, get } from 'lodash' -import { ICommonObject, INodeData } from 'flowise-components' +import { ICommonObject, getInputVariables } from 'flowise-components' + +const QUESTION_VAR_PREFIX = 'question' /** * Returns the home folder path of the user if @@ -166,13 +170,15 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD * @param {INodeDirectedGraph} graph * @param {IDepthQueue} depthQueue * @param {IComponentNodes} componentNodes + * @param {string} question */ export const buildLangchain = async ( startingNodeIds: string[], reactFlowNodes: IReactFlowNode[], graph: INodeDirectedGraph, depthQueue: IDepthQueue, - componentNodes: IComponentNodes + componentNodes: IComponentNodes, + question: string ) => { const flowNodes = cloneDeep(reactFlowNodes) @@ -200,9 +206,9 @@ export const buildLangchain = async ( const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() - const reactFlowNodeData: INodeData = resolveVariables(reactFlowNode.data, flowNodes) + const reactFlowNodeData: INodeData = resolveVariables(reactFlowNode.data, flowNodes, question) - flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData) + flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question) } catch (e: any) { console.error(e) throw new Error(e) @@ -247,11 +253,14 @@ export const buildLangchain = async ( * Get variable value from outputResponses.output * @param {string} paramValue * @param {IReactFlowNode[]} reactFlowNodes + * @param {string} question + * @param {boolean} isAcceptVariable * @returns {string} */ -export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowNode[]) => { +export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, isAcceptVariable = false) => { let returnVal = paramValue const variableStack = [] + const variableDict = {} as IVariableDict let startIdx = 0 const endIdx = returnVal.length - 1 @@ -269,17 +278,36 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN const variableEndIdx = startIdx const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx) + if (isAcceptVariable && variableFullPath === QUESTION_VAR_PREFIX) { + variableDict[`{{${variableFullPath}}}`] = question + } + // Split by first occurence of '.' to get just nodeId const [variableNodeId, _] = variableFullPath.split('.') const executedNode = reactFlowNodes.find((nd) => nd.id === variableNodeId) if (executedNode) { - const variableInstance = get(executedNode.data, 'instance') - returnVal = variableInstance + const variableValue = get(executedNode.data, 'instance') + if (isAcceptVariable) { + variableDict[`{{${variableFullPath}}}`] = variableValue + } else { + returnVal = variableValue + } } variableStack.pop() } startIdx += 1 } + + if (isAcceptVariable) { + const variablePaths = Object.keys(variableDict) + variablePaths.sort() // Sort by length of variable path because longer path could possibly contains nested variable + variablePaths.forEach((path) => { + const variableValue = variableDict[path] + // Replace all occurence + returnVal = returnVal.split(path).join(variableValue) + }) + return returnVal + } return returnVal } @@ -287,25 +315,26 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData * @param {IReactFlowNode[]} reactFlowNodes + * @param {string} question * @returns {INodeData} */ -export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[]): INodeData => { +export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string): INodeData => { const flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { for (const key in paramsObj) { const paramValue: string = paramsObj[key] - if (Array.isArray(paramValue)) { const resolvedInstances = [] for (const param of paramValue) { - const resolvedInstance = getVariableValue(param, reactFlowNodes) + const resolvedInstance = getVariableValue(param, reactFlowNodes, question) resolvedInstances.push(resolvedInstance) } paramsObj[key] = resolvedInstances } else { - const resolvedInstance = getVariableValue(paramValue, reactFlowNodes) + const isAcceptVariable = reactFlowNodeData.inputParams.find((param) => param.name === key)?.acceptVariable ?? false + const resolvedInstance = getVariableValue(paramValue, reactFlowNodes, question, isAcceptVariable) paramsObj[key] = resolvedInstance } } @@ -317,3 +346,19 @@ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: I return flowNodeData } + +/** + * Rebuild flow if LLMChain has dependency on other chains + * User Question => Prompt_0 => LLMChain_0 => Prompt-1 => LLMChain_1 + * @param {IReactFlowNode[]} startingNodes + * @returns {boolean} + */ +export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[]): boolean => { + for (const node of startingNodes) { + for (const inputName in node.data.inputs) { + const inputVariables = getInputVariables(node.data.inputs[inputName]) + if (inputVariables.length > 0) return true + } + } + return false +} diff --git a/packages/ui/.env.example b/packages/ui/.env.example new file mode 100644 index 00000000..25241b73 --- /dev/null +++ b/packages/ui/.env.example @@ -0,0 +1 @@ +PORT=8080 diff --git a/packages/ui/package.json b/packages/ui/package.json index dfbac842..fe6a709e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -25,6 +25,7 @@ "prismjs": "^1.28.0", "prop-types": "^15.7.2", "react": "^18.2.0", + "react-code-blocks": "^0.0.9-0", "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", diff --git a/packages/ui/src/assets/images/cURL.svg b/packages/ui/src/assets/images/cURL.svg new file mode 100644 index 00000000..7f3644ae --- /dev/null +++ b/packages/ui/src/assets/images/cURL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/images/javascript.svg b/packages/ui/src/assets/images/javascript.svg new file mode 100644 index 00000000..e7f9f2a3 --- /dev/null +++ b/packages/ui/src/assets/images/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/images/python.svg b/packages/ui/src/assets/images/python.svg new file mode 100644 index 00000000..9cbbf947 --- /dev/null +++ b/packages/ui/src/assets/images/python.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/store/constant.js b/packages/ui/src/store/constant.js index 972c5000..61acbd92 100644 --- a/packages/ui/src/store/constant.js +++ b/packages/ui/src/store/constant.js @@ -2,4 +2,5 @@ export const gridSpacing = 3 export const drawerWidth = 260 export const appDrawerWidth = 320 +export const maxScroll = 100000 export const baseURL = process.env.NODE_ENV === 'production' ? window.location.origin : window.location.origin.replace(':8080', ':3000') diff --git a/packages/ui/src/store/context/ReactFlowContext.js b/packages/ui/src/store/context/ReactFlowContext.js index 1a6b979f..4c35d702 100644 --- a/packages/ui/src/store/context/ReactFlowContext.js +++ b/packages/ui/src/store/context/ReactFlowContext.js @@ -1,9 +1,12 @@ import { createContext, useState } from 'react' import PropTypes from 'prop-types' +import { getUniqueNodeId } from 'utils/genericHelper' +import { cloneDeep } from 'lodash' const initialValue = { reactFlowInstance: null, setReactFlowInstance: () => {}, + duplicateNode: () => {}, deleteNode: () => {}, deleteEdge: () => {} } @@ -40,9 +43,13 @@ export const ReactFlowContext = ({ children }) => { if (node.id === targetNodeId) { let value const inputAnchor = node.data.inputAnchors.find((ancr) => ancr.name === targetInput) + const inputParam = node.data.inputParams.find((param) => param.name === targetInput) + if (inputAnchor && inputAnchor.list) { const values = node.data.inputs[targetInput] || [] value = values.filter((item) => !item.includes(sourceNodeId)) + } else if (inputParam && inputParam.acceptVariable) { + value = node.data.inputs[targetInput].replace(`{{${sourceNodeId}.data.instance}}`, '') || '' } else { value = '' } @@ -60,13 +67,53 @@ export const ReactFlowContext = ({ children }) => { } } + const duplicateNode = (id) => { + const nodes = reactFlowInstance.getNodes() + const originalNode = nodes.find((n) => n.id === id) + if (originalNode) { + const newNodeId = getUniqueNodeId(originalNode.data, nodes) + const clonedNode = cloneDeep(originalNode) + + const duplicatedNode = { + ...clonedNode, + id: newNodeId, + position: { + x: clonedNode.position.x + 400, + y: clonedNode.position.y + }, + positionAbsolute: { + x: clonedNode.positionAbsolute.x + 400, + y: clonedNode.positionAbsolute.y + }, + data: { + ...clonedNode.data, + id: newNodeId + }, + selected: false + } + + const dataKeys = ['inputParams', 'inputAnchors', 'outputAnchors'] + + for (const key of dataKeys) { + for (const item of duplicatedNode.data[key]) { + if (item.id) { + item.id = item.id.replace(id, newNodeId) + } + } + } + + reactFlowInstance.setNodes([...nodes, duplicatedNode]) + } + } + return ( {children} diff --git a/packages/ui/src/themes/palette.js b/packages/ui/src/themes/palette.js index 97abbe89..a4a5104d 100644 --- a/packages/ui/src/themes/palette.js +++ b/packages/ui/src/themes/palette.js @@ -80,8 +80,6 @@ export default function themePalette(theme) { main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50 }, canvasHeader: { - executionLight: theme.colors?.successLight, - executionDark: theme.colors?.successDark, deployLight: theme.colors?.primaryLight, deployDark: theme.colors?.primaryDark, saveLight: theme.colors?.secondaryLight, diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/ui-component/dialog/APICodeDialog.js new file mode 100644 index 00000000..e23e91b2 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/APICodeDialog.js @@ -0,0 +1,163 @@ +import { createPortal } from 'react-dom' +import { useState } from 'react' +import PropTypes from 'prop-types' + +import { Tabs, Tab, Dialog, DialogContent, DialogTitle, Box } from '@mui/material' +import { CopyBlock, atomOneDark } from 'react-code-blocks' +import { baseURL } from 'store/constant' +import pythonSVG from 'assets/images/python.svg' +import javascriptSVG from 'assets/images/javascript.svg' +import cURLSVG from 'assets/images/cURL.svg' + +function TabPanel(props) { + const { children, value, index, ...other } = props + return ( + + ) +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +} + +function a11yProps(index) { + return { + id: `attachment-tab-${index}`, + 'aria-controls': `attachment-tabpanel-${index}` + } +} + +const APICodeDialog = ({ show, dialogProps, onCancel }) => { + const portalElement = document.getElementById('portal') + const codes = ['Python', 'JavaScript', 'cURL'] + const [value, setValue] = useState(0) + + const handleChange = (event, newValue) => { + setValue(newValue) + } + + const getCode = (codeLang) => { + if (codeLang === 'Python') { + return `import requests + +API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}" + +def query(payload): + response = requests.post(API_URL, json=payload) + return response.json() + +output = query({ + "question": "Hey, how are you?", +}) +` + } else if (codeLang === 'JavaScript') { + return `async function query(data) { + const response = await fetch( + "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", + { + method: "POST", + body: { + "question": "Hey, how are you?" + }, + } + ); + const result = await response.json(); + return result; +} +` + } else if (codeLang === 'cURL') { + return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ + -X POST \\ + -d '{"question": "Hey, how are you?"}'` + } + return '' + } + + const getLang = (codeLang) => { + if (codeLang === 'Python') { + return 'python' + } else if (codeLang === 'JavaScript') { + return 'javascript' + } else if (codeLang === 'cURL') { + return 'bash' + } + return 'python' + } + + const getSVG = (codeLang) => { + if (codeLang === 'Python') { + return pythonSVG + } else if (codeLang === 'JavaScript') { + return javascriptSVG + } else if (codeLang === 'cURL') { + return cURLSVG + } + return pythonSVG + } + + const component = show ? ( + + + {dialogProps.title} + + + + {codes.map((codeLang, index) => ( + + } + iconPosition='left' + key={index} + label={codeLang} + {...a11yProps(index)} + > + ))} + +
+ {codes.map((codeLang, index) => ( + + + + ))} +
+
+ ) : null + + return createPortal(component, portalElement) +} + +APICodeDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func +} + +export default APICodeDialog diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css b/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css new file mode 100644 index 00000000..d0e2ba26 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css @@ -0,0 +1,6 @@ +.editor__textarea { + outline: 0; +} +.editor__textarea::placeholder { + color: rgba(120, 120, 120, 0.5); +} diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js b/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js new file mode 100644 index 00000000..199b1306 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js @@ -0,0 +1,256 @@ +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { + Button, + Dialog, + DialogActions, + DialogContent, + Box, + List, + ListItemButton, + ListItem, + ListItemAvatar, + ListItemText, + Typography, + Stack +} from '@mui/material' +import { useTheme } from '@mui/material/styles' +import PerfectScrollbar from 'react-perfect-scrollbar' +import { StyledButton } from 'ui-component/button/StyledButton' +import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' +import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' + +import './EditPromptValuesDialog.css' +import { baseURL } from 'store/constant' + +const EditPromptValuesDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + + const theme = useTheme() + const customization = useSelector((state) => state.customization) + const languageType = 'json' + + const [inputValue, setInputValue] = useState('') + const [inputParam, setInputParam] = useState(null) + const [textCursorPosition, setTextCursorPosition] = useState({}) + + useEffect(() => { + if (dialogProps.value) setInputValue(dialogProps.value) + if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) + + return () => { + setInputValue('') + setInputParam(null) + setTextCursorPosition({}) + } + }, [dialogProps]) + + const onMouseUp = (e) => { + if (e.target && e.target.selectionEnd && e.target.value) { + const cursorPosition = e.target.selectionEnd + const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition) + const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length) + const body = { + textBeforeCursorPosition, + textAfterCursorPosition + } + setTextCursorPosition(body) + } else { + setTextCursorPosition({}) + } + } + + const onSelectOutputResponseClick = (node, isUserQuestion = false) => { + let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance` + if (textCursorPosition) { + let newInput = '' + if (textCursorPosition.textBeforeCursorPosition === undefined && textCursorPosition.textAfterCursorPosition === undefined) + newInput = `${inputValue}${`{{${variablePath}}}`}` + else newInput = `${textCursorPosition.textBeforeCursorPosition}{{${variablePath}}}${textCursorPosition.textAfterCursorPosition}` + setInputValue(newInput) + } + } + + const component = show ? ( + + +
+ {inputParam && inputParam.type === 'string' && ( +
+ + {inputParam.label} + + + {customization.isDarkMode ? ( + setInputValue(code)} + placeholder={inputParam.placeholder} + type={languageType} + onMouseUp={(e) => onMouseUp(e)} + onBlur={(e) => onMouseUp(e)} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%' + }} + /> + ) : ( + setInputValue(code)} + placeholder={inputParam.placeholder} + type={languageType} + onMouseUp={(e) => onMouseUp(e)} + onBlur={(e) => onMouseUp(e)} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%' + }} + /> + )} + +
+ )} + {!dialogProps.disabled && inputParam && inputParam.acceptVariable && ( +
+ + Select Variable + + + + + onSelectOutputResponseClick(null, true)} + > + + +
+ AI +
+
+ +
+
+ {dialogProps.availableNodesForVariable && + dialogProps.availableNodesForVariable.length > 0 && + dialogProps.availableNodesForVariable.map((node, index) => { + const selectedOutputAnchor = node.data.outputAnchors[0].options.find( + (ancr) => ancr.name === node.data.outputs['output'] + ) + return ( + onSelectOutputResponseClick(node)} + > + + +
+ {node.data.name} +
+
+ +
+
+ ) + })} +
+
+
+
+ )} +
+
+ + + onConfirm(inputValue, inputParam.name)}> + {dialogProps.confirmButtonName} + + +
+ ) : null + + return createPortal(component, portalElement) +} + +EditPromptValuesDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default EditPromptValuesDialog diff --git a/packages/ui/src/ui-component/dropdown/Dropdown.js b/packages/ui/src/ui-component/dropdown/Dropdown.js index 691546c4..12d10bef 100644 --- a/packages/ui/src/ui-component/dropdown/Dropdown.js +++ b/packages/ui/src/ui-component/dropdown/Dropdown.js @@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({ } }) -export const Dropdown = ({ name, value, options, onSelect, disabled = false }) => { +export const Dropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => { const customization = useSelector((state) => state.customization) const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) const getDefaultOptionValue = () => '' @@ -29,6 +29,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false }) = { +export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { const theme = useTheme() return ( highlight(code, type === 'json' ? languages.json : languages.js)} @@ -32,6 +33,7 @@ export const DarkCodeEditor = ({ value, placeholder, type, style, onValueChange, DarkCodeEditor.propTypes = { value: PropTypes.string, placeholder: PropTypes.string, + disabled: PropTypes.bool, type: PropTypes.string, style: PropTypes.object, onValueChange: PropTypes.func, diff --git a/packages/ui/src/ui-component/editor/LightCodeEditor.js b/packages/ui/src/ui-component/editor/LightCodeEditor.js index 36d56fb8..86f7057d 100644 --- a/packages/ui/src/ui-component/editor/LightCodeEditor.js +++ b/packages/ui/src/ui-component/editor/LightCodeEditor.js @@ -8,11 +8,12 @@ import './prism-light.css' import PropTypes from 'prop-types' import { useTheme } from '@mui/material/styles' -export const LightCodeEditor = ({ value, placeholder, type, style, onValueChange, onMouseUp, onBlur }) => { +export const LightCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { const theme = useTheme() return ( highlight(code, type === 'json' ? languages.json : languages.js)} @@ -32,6 +33,7 @@ export const LightCodeEditor = ({ value, placeholder, type, style, onValueChange LightCodeEditor.propTypes = { value: PropTypes.string, placeholder: PropTypes.string, + disabled: PropTypes.bool, type: PropTypes.string, style: PropTypes.object, onValueChange: PropTypes.func, diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 13b457f7..0886b22f 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,28 +1,58 @@ import { useState } from 'react' import PropTypes from 'prop-types' import { FormControl, OutlinedInput } from '@mui/material' +import EditPromptValuesDialog from 'ui-component/dialog/EditPromptValuesDialog' -export const Input = ({ inputParam, value, onChange, disabled = false }) => { +export const Input = ({ inputParam, value, onChange, disabled = false, showDialog, dialogProps, onDialogCancel, onDialogConfirm }) => { const [myValue, setMyValue] = useState(value ?? '') + + const getInputType = (type) => { + switch (type) { + case 'string': + return 'text' + case 'password': + return 'password' + case 'number': + return 'number' + default: + return 'text' + } + } + return ( - - { - setMyValue(e.target.value) - onChange(e.target.value) + <> + + { + setMyValue(e.target.value) + onChange(e.target.value) + }} + inputProps={{ + style: { + height: inputParam.rows ? '90px' : 'inherit' + } + }} + /> + + { + setMyValue(newValue) + onDialogConfirm(newValue, inputParamName) }} - /> - + > + ) } @@ -30,5 +60,9 @@ Input.propTypes = { inputParam: PropTypes.object, value: PropTypes.string, onChange: PropTypes.func, - disabled: PropTypes.bool + disabled: PropTypes.bool, + showDialog: PropTypes.bool, + dialogProps: PropTypes.object, + onDialogCancel: PropTypes.func, + onDialogConfirm: PropTypes.func } diff --git a/packages/ui/src/ui-component/switch/Switch.js b/packages/ui/src/ui-component/switch/Switch.js new file mode 100644 index 00000000..04ea1704 --- /dev/null +++ b/packages/ui/src/ui-component/switch/Switch.js @@ -0,0 +1,28 @@ +import { useState } from 'react' +import PropTypes from 'prop-types' +import { FormControl, Switch } from '@mui/material' + +export const SwitchInput = ({ value, onChange, disabled = false }) => { + const [myValue, setMyValue] = useState(!!value ?? false) + + return ( + <> + + { + setMyValue(event.target.checked) + onChange(event.target.checked) + }} + /> + + + ) +} + +SwitchInput.propTypes = { + value: PropTypes.string, + onChange: PropTypes.func, + disabled: PropTypes.bool +} diff --git a/packages/ui/src/ui-component/tooltip/TooltipWithParser.js b/packages/ui/src/ui-component/tooltip/TooltipWithParser.js index e379eb18..a4708377 100644 --- a/packages/ui/src/ui-component/tooltip/TooltipWithParser.js +++ b/packages/ui/src/ui-component/tooltip/TooltipWithParser.js @@ -9,13 +9,9 @@ export const TooltipWithParser = ({ title }) => { return ( -
- - - -
+ + +
) } diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 749e1202..dd63c131 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -22,23 +22,12 @@ export const getUniqueNodeId = (nodeData, nodes) => { return nodeId } -export const initializeNodeData = (nodeParams) => { +export const initializeDefaultNodeData = (nodeParams) => { const initialValues = {} for (let i = 0; i < nodeParams.length; i += 1) { const input = nodeParams[i] - - // Load from nodeParams default values initialValues[input.name] = input.default || '' - - // Special case for array, always initialize the item if default is not set - if (input.type === 'array' && !input.default) { - const newObj = {} - for (let j = 0; j < input.array.length; j += 1) { - newObj[input.array[j].name] = input.array[j].default || '' - } - initialValues[input.name] = [newObj] - } } return initialValues @@ -46,62 +35,119 @@ export const initializeNodeData = (nodeParams) => { export const initNode = (nodeData, newNodeId) => { const inputAnchors = [] + const inputParams = [] const incoming = nodeData.inputs ? nodeData.inputs.length : 0 const outgoing = 1 - const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] + const whitelistTypes = ['options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] for (let i = 0; i < incoming; i += 1) { - if (whitelistTypes.includes(nodeData.inputs[i].type)) continue const newInput = { ...nodeData.inputs[i], id: `${newNodeId}-input-${nodeData.inputs[i].name}-${nodeData.inputs[i].type}` } - inputAnchors.push(newInput) + if (whitelistTypes.includes(nodeData.inputs[i].type)) { + inputParams.push(newInput) + } else { + inputAnchors.push(newInput) + } } const outputAnchors = [] for (let i = 0; i < outgoing; i += 1) { - const newOutput = { - id: `${newNodeId}-output-${nodeData.name}-${nodeData.baseClasses.join('|')}`, - name: nodeData.name, - label: nodeData.type, - type: nodeData.baseClasses.join(' | ') + if (nodeData.outputs && nodeData.outputs.length) { + const options = [] + for (let j = 0; j < nodeData.outputs.length; j += 1) { + let baseClasses = '' + let type = '' + + const outputBaseClasses = nodeData.outputs[j].baseClasses ?? [] + if (outputBaseClasses.length > 1) { + baseClasses = outputBaseClasses.join('|') + type = outputBaseClasses.join(' | ') + } else if (outputBaseClasses.length === 1) { + baseClasses = outputBaseClasses[0] + type = outputBaseClasses[0] + } + + const newOutputOption = { + id: `${newNodeId}-output-${nodeData.outputs[j].name}-${baseClasses}`, + name: nodeData.outputs[j].name, + label: nodeData.outputs[j].label, + type + } + options.push(newOutputOption) + } + const newOutput = { + name: 'output', + label: 'Output', + type: 'options', + options, + default: nodeData.outputs[0].name + } + outputAnchors.push(newOutput) + } else { + const newOutput = { + id: `${newNodeId}-output-${nodeData.name}-${nodeData.baseClasses.join('|')}`, + name: nodeData.name, + label: nodeData.type, + type: nodeData.baseClasses.join(' | ') + } + outputAnchors.push(newOutput) } - outputAnchors.push(newOutput) } - nodeData.id = newNodeId - nodeData.inputAnchors = inputAnchors - nodeData.outputAnchors = outputAnchors - - /* - Initial inputs = [ + /* Initial + inputs = [ { - label: 'field_label', - name: 'field' + label: 'field_label_1', + name: 'string' + }, + { + label: 'field_label_2', + name: 'CustomType' } ] - // Turn into inputs object with default values - Converted inputs = { 'field': 'defaultvalue' } + => Convert to inputs, inputParams, inputAnchors + + => inputs = { 'field': 'defaultvalue' } // Turn into inputs object with default values - // Move remaining inputs that are not part of inputAnchors to inputParams - inputParams = [ - { - label: 'field_label', - name: 'field' - } - ] + => // For inputs that are part of whitelistTypes + inputParams = [ + { + label: 'field_label_1', + name: 'string' + } + ] + + => // For inputs that are not part of whitelistTypes + inputAnchors = [ + { + label: 'field_label_2', + name: 'CustomType' + } + ] */ if (nodeData.inputs) { - nodeData.inputParams = nodeData.inputs.filter(({ name }) => !nodeData.inputAnchors.some((exclude) => exclude.name === name)) - nodeData.inputs = initializeNodeData(nodeData.inputs) + nodeData.inputAnchors = inputAnchors + nodeData.inputParams = inputParams + nodeData.inputs = initializeDefaultNodeData(nodeData.inputs) } else { + nodeData.inputAnchors = [] nodeData.inputParams = [] nodeData.inputs = {} } + if (nodeData.outputs) { + nodeData.outputs = initializeDefaultNodeData(outputAnchors) + } else { + nodeData.outputs = {} + } + + nodeData.outputAnchors = outputAnchors + nodeData.id = newNodeId + return nodeData } @@ -133,7 +179,9 @@ export const isValidConnection = (connection, reactFlowInstance) => { return true } } else { - const targetNodeInputAnchor = targetNode.data.inputAnchors.find((ancr) => ancr.id === targetHandle) + const targetNodeInputAnchor = + targetNode.data.inputAnchors.find((ancr) => ancr.id === targetHandle) || + targetNode.data.inputParams.find((ancr) => ancr.id === targetHandle) if ( (targetNodeInputAnchor && !targetNodeInputAnchor?.list && @@ -144,7 +192,6 @@ export const isValidConnection = (connection, reactFlowInstance) => { } } } - return false } @@ -200,6 +247,7 @@ export const generateExportFlowData = (flowData) => { inputAnchors: node.data.inputAnchors, inputs: {}, outputAnchors: node.data.outputAnchors, + outputs: node.data.outputs, selected: false } @@ -225,11 +273,33 @@ export const generateExportFlowData = (flowData) => { return exportJson } -export const copyToClipboard = (e) => { - const src = e.src - if (Array.isArray(src) || typeof src === 'object') { - navigator.clipboard.writeText(JSON.stringify(src, null, ' ')) - } else { - navigator.clipboard.writeText(src) +export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle) => { + // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string-llmChain_1-llmChain_1-input-promptValues-string" + // {source} -{sourceHandle} -{target} -{targetHandle} + const parentNodes = [] + const inputEdges = edges.filter((edg) => edg.target === target && edg.targetHandle === targetHandle) + if (inputEdges && inputEdges.length) { + for (let j = 0; j < inputEdges.length; j += 1) { + const node = nodes.find((nd) => nd.id === inputEdges[j].source) + parentNodes.push(node) + } } + return parentNodes +} + +export const rearrangeToolsOrdering = (newValues, sourceNodeId) => { + // RequestsGet and RequestsPost have to be in order before other tools + newValues.push(`{{${sourceNodeId}.data.instance}}`) + + const sortKey = (item) => { + if (item.includes('requestsGet')) { + return 0 + } else if (item.includes('requestsPost')) { + return 1 + } else { + return 2 + } + } + + newValues.sort((a, b) => sortKey(a) - sortKey(b)) } diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 9a5654e8..266deb70 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -8,11 +8,12 @@ import { useTheme } from '@mui/material/styles' import { Avatar, Box, ButtonBase, Typography, Stack, TextField } from '@mui/material' // icons -import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX } from '@tabler/icons' +import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconWorldWww } from '@tabler/icons' // project imports import Settings from 'views/settings' import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' +import APICodeDialog from 'ui-component/dialog/APICodeDialog' // API import chatflowsApi from 'api/chatflows' @@ -35,6 +36,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl const [flowName, setFlowName] = useState('') const [isSettingsOpen, setSettingsOpen] = useState(false) const [flowDialogOpen, setFlowDialogOpen] = useState(false) + const [apiDialogOpen, setAPIDialogOpen] = useState(false) + const [apiDialogProps, setAPIDialogProps] = useState({}) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) const canvas = useSelector((state) => state.canvas) @@ -76,6 +79,14 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl } } + const onAPIDialogClick = () => { + setAPIDialogProps({ + title: 'Use this chatflow with API', + chatflowid: chatflow.id + }) + setAPIDialogOpen(true) + } + const onSaveChatflowClick = () => { if (chatflow.id) handleSaveFlow(chatflow.name) else setFlowDialogOpen(true) @@ -219,6 +230,26 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl )} + + + + + setFlowDialogOpen(false)} onConfirm={onConfirmSaveName} /> + setAPIDialogOpen(false)} /> ) } diff --git a/packages/ui/src/views/canvas/CanvasNode.js b/packages/ui/src/views/canvas/CanvasNode.js index 1d473521..75e2f7da 100644 --- a/packages/ui/src/views/canvas/CanvasNode.js +++ b/packages/ui/src/views/canvas/CanvasNode.js @@ -12,7 +12,7 @@ import NodeOutputHandler from './NodeOutputHandler' // const import { baseURL } from 'store/constant' -import { IconTrash } from '@tabler/icons' +import { IconTrash, IconCopy } from '@tabler/icons' import { flowContext } from 'store/context/ReactFlowContext' const CardWrapper = styled(MainCard)(({ theme }) => ({ @@ -33,7 +33,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ const CanvasNode = ({ data }) => { const theme = useTheme() - const { deleteNode } = useContext(flowContext) + const { deleteNode, duplicateNode } = useContext(flowContext) return ( <> @@ -76,10 +76,22 @@ const CanvasNode = ({ data }) => {
{ + duplicateNode(data.id) + }} + sx={{ height: 35, width: 35, '&:hover': { color: theme?.palette.primary.main } }} + color={theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit'} + > + + + { deleteNode(data.id) }} - sx={{ height: 35, width: 35, mr: 1 }} + sx={{ height: 35, width: 35, mr: 1, '&:hover': { color: 'red' } }} + color={theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit'} > diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 019cddeb..686448ad 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -4,13 +4,17 @@ import { useEffect, useRef, useState, useContext } from 'react' // material-ui import { useTheme, styled } from '@mui/material/styles' -import { Box, Typography, Tooltip } from '@mui/material' +import { Box, Typography, Tooltip, IconButton } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' +import { IconArrowsMaximize } from '@tabler/icons' + +// project import import { Dropdown } from 'ui-component/dropdown/Dropdown' import { Input } from 'ui-component/input/Input' import { File } from 'ui-component/file/File' +import { SwitchInput } from 'ui-component/switch/Switch' import { flowContext } from 'store/context/ReactFlowContext' -import { isValidConnection } from 'utils/genericHelper' +import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper' const CustomWidthTooltip = styled(({ className, ...props }) => )({ [`& .${tooltipClasses.tooltip}`]: { @@ -23,9 +27,35 @@ const CustomWidthTooltip = styled(({ className, ...props }) => { const theme = useTheme() const ref = useRef(null) + const { reactFlowInstance } = useContext(flowContext) const updateNodeInternals = useUpdateNodeInternals() const [position, setPosition] = useState(0) - const { reactFlowInstance } = useContext(flowContext) + const [showExpandDialog, setShowExpandDialog] = useState(false) + const [expandDialogProps, setExpandDialogProps] = useState({}) + + const onExpandDialogClicked = (value, inputParam) => { + const dialogProp = { + value, + inputParam, + disabled, + confirmButtonName: 'Save', + cancelButtonName: 'Cancel' + } + + if (!disabled) { + const nodes = reactFlowInstance.getNodes() + const edges = reactFlowInstance.getEdges() + const nodesForVariable = inputParam.acceptVariable ? getAvailableNodesForVariable(nodes, edges, data.id, inputParam.id) : [] + dialogProp.availableNodesForVariable = nodesForVariable + } + setExpandDialogProps(dialogProp) + setShowExpandDialog(true) + } + + const onExpandDialogSave = (newValue, inputParamName) => { + setShowExpandDialog(false) + data.inputs[inputParamName] = newValue + } useEffect(() => { if (ref.current && ref.current.offsetTop && ref.current.clientHeight) { @@ -68,11 +98,47 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false }) = {inputParam && ( <> + {inputParam.acceptVariable && ( + + isValidConnection(connection, reactFlowInstance)} + style={{ + height: 10, + width: 10, + backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary, + top: position + }} + /> + + )} - - {inputParam.label} - {!inputParam.optional &&  *} - +
+ + {inputParam.label} + {!inputParam.optional &&  *} + +
+ {inputParam.type === 'string' && inputParam.rows && ( + + onExpandDialogClicked(data.inputs[inputParam.name] ?? inputParam.default ?? '', inputParam) + } + > + + + )} +
{inputParam.type === 'file' && ( )} + {inputParam.type === 'boolean' && ( + (data.inputs[inputParam.name] = newValue)} + value={data.inputs[inputParam.name] ?? inputParam.default ?? false} + /> + )} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( (data.inputs[inputParam.name] = newValue)} value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} + showDialog={showExpandDialog} + dialogProps={expandDialogProps} + onDialogCancel={() => setShowExpandDialog(false)} + onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} /> )} {inputParam.type === 'options' && ( diff --git a/packages/ui/src/views/canvas/NodeOutputHandler.js b/packages/ui/src/views/canvas/NodeOutputHandler.js index 62babb7b..c5fc1345 100644 --- a/packages/ui/src/views/canvas/NodeOutputHandler.js +++ b/packages/ui/src/views/canvas/NodeOutputHandler.js @@ -8,6 +8,7 @@ import { Box, Typography, Tooltip } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' import { flowContext } from 'store/context/ReactFlowContext' import { isValidConnection } from 'utils/genericHelper' +import { Dropdown } from 'ui-component/dropdown/Dropdown' const CustomWidthTooltip = styled(({ className, ...props }) => )({ [`& .${tooltipClasses.tooltip}`]: { @@ -17,11 +18,12 @@ const CustomWidthTooltip = styled(({ className, ...props }) => { +const NodeOutputHandler = ({ outputAnchor, data, disabled = false }) => { const theme = useTheme() const ref = useRef(null) const updateNodeInternals = useUpdateNodeInternals() const [position, setPosition] = useState(0) + const [dropdownValue, setDropdownValue] = useState(null) const { reactFlowInstance } = useContext(flowContext) useEffect(() => { @@ -39,33 +41,82 @@ const NodeOutputHandler = ({ outputAnchor, data }) => { }, 0) }, [data.id, position, updateNodeInternals]) + useEffect(() => { + if (dropdownValue) { + setTimeout(() => { + updateNodeInternals(data.id) + }, 0) + } + }, [data.id, dropdownValue, updateNodeInternals]) + return (
- - isValidConnection(connection, reactFlowInstance)} - style={{ - height: 10, - width: 10, - backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary, - top: position - }} - /> - - - {outputAnchor.label} - + {outputAnchor.type !== 'options' && !outputAnchor.options && ( + <> + + isValidConnection(connection, reactFlowInstance)} + style={{ + height: 10, + width: 10, + backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary, + top: position + }} + /> + + + {outputAnchor.label} + + + )} + {outputAnchor.type === 'options' && outputAnchor.options && outputAnchor.options.length > 0 && ( + <> + opt.name === data.outputs?.[outputAnchor.name])?.type ?? outputAnchor.type + } + > + opt.name === data.outputs?.[outputAnchor.name])?.id ?? ''} + isValidConnection={(connection) => isValidConnection(connection, reactFlowInstance)} + style={{ + height: 10, + width: 10, + backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary, + top: position + }} + /> + + + { + setDropdownValue(newValue) + data.outputs[outputAnchor.name] = newValue + }} + value={data.outputs[outputAnchor.name] ?? outputAnchor.default ?? 'choose an option'} + /> + + + )}
) } NodeOutputHandler.propTypes = { outputAnchor: PropTypes.object, - data: PropTypes.object + data: PropTypes.object, + disabled: PropTypes.bool } export default NodeOutputHandler diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index edbfbacc..ad725ff8 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -38,7 +38,7 @@ import useConfirm from 'hooks/useConfirm' import { IconX } from '@tabler/icons' // utils -import { getUniqueNodeId, initNode, getEdgeLabelName } from 'utils/genericHelper' +import { getUniqueNodeId, initNode, getEdgeLabelName, rearrangeToolsOrdering } from 'utils/genericHelper' import useNotifier from 'utils/useNotifier' const nodeTypes = { customNode: CanvasNode } @@ -108,10 +108,18 @@ const Canvas = () => { setTimeout(() => setDirty(), 0) let value const inputAnchor = node.data.inputAnchors.find((ancr) => ancr.name === targetInput) + const inputParam = node.data.inputParams.find((param) => param.name === targetInput) + if (inputAnchor && inputAnchor.list) { const newValues = node.data.inputs[targetInput] || [] - newValues.push(`{{${sourceNodeId}.data.instance}}`) + if (targetInput === 'tools') { + rearrangeToolsOrdering(newValues, sourceNodeId) + } else { + newValues.push(`{{${sourceNodeId}.data.instance}}`) + } value = newValues + } else if (inputParam && inputParam.acceptVariable) { + value = node.data.inputs[targetInput] || '' } else { value = `{{${sourceNodeId}.data.instance}}` } @@ -489,6 +497,7 @@ const Canvas = () => { onConnect={onConnect} onInit={setReactFlowInstance} fitView + minZoom={0.1} > { const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -116,7 +118,7 @@ export const ChatMessage = ({ chatflowid }) => { const scrollToBottom = () => { if (ps.current) { - ps.current.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: 'smooth' }) + ps.current.scrollTo({ top: maxScroll, behavior: 'smooth' }) } } diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvas.js b/packages/ui/src/views/marketplaces/MarketplaceCanvas.js index 6256051f..66ce0283 100644 --- a/packages/ui/src/views/marketplaces/MarketplaceCanvas.js +++ b/packages/ui/src/views/marketplaces/MarketplaceCanvas.js @@ -83,6 +83,7 @@ const MarketplaceCanvas = () => { nodeTypes={nodeTypes} edgeTypes={edgeTypes} fitView + minZoom={0.1} > { )} {data.inputAnchors.map((inputAnchor, index) => ( - + ))} {data.inputParams.map((inputParam, index) => ( @@ -108,7 +108,7 @@ const MarketplaceCanvasNode = ({ data }) => { {data.outputAnchors.map((outputAnchor, index) => ( - + ))}