Merge branch 'FlowiseAI:main' into main

This commit is contained in:
Darien Kindlund
2024-01-20 00:01:41 -05:00
committed by GitHub
104 changed files with 4615 additions and 2059 deletions
@@ -0,0 +1,26 @@
name: autoSyncMergedPullRequest
on:
pull_request:
types:
- closed
branches: [ "main" ]
jobs:
autoSyncMergedPullRequest:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: Show PR info
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo The PR #${{ github.event.pull_request.number }} was merged on main branch!
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.AUTOSYNC_TOKEN }}
repository: ${{ secrets.AUTOSYNC_CH_URL }}
event-type: ${{ secrets.AUTOSYNC_PR_EVENT_TYPE }}
client-payload: '{"ref": "${{ github.ref }}", "prNumber": "${{ github.event.pull_request.number }}", "sha": "${{ github.sha }}"}'
@@ -0,0 +1,31 @@
name: autoSyncSingleCommit
on:
push:
branches:
- main
jobs:
doNotAutoSyncSingleCommit:
if: github.event.commits[1] != null
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: IGNORE autoSyncSingleCommit
run: |
echo This single commit has came from a merged commit. We will ignore it. This case is handled in autoSyncMergedPullRequest workflow for merge commits comming from merged pull requests only! Beware, the regular merge commits are not handled by any workflow for the moment.
autoSyncSingleCommit:
if: github.event.commits[1] == null
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: autoSyncSingleCommit
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: |
echo Autosync a single commit with id: ${{ github.sha }} from openSource main branch towards cloud hosted version.
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.AUTOSYNC_TOKEN }}
repository: ${{ secrets.AUTOSYNC_CH_URL }}
event-type: ${{ secrets.AUTOSYNC_SC_EVENT_TYPE }}
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
+1
View File
@@ -138,6 +138,7 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package
| DATABASE_NAME | 数据库名称(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | | DATABASE_NAME | 数据库名称(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | |
| SECRETKEY_PATH | 保存加密密钥(用于加密/解密凭据)的位置 | 字符串 | `your-path/Flowise/packages/server` | | SECRETKEY_PATH | 保存加密密钥(用于加密/解密凭据)的位置 | 字符串 | `your-path/Flowise/packages/server` |
| FLOWISE_SECRETKEY_OVERWRITE | 加密密钥用于替代存储在 SECRETKEY_PATH 中的密钥 | 字符串 | | FLOWISE_SECRETKEY_OVERWRITE | 加密密钥用于替代存储在 SECRETKEY_PATH 中的密钥 | 字符串 |
| DISABLE_FLOWISE_TELEMETRY | 关闭遥测 | 字符串 |
您也可以在使用 `npx` 时指定环境变量。例如: 您也可以在使用 `npx` 时指定环境变量。例如:
+1
View File
@@ -141,6 +141,7 @@ Flowise support different environment variables to configure your instance. You
| DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false |
| SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` |
| FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String |
| DISABLE_FLOWISE_TELEMETRY | Turn off telemetry | Boolean |
You can also specify the env variables when using `npx`. For example: You can also specify the env variables when using `npx`. For example:
+25 -10
View File
@@ -145,25 +145,40 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package
## 🌐 自托管 ## 🌐 自托管
### [Railway](https://docs.flowiseai.com/deployment/railway) 在您现有的基础设施中部署自托管的 Flowise,我们支持各种[部署](https://docs.flowiseai.com/configuration/deployment)
[![在 Railway 上部署](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) - [AWS](https://docs.flowiseai.com/deployment/aws)
- [Azure](https://docs.flowiseai.com/deployment/azure)
- [Digital Ocean](https://docs.flowiseai.com/deployment/digital-ocean)
- [GCP](https://docs.flowiseai.com/deployment/gcp)
- <details>
<summary>其他</summary>
### [Render](https://docs.flowiseai.com/deployment/render) - [Railway](https://docs.flowiseai.com/deployment/railway)
[![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) [![在 Railway 上部署](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9)
### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) - [Render](https://docs.flowiseai.com/deployment/render)
<a href="https://huggingface.co/spaces/FlowiseAI/Flowise"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg" alt="HuggingFace Spaces"></a> [![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render)
### [AWS](https://docs.flowiseai.com/deployment/aws) - [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face)
### [Azure](https://docs.flowiseai.com/deployment/azure) <a href="https://huggingface.co/spaces/FlowiseAI/Flowise"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg" alt="HuggingFace Spaces"></a>
### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) - [Elestio](https://elest.io/open-source/flowiseai)
### [GCP](https://docs.flowiseai.com/deployment/gcp) [![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai)
- [Sealos](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise)
[![部署到 Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dflowise)
- [RepoCloud](https://repocloud.io/details/?app_id=29)
[![部署到 RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=29)
</details>
## 💻 云托管 ## 💻 云托管
+1 -1
View File
@@ -33,4 +33,4 @@ scenarios:
# Seconds # Seconds
# Total Users = 2 + 3 + 3 = 8 # Total Users = 2 + 3 + 3 = 8
# Each making 1 HTTP call # Each making 1 HTTP call
# Over a duration of 3 seconds # Over a durations of 3 seconds
+3 -1
View File
@@ -25,4 +25,6 @@ LOG_PATH=/root/.flowise/logs
# LANGCHAIN_TRACING_V2=true # LANGCHAIN_TRACING_V2=true
# LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
# LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_API_KEY=your_api_key
# LANGCHAIN_PROJECT=your_project # LANGCHAIN_PROJECT=your_project
# DISABLE_FLOWISE_TELEMETRY=true
+1 -1
View File
@@ -1,6 +1,6 @@
# Flowise Docker Hub Image # Flowise Docker Hub Image
Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/flowiseai/flowise/general) Starts Flowise from [DockerHub Image](https://hub.docker.com/r/flowiseai/flowise)
## Usage ## Usage
+1
View File
@@ -22,6 +22,7 @@ services:
- FLOWISE_SECRETKEY_OVERWRITE=${FLOWISE_SECRETKEY_OVERWRITE} - FLOWISE_SECRETKEY_OVERWRITE=${FLOWISE_SECRETKEY_OVERWRITE}
- LOG_LEVEL=${LOG_LEVEL} - LOG_LEVEL=${LOG_LEVEL}
- LOG_PATH=${LOG_PATH} - LOG_PATH=${LOG_PATH}
- DISABLE_FLOWISE_TELEMETRY=${DISABLE_FLOWISE_TELEMETRY}
ports: ports:
- '${PORT}:${PORT}' - '${PORT}:${PORT}'
volumes: volumes:
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "flowise", "name": "flowise",
"version": "1.4.9", "version": "1.4.10",
"private": true, "private": true,
"homepage": "https://flowiseai.com", "homepage": "https://flowiseai.com",
"workspaces": [ "workspaces": [
@@ -48,7 +48,7 @@
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"run-script-os": "^1.1.6", "run-script-os": "^1.1.6",
"turbo": "1.7.4", "turbo": "^1.7.4",
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"engines": { "engines": {
@@ -0,0 +1,34 @@
import { INodeParams, INodeCredential } from '../src/Interface'
class AstraDBApi implements INodeCredential {
label: string
name: string
version: number
description: string
inputs: INodeParams[]
constructor() {
this.label = 'Astra DB API'
this.name = 'AstraDBApi'
this.version = 1.0
this.inputs = [
{
label: 'Astra DB Collection Name',
name: 'collectionName',
type: 'string'
},
{
label: 'Astra DB Application Token',
name: 'applicationToken',
type: 'password'
},
{
label: 'Astra DB Api Endpoint',
name: 'dbEndPoint',
type: 'string'
}
]
}
}
module.exports = { credClass: AstraDBApi }
@@ -0,0 +1,23 @@
import { INodeParams, INodeCredential } from '../src/Interface'
class LocalAIApi implements INodeCredential {
label: string
name: string
version: number
inputs: INodeParams[]
constructor() {
this.label = 'LocalAI API'
this.name = 'localAIApi'
this.version = 1.0
this.inputs = [
{
label: 'LocalAI Api Key',
name: 'localAIApiKey',
type: 'password'
}
]
}
}
module.exports = { credClass: LocalAIApi }
@@ -16,11 +16,6 @@ class PineconeApi implements INodeCredential {
label: 'Pinecone Api Key', label: 'Pinecone Api Key',
name: 'pineconeApiKey', name: 'pineconeApiKey',
type: 'password' type: 'password'
},
{
label: 'Pinecone Environment',
name: 'pineconeEnv',
type: 'string'
} }
] ]
} }
@@ -1,11 +1,14 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecutorOptions } from 'langchain/agents'
import { Tool } from 'langchain/tools' import { Tool } from 'langchain/tools'
import { BaseChatMemory } from 'langchain/memory'
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
import { BaseChatModel } from 'langchain/chat_models/base' import { BaseChatModel } from 'langchain/chat_models/base'
import { flatten } from 'lodash' import { flatten } from 'lodash'
import { additionalCallbacks } from '../../../src/handler' import { AgentStep, BaseMessage, ChainValues, AIMessage, HumanMessage } from 'langchain/schema'
import { RunnableSequence } from 'langchain/schema/runnable'
import { getBaseClasses } from '../../../src/utils'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
import { AgentExecutor } from '../../../src/agents'
import { ChatConversationalAgent } from 'langchain/agents'
import { renderTemplate } from '@langchain/core/prompts'
const DEFAULT_PREFIX = `Assistant is a large language model trained by OpenAI. const DEFAULT_PREFIX = `Assistant is a large language model trained by OpenAI.
@@ -15,6 +18,15 @@ Assistant is constantly learning and improving, and its capabilities are constan
Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.` Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.`
const TEMPLATE_TOOL_RESPONSE = `TOOL RESPONSE:
---------------------
{observation}
USER'S INPUT
--------------------
Okay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else.`
class ConversationalAgent_Agents implements INode { class ConversationalAgent_Agents implements INode {
label: string label: string
name: string name: string
@@ -25,8 +37,9 @@ class ConversationalAgent_Agents implements INode {
category: string category: string
baseClasses: string[] baseClasses: string[]
inputs: INodeParams[] inputs: INodeParams[]
sessionId?: string
constructor() { constructor(fields?: { sessionId?: string }) {
this.label = 'Conversational Agent' this.label = 'Conversational Agent'
this.name = 'conversationalAgent' this.name = 'conversationalAgent'
this.version = 2.0 this.version = 2.0
@@ -43,7 +56,7 @@ class ConversationalAgent_Agents implements INode {
list: true list: true
}, },
{ {
label: 'Language Model', label: 'Chat Model',
name: 'model', name: 'model',
type: 'BaseChatModel' type: 'BaseChatModel'
}, },
@@ -62,52 +75,114 @@ class ConversationalAgent_Agents implements INode {
additionalParams: true additionalParams: true
} }
] ]
this.sessionId = fields?.sessionId
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const model = nodeData.inputs?.model as BaseChatModel return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
let tools = nodeData.inputs?.tools as Tool[]
tools = flatten(tools)
const memory = nodeData.inputs?.memory as BaseChatMemory
const systemMessage = nodeData.inputs?.systemMessage as string
const obj: InitializeAgentExecutorOptions = {
agentType: 'chat-conversational-react-description',
verbose: process.env.DEBUG === 'true' ? true : false
}
const agentArgs: any = {}
if (systemMessage) {
agentArgs.systemMessage = systemMessage
}
if (Object.keys(agentArgs).length) obj.agentArgs = agentArgs
const executor = await initializeAgentExecutorWithOptions(tools, model, obj)
executor.memory = memory
return executor
} }
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
const executor = nodeData.instance as AgentExecutor const memory = nodeData.inputs?.memory as FlowiseMemory
const memory = nodeData.inputs?.memory as BaseChatMemory const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
if (options && options.chatHistory) {
const chatHistoryClassName = memory.chatHistory.constructor.name
// Only replace when its In-Memory
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
memory.chatHistory = mapChatHistory(options)
executor.memory = memory
}
}
;(executor.memory as any).returnMessages = true // Return true for BaseChatModel
const loggerHandler = new ConsoleCallbackHandler(options.logger)
const callbacks = await additionalCallbacks(nodeData, options) const callbacks = await additionalCallbacks(nodeData, options)
const result = await executor.call({ input }, [...callbacks]) let res: ChainValues = {}
return result?.output
if (options.socketIO && options.socketIOClientId) {
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] })
} else {
res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] })
}
await memory.addChatMessages(
[
{
text: input,
type: 'userMessage'
},
{
text: res?.output,
type: 'apiMessage'
}
],
this.sessionId
)
return res?.output
} }
} }
const prepareAgent = async (
nodeData: INodeData,
flowObj: { sessionId?: string; chatId?: string; input?: string },
chatHistory: IMessage[] = []
) => {
const model = nodeData.inputs?.model as BaseChatModel
let tools = nodeData.inputs?.tools as Tool[]
tools = flatten(tools)
const memory = nodeData.inputs?.memory as FlowiseMemory
const systemMessage = nodeData.inputs?.systemMessage as string
const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history'
const inputKey = memory.inputKey ? memory.inputKey : 'input'
/** Bind a stop token to the model */
const modelWithStop = model.bind({
stop: ['\nObservation']
})
const outputParser = ChatConversationalAgent.getDefaultOutputParser({
llm: model,
toolNames: tools.map((tool) => tool.name)
})
const prompt = ChatConversationalAgent.createPrompt(tools, {
systemMessage: systemMessage ? systemMessage : DEFAULT_PREFIX,
outputParser
})
const runnableAgent = RunnableSequence.from([
{
[inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input,
agent_scratchpad: async (i: { input: string; steps: AgentStep[] }) => await constructScratchPad(i.steps),
[memoryKey]: async (_: { input: string; steps: AgentStep[] }) => {
const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
return messages ?? []
}
},
prompt,
modelWithStop,
outputParser
])
const executor = AgentExecutor.fromAgentAndTools({
agent: runnableAgent,
tools,
sessionId: flowObj?.sessionId,
chatId: flowObj?.chatId,
input: flowObj?.input,
verbose: process.env.DEBUG === 'true' ? true : false
})
return executor
}
const constructScratchPad = async (steps: AgentStep[]): Promise<BaseMessage[]> => {
const thoughts: BaseMessage[] = []
for (const step of steps) {
thoughts.push(new AIMessage(step.action.log))
thoughts.push(
new HumanMessage(
renderTemplate(TEMPLATE_TOOL_RESPONSE, 'f-string', {
observation: step.observation
})
)
)
}
return thoughts
}
module.exports = { nodeClass: ConversationalAgent_Agents } module.exports = { nodeClass: ConversationalAgent_Agents }
@@ -1,9 +1,14 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema'
import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents'
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
import { flatten } from 'lodash' import { flatten } from 'lodash'
import { BaseChatMemory } from 'langchain/memory' import { ChatOpenAI } from 'langchain/chat_models/openai'
import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'
import { formatToOpenAIFunction } from 'langchain/tools'
import { RunnableSequence } from 'langchain/schema/runnable'
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser'
import { AgentExecutor, formatAgentSteps } from '../../../src/agents'
const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.` const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.`
@@ -17,8 +22,9 @@ class ConversationalRetrievalAgent_Agents implements INode {
category: string category: string
baseClasses: string[] baseClasses: string[]
inputs: INodeParams[] inputs: INodeParams[]
sessionId?: string
constructor() { constructor(fields?: { sessionId?: string }) {
this.label = 'Conversational Retrieval Agent' this.label = 'Conversational Retrieval Agent'
this.name = 'conversationalRetrievalAgent' this.name = 'conversationalRetrievalAgent'
this.version = 3.0 this.version = 3.0
@@ -54,55 +60,96 @@ class ConversationalRetrievalAgent_Agents implements INode {
additionalParams: true additionalParams: true
} }
] ]
this.sessionId = fields?.sessionId
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const model = nodeData.inputs?.model return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
const memory = nodeData.inputs?.memory as BaseChatMemory
const systemMessage = nodeData.inputs?.systemMessage as string
let tools = nodeData.inputs?.tools
tools = flatten(tools)
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: 'openai-functions',
verbose: process.env.DEBUG === 'true' ? true : false,
agentArgs: {
prefix: systemMessage ?? defaultMessage
},
returnIntermediateSteps: true
})
executor.memory = memory
return executor
} }
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
const executor = nodeData.instance as AgentExecutor const memory = nodeData.inputs?.memory as FlowiseMemory
const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
if (executor.memory) {
;(executor.memory as any).memoryKey = 'chat_history'
;(executor.memory as any).outputKey = 'output'
;(executor.memory as any).returnMessages = true
const chatHistoryClassName = (executor.memory as any).chatHistory.constructor.name
// Only replace when its In-Memory
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
;(executor.memory as any).chatHistory = mapChatHistory(options)
}
}
const loggerHandler = new ConsoleCallbackHandler(options.logger) const loggerHandler = new ConsoleCallbackHandler(options.logger)
const callbacks = await additionalCallbacks(nodeData, options) const callbacks = await additionalCallbacks(nodeData, options)
let res: ChainValues = {}
if (options.socketIO && options.socketIOClientId) { if (options.socketIO && options.socketIOClientId) {
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
const result = await executor.call({ input }, [loggerHandler, handler, ...callbacks]) res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] })
return result?.output
} else { } else {
const result = await executor.call({ input }, [loggerHandler, ...callbacks]) res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] })
return result?.output
} }
await memory.addChatMessages(
[
{
text: input,
type: 'userMessage'
},
{
text: res?.output,
type: 'apiMessage'
}
],
this.sessionId
)
return res?.output
} }
} }
const prepareAgent = (
nodeData: INodeData,
flowObj: { sessionId?: string; chatId?: string; input?: string },
chatHistory: IMessage[] = []
) => {
const model = nodeData.inputs?.model as ChatOpenAI
const memory = nodeData.inputs?.memory as FlowiseMemory
const systemMessage = nodeData.inputs?.systemMessage as string
let tools = nodeData.inputs?.tools
tools = flatten(tools)
const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history'
const inputKey = memory.inputKey ? memory.inputKey : 'input'
const prompt = ChatPromptTemplate.fromMessages([
['ai', systemMessage ? systemMessage : defaultMessage],
new MessagesPlaceholder(memoryKey),
['human', `{${inputKey}}`],
new MessagesPlaceholder('agent_scratchpad')
])
const modelWithFunctions = model.bind({
functions: [...tools.map((tool: any) => formatToOpenAIFunction(tool))]
})
const runnableAgent = RunnableSequence.from([
{
[inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input,
agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps),
[memoryKey]: async (_: { input: string; steps: AgentStep[] }) => {
const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
return messages ?? []
}
},
prompt,
modelWithFunctions,
new OpenAIFunctionsAgentOutputParser()
])
const executor = AgentExecutor.fromAgentAndTools({
agent: runnableAgent,
tools,
sessionId: flowObj?.sessionId,
chatId: flowObj?.chatId,
input: flowObj?.input,
returnIntermediateSteps: true,
verbose: process.env.DEBUG === 'true' ? true : false
})
return executor
}
module.exports = { nodeClass: ConversationalRetrievalAgent_Agents } module.exports = { nodeClass: ConversationalRetrievalAgent_Agents }
@@ -96,45 +96,51 @@ class OpenAIAssistant_Agents implements INode {
return null return null
} }
//@ts-ignore async clearChatMessages(nodeData: INodeData, options: ICommonObject, sessionIdObj: { type: string; id: string }): Promise<void> {
memoryMethods = { const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { const appDataSource = options.appDataSource as DataSource
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string const databaseEntities = options.databaseEntities as IDatabaseEntity
const appDataSource = options.appDataSource as DataSource
const databaseEntities = options.databaseEntities as IDatabaseEntity
let sessionId = nodeData.inputs?.sessionId as string
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
id: selectedAssistantId id: selectedAssistantId
})
if (!assistant) {
options.logger.error(`Assistant ${selectedAssistantId} not found`)
return
}
if (!sessionIdObj) return
let sessionId = ''
if (sessionIdObj.type === 'chatId') {
const chatId = sessionIdObj.id
const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
chatId
}) })
if (!chatmsg) {
if (!assistant) { options.logger.error(`Chat Message with Chat Id: ${chatId} not found`)
options.logger.error(`Assistant ${selectedAssistantId} not found`)
return return
} }
sessionId = chatmsg.sessionId
} else if (sessionIdObj.type === 'threadId') {
sessionId = sessionIdObj.id
}
if (!sessionId && options.chatId) { const credentialData = await getCredentialData(assistant.credential ?? '', options)
const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
chatId: options.chatId if (!openAIApiKey) {
}) options.logger.error(`OpenAI ApiKey not found`)
if (!chatmsg) { return
options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) }
return
}
sessionId = chatmsg.sessionId
}
const credentialData = await getCredentialData(assistant.credential ?? '', options) const openai = new OpenAI({ apiKey: openAIApiKey })
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) options.logger.info(`Clearing OpenAI Thread ${sessionId}`)
if (!openAIApiKey) { try {
options.logger.error(`OpenAI ApiKey not found`)
return
}
const openai = new OpenAI({ apiKey: openAIApiKey })
options.logger.info(`Clearing OpenAI Thread ${sessionId}`)
if (sessionId) await openai.beta.threads.del(sessionId) if (sessionId) await openai.beta.threads.del(sessionId)
options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`)
} catch (e) {
throw new Error(e)
} }
} }
@@ -297,7 +303,11 @@ class OpenAIAssistant_Agents implements INode {
options.socketIO.to(options.socketIOClientId).emit('tool', tool.name) options.socketIO.to(options.socketIOClientId).emit('tool', tool.name)
try { try {
const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId) const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, {
sessionId: threadId,
chatId: options.chatId,
input
})
await analyticHandlers.onToolEnd(toolIds, toolOutput) await analyticHandlers.onToolEnd(toolIds, toolOutput)
submitToolOutputs.push({ submitToolOutputs.push({
tool_call_id: actions[i].toolCallId, tool_call_id: actions[i].toolCallId,
@@ -462,6 +472,7 @@ class OpenAIAssistant_Agents implements INode {
const imageRegex = /<img[^>]*\/>/g const imageRegex = /<img[^>]*\/>/g
let llmOutput = returnVal.replace(imageRegex, '') let llmOutput = returnVal.replace(imageRegex, '')
llmOutput = llmOutput.replace('<br/>', '') llmOutput = llmOutput.replace('<br/>', '')
await analyticHandlers.onLLMEnd(llmIds, llmOutput) await analyticHandlers.onLLMEnd(llmIds, llmOutput)
await analyticHandlers.onChainEnd(parentIds, messageData, true) await analyticHandlers.onChainEnd(parentIds, messageData, true)
@@ -1,17 +1,14 @@
import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema'
import { AgentExecutor as LCAgentExecutor, AgentExecutorInput } from 'langchain/agents'
import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema'
import { OutputParserException } from 'langchain/schema/output_parser'
import { CallbackManagerForChainRun } from 'langchain/callbacks'
import { formatToOpenAIFunction } from 'langchain/tools'
import { ToolInputParsingException, Tool } from '@langchain/core/tools'
import { getBaseClasses } from '../../../src/utils' import { getBaseClasses } from '../../../src/utils'
import { flatten } from 'lodash' import { flatten } from 'lodash'
import { RunnableSequence } from 'langchain/schema/runnable' import { RunnableSequence } from 'langchain/schema/runnable'
import { formatToOpenAIFunction } from 'langchain/tools'
import { ChatOpenAI } from 'langchain/chat_models/openai'
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'
import { ChatOpenAI } from 'langchain/chat_models/openai'
import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser'
import { AgentExecutor, formatAgentSteps } from '../../../src/agents'
class OpenAIFunctionAgent_Agents implements INode { class OpenAIFunctionAgent_Agents implements INode {
label: string label: string
@@ -25,7 +22,7 @@ class OpenAIFunctionAgent_Agents implements INode {
inputs: INodeParams[] inputs: INodeParams[]
sessionId?: string sessionId?: string
constructor(fields: { sessionId?: string }) { constructor(fields?: { sessionId?: string }) {
this.label = 'OpenAI Function Agent' this.label = 'OpenAI Function Agent'
this.name = 'openAIFunctionAgent' this.name = 'openAIFunctionAgent'
this.version = 3.0 this.version = 3.0
@@ -33,7 +30,7 @@ class OpenAIFunctionAgent_Agents implements INode {
this.category = 'Agents' this.category = 'Agents'
this.icon = 'function.svg' this.icon = 'function.svg'
this.description = `An agent that uses Function Calling to pick the tool and args to call` this.description = `An agent that uses Function Calling to pick the tool and args to call`
this.baseClasses = [this.type, ...getBaseClasses(LCAgentExecutor)] this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)]
this.inputs = [ this.inputs = [
{ {
label: 'Allowed Tools', label: 'Allowed Tools',
@@ -63,19 +60,13 @@ class OpenAIFunctionAgent_Agents implements INode {
this.sessionId = fields?.sessionId this.sessionId = fields?.sessionId
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const memory = nodeData.inputs?.memory as FlowiseMemory return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
const executor = prepareAgent(nodeData, this.sessionId)
if (memory) executor.memory = memory
return executor
} }
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
const memory = nodeData.inputs?.memory as FlowiseMemory const memory = nodeData.inputs?.memory as FlowiseMemory
const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
const executor = prepareAgent(nodeData, this.sessionId)
const loggerHandler = new ConsoleCallbackHandler(options.logger) const loggerHandler = new ConsoleCallbackHandler(options.logger)
const callbacks = await additionalCallbacks(nodeData, options) const callbacks = await additionalCallbacks(nodeData, options)
@@ -107,17 +98,11 @@ class OpenAIFunctionAgent_Agents implements INode {
} }
} }
const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => const prepareAgent = (
steps.flatMap(({ action, observation }) => { nodeData: INodeData,
if ('messageLog' in action && action.messageLog !== undefined) { flowObj: { sessionId?: string; chatId?: string; input?: string },
const log = action.messageLog as BaseMessage[] chatHistory: IMessage[] = []
return log.concat(new FunctionMessage(observation, action.tool)) ) => {
} else {
return [new AIMessage(action.log)]
}
})
const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
const model = nodeData.inputs?.model as ChatOpenAI const model = nodeData.inputs?.model as ChatOpenAI
const memory = nodeData.inputs?.memory as FlowiseMemory const memory = nodeData.inputs?.memory as FlowiseMemory
const systemMessage = nodeData.inputs?.systemMessage as string const systemMessage = nodeData.inputs?.systemMessage as string
@@ -127,7 +112,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
const inputKey = memory.inputKey ? memory.inputKey : 'input' const inputKey = memory.inputKey ? memory.inputKey : 'input'
const prompt = ChatPromptTemplate.fromMessages([ const prompt = ChatPromptTemplate.fromMessages([
['ai', systemMessage ? systemMessage : `You are a helpful AI assistant.`], ['system', systemMessage ? systemMessage : `You are a helpful AI assistant.`],
new MessagesPlaceholder(memoryKey), new MessagesPlaceholder(memoryKey),
['human', `{${inputKey}}`], ['human', `{${inputKey}}`],
new MessagesPlaceholder('agent_scratchpad') new MessagesPlaceholder('agent_scratchpad')
@@ -142,7 +127,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
[inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input,
agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps),
[memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => {
const messages = (await memory.getChatMessages(sessionId, true)) as BaseMessage[] const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
return messages ?? [] return messages ?? []
} }
}, },
@@ -154,231 +139,13 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
const executor = AgentExecutor.fromAgentAndTools({ const executor = AgentExecutor.fromAgentAndTools({
agent: runnableAgent, agent: runnableAgent,
tools, tools,
sessionId sessionId: flowObj?.sessionId,
chatId: flowObj?.chatId,
input: flowObj?.input,
verbose: process.env.DEBUG === 'true' ? true : false
}) })
return executor return executor
} }
type AgentExecutorOutput = ChainValues
class AgentExecutor extends LCAgentExecutor {
sessionId?: string
static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string }): AgentExecutor {
const newInstance = new AgentExecutor(fields)
if (fields.sessionId) newInstance.sessionId = fields.sessionId
return newInstance
}
shouldContinueIteration(iterations: number): boolean {
return this.maxIterations === undefined || iterations < this.maxIterations
}
async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise<AgentExecutorOutput> {
const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t]))
const steps: AgentStep[] = []
let iterations = 0
const getOutput = async (finishStep: AgentFinish): Promise<AgentExecutorOutput> => {
const { returnValues } = finishStep
const additional = await this.agent.prepareForOutput(returnValues, steps)
if (this.returnIntermediateSteps) {
return { ...returnValues, intermediateSteps: steps, ...additional }
}
await runManager?.handleAgentEnd(finishStep)
return { ...returnValues, ...additional }
}
while (this.shouldContinueIteration(iterations)) {
let output
try {
output = await this.agent.plan(steps, inputs, runManager?.getChild())
} catch (e) {
if (e instanceof OutputParserException) {
let observation
let text = e.message
if (this.handleParsingErrors === true) {
if (e.sendToLLM) {
observation = e.observation
text = e.llmOutput ?? ''
} else {
observation = 'Invalid or incomplete response'
}
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
output = {
tool: '_Exception',
toolInput: observation,
log: text
} as AgentAction
} else {
throw e
}
}
// Check if the agent has finished
if ('returnValues' in output) {
return getOutput(output)
}
let actions: AgentAction[]
if (Array.isArray(output)) {
actions = output as AgentAction[]
} else {
actions = [output as AgentAction]
}
const newSteps = await Promise.all(
actions.map(async (action) => {
await runManager?.handleAgentAction(action)
const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()]
let observation
try {
// here we need to override Tool call method to include sessionId as parameter
observation = tool
? // @ts-ignore
await tool.call(action.toolInput, runManager?.getChild(), undefined, this.sessionId)
: `${action.tool} is not a valid tool, try another one.`
} catch (e) {
if (e instanceof ToolInputParsingException) {
if (this.handleParsingErrors === true) {
observation = 'Invalid or incomplete tool input. Please try again.'
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
observation = await new ExceptionTool().call(observation, runManager?.getChild())
return { action, observation: observation ?? '' }
}
}
return { action, observation: observation ?? '' }
})
)
steps.push(...newSteps)
const lastStep = steps[steps.length - 1]
const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()]
if (lastTool?.returnDirect) {
return getOutput({
returnValues: { [this.agent.returnValues[0]]: lastStep.observation },
log: ''
})
}
iterations += 1
}
const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs)
return getOutput(finish)
}
async _takeNextStep(
nameToolMap: Record<string, Tool>,
inputs: ChainValues,
intermediateSteps: AgentStep[],
runManager?: CallbackManagerForChainRun
): Promise<AgentFinish | AgentStep[]> {
let output
try {
output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild())
} catch (e) {
if (e instanceof OutputParserException) {
let observation
let text = e.message
if (this.handleParsingErrors === true) {
if (e.sendToLLM) {
observation = e.observation
text = e.llmOutput ?? ''
} else {
observation = 'Invalid or incomplete response'
}
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
output = {
tool: '_Exception',
toolInput: observation,
log: text
} as AgentAction
} else {
throw e
}
}
if ('returnValues' in output) {
return output
}
let actions: AgentAction[]
if (Array.isArray(output)) {
actions = output as AgentAction[]
} else {
actions = [output as AgentAction]
}
const result: AgentStep[] = []
for (const agentAction of actions) {
let observation = ''
if (runManager) {
await runManager?.handleAgentAction(agentAction)
}
if (agentAction.tool in nameToolMap) {
const tool = nameToolMap[agentAction.tool]
try {
// here we need to override Tool call method to include sessionId as parameter
// @ts-ignore
observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, this.sessionId)
} catch (e) {
if (e instanceof ToolInputParsingException) {
if (this.handleParsingErrors === true) {
observation = 'Invalid or incomplete tool input. Please try again.'
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
observation = await new ExceptionTool().call(observation, runManager?.getChild())
}
}
} else {
observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}`
}
result.push({
action: agentAction,
observation
})
}
return result
}
}
class ExceptionTool extends Tool {
name = '_Exception'
description = 'Exception tool'
async _call(query: string) {
return query
}
}
module.exports = { nodeClass: OpenAIFunctionAgent_Agents } module.exports = { nodeClass: OpenAIFunctionAgent_Agents }
@@ -1,14 +1,15 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
import { ConversationChain } from 'langchain/chains' import { ConversationChain } from 'langchain/chains'
import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { getBaseClasses, handleEscapeCharacters } from '../../../src/utils'
import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts'
import { BufferMemory } from 'langchain/memory'
import { BaseChatModel } from 'langchain/chat_models/base' import { BaseChatModel } from 'langchain/chat_models/base'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
import { flatten } from 'lodash' import { RunnableSequence } from 'langchain/schema/runnable'
import { Document } from 'langchain/document' import { StringOutputParser } from 'langchain/schema/output_parser'
import { ConsoleCallbackHandler as LCConsoleCallbackHandler } from '@langchain/core/tracers/console'
let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.`
const inputKey = 'input'
class ConversationChain_Chains implements INode { class ConversationChain_Chains implements INode {
label: string label: string
@@ -20,11 +21,12 @@ class ConversationChain_Chains implements INode {
baseClasses: string[] baseClasses: string[]
description: string description: string
inputs: INodeParams[] inputs: INodeParams[]
sessionId?: string
constructor() { constructor(fields?: { sessionId?: string }) {
this.label = 'Conversation Chain' this.label = 'Conversation Chain'
this.name = 'conversationChain' this.name = 'conversationChain'
this.version = 1.0 this.version = 2.0
this.type = 'ConversationChain' this.type = 'ConversationChain'
this.icon = 'conv.svg' this.icon = 'conv.svg'
this.category = 'Chains' this.category = 'Chains'
@@ -32,7 +34,7 @@ class ConversationChain_Chains implements INode {
this.baseClasses = [this.type, ...getBaseClasses(ConversationChain)] this.baseClasses = [this.type, ...getBaseClasses(ConversationChain)]
this.inputs = [ this.inputs = [
{ {
label: 'Language Model', label: 'Chat Model',
name: 'model', name: 'model',
type: 'BaseChatModel' type: 'BaseChatModel'
}, },
@@ -41,6 +43,14 @@ class ConversationChain_Chains implements INode {
name: 'memory', name: 'memory',
type: 'BaseMemory' type: 'BaseMemory'
}, },
{
label: 'Chat Prompt Template',
name: 'chatPromptTemplate',
type: 'ChatPromptTemplate',
description: 'Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable',
optional: true
},
/* Deprecated
{ {
label: 'Document', label: 'Document',
name: 'document', name: 'document',
@@ -49,87 +59,133 @@ class ConversationChain_Chains implements INode {
'Include whole document into the context window, if you get maximum context length error, please use model with higher context window like Claude 100k, or gpt4 32k', 'Include whole document into the context window, if you get maximum context length error, please use model with higher context window like Claude 100k, or gpt4 32k',
optional: true, optional: true,
list: true list: true
}, },*/
{ {
label: 'System Message', label: 'System Message',
name: 'systemMessagePrompt', name: 'systemMessagePrompt',
type: 'string', type: 'string',
rows: 4, rows: 4,
description: 'If Chat Prompt Template is provided, this will be ignored',
additionalParams: true, additionalParams: true,
optional: true, optional: true,
placeholder: 'You are a helpful assistant that write codes' default: systemMessage,
placeholder: systemMessage
} }
] ]
this.sessionId = fields?.sessionId
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const model = nodeData.inputs?.model as BaseChatModel const chain = prepareChain(nodeData, this.sessionId, options.chatHistory)
const memory = nodeData.inputs?.memory as BufferMemory
const prompt = nodeData.inputs?.systemMessagePrompt as string
const docs = nodeData.inputs?.document as Document[]
const flattenDocs = docs && docs.length ? flatten(docs) : []
const finalDocs = []
for (let i = 0; i < flattenDocs.length; i += 1) {
if (flattenDocs[i] && flattenDocs[i].pageContent) {
finalDocs.push(new Document(flattenDocs[i]))
}
}
let finalText = ''
for (let i = 0; i < finalDocs.length; i += 1) {
finalText += finalDocs[i].pageContent
}
const replaceChar: string[] = ['{', '}']
for (const char of replaceChar) finalText = finalText.replaceAll(char, '')
if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}`
const obj: any = {
llm: model,
memory,
verbose: process.env.DEBUG === 'true' ? true : false
}
const chatPrompt = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage),
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
HumanMessagePromptTemplate.fromTemplate('{input}')
])
obj.prompt = chatPrompt
const chain = new ConversationChain(obj)
return chain return chain
} }
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
const chain = nodeData.instance as ConversationChain const memory = nodeData.inputs?.memory
const memory = nodeData.inputs?.memory as BufferMemory const chain = prepareChain(nodeData, this.sessionId, options.chatHistory)
memory.returnMessages = true // Return true for BaseChatModel
if (options && options.chatHistory) {
const chatHistoryClassName = memory.chatHistory.constructor.name
// Only replace when its In-Memory
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
memory.chatHistory = mapChatHistory(options)
}
}
chain.memory = memory
const loggerHandler = new ConsoleCallbackHandler(options.logger) const loggerHandler = new ConsoleCallbackHandler(options.logger)
const callbacks = await additionalCallbacks(nodeData, options) const additionalCallback = await additionalCallbacks(nodeData, options)
let res = ''
let callbacks = [loggerHandler, ...additionalCallback]
if (process.env.DEBUG === 'true') {
callbacks.push(new LCConsoleCallbackHandler())
}
if (options.socketIO && options.socketIOClientId) { if (options.socketIO && options.socketIOClientId) {
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
const res = await chain.call({ input }, [loggerHandler, handler, ...callbacks]) callbacks.push(handler)
return res?.response res = await chain.invoke({ input }, { callbacks })
} else { } else {
const res = await chain.call({ input }, [loggerHandler, ...callbacks]) res = await chain.invoke({ input }, { callbacks })
return res?.response
} }
await memory.addChatMessages(
[
{
text: input,
type: 'userMessage'
},
{
text: res,
type: 'apiMessage'
}
],
this.sessionId
)
return res
} }
} }
const prepareChatPrompt = (nodeData: INodeData) => {
const memory = nodeData.inputs?.memory as FlowiseMemory
const prompt = nodeData.inputs?.systemMessagePrompt as string
const chatPromptTemplate = nodeData.inputs?.chatPromptTemplate as ChatPromptTemplate
if (chatPromptTemplate && chatPromptTemplate.promptMessages.length) {
const sysPrompt = chatPromptTemplate.promptMessages[0]
const humanPrompt = chatPromptTemplate.promptMessages[chatPromptTemplate.promptMessages.length - 1]
const chatPrompt = ChatPromptTemplate.fromMessages([
sysPrompt,
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
humanPrompt
])
if ((chatPromptTemplate as any).promptValues) {
// @ts-ignore
chatPrompt.promptValues = (chatPromptTemplate as any).promptValues
}
return chatPrompt
}
const chatPrompt = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate(prompt ? prompt : systemMessage),
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`)
])
return chatPrompt
}
const prepareChain = (nodeData: INodeData, sessionId?: string, chatHistory: IMessage[] = []) => {
const model = nodeData.inputs?.model as BaseChatModel
const memory = nodeData.inputs?.memory as FlowiseMemory
const memoryKey = memory.memoryKey ?? 'chat_history'
const chatPrompt = prepareChatPrompt(nodeData)
let promptVariables = {}
const promptValuesRaw = (chatPrompt as any).promptValues
if (promptValuesRaw) {
const promptValues = handleEscapeCharacters(promptValuesRaw, true)
for (const val in promptValues) {
promptVariables = {
...promptVariables,
[val]: () => {
return promptValues[val]
}
}
}
}
const conversationChain = RunnableSequence.from([
{
[inputKey]: (input: { input: string }) => input.input,
[memoryKey]: async () => {
const history = await memory.getChatMessages(sessionId, true, chatHistory)
return history
},
...promptVariables
},
chatPrompt,
model,
new StringOutputParser()
])
return conversationChain
}
module.exports = { nodeClass: ConversationChain_Chains } module.exports = { nodeClass: ConversationChain_Chains }
@@ -1,20 +1,26 @@
import { BaseLanguageModel } from 'langchain/base_language' import { BaseLanguageModel } from 'langchain/base_language'
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationalRetrievalQAChain } from 'langchain/chains'
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains'
import { BaseRetriever } from 'langchain/schema/retriever' import { BaseRetriever } from 'langchain/schema/retriever'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemoryInput } from 'langchain/memory'
import { PromptTemplate } from 'langchain/prompts' import { PromptTemplate } from 'langchain/prompts'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { QA_TEMPLATE, REPHRASE_TEMPLATE, RESPONSE_TEMPLATE } from './prompts'
import { import { Runnable, RunnableSequence, RunnableMap, RunnableBranch, RunnableLambda } from 'langchain/schema/runnable'
default_map_reduce_template, import { BaseMessage, HumanMessage, AIMessage } from 'langchain/schema'
default_qa_template, import { StringOutputParser } from 'langchain/schema/output_parser'
qa_template, import type { Document } from 'langchain/document'
map_reduce_template, import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'
CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT, import { applyPatch } from 'fast-json-patch'
refine_question_template, import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
refine_template import { ConsoleCallbackHandler, additionalCallbacks } from '../../../src/handler'
} from './prompts' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
import { ConsoleCallbackHandler as LCConsoleCallbackHandler } from '@langchain/core/tracers/console'
type RetrievalChainInput = {
chat_history: string
question: string
}
const sourceRunnableName = 'FindDocs'
class ConversationalRetrievalQAChain_Chains implements INode { class ConversationalRetrievalQAChain_Chains implements INode {
label: string label: string
@@ -26,11 +32,12 @@ class ConversationalRetrievalQAChain_Chains implements INode {
baseClasses: string[] baseClasses: string[]
description: string description: string
inputs: INodeParams[] inputs: INodeParams[]
sessionId?: string
constructor() { constructor(fields?: { sessionId?: string }) {
this.label = 'Conversational Retrieval QA Chain' this.label = 'Conversational Retrieval QA Chain'
this.name = 'conversationalRetrievalQAChain' this.name = 'conversationalRetrievalQAChain'
this.version = 1.0 this.version = 2.0
this.type = 'ConversationalRetrievalQAChain' this.type = 'ConversationalRetrievalQAChain'
this.icon = 'qa.svg' this.icon = 'qa.svg'
this.category = 'Chains' this.category = 'Chains'
@@ -38,9 +45,9 @@ class ConversationalRetrievalQAChain_Chains implements INode {
this.baseClasses = [this.type, ...getBaseClasses(ConversationalRetrievalQAChain)] this.baseClasses = [this.type, ...getBaseClasses(ConversationalRetrievalQAChain)]
this.inputs = [ this.inputs = [
{ {
label: 'Language Model', label: 'Chat Model',
name: 'model', name: 'model',
type: 'BaseLanguageModel' type: 'BaseChatModel'
}, },
{ {
label: 'Vector Store Retriever', label: 'Vector Store Retriever',
@@ -60,6 +67,29 @@ class ConversationalRetrievalQAChain_Chains implements INode {
type: 'boolean', type: 'boolean',
optional: true optional: true
}, },
{
label: 'Rephrase Prompt',
name: 'rephrasePrompt',
type: 'string',
description: 'Using previous chat history, rephrase question into a standalone question',
warning: 'Prompt must include input variables: {chat_history} and {question}',
rows: 4,
additionalParams: true,
optional: true,
default: REPHRASE_TEMPLATE
},
{
label: 'Response Prompt',
name: 'responsePrompt',
type: 'string',
description: 'Taking the rephrased question, search for answer from the provided context',
warning: 'Prompt must include input variable: {context}',
rows: 4,
additionalParams: true,
optional: true,
default: RESPONSE_TEMPLATE
}
/** Deprecated
{ {
label: 'System Message', label: 'System Message',
name: 'systemMessagePrompt', name: 'systemMessagePrompt',
@@ -70,6 +100,7 @@ class ConversationalRetrievalQAChain_Chains implements INode {
placeholder: placeholder:
'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.' 'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.'
}, },
// TODO: create standalone chains for these 3 modes as they are not compatible with memory
{ {
label: 'Chain Option', label: 'Chain Option',
name: 'chainOption', name: 'chainOption',
@@ -95,124 +126,252 @@ class ConversationalRetrievalQAChain_Chains implements INode {
additionalParams: true, additionalParams: true,
optional: true optional: true
} }
*/
] ]
this.sessionId = fields?.sessionId
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData): Promise<any> {
const model = nodeData.inputs?.model as BaseLanguageModel const model = nodeData.inputs?.model as BaseLanguageModel
const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever
const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string
const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string
const chainOption = nodeData.inputs?.chainOption as string const responsePrompt = nodeData.inputs?.responsePrompt as string
const externalMemory = nodeData.inputs?.memory
const obj: any = { let customResponsePrompt = responsePrompt
verbose: process.env.DEBUG === 'true' ? true : false, // If the deprecated systemMessagePrompt is still exists
questionGeneratorChainOptions: { if (systemMessagePrompt) {
template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}`
}
} }
if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt)
return answerChain
if (chainOption === 'map_reduce') {
obj.qaChainOptions = {
type: 'map_reduce',
combinePrompt: PromptTemplate.fromTemplate(
systemMessagePrompt ? `${systemMessagePrompt}\n${map_reduce_template}` : default_map_reduce_template
)
} as QAChainParams
} else if (chainOption === 'refine') {
const qprompt = new PromptTemplate({
inputVariables: ['context', 'question'],
template: refine_question_template(systemMessagePrompt)
})
const rprompt = new PromptTemplate({
inputVariables: ['context', 'question', 'existing_answer'],
template: refine_template
})
obj.qaChainOptions = {
type: 'refine',
questionPrompt: qprompt,
refinePrompt: rprompt
} as QAChainParams
} else {
obj.qaChainOptions = {
type: 'stuff',
prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template)
} as QAChainParams
}
if (externalMemory) {
externalMemory.memoryKey = 'chat_history'
externalMemory.inputKey = 'question'
externalMemory.outputKey = 'text'
externalMemory.returnMessages = true
if (chainOption === 'refine') externalMemory.outputKey = 'output_text'
obj.memory = externalMemory
} else {
const fields: BufferMemoryInput = {
memoryKey: 'chat_history',
inputKey: 'question',
outputKey: 'text',
returnMessages: true
}
if (chainOption === 'refine') fields.outputKey = 'output_text'
obj.memory = new BufferMemory(fields)
}
const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj)
return chain
} }
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | ICommonObject> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | ICommonObject> {
const chain = nodeData.instance as ConversationalRetrievalQAChain const model = nodeData.inputs?.model as BaseLanguageModel
const externalMemory = nodeData.inputs?.memory
const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever
const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string
const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string
const responsePrompt = nodeData.inputs?.responsePrompt as string
const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean
const chainOption = nodeData.inputs?.chainOption as string
let model = nodeData.inputs?.model let customResponsePrompt = responsePrompt
// If the deprecated systemMessagePrompt is still exists
// Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 if (systemMessagePrompt) {
model.streaming = false customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}`
chain.questionGeneratorChain.llm = model
const obj = { question: input }
if (options && options.chatHistory && chain.memory) {
const chatHistoryClassName = (chain.memory as any).chatHistory.constructor.name
// Only replace when its In-Memory
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
;(chain.memory as any).chatHistory = mapChatHistory(options)
}
} }
let memory: FlowiseMemory | undefined = externalMemory
if (!memory) {
memory = new BufferMemory({
returnMessages: true,
memoryKey: 'chat_history',
inputKey: 'input'
})
}
const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt)
const history = ((await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[]) ?? []
const loggerHandler = new ConsoleCallbackHandler(options.logger) const loggerHandler = new ConsoleCallbackHandler(options.logger)
const callbacks = await additionalCallbacks(nodeData, options) const additionalCallback = await additionalCallbacks(nodeData, options)
if (options.socketIO && options.socketIOClientId) { let callbacks = [loggerHandler, ...additionalCallback]
const handler = new CustomChainHandler(
options.socketIO, if (process.env.DEBUG === 'true') {
options.socketIOClientId, callbacks.push(new LCConsoleCallbackHandler())
chainOption === 'refine' ? 4 : undefined,
returnSourceDocuments
)
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
if (chainOption === 'refine') {
if (res.output_text && res.sourceDocuments) {
return {
text: res.output_text,
sourceDocuments: res.sourceDocuments
}
}
return res?.output_text
}
if (res.text && res.sourceDocuments) return res
return res?.text
} else {
const res = await chain.call(obj, [loggerHandler, ...callbacks])
if (res.text && res.sourceDocuments) return res
return res?.text
} }
const stream = answerChain.streamLog(
{ question: input, chat_history: history },
{ callbacks },
{
includeNames: [sourceRunnableName]
}
)
let streamedResponse: Record<string, any> = {}
let sourceDocuments: ICommonObject[] = []
let text = ''
let isStreamingStarted = false
const isStreamingEnabled = options.socketIO && options.socketIOClientId
for await (const chunk of stream) {
streamedResponse = applyPatch(streamedResponse, chunk.ops).newDocument
if (streamedResponse.final_output) {
text = streamedResponse.final_output?.output
if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('end')
if (Array.isArray(streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output)) {
sourceDocuments = streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output
if (isStreamingEnabled && returnSourceDocuments)
options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', sourceDocuments)
}
}
if (
Array.isArray(streamedResponse?.streamed_output) &&
streamedResponse?.streamed_output.length &&
!streamedResponse.final_output
) {
const token = streamedResponse.streamed_output[streamedResponse.streamed_output.length - 1]
if (!isStreamingStarted) {
isStreamingStarted = true
if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('start', token)
}
if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('token', token)
}
}
await memory.addChatMessages(
[
{
text: input,
type: 'userMessage'
},
{
text: text,
type: 'apiMessage'
}
],
this.sessionId
)
if (returnSourceDocuments) return { text, sourceDocuments }
else return { text }
}
}
const createRetrieverChain = (llm: BaseLanguageModel, retriever: Runnable, rephrasePrompt: string) => {
// Small speed/accuracy optimization: no need to rephrase the first question
// since there shouldn't be any meta-references to prior chat history
const CONDENSE_QUESTION_PROMPT = PromptTemplate.fromTemplate(rephrasePrompt)
const condenseQuestionChain = RunnableSequence.from([CONDENSE_QUESTION_PROMPT, llm, new StringOutputParser()]).withConfig({
runName: 'CondenseQuestion'
})
const hasHistoryCheckFn = RunnableLambda.from((input: RetrievalChainInput) => input.chat_history.length > 0).withConfig({
runName: 'HasChatHistoryCheck'
})
const conversationChain = condenseQuestionChain.pipe(retriever).withConfig({
runName: 'RetrievalChainWithHistory'
})
const basicRetrievalChain = RunnableLambda.from((input: RetrievalChainInput) => input.question)
.withConfig({
runName: 'Itemgetter:question'
})
.pipe(retriever)
.withConfig({ runName: 'RetrievalChainWithNoHistory' })
return RunnableBranch.from([[hasHistoryCheckFn, conversationChain], basicRetrievalChain]).withConfig({ runName: sourceRunnableName })
}
const formatDocs = (docs: Document[]) => {
return docs.map((doc, i) => `<doc id='${i}'>${doc.pageContent}</doc>`).join('\n')
}
const formatChatHistoryAsString = (history: BaseMessage[]) => {
return history.map((message) => `${message._getType()}: ${message.content}`).join('\n')
}
const serializeHistory = (input: any) => {
const chatHistory: IMessage[] = input.chat_history || []
const convertedChatHistory = []
for (const message of chatHistory) {
if (message.type === 'userMessage') {
convertedChatHistory.push(new HumanMessage({ content: message.message }))
}
if (message.type === 'apiMessage') {
convertedChatHistory.push(new AIMessage({ content: message.message }))
}
}
return convertedChatHistory
}
const createChain = (
llm: BaseLanguageModel,
retriever: Runnable,
rephrasePrompt = REPHRASE_TEMPLATE,
responsePrompt = RESPONSE_TEMPLATE
) => {
const retrieverChain = createRetrieverChain(llm, retriever, rephrasePrompt)
const context = RunnableMap.from({
context: RunnableSequence.from([
({ question, chat_history }) => ({
question,
chat_history: formatChatHistoryAsString(chat_history)
}),
retrieverChain,
RunnableLambda.from(formatDocs).withConfig({
runName: 'FormatDocumentChunks'
})
]),
question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({
runName: 'Itemgetter:question'
}),
chat_history: RunnableLambda.from((input: RetrievalChainInput) => input.chat_history).withConfig({
runName: 'Itemgetter:chat_history'
})
}).withConfig({ tags: ['RetrieveDocs'] })
const prompt = ChatPromptTemplate.fromMessages([
['system', responsePrompt],
new MessagesPlaceholder('chat_history'),
['human', `{question}`]
])
const responseSynthesizerChain = RunnableSequence.from([prompt, llm, new StringOutputParser()]).withConfig({
tags: ['GenerateResponse']
})
const conversationalQAChain = RunnableSequence.from([
{
question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({
runName: 'Itemgetter:question'
}),
chat_history: RunnableLambda.from(serializeHistory).withConfig({
runName: 'SerializeHistory'
})
},
context,
responseSynthesizerChain
])
return conversationalQAChain
}
class BufferMemory extends FlowiseMemory implements MemoryMethods {
constructor(fields: BufferMemoryInput) {
super(fields)
}
async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
await this.chatHistory.clear()
for (const msg of prevHistory) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
const memoryResult = await this.loadMemoryVariables({})
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
}
async addChatMessages(): Promise<void> {
// adding chat messages will be done on the fly in getChatMessages()
return
}
async clearChatMessages(): Promise<void> {
await this.clear()
} }
} }
@@ -1,64 +1,27 @@
export const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Helpful Answer:`
export const qa_template = `Use the following pieces of context to answer the question at the end.
{context}
Question: {question}
Helpful Answer:`
export const default_map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer.
If you don't know the answer, just say that you don't know. Don't try to make up an answer.
{summaries}
Question: {question}
Helpful Answer:`
export const map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer.
{summaries}
Question: {question}
Helpful Answer:`
export const refine_question_template = (sysPrompt?: string) => {
let returnPrompt = ''
if (sysPrompt)
returnPrompt = `Context information is below.
---------------------
{context}
---------------------
Given the context information and not prior knowledge, ${sysPrompt}
Answer the question: {question}.
Answer:`
if (!sysPrompt)
returnPrompt = `Context information is below.
---------------------
{context}
---------------------
Given the context information and not prior knowledge, answer the question: {question}.
Answer:`
return returnPrompt
}
export const refine_template = `The original question is as follows: {question}
We have provided an existing answer: {existing_answer}
We have the opportunity to refine the existing answer (only if needed) with some more context below.
------------
{context}
------------
Given the new context, refine the original answer to better answer the question.
If you can't find answer from the context, return the original answer.`
export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, answer in the same language as the follow up question. include it in the standalone question. export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, answer in the same language as the follow up question. include it in the standalone question.
Chat History: Chat History:
{chat_history} {chat_history}
Follow Up Input: {question} Follow Up Input: {question}
Standalone question:` Standalone question:`
export const RESPONSE_TEMPLATE = `I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". Using the provided context, answer the user's question to the best of your ability using the resources provided.
If there is nothing in the context relevant to the question at hand, just say "Hmm, I'm not sure" and stop after that. Refuse to answer any question not about the info. Never break character.
------------
{context}
------------
REMEMBER: If there is no relevant information within the context, just say "Hmm, I'm not sure". Don't try to make up an answer. Never break character.`
export const QA_TEMPLATE = `Use the following pieces of context to answer the question at the end.
{context}
Question: {question}
Helpful Answer:`
export const REPHRASE_TEMPLATE = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.
Chat History:
{chat_history}
Follow Up Input: {question}
Standalone Question:`
@@ -82,7 +82,7 @@ class LLMChain_Chains implements INode {
const model = nodeData.inputs?.model as BaseLanguageModel const model = nodeData.inputs?.model as BaseLanguageModel
const prompt = nodeData.inputs?.prompt const prompt = nodeData.inputs?.prompt
const output = nodeData.outputs?.output as string const output = nodeData.outputs?.output as string
const promptValues = prompt.promptValues as ICommonObject let promptValues: ICommonObject | undefined = nodeData.inputs?.prompt.promptValues as ICommonObject
const llmOutputParser = nodeData.inputs?.outputParser as BaseOutputParser const llmOutputParser = nodeData.inputs?.outputParser as BaseOutputParser
this.outputParser = llmOutputParser this.outputParser = llmOutputParser
if (llmOutputParser) { if (llmOutputParser) {
@@ -107,17 +107,24 @@ class LLMChain_Chains implements INode {
verbose: process.env.DEBUG === 'true' verbose: process.env.DEBUG === 'true'
}) })
const inputVariables = chain.prompt.inputVariables as string[] // ["product"] const inputVariables = chain.prompt.inputVariables as string[] // ["product"]
promptValues = injectOutputParser(this.outputParser, chain, promptValues)
const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData) const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData)
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m') console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m')
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(res) console.log(res)
let finalRes = res
if (this.outputParser && typeof res === 'object' && Object.prototype.hasOwnProperty.call(res, 'json')) {
finalRes = (res as ICommonObject).json
}
/** /**
* Apply string transformation to convert special chars: * Apply string transformation to convert special chars:
* FROM: hello i am ben\n\n\thow are you? * FROM: hello i am ben\n\n\thow are you?
* TO: hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you? * TO: hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you?
*/ */
return handleEscapeCharacters(res, false) return handleEscapeCharacters(finalRes, false)
} }
} }
@@ -69,22 +69,23 @@ class VectaraChain_Chains implements INode {
options: [ options: [
{ {
label: 'vectara-summary-ext-v1.2.0 (gpt-3.5-turbo)', label: 'vectara-summary-ext-v1.2.0 (gpt-3.5-turbo)',
name: 'vectara-summary-ext-v1.2.0' name: 'vectara-summary-ext-v1.2.0',
description: 'base summarizer, available to all Vectara users'
}, },
{ {
label: 'vectara-experimental-summary-ext-2023-10-23-small (gpt-3.5-turbo)', label: 'vectara-experimental-summary-ext-2023-10-23-small (gpt-3.5-turbo)',
name: 'vectara-experimental-summary-ext-2023-10-23-small', name: 'vectara-experimental-summary-ext-2023-10-23-small',
description: 'In beta, available to both Growth and Scale Vectara users' description: `In beta, available to both Growth and <a target="_blank" href="https://vectara.com/pricing/">Scale</a> Vectara users`
}, },
{ {
label: 'vectara-summary-ext-v1.3.0 (gpt-4.0)', label: 'vectara-summary-ext-v1.3.0 (gpt-4.0)',
name: 'vectara-summary-ext-v1.3.0', name: 'vectara-summary-ext-v1.3.0',
description: 'Only available to paying Scale Vectara users' description: 'Only available to <a target="_blank" href="https://vectara.com/pricing/">Scale</a> Vectara users'
}, },
{ {
label: 'vectara-experimental-summary-ext-2023-10-23-med (gpt-4.0)', label: 'vectara-experimental-summary-ext-2023-10-23-med (gpt-4.0)',
name: 'vectara-experimental-summary-ext-2023-10-23-med', name: 'vectara-experimental-summary-ext-2023-10-23-med',
description: 'In beta, only available to paying Scale Vectara users' description: `In beta, only available to <a target="_blank" href="https://vectara.com/pricing/">Scale</a> Vectara users`
} }
], ],
default: 'vectara-summary-ext-v1.2.0' default: 'vectara-summary-ext-v1.2.0'
@@ -228,7 +229,7 @@ class VectaraChain_Chains implements INode {
async run(nodeData: INodeData, input: string): Promise<object> { async run(nodeData: INodeData, input: string): Promise<object> {
const vectorStore = nodeData.inputs?.vectaraStore as VectaraStore const vectorStore = nodeData.inputs?.vectaraStore as VectaraStore
const responseLang = (nodeData.inputs?.responseLang as string) ?? 'auto' const responseLang = (nodeData.inputs?.responseLang as string) ?? 'eng'
const summarizerPromptName = nodeData.inputs?.summarizerPromptName as string const summarizerPromptName = nodeData.inputs?.summarizerPromptName as string
const maxSummarizedResultsStr = nodeData.inputs?.maxSummarizedResults as string const maxSummarizedResultsStr = nodeData.inputs?.maxSummarizedResults as string
const maxSummarizedResults = maxSummarizedResultsStr ? parseInt(maxSummarizedResultsStr, 10) : 7 const maxSummarizedResults = maxSummarizedResultsStr ? parseInt(maxSummarizedResultsStr, 10) : 7
@@ -247,17 +248,31 @@ class VectaraChain_Chains implements INode {
lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 } lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 }
})) }))
// Vectara reranker ID for MMR (https://docs.vectara.com/docs/api-reference/search-apis/reranking#maximal-marginal-relevance-mmr-reranker)
const mmrRerankerId = 272725718
const mmrEnabled = vectaraFilter?.mmrConfig?.enabled
const data = { const data = {
query: [ query: [
{ {
query: input, query: input,
start: 0, start: 0,
numResults: topK, numResults: mmrEnabled ? vectaraFilter?.mmrTopK : topK,
corpusKey: corpusKeys,
contextConfig: { contextConfig: {
sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2, sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2,
sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2 sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2
}, },
corpusKey: corpusKeys, ...(mmrEnabled
? {
rerankingConfig: {
rerankerId: mmrRerankerId,
mmrConfig: {
diversityBias: vectaraFilter?.mmrConfig.diversityBias
}
}
}
: {}),
summary: [ summary: [
{ {
summarizerPromptName, summarizerPromptName,
@@ -285,6 +300,14 @@ class VectaraChain_Chains implements INode {
const documents = result.responseSet[0].document const documents = result.responseSet[0].document
let rawSummarizedText = '' let rawSummarizedText = ''
// remove responses that are not in the topK (in case of MMR)
// Note that this does not really matter functionally due to the reorder citations, but it is more efficient
const maxResponses = mmrEnabled ? Math.min(responses.length, topK) : responses.length
if (responses.length > maxResponses) {
responses.splice(0, maxResponses)
}
// Add metadata to each text response given its corresponding document metadata
for (let i = 0; i < responses.length; i += 1) { for (let i = 0; i < responses.length; i += 1) {
const responseMetadata = responses[i].metadata const responseMetadata = responses[i].metadata
const documentMetadata = documents[responses[i].documentIndex].metadata const documentMetadata = documents[responses[i].documentIndex].metadata
@@ -301,13 +324,13 @@ class VectaraChain_Chains implements INode {
responses[i].metadata = combinedMetadata responses[i].metadata = combinedMetadata
} }
// Create the summarization response
const summaryStatus = result.responseSet[0].summary[0].status const summaryStatus = result.responseSet[0].summary[0].status
if (summaryStatus.length > 0 && summaryStatus[0].code === 'BAD_REQUEST') { if (summaryStatus.length > 0 && summaryStatus[0].code === 'BAD_REQUEST') {
throw new Error( throw new Error(
`BAD REQUEST: Too much text for the summarizer to summarize. Please try reducing the number of search results to summarize, or the context of each result by adjusting the 'summary_num_sentences', and 'summary_num_results' parameters respectively.` `BAD REQUEST: Too much text for the summarizer to summarize. Please try reducing the number of search results to summarize, or the context of each result by adjusting the 'summary_num_sentences', and 'summary_num_results' parameters respectively.`
) )
} }
if ( if (
summaryStatus.length > 0 && summaryStatus.length > 0 &&
summaryStatus[0].code === 'NOT_FOUND' && summaryStatus[0].code === 'NOT_FOUND' &&
@@ -316,8 +339,8 @@ class VectaraChain_Chains implements INode {
throw new Error(`BAD REQUEST: summarizer ${summarizerPromptName} is invalid for this account.`) throw new Error(`BAD REQUEST: summarizer ${summarizerPromptName} is invalid for this account.`)
} }
// Reorder citations in summary and create the list of returned source documents
rawSummarizedText = result.responseSet[0].summary[0]?.text rawSummarizedText = result.responseSet[0].summary[0]?.text
let summarizedText = reorderCitations(rawSummarizedText) let summarizedText = reorderCitations(rawSummarizedText)
let summaryResponses = applyCitationOrder(responses, rawSummarizedText) let summaryResponses = applyCitationOrder(responses, rawSummarizedText)
@@ -1,7 +1,9 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { convertMultiOptionsToStringArray, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { BaseCache } from 'langchain/schema' import { BaseCache } from 'langchain/schema'
import { ChatGoogleGenerativeAI } from '@langchain/google-genai' import { ChatGoogleGenerativeAI, GoogleGenerativeAIChatInput } from '@langchain/google-genai'
import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
import type { SafetySetting } from '@google/generative-ai'
class GoogleGenerativeAI_ChatModels implements INode { class GoogleGenerativeAI_ChatModels implements INode {
label: string label: string
@@ -74,6 +76,73 @@ class GoogleGenerativeAI_ChatModels implements INode {
step: 0.1, step: 0.1,
optional: true, optional: true,
additionalParams: true additionalParams: true
},
{
label: 'Top Next Highest Probability Tokens',
name: 'topK',
type: 'number',
description: `Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive`,
step: 1,
optional: true,
additionalParams: true
},
{
label: 'Harm Category',
name: 'harmCategory',
type: 'multiOptions',
description:
'Refer to <a target="_blank" href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_attribute_definitions">official guide</a> on how to use Harm Category',
options: [
{
label: 'Dangerous',
name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT
},
{
label: 'Harassment',
name: HarmCategory.HARM_CATEGORY_HARASSMENT
},
{
label: 'Hate Speech',
name: HarmCategory.HARM_CATEGORY_HATE_SPEECH
},
{
label: 'Sexually Explicit',
name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT
}
],
optional: true,
additionalParams: true
},
{
label: 'Harm Block Threshold',
name: 'harmBlockThreshold',
type: 'multiOptions',
description:
'Refer to <a target="_blank" href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_setting_thresholds">official guide</a> on how to use Harm Block Threshold',
options: [
{
label: 'Low and Above',
name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
},
{
label: 'Medium and Above',
name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
},
{
label: 'None',
name: HarmBlockThreshold.BLOCK_NONE
},
{
label: 'Only High',
name: HarmBlockThreshold.BLOCK_ONLY_HIGH
},
{
label: 'Threshold Unspecified',
name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED
}
],
optional: true,
additionalParams: true
} }
] ]
} }
@@ -86,9 +155,12 @@ class GoogleGenerativeAI_ChatModels implements INode {
const modelName = nodeData.inputs?.modelName as string const modelName = nodeData.inputs?.modelName as string
const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string
const topP = nodeData.inputs?.topP as string const topP = nodeData.inputs?.topP as string
const topK = nodeData.inputs?.topK as string
const harmCategory = nodeData.inputs?.harmCategory as string
const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string
const cache = nodeData.inputs?.cache as BaseCache const cache = nodeData.inputs?.cache as BaseCache
const obj = { const obj: Partial<GoogleGenerativeAIChatInput> = {
apiKey: apiKey, apiKey: apiKey,
modelName: modelName, modelName: modelName,
maxOutputTokens: 2048 maxOutputTokens: 2048
@@ -98,8 +170,23 @@ class GoogleGenerativeAI_ChatModels implements INode {
const model = new ChatGoogleGenerativeAI(obj) const model = new ChatGoogleGenerativeAI(obj)
if (topP) model.topP = parseFloat(topP) if (topP) model.topP = parseFloat(topP)
if (topK) model.topK = parseFloat(topK)
if (cache) model.cache = cache if (cache) model.cache = cache
if (temperature) model.temperature = parseFloat(temperature) if (temperature) model.temperature = parseFloat(temperature)
// Safety Settings
let harmCategories: string[] = convertMultiOptionsToStringArray(harmCategory)
let harmBlockThresholds: string[] = convertMultiOptionsToStringArray(harmBlockThreshold)
if (harmCategories.length != harmBlockThresholds.length)
throw new Error(`Harm Category & Harm Block Threshold are not the same length`)
const safetySettings: SafetySetting[] = harmCategories.map((harmCategory, index) => {
return {
category: harmCategory as HarmCategory,
threshold: harmBlockThresholds[index] as HarmBlockThreshold
}
})
if (safetySettings.length > 0) model.safetySettings = safetySettings
return model return model
} }
} }
@@ -1,5 +1,5 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { OpenAIChat } from 'langchain/llms/openai' import { OpenAIChat } from 'langchain/llms/openai'
import { OpenAIChatInput } from 'langchain/chat_models/openai' import { OpenAIChatInput } from 'langchain/chat_models/openai'
import { BaseCache } from 'langchain/schema' import { BaseCache } from 'langchain/schema'
@@ -14,6 +14,7 @@ class ChatLocalAI_ChatModels implements INode {
category: string category: string
description: string description: string
baseClasses: string[] baseClasses: string[]
credential: INodeParams
inputs: INodeParams[] inputs: INodeParams[]
constructor() { constructor() {
@@ -25,6 +26,13 @@ class ChatLocalAI_ChatModels implements INode {
this.category = 'Chat Models' this.category = 'Chat Models'
this.description = 'Use local LLMs like llama.cpp, gpt4all using LocalAI' this.description = 'Use local LLMs like llama.cpp, gpt4all using LocalAI'
this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(OpenAIChat)] this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(OpenAIChat)]
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['localAIApi'],
optional: true
}
this.inputs = [ this.inputs = [
{ {
label: 'Cache', label: 'Cache',
@@ -79,13 +87,16 @@ class ChatLocalAI_ChatModels implements INode {
] ]
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const temperature = nodeData.inputs?.temperature as string const temperature = nodeData.inputs?.temperature as string
const modelName = nodeData.inputs?.modelName as string const modelName = nodeData.inputs?.modelName as string
const maxTokens = nodeData.inputs?.maxTokens as string const maxTokens = nodeData.inputs?.maxTokens as string
const topP = nodeData.inputs?.topP as string const topP = nodeData.inputs?.topP as string
const timeout = nodeData.inputs?.timeout as string const timeout = nodeData.inputs?.timeout as string
const basePath = nodeData.inputs?.basePath as string const basePath = nodeData.inputs?.basePath as string
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData)
const cache = nodeData.inputs?.cache as BaseCache const cache = nodeData.inputs?.cache as BaseCache
const obj: Partial<OpenAIChatInput> & BaseLLMParams & { openAIApiKey?: string } = { const obj: Partial<OpenAIChatInput> & BaseLLMParams & { openAIApiKey?: string } = {
@@ -98,6 +109,7 @@ class ChatLocalAI_ChatModels implements INode {
if (topP) obj.topP = parseFloat(topP) if (topP) obj.topP = parseFloat(topP)
if (timeout) obj.timeout = parseInt(timeout, 10) if (timeout) obj.timeout = parseInt(timeout, 10)
if (cache) obj.cache = cache if (cache) obj.cache = cache
if (localAIApiKey) obj.openAIApiKey = localAIApiKey
const model = new OpenAIChat(obj, { basePath }) const model = new OpenAIChat(obj, { basePath })
@@ -1,6 +1,7 @@
import { getCredentialData, getCredentialParam } from '../../../src' import { getCredentialData, getCredentialParam } from '../../../src'
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma' import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma'
import { TextSplitter } from 'langchain/text_splitter'
class Figma_DocumentLoaders implements INode { class Figma_DocumentLoaders implements INode {
label: string label: string
@@ -71,6 +72,8 @@ class Figma_DocumentLoaders implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const nodeIds = (nodeData.inputs?.nodeIds as string)?.trim().split(',') || [] const nodeIds = (nodeData.inputs?.nodeIds as string)?.trim().split(',') || []
const fileKey = nodeData.inputs?.fileKey as string const fileKey = nodeData.inputs?.fileKey as string
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
const metadata = nodeData.inputs?.metadata
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const accessToken = getCredentialParam('accessToken', credentialData, nodeData) const accessToken = getCredentialParam('accessToken', credentialData, nodeData)
@@ -82,7 +85,21 @@ class Figma_DocumentLoaders implements INode {
} }
const loader = new FigmaFileLoader(figmaOptions) const loader = new FigmaFileLoader(figmaOptions)
const docs = await loader.load()
const docs = textSplitter ? await loader.loadAndSplit() : await loader.load()
if (metadata) {
const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata)
return docs.map((doc) => {
return {
...doc,
metadata: {
...doc.metadata,
...parsedMetadata
}
}
})
}
return docs return docs
} }
@@ -1,4 +1,5 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getCredentialData, getCredentialParam } from '../../../src/utils'
import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai'
class LocalAIEmbedding_Embeddings implements INode { class LocalAIEmbedding_Embeddings implements INode {
@@ -10,6 +11,7 @@ class LocalAIEmbedding_Embeddings implements INode {
category: string category: string
description: string description: string
baseClasses: string[] baseClasses: string[]
credential: INodeParams
inputs: INodeParams[] inputs: INodeParams[]
constructor() { constructor() {
@@ -21,6 +23,13 @@ class LocalAIEmbedding_Embeddings implements INode {
this.category = 'Embeddings' this.category = 'Embeddings'
this.description = 'Use local embeddings models like llama.cpp' this.description = 'Use local embeddings models like llama.cpp'
this.baseClasses = [this.type, 'Embeddings'] this.baseClasses = [this.type, 'Embeddings']
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['localAIApi'],
optional: true
}
this.inputs = [ this.inputs = [
{ {
label: 'Base Path', label: 'Base Path',
@@ -37,15 +46,20 @@ class LocalAIEmbedding_Embeddings implements INode {
] ]
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const modelName = nodeData.inputs?.modelName as string const modelName = nodeData.inputs?.modelName as string
const basePath = nodeData.inputs?.basePath as string const basePath = nodeData.inputs?.basePath as string
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData)
const obj: Partial<OpenAIEmbeddingsParams> & { openAIApiKey?: string } = { const obj: Partial<OpenAIEmbeddingsParams> & { openAIApiKey?: string } = {
modelName, modelName,
openAIApiKey: 'sk-' openAIApiKey: 'sk-'
} }
if (localAIApiKey) obj.openAIApiKey = localAIApiKey
const model = new OpenAIEmbeddings(obj, { basePath }) const model = new OpenAIEmbeddings(obj, { basePath })
return model return model
@@ -1,4 +1,4 @@
import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { BaseMessage } from 'langchain/schema' import { BaseMessage } from 'langchain/schema'
@@ -55,36 +55,27 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
super(fields) super(fields)
} }
async getChatMessages(_?: string, returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> { async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
await this.chatHistory.clear()
for (const msg of prevHistory) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
const memoryResult = await this.loadMemoryVariables({}) const memoryResult = await this.loadMemoryVariables({})
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
} }
async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise<void> { async addChatMessages(): Promise<void> {
const input = msgArray.find((msg) => msg.type === 'userMessage') // adding chat messages will be done on the fly in getChatMessages()
const output = msgArray.find((msg) => msg.type === 'apiMessage') return
const inputValues = { [this.inputKey ?? 'input']: input?.text }
const outputValues = { output: output?.text }
await this.saveContext(inputValues, outputValues)
} }
async clearChatMessages(): Promise<void> { async clearChatMessages(): Promise<void> {
await this.clear() await this.clear()
} }
async resumeMessages(messages: IMessage[]): Promise<void> {
// Clear existing chatHistory to avoid duplication
if (messages.length) await this.clear()
// Insert into chatHistory
for (const msg of messages) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
}
} }
module.exports = { nodeClass: BufferMemory_Memory } module.exports = { nodeClass: BufferMemory_Memory }
@@ -1,4 +1,4 @@
import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory' import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory'
import { BaseMessage } from 'langchain/schema' import { BaseMessage } from 'langchain/schema'
@@ -67,36 +67,28 @@ class BufferWindowMemoryExtended extends FlowiseWindowMemory implements MemoryMe
super(fields) super(fields)
} }
async getChatMessages(_?: string, returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> { async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
await this.chatHistory.clear()
// Insert into chatHistory
for (const msg of prevHistory) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
const memoryResult = await this.loadMemoryVariables({}) const memoryResult = await this.loadMemoryVariables({})
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
} }
async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise<void> { async addChatMessages(): Promise<void> {
const input = msgArray.find((msg) => msg.type === 'userMessage') // adding chat messages will be done on the fly in getChatMessages()
const output = msgArray.find((msg) => msg.type === 'apiMessage') return
const inputValues = { [this.inputKey ?? 'input']: input?.text }
const outputValues = { output: output?.text }
await this.saveContext(inputValues, outputValues)
} }
async clearChatMessages(): Promise<void> { async clearChatMessages(): Promise<void> {
await this.clear() await this.clear()
} }
async resumeMessages(messages: IMessage[]): Promise<void> {
// Clear existing chatHistory to avoid duplication
if (messages.length) await this.clear()
// Insert into chatHistory
for (const msg of messages) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
}
} }
module.exports = { nodeClass: BufferWindowMemory_Memory } module.exports = { nodeClass: BufferWindowMemory_Memory }
@@ -1,4 +1,4 @@
import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory' import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory'
import { BaseLanguageModel } from 'langchain/base_language' import { BaseLanguageModel } from 'langchain/base_language'
@@ -66,40 +66,32 @@ class ConversationSummaryMemoryExtended extends FlowiseSummaryMemory implements
super(fields) super(fields)
} }
async getChatMessages(_?: string, returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> { async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
await this.chatHistory.clear()
this.buffer = ''
for (const msg of prevHistory) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
// Get summary
const chatMessages = await this.chatHistory.getMessages()
this.buffer = chatMessages.length ? await this.predictNewSummary(chatMessages.slice(-2), this.buffer) : ''
const memoryResult = await this.loadMemoryVariables({}) const memoryResult = await this.loadMemoryVariables({})
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
} }
async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise<void> { async addChatMessages(): Promise<void> {
const input = msgArray.find((msg) => msg.type === 'userMessage') // adding chat messages will be done on the fly in getChatMessages()
const output = msgArray.find((msg) => msg.type === 'apiMessage') return
const inputValues = { [this.inputKey ?? 'input']: input?.text }
const outputValues = { output: output?.text }
await this.saveContext(inputValues, outputValues)
} }
async clearChatMessages(): Promise<void> { async clearChatMessages(): Promise<void> {
await this.clear() await this.clear()
} }
async resumeMessages(messages: IMessage[]): Promise<void> {
// Clear existing chatHistory to avoid duplication
if (messages.length) await this.clear()
// Insert into chatHistory
for (const msg of messages) {
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
}
// Replace buffer
const chatMessages = await this.chatHistory.getMessages()
this.buffer = await this.predictNewSummary(chatMessages.slice(-2), this.buffer)
}
} }
module.exports = { nodeClass: ConversationSummaryMemory_Memory } module.exports = { nodeClass: ConversationSummaryMemory_Memory }
@@ -12,13 +12,7 @@ import {
import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema'
import { import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
convertBaseMessagetoIMessage,
getBaseClasses,
getCredentialData,
getCredentialParam,
serializeChatHistory
} from '../../../src/utils'
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
class DynamoDb_Memory implements INode { class DynamoDb_Memory implements INode {
@@ -70,7 +64,8 @@ class DynamoDb_Memory implements INode {
label: 'Session ID', label: 'Session ID',
name: 'sessionId', name: 'sessionId',
type: 'string', type: 'string',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', description:
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
default: '', default: '',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -88,25 +83,6 @@ class DynamoDb_Memory implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return initalizeDynamoDB(nodeData, options) return initalizeDynamoDB(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const dynamodbMemory = await initalizeDynamoDB(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`)
await dynamodbMemory.clear()
options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const memoryKey = nodeData.inputs?.memoryKey as string
const dynamodbMemory = await initalizeDynamoDB(nodeData, options)
const key = memoryKey ?? 'chat_history'
const memoryResult = await dynamodbMemory.loadMemoryVariables({})
return serializeChatHistory(memoryResult[key])
}
}
} }
const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => { const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
@@ -114,17 +90,7 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
const partitionKey = nodeData.inputs?.partitionKey as string const partitionKey = nodeData.inputs?.partitionKey as string
const region = nodeData.inputs?.region as string const region = nodeData.inputs?.region as string
const memoryKey = nodeData.inputs?.memoryKey as string const memoryKey = nodeData.inputs?.memoryKey as string
const chatId = options.chatId const sessionId = nodeData.inputs?.sessionId as string
let isSessionIdUsingChatMessageId = false
let sessionId = ''
if (!nodeData.inputs?.sessionId && chatId) {
isSessionIdUsingChatMessageId = true
sessionId = chatId
} else {
sessionId = nodeData.inputs?.sessionId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData) const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData)
@@ -150,7 +116,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
const memory = new BufferMemoryExtended({ const memory = new BufferMemoryExtended({
memoryKey: memoryKey ?? 'chat_history', memoryKey: memoryKey ?? 'chat_history',
chatHistory: dynamoDb, chatHistory: dynamoDb,
isSessionIdUsingChatMessageId,
sessionId, sessionId,
dynamodbClient: client dynamodbClient: client
}) })
@@ -158,7 +123,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
} }
interface BufferMemoryExtendedInput { interface BufferMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
dynamodbClient: DynamoDBClient dynamodbClient: DynamoDBClient
sessionId: string sessionId: string
} }
@@ -178,7 +142,6 @@ interface DynamoDBSerializedChatMessage {
} }
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
isSessionIdUsingChatMessageId = false
sessionId = '' sessionId = ''
dynamodbClient: DynamoDBClient dynamodbClient: DynamoDBClient
@@ -306,10 +269,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
await this.dynamodbClient.send(new DeleteItemCommand(params)) await this.dynamodbClient.send(new DeleteItemCommand(params))
await this.clear() await this.clear()
} }
async resumeMessages(): Promise<void> {
return
}
} }
module.exports = { nodeClass: DynamoDb_Memory } module.exports = { nodeClass: DynamoDb_Memory }
@@ -2,13 +2,7 @@ import { MongoClient, Collection, Document } from 'mongodb'
import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, BaseMessage } from 'langchain/schema'
import { import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
convertBaseMessagetoIMessage,
getBaseClasses,
getCredentialData,
getCredentialParam,
serializeChatHistory
} from '../../../src/utils'
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
class MongoDB_Memory implements INode { class MongoDB_Memory implements INode {
@@ -55,7 +49,8 @@ class MongoDB_Memory implements INode {
label: 'Session Id', label: 'Session Id',
name: 'sessionId', name: 'sessionId',
type: 'string', type: 'string',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', description:
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
default: '', default: '',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -73,42 +68,13 @@ class MongoDB_Memory implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return initializeMongoDB(nodeData, options) return initializeMongoDB(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const mongodbMemory = await initializeMongoDB(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`)
await mongodbMemory.clear()
options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const memoryKey = nodeData.inputs?.memoryKey as string
const mongodbMemory = await initializeMongoDB(nodeData, options)
const key = memoryKey ?? 'chat_history'
const memoryResult = await mongodbMemory.loadMemoryVariables({})
return serializeChatHistory(memoryResult[key])
}
}
} }
const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => { const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
const databaseName = nodeData.inputs?.databaseName as string const databaseName = nodeData.inputs?.databaseName as string
const collectionName = nodeData.inputs?.collectionName as string const collectionName = nodeData.inputs?.collectionName as string
const memoryKey = nodeData.inputs?.memoryKey as string const memoryKey = nodeData.inputs?.memoryKey as string
const chatId = options?.chatId as string const sessionId = nodeData.inputs?.sessionId as string
let isSessionIdUsingChatMessageId = false
let sessionId = ''
if (!nodeData.inputs?.sessionId && chatId) {
isSessionIdUsingChatMessageId = true
sessionId = chatId
} else {
sessionId = nodeData.inputs?.sessionId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) const mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData)
@@ -149,14 +115,12 @@ const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): P
return new BufferMemoryExtended({ return new BufferMemoryExtended({
memoryKey: memoryKey ?? 'chat_history', memoryKey: memoryKey ?? 'chat_history',
chatHistory: mongoDBChatMessageHistory, chatHistory: mongoDBChatMessageHistory,
isSessionIdUsingChatMessageId,
sessionId, sessionId,
collection collection
}) })
} }
interface BufferMemoryExtendedInput { interface BufferMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
collection: Collection<Document> collection: Collection<Document>
sessionId: string sessionId: string
} }
@@ -164,7 +128,6 @@ interface BufferMemoryExtendedInput {
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
sessionId = '' sessionId = ''
collection: Collection<Document> collection: Collection<Document>
isSessionIdUsingChatMessageId? = false
constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) {
super(fields) super(fields)
@@ -221,10 +184,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
await this.collection.deleteOne({ sessionId: id }) await this.collection.deleteOne({ sessionId: id })
await this.clear() await this.clear()
} }
async resumeMessages(): Promise<void> {
return
}
} }
module.exports = { nodeClass: MongoDB_Memory } module.exports = { nodeClass: MongoDB_Memory }
@@ -1,9 +1,14 @@
import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ICommonObject } from '../../../src' import { ICommonObject } from '../../../src'
import { MotorheadMemory, MotorheadMemoryInput, InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' import { MotorheadMemory, MotorheadMemoryInput, InputValues, OutputValues } from 'langchain/memory'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { BaseMessage } from 'langchain/schema' import { AIMessage, BaseMessage, ChatMessage, HumanMessage } from 'langchain/schema'
type MotorheadMessage = {
content: string
role: 'Human' | 'AI'
}
class MotorMemory_Memory implements INode { class MotorMemory_Memory implements INode {
label: string label: string
@@ -46,7 +51,8 @@ class MotorMemory_Memory implements INode {
label: 'Session Id', label: 'Session Id',
name: 'sessionId', name: 'sessionId',
type: 'string', type: 'string',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', description:
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
default: '', default: '',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -64,49 +70,19 @@ class MotorMemory_Memory implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return initalizeMotorhead(nodeData, options) return initalizeMotorhead(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const motorhead = await initalizeMotorhead(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`)
await motorhead.clear()
options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const memoryKey = nodeData.inputs?.memoryKey as string
const motorhead = await initalizeMotorhead(nodeData, options)
const key = memoryKey ?? 'chat_history'
const memoryResult = await motorhead.loadMemoryVariables({})
return getBufferString(memoryResult[key])
}
}
} }
const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise<MotorheadMemory> => { const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise<MotorheadMemory> => {
const memoryKey = nodeData.inputs?.memoryKey as string const memoryKey = nodeData.inputs?.memoryKey as string
const baseURL = nodeData.inputs?.baseURL as string const baseURL = nodeData.inputs?.baseURL as string
const chatId = options?.chatId as string const sessionId = nodeData.inputs?.sessionId as string
let isSessionIdUsingChatMessageId = false
let sessionId = ''
if (!nodeData.inputs?.sessionId && chatId) {
isSessionIdUsingChatMessageId = true
sessionId = chatId
} else {
sessionId = nodeData.inputs?.sessionId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
const clientId = getCredentialParam('clientId', credentialData, nodeData) const clientId = getCredentialParam('clientId', credentialData, nodeData)
let obj: MotorheadMemoryInput & MotorheadMemoryExtendedInput = { let obj: MotorheadMemoryInput = {
returnMessages: true, returnMessages: true,
isSessionIdUsingChatMessageId,
sessionId, sessionId,
memoryKey memoryKey
} }
@@ -132,23 +108,9 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject):
return motorheadMemory return motorheadMemory
} }
interface MotorheadMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
}
class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods {
isSessionIdUsingChatMessageId? = false constructor(fields: MotorheadMemoryInput) {
constructor(fields: MotorheadMemoryInput & MotorheadMemoryExtendedInput) {
super(fields) super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
}
async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise<MemoryVariables> {
if (overrideSessionId) {
this.sessionId = overrideSessionId
}
return super.loadMemoryVariables({ values })
} }
async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise<void> { async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise<void> {
@@ -180,9 +142,33 @@ class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods {
async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> { async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
const id = overrideSessionId ?? this.sessionId const id = overrideSessionId ?? this.sessionId
const memoryVariables = await this.loadMemoryVariables({}, id) try {
const baseMessages = memoryVariables[this.memoryKey] const resp = await this.caller.call(fetch, `${this.url}/sessions/${id}/memory`, {
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) //@ts-ignore
signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined,
headers: this._getHeaders() as ICommonObject,
method: 'GET'
})
const data = await resp.json()
const rawStoredMessages: MotorheadMessage[] = data?.data?.messages ?? []
const baseMessages = rawStoredMessages.reverse().map((message) => {
const { content, role } = message
if (role === 'Human') {
return new HumanMessage(content)
} else if (role === 'AI') {
return new AIMessage(content)
} else {
// default to generic ChatMessage
return new ChatMessage(content, role)
}
})
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
} catch (error) {
console.error('Error getting session: ', error)
return []
}
} }
async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise<void> { async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise<void> {
@@ -1,15 +1,9 @@
import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType, FlowiseMemory, MemoryMethods } from '../../../src/Interface' import { Redis } from 'ioredis'
import {
convertBaseMessagetoIMessage,
getBaseClasses,
getCredentialData,
getCredentialParam,
serializeChatHistory
} from '../../../src/utils'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis'
import { mapStoredMessageToChatMessage, BaseMessage, AIMessage, HumanMessage } from 'langchain/schema' import { mapStoredMessageToChatMessage, BaseMessage, AIMessage, HumanMessage } from 'langchain/schema'
import { Redis } from 'ioredis' import { INode, INodeData, INodeParams, ICommonObject, MessageType, IMessage, MemoryMethods, FlowiseMemory } from '../../../src/Interface'
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
class RedisBackedChatMemory_Memory implements INode { class RedisBackedChatMemory_Memory implements INode {
label: string label: string
@@ -44,7 +38,8 @@ class RedisBackedChatMemory_Memory implements INode {
label: 'Session Id', label: 'Session Id',
name: 'sessionId', name: 'sessionId',
type: 'string', type: 'string',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', description:
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
default: '', default: '',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -78,47 +73,19 @@ class RedisBackedChatMemory_Memory implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return await initalizeRedis(nodeData, options) return await initalizeRedis(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const redis = await initalizeRedis(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`)
await redis.clear()
options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const memoryKey = nodeData.inputs?.memoryKey as string
const redis = await initalizeRedis(nodeData, options)
const key = memoryKey ?? 'chat_history'
const memoryResult = await redis.loadMemoryVariables({})
return serializeChatHistory(memoryResult[key])
}
}
} }
const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => { const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
const sessionTTL = nodeData.inputs?.sessionTTL as number const sessionTTL = nodeData.inputs?.sessionTTL as number
const memoryKey = nodeData.inputs?.memoryKey as string const memoryKey = nodeData.inputs?.memoryKey as string
const sessionId = nodeData.inputs?.sessionId as string
const windowSize = nodeData.inputs?.windowSize as number const windowSize = nodeData.inputs?.windowSize as number
const chatId = options?.chatId as string
let isSessionIdUsingChatMessageId = false
let sessionId = ''
if (!nodeData.inputs?.sessionId && chatId) {
isSessionIdUsingChatMessageId = true
sessionId = chatId
} else {
sessionId = nodeData.inputs?.sessionId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
let client: Redis let client: Redis
if (!redisUrl || redisUrl === '') { if (!redisUrl || redisUrl === '') {
const username = getCredentialParam('redisCacheUser', credentialData, nodeData) const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
@@ -153,7 +120,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom
const redisChatMessageHistory = new RedisChatMessageHistory(obj) const redisChatMessageHistory = new RedisChatMessageHistory(obj)
redisChatMessageHistory.getMessages = async (): Promise<BaseMessage[]> => { /*redisChatMessageHistory.getMessages = async (): Promise<BaseMessage[]> => {
const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1) const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1)
const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message))
return orderedMessages.map(mapStoredMessageToChatMessage) return orderedMessages.map(mapStoredMessageToChatMessage)
@@ -169,44 +136,45 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom
redisChatMessageHistory.clear = async (): Promise<void> => { redisChatMessageHistory.clear = async (): Promise<void> => {
await client.del((redisChatMessageHistory as any).sessionId) await client.del((redisChatMessageHistory as any).sessionId)
} }*/
const memory = new BufferMemoryExtended({ const memory = new BufferMemoryExtended({
memoryKey: memoryKey ?? 'chat_history', memoryKey: memoryKey ?? 'chat_history',
chatHistory: redisChatMessageHistory, chatHistory: redisChatMessageHistory,
isSessionIdUsingChatMessageId,
sessionId, sessionId,
windowSize,
redisClient: client redisClient: client
}) })
return memory return memory
} }
interface BufferMemoryExtendedInput { interface BufferMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
redisClient: Redis redisClient: Redis
sessionId: string sessionId: string
windowSize?: number
} }
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
isSessionIdUsingChatMessageId? = false
sessionId = '' sessionId = ''
redisClient: Redis redisClient: Redis
windowSize?: number
constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) {
super(fields) super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
this.sessionId = fields.sessionId this.sessionId = fields.sessionId
this.redisClient = fields.redisClient this.redisClient = fields.redisClient
this.windowSize = fields.windowSize
} }
async getChatMessages(overrideSessionId = '', returnBaseMessage = false): Promise<IMessage[] | BaseMessage[]> { async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
if (!this.redisClient) return [] if (!this.redisClient) return []
const id = overrideSessionId ?? this.sessionId const id = overrideSessionId ?? this.sessionId
const rawStoredMessages = await this.redisClient.lrange(id, 0, -1) const rawStoredMessages = await this.redisClient.lrange(id, this.windowSize ? this.windowSize * -1 : 0, -1)
const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message))
const baseMessages = orderedMessages.map(mapStoredMessageToChatMessage) const baseMessages = orderedMessages.map(mapStoredMessageToChatMessage)
return returnBaseMessage ? baseMessages : convertBaseMessagetoIMessage(baseMessages) return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
} }
async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise<void> { async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise<void> {
@@ -236,10 +204,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
await this.redisClient.del(id) await this.redisClient.del(id)
await this.clear() await this.clear()
} }
async resumeMessages(): Promise<void> {
return
}
} }
module.exports = { nodeClass: RedisBackedChatMemory_Memory } module.exports = { nodeClass: RedisBackedChatMemory_Memory }
@@ -3,13 +3,7 @@ import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis'
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema'
import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
import { import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
convertBaseMessagetoIMessage,
getBaseClasses,
getCredentialData,
getCredentialParam,
serializeChatHistory
} from '../../../src/utils'
import { ICommonObject } from '../../../src/Interface' import { ICommonObject } from '../../../src/Interface'
class UpstashRedisBackedChatMemory_Memory implements INode { class UpstashRedisBackedChatMemory_Memory implements INode {
@@ -51,7 +45,8 @@ class UpstashRedisBackedChatMemory_Memory implements INode {
label: 'Session Id', label: 'Session Id',
name: 'sessionId', name: 'sessionId',
type: 'string', type: 'string',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', description:
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
default: '', default: '',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -70,40 +65,12 @@ class UpstashRedisBackedChatMemory_Memory implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return initalizeUpstashRedis(nodeData, options) return initalizeUpstashRedis(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const redis = await initalizeUpstashRedis(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`)
await redis.clear()
options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const redis = await initalizeUpstashRedis(nodeData, options)
const key = 'chat_history'
const memoryResult = await redis.loadMemoryVariables({})
return serializeChatHistory(memoryResult[key])
}
}
} }
const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => { const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
const baseURL = nodeData.inputs?.baseURL as string const baseURL = nodeData.inputs?.baseURL as string
const sessionTTL = nodeData.inputs?.sessionTTL as string const sessionTTL = nodeData.inputs?.sessionTTL as string
const chatId = options?.chatId as string const sessionId = nodeData.inputs?.sessionId as string
let isSessionIdUsingChatMessageId = false
let sessionId = ''
if (!nodeData.inputs?.sessionId && chatId) {
isSessionIdUsingChatMessageId = true
sessionId = chatId
} else {
sessionId = nodeData.inputs?.sessionId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData) const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData)
@@ -122,7 +89,6 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject
const memory = new BufferMemoryExtended({ const memory = new BufferMemoryExtended({
memoryKey: 'chat_history', memoryKey: 'chat_history',
chatHistory: redisChatMessageHistory, chatHistory: redisChatMessageHistory,
isSessionIdUsingChatMessageId,
sessionId, sessionId,
redisClient: client redisClient: client
}) })
@@ -131,19 +97,16 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject
} }
interface BufferMemoryExtendedInput { interface BufferMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
redisClient: Redis redisClient: Redis
sessionId: string sessionId: string
} }
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
isSessionIdUsingChatMessageId? = false
sessionId = '' sessionId = ''
redisClient: Redis redisClient: Redis
constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) {
super(fields) super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
this.sessionId = fields.sessionId this.sessionId = fields.sessionId
this.redisClient = fields.redisClient this.redisClient = fields.redisClient
} }
@@ -186,10 +149,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
await this.redisClient.del(id) await this.redisClient.del(id)
await this.clear() await this.clear()
} }
async resumeMessages(): Promise<void> {
return
}
} }
module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory } module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory }
@@ -2,7 +2,7 @@ import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } f
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
import { ICommonObject } from '../../../src' import { ICommonObject } from '../../../src'
import { InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' import { InputValues, MemoryVariables, OutputValues } from 'langchain/memory'
import { BaseMessage } from 'langchain/schema' import { BaseMessage } from 'langchain/schema'
class ZepMemory_Memory implements INode { class ZepMemory_Memory implements INode {
@@ -55,10 +55,9 @@ class ZepMemory_Memory implements INode {
label: 'Size', label: 'Size',
name: 'k', name: 'k',
type: 'number', type: 'number',
placeholder: '10', default: '10',
description: 'Window of size k to surface the last k back-and-forth to use as memory.', description: 'Window of size k to surface the last k back-and-forth to use as memory.',
additionalParams: true, additionalParams: true
optional: true
}, },
{ {
label: 'AI Prefix', label: 'AI Prefix',
@@ -101,27 +100,6 @@ class ZepMemory_Memory implements INode {
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return await initalizeZep(nodeData, options) return await initalizeZep(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const zep = await initalizeZep(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`)
await zep.clear()
options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const memoryKey = nodeData.inputs?.memoryKey as string
const aiPrefix = nodeData.inputs?.aiPrefix as string
const humanPrefix = nodeData.inputs?.humanPrefix as string
const zep = await initalizeZep(nodeData, options)
const key = memoryKey ?? 'chat_history'
const memoryResult = await zep.loadMemoryVariables({})
return getBufferString(memoryResult[key], humanPrefix, aiPrefix)
}
}
} }
const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promise<ZepMemory> => { const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promise<ZepMemory> => {
@@ -131,30 +109,19 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
const memoryKey = nodeData.inputs?.memoryKey as string const memoryKey = nodeData.inputs?.memoryKey as string
const inputKey = nodeData.inputs?.inputKey as string const inputKey = nodeData.inputs?.inputKey as string
const k = nodeData.inputs?.k as string const k = nodeData.inputs?.k as string
const chatId = options?.chatId as string const sessionId = nodeData.inputs?.sessionId as string
let isSessionIdUsingChatMessageId = false
let sessionId = ''
if (!nodeData.inputs?.sessionId && chatId) {
isSessionIdUsingChatMessageId = true
sessionId = chatId
} else {
sessionId = nodeData.inputs?.sessionId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
const obj: ZepMemoryInput & ZepMemoryExtendedInput = { const obj: ZepMemoryInput & ZepMemoryExtendedInput = {
baseURL, baseURL,
sessionId,
aiPrefix, aiPrefix,
humanPrefix, humanPrefix,
returnMessages: true, returnMessages: true,
memoryKey, memoryKey,
inputKey, inputKey,
isSessionIdUsingChatMessageId, sessionId,
k: k ? parseInt(k, 10) : undefined k: k ? parseInt(k, 10) : undefined
} }
if (apiKey) obj.apiKey = apiKey if (apiKey) obj.apiKey = apiKey
@@ -163,17 +130,14 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
} }
interface ZepMemoryExtendedInput { interface ZepMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
k?: number k?: number
} }
class ZepMemoryExtended extends ZepMemory implements MemoryMethods { class ZepMemoryExtended extends ZepMemory implements MemoryMethods {
isSessionIdUsingChatMessageId? = false
lastN?: number lastN?: number
constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) { constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) {
super(fields) super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
this.lastN = fields.k this.lastN = fields.k
} }
@@ -0,0 +1 @@
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.776 18.304c.64 0 1.92-.032 3.712-.768 2.08-.864 6.176-2.4 9.152-4 2.08-1.12 2.976-2.592 2.976-4.576 0-2.72-2.208-4.96-4.96-4.96h-11.52A7.143 7.143 0 0 0 4 11.136c0 3.936 3.008 7.168 7.776 7.168Z" fill="#39594D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.728 23.2c0-1.92 1.152-3.68 2.944-4.416l3.616-1.504C23.968 15.776 28 18.464 28 22.432A5.572 5.572 0 0 1 22.432 28h-3.936c-2.624 0-4.768-2.144-4.768-4.8Z" fill="#D18EE2"/><path d="M8.128 19.232A4.138 4.138 0 0 0 4 23.36v.544C4 26.144 5.856 28 8.128 28a4.138 4.138 0 0 0 4.128-4.128v-.544c-.032-2.24-1.856-4.096-4.128-4.096Z" fill="#FF7759"/></svg>

After

Width:  |  Height:  |  Size: 738 B

@@ -0,0 +1,55 @@
import { Callbacks } from 'langchain/callbacks'
import { Document } from 'langchain/document'
import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors'
import axios from 'axios'
export class CohereRerank extends BaseDocumentCompressor {
private cohereAPIKey: any
private COHERE_API_URL = 'https://api.cohere.ai/v1/rerank'
private readonly model: string
private readonly k: number
private readonly maxChunksPerDoc: number
constructor(cohereAPIKey: string, model: string, k: number, maxChunksPerDoc: number) {
super()
this.cohereAPIKey = cohereAPIKey
this.model = model
this.k = k
this.maxChunksPerDoc = maxChunksPerDoc
}
async compressDocuments(
documents: Document<Record<string, any>>[],
query: string,
_?: Callbacks | undefined
): Promise<Document<Record<string, any>>[]> {
// avoid empty api call
if (documents.length === 0) {
return []
}
const config = {
headers: {
Authorization: `Bearer ${this.cohereAPIKey}`,
'Content-Type': 'application/json',
Accept: 'application/json'
}
}
const data = {
model: this.model,
topN: this.k,
max_chunks_per_doc: this.maxChunksPerDoc,
query: query,
return_documents: false,
documents: documents.map((doc) => doc.pageContent)
}
try {
let returnedDocs = await axios.post(this.COHERE_API_URL, data, config)
const finalResults: Document<Record<string, any>>[] = []
returnedDocs.data.results.forEach((result: any) => {
const doc = documents[result.index]
doc.metadata.relevance_score = result.relevance_score
finalResults.push(doc)
})
return finalResults
} catch (error) {
return documents
}
}
}
@@ -0,0 +1,142 @@
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { BaseRetriever } from 'langchain/schema/retriever'
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src'
import { CohereRerank } from './CohereRerank'
import { VectorStoreRetriever } from 'langchain/vectorstores/base'
class CohereRerankRetriever_Retrievers implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
credential: INodeParams
badge: string
outputs: INodeOutputsValue[]
constructor() {
this.label = 'Cohere Rerank Retriever'
this.name = 'cohereRerankRetriever'
this.version = 1.0
this.type = 'Cohere Rerank Retriever'
this.icon = 'Cohere.svg'
this.category = 'Retrievers'
this.badge = 'NEW'
this.description = 'Cohere Rerank indexes the documents from most to least semantically relevant to the query.'
this.baseClasses = [this.type, 'BaseRetriever']
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['cohereApi']
}
this.inputs = [
{
label: 'Vector Store Retriever',
name: 'baseRetriever',
type: 'VectorStoreRetriever'
},
{
label: 'Model Name',
name: 'model',
type: 'options',
options: [
{
label: 'rerank-english-v2.0',
name: 'rerank-english-v2.0'
},
{
label: 'rerank-multilingual-v2.0',
name: 'rerank-multilingual-v2.0'
}
],
default: 'rerank-english-v2.0',
optional: true
},
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
},
{
label: 'Top K',
name: 'topK',
description: 'Number of top results to fetch. Default to the TopK of the Base Retriever',
placeholder: '4',
type: 'number',
additionalParams: true,
optional: true
},
{
label: 'Max Chunks Per Doc',
name: 'maxChunksPerDoc',
description: 'The maximum number of chunks to produce internally from a document. Default to 10',
placeholder: '10',
type: 'number',
additionalParams: true,
optional: true
}
]
this.outputs = [
{
label: 'Cohere Rerank Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Document',
name: 'document',
baseClasses: ['Document']
},
{
label: 'Text',
name: 'text',
baseClasses: ['string', 'json']
}
]
}
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
const model = nodeData.inputs?.model as string
const query = nodeData.inputs?.query as string
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData)
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4
const maxChunksPerDoc = nodeData.inputs?.maxChunksPerDoc as string
const max_chunks_per_doc = maxChunksPerDoc ? parseFloat(maxChunksPerDoc) : 10
const output = nodeData.outputs?.output as string
const cohereCompressor = new CohereRerank(cohereApiKey, model, k, max_chunks_per_doc)
const retriever = new ContextualCompressionRetriever({
baseCompressor: cohereCompressor,
baseRetriever: baseRetriever
})
if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') {
let finaltext = ''
const docs = await retriever.getRelevantDocuments(query ? query : input)
for (const doc of docs) finaltext += `${doc.pageContent}\n`
return handleEscapeCharacters(finaltext, false)
}
return retriever
}
}
module.exports = { nodeClass: CohereRerankRetriever_Retrievers }
@@ -0,0 +1,133 @@
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { BaseRetriever } from 'langchain/schema/retriever'
import { Embeddings } from 'langchain/embeddings/base'
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
import { EmbeddingsFilter } from 'langchain/retrievers/document_compressors/embeddings_filter'
import { handleEscapeCharacters } from '../../../src/utils'
class EmbeddingsFilterRetriever_Retrievers implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
outputs: INodeOutputsValue[]
badge: string
constructor() {
this.label = 'Embeddings Filter Retriever'
this.name = 'embeddingsFilterRetriever'
this.version = 1.0
this.type = 'EmbeddingsFilterRetriever'
this.icon = 'compressionRetriever.svg'
this.category = 'Retrievers'
this.badge = 'NEW'
this.description = 'A document compressor that uses embeddings to drop documents unrelated to the query'
this.baseClasses = [this.type, 'BaseRetriever']
this.inputs = [
{
label: 'Vector Store Retriever',
name: 'baseRetriever',
type: 'VectorStoreRetriever'
},
{
label: 'Embeddings',
name: 'embeddings',
type: 'Embeddings'
},
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
},
{
label: 'Similarity Threshold',
name: 'similarityThreshold',
description:
'Threshold for determining when two documents are similar enough to be considered redundant. Must be specified if `k` is not set',
type: 'number',
default: 0.8,
step: 0.1,
optional: true
},
{
label: 'K',
name: 'k',
description:
'The number of relevant documents to return. Can be explicitly set to undefined, in which case similarity_threshold must be specified. Defaults to 20',
type: 'number',
default: 20,
step: 1,
optional: true,
additionalParams: true
}
]
this.outputs = [
{
label: 'Embeddings Filter Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Document',
name: 'document',
baseClasses: ['Document']
},
{
label: 'Text',
name: 'text',
baseClasses: ['string', 'json']
}
]
}
async init(nodeData: INodeData, input: string): Promise<any> {
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
const embeddings = nodeData.inputs?.embeddings as Embeddings
const query = nodeData.inputs?.query as string
const similarityThreshold = nodeData.inputs?.similarityThreshold as string
const k = nodeData.inputs?.k as string
const output = nodeData.outputs?.output as string
if (k === undefined && similarityThreshold === undefined) {
throw new Error(`Must specify one of "k" or "similarity_threshold".`)
}
const similarityThresholdNumber = similarityThreshold ? parseFloat(similarityThreshold) : 0.8
const kNumber = k ? parseFloat(k) : undefined
const baseCompressor = new EmbeddingsFilter({
embeddings: embeddings,
similarityThreshold: similarityThresholdNumber,
k: kNumber
})
const retriever = new ContextualCompressionRetriever({
baseCompressor,
baseRetriever: baseRetriever
})
if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') {
let finaltext = ''
const docs = await retriever.getRelevantDocuments(query ? query : input)
for (const doc of docs) finaltext += `${doc.pageContent}\n`
return handleEscapeCharacters(finaltext, false)
}
return retriever
}
}
module.exports = { nodeClass: EmbeddingsFilterRetriever_Retrievers }
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chart-bar" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M3 12m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
<path d="M9 8m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
<path d="M15 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
<path d="M4 20l14 0" />
</svg>

After

Width:  |  Height:  |  Size: 600 B

@@ -1,8 +1,9 @@
import { VectorStore } from 'langchain/vectorstores/base' import { VectorStore } from 'langchain/vectorstores/base'
import { INode, INodeData, INodeParams } from '../../../src/Interface' import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { HydeRetriever, HydeRetrieverOptions, PromptKey } from 'langchain/retrievers/hyde' import { HydeRetriever, HydeRetrieverOptions, PromptKey } from 'langchain/retrievers/hyde'
import { BaseLanguageModel } from 'langchain/base_language' import { BaseLanguageModel } from 'langchain/base_language'
import { PromptTemplate } from 'langchain/prompts' import { PromptTemplate } from 'langchain/prompts'
import { handleEscapeCharacters } from '../../../src/utils'
class HydeRetriever_Retrievers implements INode { class HydeRetriever_Retrievers implements INode {
label: string label: string
@@ -14,11 +15,12 @@ class HydeRetriever_Retrievers implements INode {
category: string category: string
baseClasses: string[] baseClasses: string[]
inputs: INodeParams[] inputs: INodeParams[]
outputs: INodeOutputsValue[]
constructor() { constructor() {
this.label = 'Hyde Retriever' this.label = 'HyDE Retriever'
this.name = 'HydeRetriever' this.name = 'HydeRetriever'
this.version = 2.0 this.version = 3.0
this.type = 'HydeRetriever' this.type = 'HydeRetriever'
this.icon = 'hyderetriever.svg' this.icon = 'hyderetriever.svg'
this.category = 'Retrievers' this.category = 'Retrievers'
@@ -35,6 +37,14 @@ class HydeRetriever_Retrievers implements INode {
name: 'vectorStore', name: 'vectorStore',
type: 'VectorStore' type: 'VectorStore'
}, },
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
},
{ {
label: 'Select Defined Prompt', label: 'Select Defined Prompt',
name: 'promptKey', name: 'promptKey',
@@ -121,15 +131,34 @@ Passage:`
optional: true optional: true
} }
] ]
this.outputs = [
{
label: 'HyDE Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Document',
name: 'document',
baseClasses: ['Document']
},
{
label: 'Text',
name: 'text',
baseClasses: ['string', 'json']
}
]
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData, input: string): Promise<any> {
const llm = nodeData.inputs?.model as BaseLanguageModel const llm = nodeData.inputs?.model as BaseLanguageModel
const vectorStore = nodeData.inputs?.vectorStore as VectorStore const vectorStore = nodeData.inputs?.vectorStore as VectorStore
const promptKey = nodeData.inputs?.promptKey as PromptKey const promptKey = nodeData.inputs?.promptKey as PromptKey
const customPrompt = nodeData.inputs?.customPrompt as string const customPrompt = nodeData.inputs?.customPrompt as string
const query = nodeData.inputs?.query as string
const topK = nodeData.inputs?.topK as string const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4 const k = topK ? parseFloat(topK) : 4
const output = nodeData.outputs?.output as string
const obj: HydeRetrieverOptions<any> = { const obj: HydeRetrieverOptions<any> = {
llm, llm,
@@ -141,6 +170,19 @@ Passage:`
else if (promptKey) obj.promptTemplate = promptKey else if (promptKey) obj.promptTemplate = promptKey
const retriever = new HydeRetriever(obj) const retriever = new HydeRetriever(obj)
if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') {
let finaltext = ''
const docs = await retriever.getRelevantDocuments(query ? query : input)
for (const doc of docs) finaltext += `${doc.pageContent}\n`
return handleEscapeCharacters(finaltext, false)
}
return retriever return retriever
} }
} }
@@ -0,0 +1,100 @@
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { BaseRetriever } from 'langchain/schema/retriever'
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
import { BaseLanguageModel } from 'langchain/base_language'
import { LLMChainExtractor } from 'langchain/retrievers/document_compressors/chain_extract'
import { handleEscapeCharacters } from '../../../src/utils'
class LLMFilterCompressionRetriever_Retrievers implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
outputs: INodeOutputsValue[]
badge: string
constructor() {
this.label = 'LLM Filter Retriever'
this.name = 'llmFilterRetriever'
this.version = 1.0
this.type = 'LLMFilterRetriever'
this.icon = 'llmFilterRetriever.svg'
this.category = 'Retrievers'
this.badge = 'NEW'
this.description =
'Iterate over the initially returned documents and extract, from each, only the content that is relevant to the query'
this.baseClasses = [this.type, 'BaseRetriever']
this.inputs = [
{
label: 'Vector Store Retriever',
name: 'baseRetriever',
type: 'VectorStoreRetriever'
},
{
label: 'Language Model',
name: 'model',
type: 'BaseLanguageModel'
},
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
}
]
this.outputs = [
{
label: 'LLM Filter Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Document',
name: 'document',
baseClasses: ['Document']
},
{
label: 'Text',
name: 'text',
baseClasses: ['string', 'json']
}
]
}
async init(nodeData: INodeData, input: string): Promise<any> {
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
const model = nodeData.inputs?.model as BaseLanguageModel
const query = nodeData.inputs?.query as string
const output = nodeData.outputs?.output as string
if (!model) throw new Error('There must be a LLM model connected to LLM Filter Retriever')
const retriever = new ContextualCompressionRetriever({
baseCompressor: LLMChainExtractor.fromLLM(model),
baseRetriever: baseRetriever
})
if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') {
let finaltext = ''
const docs = await retriever.getRelevantDocuments(query ? query : input)
for (const doc of docs) finaltext += `${doc.pageContent}\n`
return handleEscapeCharacters(finaltext, false)
}
return retriever
}
}
module.exports = { nodeClass: LLMFilterCompressionRetriever_Retrievers }
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-filter-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11.18 20.274l-2.18 .726v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v3" /><path d="M15 19l2 2l4 -4" /></svg>

After

Width:  |  Height:  |  Size: 446 B

@@ -0,0 +1,136 @@
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { BaseLanguageModel } from 'langchain/base_language'
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
import { BaseRetriever } from 'langchain/schema/retriever'
import { ReciprocalRankFusion } from './ReciprocalRankFusion'
import { VectorStoreRetriever } from 'langchain/vectorstores/base'
import { handleEscapeCharacters } from '../../../src/utils'
class RRFRetriever_Retrievers implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
badge: string
outputs: INodeOutputsValue[]
constructor() {
this.label = 'Reciprocal Rank Fusion Retriever'
this.name = 'RRFRetriever'
this.version = 1.0
this.type = 'RRFRetriever'
this.badge = 'NEW'
this.icon = 'rrfRetriever.svg'
this.category = 'Retrievers'
this.description = 'Reciprocal Rank Fusion to re-rank search results by multiple query generation.'
this.baseClasses = [this.type, 'BaseRetriever']
this.inputs = [
{
label: 'Vector Store Retriever',
name: 'baseRetriever',
type: 'VectorStoreRetriever'
},
{
label: 'Language Model',
name: 'model',
type: 'BaseLanguageModel'
},
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
},
{
label: 'Query Count',
name: 'queryCount',
description: 'Number of synthetic queries to generate. Default to 4',
placeholder: '4',
type: 'number',
default: 4,
additionalParams: true,
optional: true
},
{
label: 'Top K',
name: 'topK',
description: 'Number of top results to fetch. Default to the TopK of the Base Retriever',
placeholder: '0',
type: 'number',
additionalParams: true,
optional: true
},
{
label: 'Constant',
name: 'c',
description:
'A constant added to the rank, controlling the balance between the importance of high-ranked items and the consideration given to lower-ranked items.\n' +
'Default is 60',
placeholder: '60',
type: 'number',
default: 60,
additionalParams: true,
optional: true
}
]
this.outputs = [
{
label: 'Reciprocal Rank Fusion Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Document',
name: 'document',
baseClasses: ['Document']
},
{
label: 'Text',
name: 'text',
baseClasses: ['string', 'json']
}
]
}
async init(nodeData: INodeData, input: string): Promise<any> {
const llm = nodeData.inputs?.model as BaseLanguageModel
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
const query = nodeData.inputs?.query as string
const queryCount = nodeData.inputs?.queryCount as string
const q = queryCount ? parseFloat(queryCount) : 4
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4
const constantC = nodeData.inputs?.c as string
const c = topK ? parseFloat(constantC) : 60
const output = nodeData.outputs?.output as string
const ragFusion = new ReciprocalRankFusion(llm, baseRetriever as VectorStoreRetriever, q, k, c)
const retriever = new ContextualCompressionRetriever({
baseCompressor: ragFusion,
baseRetriever: baseRetriever
})
if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') {
let finaltext = ''
const docs = await retriever.getRelevantDocuments(query ? query : input)
for (const doc of docs) finaltext += `${doc.pageContent}\n`
return handleEscapeCharacters(finaltext, false)
}
return retriever
}
}
module.exports = { nodeClass: RRFRetriever_Retrievers }
@@ -0,0 +1,96 @@
import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors'
import { Document } from 'langchain/document'
import { Callbacks } from 'langchain/callbacks'
import { BaseLanguageModel } from 'langchain/base_language'
import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate } from 'langchain/prompts'
import { LLMChain } from 'langchain/chains'
import { VectorStoreRetriever } from 'langchain/vectorstores/base'
export class ReciprocalRankFusion extends BaseDocumentCompressor {
private readonly llm: BaseLanguageModel
private readonly queryCount: number
private readonly topK: number
private readonly c: number
private baseRetriever: VectorStoreRetriever
constructor(llm: BaseLanguageModel, baseRetriever: VectorStoreRetriever, queryCount: number, topK: number, c: number) {
super()
this.queryCount = queryCount
this.llm = llm
this.baseRetriever = baseRetriever
this.topK = topK
this.c = c
}
async compressDocuments(
documents: Document<Record<string, any>>[],
query: string,
_?: Callbacks | undefined
): Promise<Document<Record<string, any>>[]> {
// avoid empty api call
if (documents.length === 0) {
return []
}
const chatPrompt = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate(
'You are a helpful assistant that generates multiple search queries based on a single input query.'
),
HumanMessagePromptTemplate.fromTemplate(
'Generate multiple search queries related to: {input}. Provide these alternative questions separated by newlines, do not add any numbers.'
),
HumanMessagePromptTemplate.fromTemplate('OUTPUT (' + this.queryCount + ' queries):')
])
const llmChain = new LLMChain({
llm: this.llm,
prompt: chatPrompt
})
const multipleQueries = await llmChain.call({ input: query })
const queries = []
queries.push(query)
multipleQueries.text.split('\n').map((q: string) => {
queries.push(q)
})
const docList: Document<Record<string, any>>[][] = []
for (let i = 0; i < queries.length; i++) {
const resultOne = await this.baseRetriever.vectorStore.similaritySearch(queries[i], 5)
const docs: any[] = []
resultOne.forEach((doc) => {
docs.push(doc)
})
docList.push(docs)
}
return this.reciprocalRankFunction(docList, this.c)
}
reciprocalRankFunction(docList: Document<Record<string, any>>[][], k: number): Document<Record<string, any>>[] {
docList.forEach((docs: Document<Record<string, any>>[]) => {
docs.forEach((doc: any, index: number) => {
let rank = index + 1
if (doc.metadata.relevancy_score) {
doc.metadata.relevancy_score += 1 / (rank + k)
} else {
doc.metadata.relevancy_score = 1 / (rank + k)
}
})
})
const scoreArray: any[] = []
docList.forEach((docs: Document<Record<string, any>>[]) => {
docs.forEach((doc: any) => {
scoreArray.push(doc.metadata.relevancy_score)
})
})
scoreArray.sort((a, b) => b - a)
const rerankedDocuments: Document<Record<string, any>>[] = []
const seenScores: any[] = []
scoreArray.forEach((score) => {
docList.forEach((docs) => {
docs.forEach((doc: any) => {
if (doc.metadata.relevancy_score === score && seenScores.indexOf(score) === -1) {
rerankedDocuments.push(doc)
seenScores.push(doc.metadata.relevancy_score)
}
})
})
})
return rerankedDocuments.splice(0, this.topK)
}
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-math-x-divide-y-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 21l18 -18" /><path d="M15 14l3 4.5" /><path d="M21 14l-4.5 7" /><path d="M3 4l6 6" /><path d="M3 10l6 -6" /></svg>

After

Width:  |  Height:  |  Size: 413 B

@@ -18,7 +18,7 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
constructor() { constructor() {
this.label = 'Similarity Score Threshold Retriever' this.label = 'Similarity Score Threshold Retriever'
this.name = 'similarityThresholdRetriever' this.name = 'similarityThresholdRetriever'
this.version = 1.0 this.version = 2.0
this.type = 'SimilarityThresholdRetriever' this.type = 'SimilarityThresholdRetriever'
this.icon = 'similaritythreshold.svg' this.icon = 'similaritythreshold.svg'
this.category = 'Retrievers' this.category = 'Retrievers'
@@ -30,6 +30,14 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
name: 'vectorStore', name: 'vectorStore',
type: 'VectorStore' type: 'VectorStore'
}, },
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
},
{ {
label: 'Minimum Similarity Score (%)', label: 'Minimum Similarity Score (%)',
name: 'minSimilarityScore', name: 'minSimilarityScore',
@@ -44,7 +52,8 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
description: `The maximum number of results to fetch`, description: `The maximum number of results to fetch`,
type: 'number', type: 'number',
default: 20, default: 20,
step: 1 step: 1,
additionalParams: true
}, },
{ {
label: 'K Increment', label: 'K Increment',
@@ -52,7 +61,8 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
description: `How much to increase K by each time. It'll fetch N results, then N + kIncrement, then N + kIncrement * 2, etc.`, description: `How much to increase K by each time. It'll fetch N results, then N + kIncrement, then N + kIncrement * 2, etc.`,
type: 'number', type: 'number',
default: 2, default: 2,
step: 1 step: 1,
additionalParams: true
} }
] ]
this.outputs = [ this.outputs = [
@@ -77,6 +87,7 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
async init(nodeData: INodeData, input: string): Promise<any> { async init(nodeData: INodeData, input: string): Promise<any> {
const vectorStore = nodeData.inputs?.vectorStore as VectorStore const vectorStore = nodeData.inputs?.vectorStore as VectorStore
const minSimilarityScore = nodeData.inputs?.minSimilarityScore as number const minSimilarityScore = nodeData.inputs?.minSimilarityScore as number
const query = nodeData.inputs?.query as string
const maxK = nodeData.inputs?.maxK as string const maxK = nodeData.inputs?.maxK as string
const kIncrement = nodeData.inputs?.kIncrement as string const kIncrement = nodeData.inputs?.kIncrement as string
@@ -89,11 +100,11 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
}) })
if (output === 'retriever') return retriever if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(input) else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') { else if (output === 'text') {
let finaltext = '' let finaltext = ''
const docs = await retriever.getRelevantDocuments(input) const docs = await retriever.getRelevantDocuments(query ? query : input)
for (const doc of docs) finaltext += `${doc.pageContent}\n` for (const doc of docs) finaltext += `${doc.pageContent}\n`
@@ -1,5 +1,5 @@
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
import { convertSchemaToZod, getBaseClasses } from '../../../src/utils' import { convertSchemaToZod, getBaseClasses, getVars } from '../../../src/utils'
import { DynamicStructuredTool } from './core' import { DynamicStructuredTool } from './core'
import { z } from 'zod' import { z } from 'zod'
import { DataSource } from 'typeorm' import { DataSource } from 'typeorm'
@@ -60,7 +60,7 @@ class CustomTool_Tools implements INode {
} }
} }
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const selectedToolId = nodeData.inputs?.selectedTool as string const selectedToolId = nodeData.inputs?.selectedTool as string
const customToolFunc = nodeData.inputs?.customToolFunc as string const customToolFunc = nodeData.inputs?.customToolFunc as string
@@ -81,29 +81,9 @@ class CustomTool_Tools implements INode {
} }
if (customToolFunc) obj.code = customToolFunc if (customToolFunc) obj.code = customToolFunc
const variables = await appDataSource.getRepository(databaseEntities['Variable']).find() const variables = await getVars(appDataSource, databaseEntities, nodeData)
// override variables defined in overrideConfig const flow = { chatflowId: options.chatflowid }
// nodeData.inputs.variables is an Object, check each property and override the variable
if (nodeData?.inputs?.vars) {
for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) {
const foundVar = variables.find((v) => v.name === propertyName)
if (foundVar) {
// even if the variable was defined as runtime, we override it with static value
foundVar.type = 'static'
foundVar.value = nodeData.inputs.vars[propertyName]
} else {
// add it the variables, if not found locally in the db
variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] })
}
}
}
const flow = {
chatId: options.chatId, // id is uppercase (I)
chatflowId: options.chatflowid, // id is lowercase (i)
input
}
let dynamicStructuredTool = new DynamicStructuredTool(obj) let dynamicStructuredTool = new DynamicStructuredTool(obj)
dynamicStructuredTool.setVariables(variables) dynamicStructuredTool.setVariables(variables)
@@ -1,6 +1,6 @@
import { z } from 'zod' import { z } from 'zod'
import { NodeVM } from 'vm2' import { NodeVM } from 'vm2'
import { availableDependencies } from '../../../src/utils' import { availableDependencies, defaultAllowBuiltInDep, prepareSandboxVars } from '../../../src/utils'
import { RunnableConfig } from '@langchain/core/runnables' import { RunnableConfig } from '@langchain/core/runnables'
import { StructuredTool, ToolParams } from '@langchain/core/tools' import { StructuredTool, ToolParams } from '@langchain/core/tools'
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager' import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
@@ -55,7 +55,12 @@ export class DynamicStructuredTool<
this.schema = fields.schema this.schema = fields.schema
} }
async call(arg: z.output<T>, configArg?: RunnableConfig | Callbacks, tags?: string[], overrideSessionId?: string): Promise<string> { async call(
arg: z.output<T>,
configArg?: RunnableConfig | Callbacks,
tags?: string[],
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
): Promise<string> {
const config = parseCallbackConfigArg(configArg) const config = parseCallbackConfigArg(configArg)
if (config.runName === undefined) { if (config.runName === undefined) {
config.runName = this.name config.runName = this.name
@@ -86,7 +91,7 @@ export class DynamicStructuredTool<
) )
let result let result
try { try {
result = await this._call(parsed, runManager, overrideSessionId) result = await this._call(parsed, runManager, flowConfig)
} catch (e) { } catch (e) {
await runManager?.handleToolError(e) await runManager?.handleToolError(e)
throw e throw e
@@ -95,7 +100,11 @@ export class DynamicStructuredTool<
return result return result
} }
protected async _call(arg: z.output<T>, _?: CallbackManagerForToolRun, overrideSessionId?: string): Promise<string> { protected async _call(
arg: z.output<T>,
_?: CallbackManagerForToolRun,
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
): Promise<string> {
let sandbox: any = {} let sandbox: any = {}
if (typeof arg === 'object' && Object.keys(arg).length) { if (typeof arg === 'object' && Object.keys(arg).length) {
for (const item in arg) { for (const item in arg) {
@@ -103,48 +112,13 @@ export class DynamicStructuredTool<
} }
} }
// inject variables sandbox['$vars'] = prepareSandboxVars(this.variables)
let vars = {}
if (this.variables) {
for (const item of this.variables) {
let value = item.value
// read from .env file
if (item.type === 'runtime') {
value = process.env[item.name]
}
Object.defineProperty(vars, item.name, {
enumerable: true,
configurable: true,
writable: true,
value: value
})
}
}
sandbox['$vars'] = vars
// inject flow properties // inject flow properties
if (this.flowObj) { if (this.flowObj) {
sandbox['$flow'] = { ...this.flowObj, sessionId: overrideSessionId } sandbox['$flow'] = { ...this.flowObj, ...flowConfig }
} }
const defaultAllowBuiltInDep = [
'assert',
'buffer',
'crypto',
'events',
'http',
'https',
'net',
'path',
'querystring',
'timers',
'tls',
'url',
'zlib'
]
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep : defaultAllowBuiltInDep
@@ -1,6 +1,7 @@
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { NodeVM } from 'vm2' import { NodeVM } from 'vm2'
import { availableDependencies, handleEscapeCharacters } from '../../../src/utils' import { DataSource } from 'typeorm'
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
class CustomFunction_Utilities implements INode { class CustomFunction_Utilities implements INode {
label: string label: string
@@ -55,9 +56,19 @@ class CustomFunction_Utilities implements INode {
] ]
} }
async init(nodeData: INodeData, input: string): Promise<any> { async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const javascriptFunction = nodeData.inputs?.javascriptFunction as string const javascriptFunction = nodeData.inputs?.javascriptFunction as string
const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables
const appDataSource = options.appDataSource as DataSource
const databaseEntities = options.databaseEntities as IDatabaseEntity
const variables = await getVars(appDataSource, databaseEntities, nodeData)
const flow = {
chatflowId: options.chatflowid,
sessionId: options.sessionId,
chatId: options.chatId,
input
}
let inputVars: ICommonObject = {} let inputVars: ICommonObject = {}
if (functionInputVariablesRaw) { if (functionInputVariablesRaw) {
@@ -69,29 +80,30 @@ class CustomFunction_Utilities implements INode {
} }
} }
let sandbox: any = { $input: input } // Some values might be a stringified JSON, parse it
for (const key in inputVars) {
if (Object.keys(inputVars).length) { if (typeof inputVars[key] === 'string' && inputVars[key].startsWith('{') && inputVars[key].endsWith('}')) {
for (const item in inputVars) { try {
sandbox[`$${item}`] = inputVars[item] inputVars[key] = JSON.parse(inputVars[key])
} catch (e) {
continue
}
} }
} }
const defaultAllowBuiltInDep = [ let sandbox: any = { $input: input }
'assert', sandbox['$vars'] = prepareSandboxVars(variables)
'buffer', sandbox['$flow'] = flow
'crypto',
'events', if (Object.keys(inputVars).length) {
'http', for (const item in inputVars) {
'https', let value = inputVars[item]
'net', if (typeof value === 'string') {
'path', value = handleEscapeCharacters(value, true)
'querystring', }
'timers', sandbox[`$${item}`] = value
'tls', }
'url', }
'zlib'
]
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
@@ -1,6 +1,7 @@
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { NodeVM } from 'vm2' import { NodeVM } from 'vm2'
import { availableDependencies } from '../../../src/utils' import { DataSource } from 'typeorm'
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
class IfElseFunction_Utilities implements INode { class IfElseFunction_Utilities implements INode {
label: string label: string
@@ -73,10 +74,20 @@ class IfElseFunction_Utilities implements INode {
] ]
} }
async init(nodeData: INodeData, input: string): Promise<any> { async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const ifFunction = nodeData.inputs?.ifFunction as string const ifFunction = nodeData.inputs?.ifFunction as string
const elseFunction = nodeData.inputs?.elseFunction as string const elseFunction = nodeData.inputs?.elseFunction as string
const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables
const appDataSource = options.appDataSource as DataSource
const databaseEntities = options.databaseEntities as IDatabaseEntity
const variables = await getVars(appDataSource, databaseEntities, nodeData)
const flow = {
chatflowId: options.chatflowid,
sessionId: options.sessionId,
chatId: options.chatId,
input
}
let inputVars: ICommonObject = {} let inputVars: ICommonObject = {}
if (functionInputVariablesRaw) { if (functionInputVariablesRaw) {
@@ -84,34 +95,35 @@ class IfElseFunction_Utilities implements INode {
inputVars = inputVars =
typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw)
} catch (exception) { } catch (exception) {
throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) throw new Error("Invalid JSON in the IfElse's Input Variables: " + exception)
}
}
// Some values might be a stringified JSON, parse it
for (const key in inputVars) {
if (typeof inputVars[key] === 'string' && inputVars[key].startsWith('{') && inputVars[key].endsWith('}')) {
try {
inputVars[key] = JSON.parse(inputVars[key])
} catch (e) {
continue
}
} }
} }
let sandbox: any = { $input: input } let sandbox: any = { $input: input }
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
if (Object.keys(inputVars).length) { if (Object.keys(inputVars).length) {
for (const item in inputVars) { for (const item in inputVars) {
sandbox[`$${item}`] = inputVars[item] let value = inputVars[item]
if (typeof value === 'string') {
value = handleEscapeCharacters(value, true)
}
sandbox[`$${item}`] = value
} }
} }
const defaultAllowBuiltInDep = [
'assert',
'buffer',
'crypto',
'events',
'http',
'https',
'net',
'path',
'querystring',
'timers',
'tls',
'url',
'zlib'
]
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep : defaultAllowBuiltInDep
@@ -0,0 +1,40 @@
import { INode, INodeParams } from '../../../src/Interface'
class StickyNote implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
constructor() {
this.label = 'Sticky Note'
this.name = 'stickyNote'
this.version = 1.0
this.type = 'StickyNote'
this.icon = 'stickyNote.svg'
this.category = 'Utilities'
this.description = 'Add a sticky note'
this.inputs = [
{
label: '',
name: 'note',
type: 'string',
rows: 1,
placeholder: 'Type something here',
optional: true
}
]
this.baseClasses = [this.type]
}
async init(): Promise<any> {
return new StickyNote()
}
}
module.exports = { nodeClass: StickyNote }
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3Z"/>
<path d="M15 3v6h6"/>
</svg>

After

Width:  |  Height:  |  Size: 305 B

@@ -0,0 +1,182 @@
import { flatten } from 'lodash'
import { Embeddings } from 'langchain/embeddings/base'
import { Document } from 'langchain/document'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData } from '../../../src/utils'
import { AstraDBVectorStore, AstraLibArgs } from '@langchain/community/vectorstores/astradb'
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
class Astra_VectorStores implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
badge: string
baseClasses: string[]
inputs: INodeParams[]
credential: INodeParams
outputs: INodeOutputsValue[]
constructor() {
this.label = 'Astra'
this.name = 'Astra'
this.version = 1.0
this.type = 'Astra'
this.icon = 'astra.svg'
this.category = 'Vector Stores'
this.description = `Upsert embedded data and perform similarity or mmr search upon query using DataStax Astra DB, a serverless vector database thats perfect for managing mission-critical AI workloads`
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
this.badge = 'NEW'
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['AstraDBApi']
}
this.inputs = [
{
label: 'Document',
name: 'document',
type: 'Document',
list: true,
optional: true
},
{
label: 'Embeddings',
name: 'embeddings',
type: 'Embeddings'
},
{
label: 'Vector Dimension',
name: 'vectorDimension',
type: 'number',
placeholder: '1536',
optional: true,
description: 'Dimension used for storing vector embedding'
},
{
label: 'Similarity Metric',
name: 'similarityMetric',
type: 'string',
placeholder: 'cosine',
optional: true,
description: 'cosine | euclidean | dot_product'
},
{
label: 'Top K',
name: 'topK',
description: 'Number of top results to fetch. Default to 4',
placeholder: '4',
type: 'number',
additionalParams: true,
optional: true
}
]
addMMRInputParams(this.inputs)
this.outputs = [
{
label: 'Astra Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Astra Vector Store',
name: 'vectorStore',
baseClasses: [this.type, ...getBaseClasses(AstraDBVectorStore)]
}
]
}
//@ts-ignore
vectorStoreMethods = {
async upsert(nodeData: INodeData, options: ICommonObject): Promise<void> {
const docs = nodeData.inputs?.document as Document[]
const embeddings = nodeData.inputs?.embeddings as Embeddings
const vectorDimension = nodeData.inputs?.vectorDimension as number
const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product']
if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) {
throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`)
}
const clientConfig = {
token: credentialData?.applicationToken,
endpoint: credentialData?.dbEndPoint
}
const astraConfig: AstraLibArgs = {
...clientConfig,
collection: credentialData.collectionName ?? 'flowise_test',
collectionOptions: {
vector: {
dimension: vectorDimension ?? 1536,
metric: similarityMetric ?? 'cosine'
}
}
}
const flattenDocs = docs && docs.length ? flatten(docs) : []
const finalDocs = []
for (let i = 0; i < flattenDocs.length; i += 1) {
if (flattenDocs[i] && flattenDocs[i].pageContent) {
finalDocs.push(new Document(flattenDocs[i]))
}
}
try {
await AstraDBVectorStore.fromDocuments(finalDocs, embeddings, astraConfig)
} catch (e) {
throw new Error(e)
}
}
}
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const docs = nodeData.inputs?.document as Document[]
const embeddings = nodeData.inputs?.embeddings as Embeddings
const vectorDimension = nodeData.inputs?.vectorDimension as number
const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product']
if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) {
throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`)
}
const clientConfig = {
token: credentialData?.applicationToken,
endpoint: credentialData?.dbEndPoint
}
const astraConfig: AstraLibArgs = {
...clientConfig,
collection: credentialData.collectionName ?? 'flowise_test',
collectionOptions: {
vector: {
dimension: vectorDimension ?? 1536,
metric: similarityMetric ?? 'cosine'
}
}
}
const flattenDocs = docs && docs.length ? flatten(docs) : []
const finalDocs = []
for (let i = 0; i < flattenDocs.length; i += 1) {
if (flattenDocs[i] && flattenDocs[i].pageContent) {
finalDocs.push(new Document(flattenDocs[i]))
}
}
const vectorStore = await AstraDBVectorStore.fromExistingIndex(embeddings, astraConfig)
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
}
}
module.exports = { nodeClass: Astra_VectorStores }
@@ -0,0 +1,12 @@
<svg width="1200" height="1200" viewBox="0 0 1200 1200" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1200" height="1200" fill="black"/>
<g clip-path="url(#clip0_102_1968)">
<path d="M508.819 464.97H267.001V737.697H508.819L569.566 690.526V512.14L508.819 464.97ZM313.864 512.14H522.703V690.575H313.864V512.14Z" fill="white"/>
<path d="M917.531 514.121V468H696.425L636.389 514.121V577.447L696.425 623.568H889.124V688.545H648.348V734.667H875.409L935.444 688.545V623.568L875.409 577.447H682.709V514.121H917.531Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_102_1968">
<rect width="668.444" height="266.667" fill="white" transform="translate(267 468)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 694 B

@@ -65,6 +65,14 @@ class Milvus_VectorStores implements INode {
name: 'milvusCollection', name: 'milvusCollection',
type: 'string' type: 'string'
}, },
{
label: 'Milvus Text Field',
name: 'milvusTextField',
type: 'string',
placeholder: 'langchain_text',
optional: true,
additionalParams: true
},
{ {
label: 'Milvus Filter', label: 'Milvus Filter',
name: 'milvusFilter', name: 'milvusFilter',
@@ -150,6 +158,7 @@ class Milvus_VectorStores implements INode {
const address = nodeData.inputs?.milvusServerUrl as string const address = nodeData.inputs?.milvusServerUrl as string
const collectionName = nodeData.inputs?.milvusCollection as string const collectionName = nodeData.inputs?.milvusCollection as string
const milvusFilter = nodeData.inputs?.milvusFilter as string const milvusFilter = nodeData.inputs?.milvusFilter as string
const textField = nodeData.inputs?.milvusTextField as string
// embeddings // embeddings
const embeddings = nodeData.inputs?.embeddings as Embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings
@@ -169,7 +178,8 @@ class Milvus_VectorStores implements INode {
// init MilvusLibArgs // init MilvusLibArgs
const milVusArgs: MilvusLibArgs = { const milVusArgs: MilvusLibArgs = {
url: address, url: address,
collectionName: collectionName collectionName: collectionName,
textField: textField
} }
if (milvusUser) milVusArgs.username = milvusUser if (milvusUser) milVusArgs.username = milvusUser
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
import { Document } from 'langchain/document' import { Document } from 'langchain/document'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
class MongoDBAtlas_VectorStores implements INode { class MongoDBAtlas_VectorStores implements INode {
label: string label: string
@@ -24,7 +25,7 @@ class MongoDBAtlas_VectorStores implements INode {
this.label = 'MongoDB Atlas' this.label = 'MongoDB Atlas'
this.name = 'mongoDBAtlas' this.name = 'mongoDBAtlas'
this.version = 1.0 this.version = 1.0
this.description = `Upsert embedded data and perform similarity search upon query using MongoDB Atlas, a managed cloud mongodb database` this.description = `Upsert embedded data and perform similarity or mmr search upon query using MongoDB Atlas, a managed cloud mongodb database`
this.type = 'MongoDB Atlas' this.type = 'MongoDB Atlas'
this.icon = 'mongodb.svg' this.icon = 'mongodb.svg'
this.category = 'Vector Stores' this.category = 'Vector Stores'
@@ -95,6 +96,7 @@ class MongoDBAtlas_VectorStores implements INode {
optional: true optional: true
} }
] ]
addMMRInputParams(this.inputs)
this.outputs = [ this.outputs = [
{ {
label: 'MongoDB Retriever', label: 'MongoDB Retriever',
@@ -162,9 +164,6 @@ class MongoDBAtlas_VectorStores implements INode {
let textKey = nodeData.inputs?.textKey as string let textKey = nodeData.inputs?.textKey as string
let embeddingKey = nodeData.inputs?.embeddingKey as string let embeddingKey = nodeData.inputs?.embeddingKey as string
const embeddings = nodeData.inputs?.embeddings as Embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
const output = nodeData.outputs?.output as string
let mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) let mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData)
@@ -181,13 +180,7 @@ class MongoDBAtlas_VectorStores implements INode {
embeddingKey embeddingKey
}) })
if (output === 'retriever') { return resolveVectorStoreOrRetriever(nodeData, vectorStore)
return vectorStore.asRetriever(k)
} else if (output === 'vectorStore') {
;(vectorStore as any).k = k
return vectorStore
}
return vectorStore
} }
} }
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
import { Document } from 'langchain/document' import { Document } from 'langchain/document'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
class Pinecone_VectorStores implements INode { class Pinecone_VectorStores implements INode {
label: string label: string
@@ -23,11 +24,11 @@ class Pinecone_VectorStores implements INode {
constructor() { constructor() {
this.label = 'Pinecone' this.label = 'Pinecone'
this.name = 'pinecone' this.name = 'pinecone'
this.version = 1.0 this.version = 2.0
this.type = 'Pinecone' this.type = 'Pinecone'
this.icon = 'pinecone.svg' this.icon = 'pinecone.svg'
this.category = 'Vector Stores' this.category = 'Vector Stores'
this.description = `Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database` this.description = `Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database`
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
this.badge = 'NEW' this.badge = 'NEW'
this.credential = { this.credential = {
@@ -79,6 +80,7 @@ class Pinecone_VectorStores implements INode {
optional: true optional: true
} }
] ]
addMMRInputParams(this.inputs)
this.outputs = [ this.outputs = [
{ {
label: 'Pinecone Retriever', label: 'Pinecone Retriever',
@@ -106,8 +108,7 @@ class Pinecone_VectorStores implements INode {
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
const client = new Pinecone({ const client = new Pinecone({
apiKey: pineconeApiKey, apiKey: pineconeApiKey
environment: pineconeEnv
}) })
const pineconeIndex = client.Index(index) const pineconeIndex = client.Index(index)
@@ -140,17 +141,13 @@ class Pinecone_VectorStores implements INode {
const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter
const docs = nodeData.inputs?.document as Document[] const docs = nodeData.inputs?.document as Document[]
const embeddings = nodeData.inputs?.embeddings as Embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings
const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
const client = new Pinecone({ const client = new Pinecone({
apiKey: pineconeApiKey, apiKey: pineconeApiKey
environment: pineconeEnv
}) })
const pineconeIndex = client.Index(index) const pineconeIndex = client.Index(index)
@@ -175,14 +172,7 @@ class Pinecone_VectorStores implements INode {
const vectorStore = await PineconeStore.fromExistingIndex(embeddings, obj) const vectorStore = await PineconeStore.fromExistingIndex(embeddings, obj)
if (output === 'retriever') { return resolveVectorStoreOrRetriever(nodeData, vectorStore)
const retriever = vectorStore.asRetriever(k)
return retriever
} else if (output === 'vectorStore') {
;(vectorStore as any).k = k
return vectorStore
}
return vectorStore
} }
} }
@@ -95,11 +95,9 @@ class Pinecone_Existing_VectorStores implements INode {
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
const client = new Pinecone({ const client = new Pinecone({
apiKey: pineconeApiKey, apiKey: pineconeApiKey
environment: pineconeEnv
}) })
const pineconeIndex = client.Index(index) const pineconeIndex = client.Index(index)
@@ -96,11 +96,9 @@ class PineconeUpsert_VectorStores implements INode {
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
const client = new Pinecone({ const client = new Pinecone({
apiKey: pineconeApiKey, apiKey: pineconeApiKey
environment: pineconeEnv
}) })
const pineconeIndex = client.Index(index) const pineconeIndex = client.Index(index)
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { SupabaseLibArgs, SupabaseVectorStore } from 'langchain/vectorstores/supabase' import { SupabaseLibArgs, SupabaseVectorStore } from 'langchain/vectorstores/supabase'
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
class Supabase_VectorStores implements INode { class Supabase_VectorStores implements INode {
label: string label: string
@@ -23,11 +24,11 @@ class Supabase_VectorStores implements INode {
constructor() { constructor() {
this.label = 'Supabase' this.label = 'Supabase'
this.name = 'supabase' this.name = 'supabase'
this.version = 1.0 this.version = 2.0
this.type = 'Supabase' this.type = 'Supabase'
this.icon = 'supabase.svg' this.icon = 'supabase.svg'
this.category = 'Vector Stores' this.category = 'Vector Stores'
this.description = 'Upsert embedded data and perform similarity search upon query using Supabase via pgvector extension' this.description = 'Upsert embedded data and perform similarity or mmr search upon query using Supabase via pgvector extension'
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
this.badge = 'NEW' this.badge = 'NEW'
this.credential = { this.credential = {
@@ -81,6 +82,7 @@ class Supabase_VectorStores implements INode {
optional: true optional: true
} }
] ]
addMMRInputParams(this.inputs)
this.outputs = [ this.outputs = [
{ {
label: 'Supabase Retriever', label: 'Supabase Retriever',
@@ -135,9 +137,6 @@ class Supabase_VectorStores implements INode {
const queryName = nodeData.inputs?.queryName as string const queryName = nodeData.inputs?.queryName as string
const embeddings = nodeData.inputs?.embeddings as Embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings
const supabaseMetadataFilter = nodeData.inputs?.supabaseMetadataFilter const supabaseMetadataFilter = nodeData.inputs?.supabaseMetadataFilter
const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const supabaseApiKey = getCredentialParam('supabaseApiKey', credentialData, nodeData) const supabaseApiKey = getCredentialParam('supabaseApiKey', credentialData, nodeData)
@@ -157,14 +156,7 @@ class Supabase_VectorStores implements INode {
const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, obj) const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, obj)
if (output === 'retriever') { return resolveVectorStoreOrRetriever(nodeData, vectorStore)
const retriever = vectorStore.asRetriever(k)
return retriever
} else if (output === 'vectorStore') {
;(vectorStore as any).k = k
return vectorStore
}
return vectorStore
} }
} }
@@ -1,5 +1,5 @@
import { flatten } from 'lodash' import { flatten } from 'lodash'
import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile } from 'langchain/vectorstores/vectara' import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile, MMRConfig } from 'langchain/vectorstores/vectara'
import { Document } from 'langchain/document' import { Document } from 'langchain/document'
import { Embeddings } from 'langchain/embeddings/base' import { Embeddings } from 'langchain/embeddings/base'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
@@ -22,7 +22,7 @@ class Vectara_VectorStores implements INode {
constructor() { constructor() {
this.label = 'Vectara' this.label = 'Vectara'
this.name = 'vectara' this.name = 'vectara'
this.version = 1.0 this.version = 2.0
this.type = 'Vectara' this.type = 'Vectara'
this.icon = 'vectara.png' this.icon = 'vectara.png'
this.category = 'Vector Stores' this.category = 'Vector Stores'
@@ -82,7 +82,9 @@ class Vectara_VectorStores implements INode {
label: 'Lambda', label: 'Lambda',
name: 'lambda', name: 'lambda',
description: description:
'Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.', 'Enable hybrid search to improve retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.' +
'A value of 0.0 means that only neural search is used, while a value of 1.0 means that only keyword-based search is used. Defaults to 0.0 (neural only).',
default: 0.0,
type: 'number', type: 'number',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -90,8 +92,30 @@ class Vectara_VectorStores implements INode {
{ {
label: 'Top K', label: 'Top K',
name: 'topK', name: 'topK',
description: 'Number of top results to fetch. Defaults to 4', description: 'Number of top results to fetch. Defaults to 5',
placeholder: '4', placeholder: '5',
type: 'number',
additionalParams: true,
optional: true
},
{
label: 'MMR K',
name: 'mmrK',
description: 'Number of top results to fetch for MMR. Defaults to 50',
placeholder: '50',
type: 'number',
additionalParams: true,
optional: true
},
{
label: 'MMR diversity bias',
name: 'mmrDiversityBias',
step: 0.1,
description:
'The diversity bias to use for MMR. This is a value between 0.0 and 1.0' +
'Values closer to 1.0 optimize for the most diverse results.' +
'Defaults to 0 (MMR disabled)',
placeholder: '0.0',
type: 'number', type: 'number',
additionalParams: true, additionalParams: true,
optional: true optional: true
@@ -191,7 +215,9 @@ class Vectara_VectorStores implements INode {
const lambda = nodeData.inputs?.lambda as number const lambda = nodeData.inputs?.lambda as number
const output = nodeData.outputs?.output as string const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4 const k = topK ? parseFloat(topK) : 5
const mmrK = nodeData.inputs?.mmrK as number
const mmrDiversityBias = nodeData.inputs?.mmrDiversityBias as number
const vectaraArgs: VectaraLibArgs = { const vectaraArgs: VectaraLibArgs = {
apiKey: apiKey, apiKey: apiKey,
@@ -208,6 +234,11 @@ class Vectara_VectorStores implements INode {
if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore
if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter
vectaraFilter.contextConfig = vectaraContextConfig vectaraFilter.contextConfig = vectaraContextConfig
const mmrConfig: MMRConfig = {}
mmrConfig.enabled = mmrDiversityBias > 0
mmrConfig.mmrTopK = mmrK
mmrConfig.diversityBias = mmrDiversityBias
vectaraFilter.mmrConfig = mmrConfig
const vectorStore = new VectaraStore(vectaraArgs) const vectorStore = new VectaraStore(vectaraArgs)
@@ -0,0 +1,75 @@
import { INodeData } from '../../src'
export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: any) => {
const output = nodeData.outputs?.output as string
const searchType = nodeData.outputs?.searchType as string
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
if (output === 'retriever') {
if ('mmr' === searchType) {
const fetchK = nodeData.inputs?.fetchK as string
const lambda = nodeData.inputs?.lambda as string
const f = fetchK ? parseInt(fetchK) : 20
const l = lambda ? parseFloat(lambda) : 0.5
return vectorStore.asRetriever({
searchType: 'mmr',
k: k,
searchKwargs: {
fetchK: f,
lambda: l
}
})
} else {
// "searchType" is "similarity"
return vectorStore.asRetriever(k)
}
} else if (output === 'vectorStore') {
;(vectorStore as any).k = k
return vectorStore
}
}
export const addMMRInputParams = (inputs: any[]) => {
const mmrInputParams = [
{
label: 'Search Type',
name: 'searchType',
type: 'options',
default: 'similarity',
options: [
{
label: 'Similarity',
name: 'similarity'
},
{
label: 'Max Marginal Relevance',
name: 'mmr'
}
],
additionalParams: true,
optional: true
},
{
label: 'Fetch K (for MMR Search)',
name: 'fetchK',
description: 'Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR',
placeholder: '20',
type: 'number',
additionalParams: true,
optional: true
},
{
label: 'Lambda (for MMR Search)',
name: 'lambda',
description:
'Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR',
placeholder: '0.5',
type: 'number',
additionalParams: true,
optional: true
}
]
inputs.push(...mmrInputParams)
}
@@ -5,6 +5,7 @@ import { Document } from 'langchain/document'
import { Embeddings } from 'langchain/embeddings/base' import { Embeddings } from 'langchain/embeddings/base'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
class Weaviate_VectorStores implements INode { class Weaviate_VectorStores implements INode {
label: string label: string
@@ -23,12 +24,12 @@ class Weaviate_VectorStores implements INode {
constructor() { constructor() {
this.label = 'Weaviate' this.label = 'Weaviate'
this.name = 'weaviate' this.name = 'weaviate'
this.version = 1.0 this.version = 2.0
this.type = 'Weaviate' this.type = 'Weaviate'
this.icon = 'weaviate.png' this.icon = 'weaviate.png'
this.category = 'Vector Stores' this.category = 'Vector Stores'
this.description = this.description =
'Upsert embedded data and perform similarity search upon query using Weaviate, a scalable open-source vector database' 'Upsert embedded data and perform similarity or mmr search using Weaviate, a scalable open-source vector database'
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
this.badge = 'NEW' this.badge = 'NEW'
this.credential = { this.credential = {
@@ -107,6 +108,7 @@ class Weaviate_VectorStores implements INode {
optional: true optional: true
} }
] ]
addMMRInputParams(this.inputs)
this.outputs = [ this.outputs = [
{ {
label: 'Weaviate Retriever', label: 'Weaviate Retriever',
@@ -174,9 +176,6 @@ class Weaviate_VectorStores implements INode {
const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string
const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string
const embeddings = nodeData.inputs?.embeddings as Embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings
const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const weaviateApiKey = getCredentialParam('weaviateApiKey', credentialData, nodeData) const weaviateApiKey = getCredentialParam('weaviateApiKey', credentialData, nodeData)
@@ -199,14 +198,7 @@ class Weaviate_VectorStores implements INode {
const vectorStore = await WeaviateStore.fromExistingIndex(embeddings, obj) const vectorStore = await WeaviateStore.fromExistingIndex(embeddings, obj)
if (output === 'retriever') { return resolveVectorStoreOrRetriever(nodeData, vectorStore)
const retriever = vectorStore.asRetriever(k)
return retriever
} else if (output === 'vectorStore') {
;(vectorStore as any).k = k
return vectorStore
}
return vectorStore
} }
} }
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
import { Document } from 'langchain/document' import { Document } from 'langchain/document'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
class Zep_VectorStores implements INode { class Zep_VectorStores implements INode {
label: string label: string
@@ -23,12 +24,12 @@ class Zep_VectorStores implements INode {
constructor() { constructor() {
this.label = 'Zep' this.label = 'Zep'
this.name = 'zep' this.name = 'zep'
this.version = 1.0 this.version = 2.0
this.type = 'Zep' this.type = 'Zep'
this.icon = 'zep.svg' this.icon = 'zep.svg'
this.category = 'Vector Stores' this.category = 'Vector Stores'
this.description = this.description =
'Upsert embedded data and perform similarity search upon query using Zep, a fast and scalable building block for LLM apps' 'Upsert embedded data and perform similarity or mmr search upon query using Zep, a fast and scalable building block for LLM apps'
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
this.badge = 'NEW' this.badge = 'NEW'
this.credential = { this.credential = {
@@ -88,6 +89,7 @@ class Zep_VectorStores implements INode {
optional: true optional: true
} }
] ]
addMMRInputParams(this.inputs)
this.outputs = [ this.outputs = [
{ {
label: 'Zep Retriever', label: 'Zep Retriever',
@@ -144,9 +146,6 @@ class Zep_VectorStores implements INode {
const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter
const dimension = nodeData.inputs?.dimension as number const dimension = nodeData.inputs?.dimension as number
const embeddings = nodeData.inputs?.embeddings as Embeddings const embeddings = nodeData.inputs?.embeddings as Embeddings
const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
@@ -165,14 +164,7 @@ class Zep_VectorStores implements INode {
const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig) const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig)
if (output === 'retriever') { return resolveVectorStoreOrRetriever(nodeData, vectorStore)
const retriever = vectorStore.asRetriever(k)
return retriever
} else if (output === 'vectorStore') {
;(vectorStore as any).k = k
return vectorStore
}
return vectorStore
} }
} }
@@ -210,7 +202,7 @@ class ZepExistingVS extends ZepVectorStore {
this.args = args this.args = args
} }
async initalizeCollection(args: IZepConfig & Partial<ZepFilter>) { async initializeCollection(args: IZepConfig & Partial<ZepFilter>) {
this.client = await ZepClient.init(args.apiUrl, args.apiKey) this.client = await ZepClient.init(args.apiUrl, args.apiKey)
try { try {
this.collection = await this.client.document.getCollection(args.collectionName) this.collection = await this.client.document.getCollection(args.collectionName)
@@ -259,7 +251,7 @@ class ZepExistingVS extends ZepVectorStore {
const newfilter = { const newfilter = {
where: { and: ANDFilters } where: { and: ANDFilters }
} }
await this.initalizeCollection(this.args!).catch((err) => { await this.initializeCollection(this.args!).catch((err) => {
console.error('Error initializing collection:', err) console.error('Error initializing collection:', err)
throw err throw err
}) })
+9 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "flowise-components", "name": "flowise-components",
"version": "1.5.0", "version": "1.5.1",
"description": "Flowiseai Components", "description": "Flowiseai Components",
"main": "dist/src/index", "main": "dist/src/index",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@@ -19,23 +19,26 @@
"@aws-sdk/client-bedrock-runtime": "3.422.0", "@aws-sdk/client-bedrock-runtime": "3.422.0",
"@aws-sdk/client-dynamodb": "^3.360.0", "@aws-sdk/client-dynamodb": "^3.360.0",
"@aws-sdk/client-s3": "^3.427.0", "@aws-sdk/client-s3": "^3.427.0",
"@datastax/astra-db-ts": "^0.1.2",
"@dqbd/tiktoken": "^1.0.7", "@dqbd/tiktoken": "^1.0.7",
"@elastic/elasticsearch": "^8.9.0", "@elastic/elasticsearch": "^8.9.0",
"@getzep/zep-js": "^0.9.0", "@getzep/zep-js": "^0.9.0",
"@gomomento/sdk": "^1.51.1", "@gomomento/sdk": "^1.51.1",
"@gomomento/sdk-core": "^1.51.1", "@gomomento/sdk-core": "^1.51.1",
"@google-ai/generativelanguage": "^0.2.1", "@google-ai/generativelanguage": "^0.2.1",
"@google/generative-ai": "^0.1.3",
"@huggingface/inference": "^2.6.1", "@huggingface/inference": "^2.6.1",
"@langchain/community": "^0.0.16",
"@langchain/google-genai": "^0.0.6", "@langchain/google-genai": "^0.0.6",
"@langchain/mistralai": "^0.0.6", "@langchain/mistralai": "^0.0.6",
"@notionhq/client": "^2.2.8", "@notionhq/client": "^2.2.8",
"@opensearch-project/opensearch": "^1.2.0", "@opensearch-project/opensearch": "^1.2.0",
"@pinecone-database/pinecone": "^1.1.1", "@pinecone-database/pinecone": "^2.0.1",
"@qdrant/js-client-rest": "^1.2.2", "@qdrant/js-client-rest": "^1.2.2",
"@supabase/supabase-js": "^2.29.0", "@supabase/supabase-js": "^2.29.0",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/jsdom": "^21.1.1", "@types/jsdom": "^21.1.1",
"@upstash/redis": "^1.22.1", "@upstash/redis": "1.22.1",
"@zilliz/milvus2-sdk-node": "^2.2.24", "@zilliz/milvus2-sdk-node": "^2.2.24",
"apify-client": "^2.7.1", "apify-client": "^2.7.1",
"axios": "1.6.2", "axios": "1.6.2",
@@ -46,15 +49,16 @@
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"faiss-node": "^0.2.2", "faiss-node": "^0.2.2",
"fast-json-patch": "^3.1.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"google-auth-library": "^9.0.0", "google-auth-library": "^9.4.0",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"husky": "^8.0.3", "husky": "^8.0.3",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"langchain": "^0.0.214", "langchain": "^0.0.214",
"langfuse": "2.0.2", "langfuse": "2.0.2",
"langfuse-langchain": "2.0.2", "langfuse-langchain": "2.3.3",
"langsmith": "0.0.53", "langsmith": "0.0.53",
"linkifyjs": "^4.1.1", "linkifyjs": "^4.1.1",
"llmonitor": "^0.5.5", "llmonitor": "^0.5.5",
+23 -13
View File
@@ -29,6 +29,12 @@ export interface ICommonObject {
[key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[] [key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[]
} }
export interface IVariable {
name: string
value: string
type: string
}
export type IDatabaseEntity = { export type IDatabaseEntity = {
[key: string]: any [key: string]: any
} }
@@ -90,7 +96,7 @@ export interface INodeProperties {
type: string type: string
icon: string icon: string
version: number version: number
category: string category: string // TODO: use enum instead of string
baseClasses: string[] baseClasses: string[]
description?: string description?: string
filePath?: string filePath?: string
@@ -108,10 +114,6 @@ export interface INode extends INodeProperties {
search: (nodeData: INodeData, options?: ICommonObject) => Promise<any> search: (nodeData: INodeData, options?: ICommonObject) => Promise<any>
delete: (nodeData: INodeData, options?: ICommonObject) => Promise<void> delete: (nodeData: INodeData, options?: ICommonObject) => Promise<void>
} }
memoryMethods?: {
clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise<void>
getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise<string>
}
init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<any> init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<any>
run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<string | ICommonObject> run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<string | ICommonObject>
} }
@@ -204,29 +206,37 @@ import { BaseMessage } from 'langchain/schema'
import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory' import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory'
export interface MemoryMethods { export interface MemoryMethods {
getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise<IMessage[] | BaseMessage[]> getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean, prevHistory?: IMessage[]): Promise<IMessage[] | BaseMessage[]>
addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void> addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
clearChatMessages(overrideSessionId?: string): Promise<void> clearChatMessages(overrideSessionId?: string): Promise<void>
resumeMessages?(messages: IMessage[]): Promise<void>
} }
export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods { export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods {
abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise<IMessage[] | BaseMessage[]> abstract getChatMessages(
overrideSessionId?: string,
returnBaseMessages?: boolean,
prevHistory?: IMessage[]
): Promise<IMessage[] | BaseMessage[]>
abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void> abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
abstract clearChatMessages(overrideSessionId?: string): Promise<void> abstract clearChatMessages(overrideSessionId?: string): Promise<void>
abstract resumeMessages(messages: IMessage[]): Promise<void>
} }
export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods { export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods {
abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise<IMessage[] | BaseMessage[]> abstract getChatMessages(
overrideSessionId?: string,
returnBaseMessages?: boolean,
prevHistory?: IMessage[]
): Promise<IMessage[] | BaseMessage[]>
abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void> abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
abstract clearChatMessages(overrideSessionId?: string): Promise<void> abstract clearChatMessages(overrideSessionId?: string): Promise<void>
abstract resumeMessages(messages: IMessage[]): Promise<void>
} }
export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods { export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods {
abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise<IMessage[] | BaseMessage[]> abstract getChatMessages(
overrideSessionId?: string,
returnBaseMessages?: boolean,
prevHistory?: IMessage[]
): Promise<IMessage[] | BaseMessage[]>
abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void> abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
abstract clearChatMessages(overrideSessionId?: string): Promise<void> abstract clearChatMessages(overrideSessionId?: string): Promise<void>
abstract resumeMessages(messages: IMessage[]): Promise<void>
} }
+624
View File
@@ -0,0 +1,624 @@
import { AgentExecutorInput, BaseSingleActionAgent, BaseMultiActionAgent, RunnableAgent, StoppingMethod } from 'langchain/agents'
import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema'
import { OutputParserException } from 'langchain/schema/output_parser'
import { CallbackManager, CallbackManagerForChainRun, Callbacks } from 'langchain/callbacks'
import { ToolInputParsingException, Tool } from '@langchain/core/tools'
import { Runnable } from 'langchain/schema/runnable'
import { BaseChain, SerializedLLMChain } from 'langchain/chains'
import { Serializable } from '@langchain/core/load/serializable'
type AgentExecutorOutput = ChainValues
interface AgentExecutorIteratorInput {
agentExecutor: AgentExecutor
inputs: Record<string, string>
callbacks?: Callbacks
tags?: string[]
metadata?: Record<string, unknown>
runName?: string
runManager?: CallbackManagerForChainRun
}
//TODO: stream tools back
export class AgentExecutorIterator extends Serializable implements AgentExecutorIteratorInput {
lc_namespace = ['langchain', 'agents', 'executor_iterator']
agentExecutor: AgentExecutor
inputs: Record<string, string>
callbacks: Callbacks
tags: string[] | undefined
metadata: Record<string, unknown> | undefined
runName: string | undefined
private _finalOutputs: Record<string, unknown> | undefined
get finalOutputs(): Record<string, unknown> | undefined {
return this._finalOutputs
}
/** Intended to be used as a setter method, needs to be async. */
async setFinalOutputs(value: Record<string, unknown> | undefined) {
this._finalOutputs = undefined
if (value) {
const preparedOutputs: Record<string, unknown> = await this.agentExecutor.prepOutputs(this.inputs, value, true)
this._finalOutputs = preparedOutputs
}
}
runManager: CallbackManagerForChainRun | undefined
intermediateSteps: AgentStep[] = []
iterations = 0
get nameToToolMap(): Record<string, Tool> {
const toolMap = this.agentExecutor.tools.map((tool) => ({
[tool.name]: tool
}))
return Object.assign({}, ...toolMap)
}
constructor(fields: AgentExecutorIteratorInput) {
super(fields)
this.agentExecutor = fields.agentExecutor
this.inputs = fields.inputs
this.tags = fields.tags
this.metadata = fields.metadata
this.runName = fields.runName
this.runManager = fields.runManager
}
/**
* Reset the iterator to its initial state, clearing intermediate steps,
* iterations, and the final output.
*/
reset(): void {
this.intermediateSteps = []
this.iterations = 0
this._finalOutputs = undefined
}
updateIterations(): void {
this.iterations += 1
}
async *streamIterator() {
this.reset()
// Loop to handle iteration
while (true) {
try {
if (this.iterations === 0) {
await this.onFirstStep()
}
const result = await this._callNext()
yield result
} catch (e: any) {
if ('message' in e && e.message.startsWith('Final outputs already reached: ')) {
if (!this.finalOutputs) {
throw e
}
return this.finalOutputs
}
if (this.runManager) {
await this.runManager.handleChainError(e)
}
throw e
}
}
}
/**
* Perform any necessary setup for the first step
* of the asynchronous iterator.
*/
async onFirstStep(): Promise<void> {
if (this.iterations === 0) {
const callbackManager = await CallbackManager.configure(
this.callbacks,
this.agentExecutor.callbacks,
this.tags,
this.agentExecutor.tags,
this.metadata,
this.agentExecutor.metadata,
{
verbose: this.agentExecutor.verbose
}
)
this.runManager = await callbackManager?.handleChainStart(
this.agentExecutor.toJSON(),
this.inputs,
undefined,
undefined,
this.tags,
this.metadata,
this.runName
)
}
}
/**
* Execute the next step in the chain using the
* AgentExecutor's _takeNextStep method.
*/
async _executeNextStep(runManager?: CallbackManagerForChainRun): Promise<AgentFinish | AgentStep[]> {
return this.agentExecutor._takeNextStep(this.nameToToolMap, this.inputs, this.intermediateSteps, runManager)
}
/**
* Process the output of the next step,
* handling AgentFinish and tool return cases.
*/
async _processNextStepOutput(
nextStepOutput: AgentFinish | AgentStep[],
runManager?: CallbackManagerForChainRun
): Promise<Record<string, string | AgentStep[]>> {
if ('returnValues' in nextStepOutput) {
const output = await this.agentExecutor._return(nextStepOutput as AgentFinish, this.intermediateSteps, runManager)
if (this.runManager) {
await this.runManager.handleChainEnd(output)
}
await this.setFinalOutputs(output)
return output
}
this.intermediateSteps = this.intermediateSteps.concat(nextStepOutput as AgentStep[])
let output: Record<string, string | AgentStep[]> = {}
if (Array.isArray(nextStepOutput) && nextStepOutput.length === 1) {
const nextStep = nextStepOutput[0]
const toolReturn = await this.agentExecutor._getToolReturn(nextStep)
if (toolReturn) {
output = await this.agentExecutor._return(toolReturn, this.intermediateSteps, runManager)
if (this.runManager) {
await this.runManager.handleChainEnd(output)
}
await this.setFinalOutputs(output)
}
}
output = { intermediateSteps: nextStepOutput as AgentStep[] }
return output
}
async _stop(): Promise<Record<string, unknown>> {
const output = await this.agentExecutor.agent.returnStoppedResponse(
this.agentExecutor.earlyStoppingMethod,
this.intermediateSteps,
this.inputs
)
const returnedOutput = await this.agentExecutor._return(output, this.intermediateSteps, this.runManager)
await this.setFinalOutputs(returnedOutput)
return returnedOutput
}
async _callNext(): Promise<Record<string, unknown>> {
// final output already reached: stopiteration (final output)
if (this.finalOutputs) {
throw new Error(`Final outputs already reached: ${JSON.stringify(this.finalOutputs, null, 2)}`)
}
// timeout/max iterations: stopiteration (stopped response)
if (!this.agentExecutor.shouldContinueGetter(this.iterations)) {
return this._stop()
}
const nextStepOutput = await this._executeNextStep(this.runManager)
const output = await this._processNextStepOutput(nextStepOutput, this.runManager)
this.updateIterations()
return output
}
}
export class AgentExecutor extends BaseChain<ChainValues, AgentExecutorOutput> {
static lc_name() {
return 'AgentExecutor'
}
get lc_namespace() {
return ['langchain', 'agents', 'executor']
}
agent: BaseSingleActionAgent | BaseMultiActionAgent
tools: this['agent']['ToolType'][]
returnIntermediateSteps = false
maxIterations?: number = 15
earlyStoppingMethod: StoppingMethod = 'force'
sessionId?: string
chatId?: string
input?: string
/**
* How to handle errors raised by the agent's output parser.
Defaults to `False`, which raises the error.
If `true`, the error will be sent back to the LLM as an observation.
If a string, the string itself will be sent to the LLM as an observation.
If a callable function, the function will be called with the exception
as an argument, and the result of that function will be passed to the agent
as an observation.
*/
handleParsingErrors: boolean | string | ((e: OutputParserException | ToolInputParsingException) => string) = false
get inputKeys() {
return this.agent.inputKeys
}
get outputKeys() {
return this.agent.returnValues
}
constructor(input: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }) {
let agent: BaseSingleActionAgent | BaseMultiActionAgent
if (Runnable.isRunnable(input.agent)) {
agent = new RunnableAgent({ runnable: input.agent })
} else {
agent = input.agent
}
super(input)
this.agent = agent
this.tools = input.tools
this.handleParsingErrors = input.handleParsingErrors ?? this.handleParsingErrors
/* Getting rid of this because RunnableAgent doesnt allow return direct
if (this.agent._agentActionType() === "multi") {
for (const tool of this.tools) {
if (tool.returnDirect) {
throw new Error(
`Tool with return direct ${tool.name} not supported for multi-action agent.`
);
}
}
}*/
this.returnIntermediateSteps = input.returnIntermediateSteps ?? this.returnIntermediateSteps
this.maxIterations = input.maxIterations ?? this.maxIterations
this.earlyStoppingMethod = input.earlyStoppingMethod ?? this.earlyStoppingMethod
this.sessionId = input.sessionId
this.chatId = input.chatId
this.input = input.input
}
static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }): AgentExecutor {
const newInstance = new AgentExecutor(fields)
if (fields.sessionId) newInstance.sessionId = fields.sessionId
if (fields.chatId) newInstance.chatId = fields.chatId
if (fields.input) newInstance.input = fields.input
return newInstance
}
get shouldContinueGetter() {
return this.shouldContinue.bind(this)
}
/**
* Method that checks if the agent execution should continue based on the
* number of iterations.
* @param iterations The current number of iterations.
* @returns A boolean indicating whether the agent execution should continue.
*/
private shouldContinue(iterations: number): boolean {
return this.maxIterations === undefined || iterations < this.maxIterations
}
async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise<AgentExecutorOutput> {
const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t]))
const steps: AgentStep[] = []
let iterations = 0
const getOutput = async (finishStep: AgentFinish): Promise<AgentExecutorOutput> => {
const { returnValues } = finishStep
const additional = await this.agent.prepareForOutput(returnValues, steps)
if (this.returnIntermediateSteps) {
return { ...returnValues, intermediateSteps: steps, ...additional }
}
await runManager?.handleAgentEnd(finishStep)
return { ...returnValues, ...additional }
}
while (this.shouldContinue(iterations)) {
let output
try {
output = await this.agent.plan(steps, inputs, runManager?.getChild())
} catch (e) {
if (e instanceof OutputParserException) {
let observation
let text = e.message
if (this.handleParsingErrors === true) {
if (e.sendToLLM) {
observation = e.observation
text = e.llmOutput ?? ''
} else {
observation = 'Invalid or incomplete response'
}
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
output = {
tool: '_Exception',
toolInput: observation,
log: text
} as AgentAction
} else {
throw e
}
}
// Check if the agent has finished
if ('returnValues' in output) {
return getOutput(output)
}
let actions: AgentAction[]
if (Array.isArray(output)) {
actions = output as AgentAction[]
} else {
actions = [output as AgentAction]
}
const newSteps = await Promise.all(
actions.map(async (action) => {
await runManager?.handleAgentAction(action)
const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()]
let observation
try {
/* Here we need to override Tool call method to include sessionId, chatId, input as parameter
* Tool Call Parameters:
* - arg: z.output<T>
* - configArg?: RunnableConfig | Callbacks
* - tags?: string[]
* - flowConfig?: { sessionId?: string, chatId?: string, input?: string }
*/
observation = tool
? // @ts-ignore
await tool.call(action.toolInput, runManager?.getChild(), undefined, {
sessionId: this.sessionId,
chatId: this.chatId,
input: this.input
})
: `${action.tool} is not a valid tool, try another one.`
} catch (e) {
if (e instanceof ToolInputParsingException) {
if (this.handleParsingErrors === true) {
observation = 'Invalid or incomplete tool input. Please try again.'
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
observation = await new ExceptionTool().call(observation, runManager?.getChild())
return { action, observation: observation ?? '' }
}
}
return { action, observation: observation ?? '' }
})
)
steps.push(...newSteps)
const lastStep = steps[steps.length - 1]
const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()]
if (lastTool?.returnDirect) {
return getOutput({
returnValues: { [this.agent.returnValues[0]]: lastStep.observation },
log: ''
})
}
iterations += 1
}
const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs)
return getOutput(finish)
}
async _takeNextStep(
nameToolMap: Record<string, Tool>,
inputs: ChainValues,
intermediateSteps: AgentStep[],
runManager?: CallbackManagerForChainRun
): Promise<AgentFinish | AgentStep[]> {
let output
try {
output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild())
} catch (e) {
if (e instanceof OutputParserException) {
let observation
let text = e.message
if (this.handleParsingErrors === true) {
if (e.sendToLLM) {
observation = e.observation
text = e.llmOutput ?? ''
} else {
observation = 'Invalid or incomplete response'
}
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
output = {
tool: '_Exception',
toolInput: observation,
log: text
} as AgentAction
} else {
throw e
}
}
if ('returnValues' in output) {
return output
}
let actions: AgentAction[]
if (Array.isArray(output)) {
actions = output as AgentAction[]
} else {
actions = [output as AgentAction]
}
const result: AgentStep[] = []
for (const agentAction of actions) {
let observation = ''
if (runManager) {
await runManager?.handleAgentAction(agentAction)
}
if (agentAction.tool in nameToolMap) {
const tool = nameToolMap[agentAction.tool]
try {
/* Here we need to override Tool call method to include sessionId, chatId, input as parameter
* Tool Call Parameters:
* - arg: z.output<T>
* - configArg?: RunnableConfig | Callbacks
* - tags?: string[]
* - flowConfig?: { sessionId?: string, chatId?: string, input?: string }
*/
// @ts-ignore
observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, {
sessionId: this.sessionId,
chatId: this.chatId,
input: this.input
})
} catch (e) {
if (e instanceof ToolInputParsingException) {
if (this.handleParsingErrors === true) {
observation = 'Invalid or incomplete tool input. Please try again.'
} else if (typeof this.handleParsingErrors === 'string') {
observation = this.handleParsingErrors
} else if (typeof this.handleParsingErrors === 'function') {
observation = this.handleParsingErrors(e)
} else {
throw e
}
observation = await new ExceptionTool().call(observation, runManager?.getChild())
}
}
} else {
observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}`
}
result.push({
action: agentAction,
observation
})
}
return result
}
async _return(
output: AgentFinish,
intermediateSteps: AgentStep[],
runManager?: CallbackManagerForChainRun
): Promise<AgentExecutorOutput> {
if (runManager) {
await runManager.handleAgentEnd(output)
}
const finalOutput: Record<string, unknown> = output.returnValues
if (this.returnIntermediateSteps) {
finalOutput.intermediateSteps = intermediateSteps
}
return finalOutput
}
async _getToolReturn(nextStepOutput: AgentStep): Promise<AgentFinish | null> {
const { action, observation } = nextStepOutput
const nameToolMap = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t]))
const [returnValueKey = 'output'] = this.agent.returnValues
// Invalid tools won't be in the map, so we return False.
if (action.tool in nameToolMap) {
if (nameToolMap[action.tool].returnDirect) {
return {
returnValues: { [returnValueKey]: observation },
log: ''
}
}
}
return null
}
_returnStoppedResponse(earlyStoppingMethod: StoppingMethod) {
if (earlyStoppingMethod === 'force') {
return {
returnValues: {
output: 'Agent stopped due to iteration limit or time limit.'
},
log: ''
} as AgentFinish
}
throw new Error(`Got unsupported early_stopping_method: ${earlyStoppingMethod}`)
}
async *_streamIterator(inputs: Record<string, any>): AsyncGenerator<ChainValues> {
const agentExecutorIterator = new AgentExecutorIterator({
inputs,
agentExecutor: this,
metadata: this.metadata,
tags: this.tags,
callbacks: this.callbacks
})
const iterator = agentExecutorIterator.streamIterator()
for await (const step of iterator) {
if (!step) {
continue
}
yield step
}
}
_chainType() {
return 'agent_executor' as const
}
serialize(): SerializedLLMChain {
throw new Error('Cannot serialize an AgentExecutor')
}
}
class ExceptionTool extends Tool {
name = '_Exception'
description = 'Exception tool'
async _call(query: string) {
return query
}
}
export const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] =>
steps.flatMap(({ action, observation }) => {
const create_function_message = (observation: string, action: AgentAction) => {
let content: string
if (typeof observation !== 'string') {
content = JSON.stringify(observation)
} else {
content = observation
}
return new FunctionMessage(content, action.tool)
}
if ('messageLog' in action && action.messageLog !== undefined) {
const log = action.messageLog as BaseMessage[]
return log.concat(create_function_message(observation, action))
} else {
return [new AIMessage(action.log)]
}
})
+29 -12
View File
@@ -1,13 +1,13 @@
import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks' import { BaseTracer, Run, BaseCallbackHandler, LangChainTracer } from 'langchain/callbacks'
import { AgentAction, ChainValues } from 'langchain/schema' import { AgentAction, ChainValues } from 'langchain/schema'
import { Logger } from 'winston' import { Logger } from 'winston'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import { Client } from 'langsmith' import { Client } from 'langsmith'
import { LangChainTracer } from 'langchain/callbacks' import { LLMonitorHandler, LLMonitorHandlerFields } from 'langchain/callbacks/handlers/llmonitor'
import { LLMonitorHandler } from 'langchain/callbacks/handlers/llmonitor'
import { getCredentialData, getCredentialParam } from './utils' import { getCredentialData, getCredentialParam } from './utils'
import { ICommonObject, INodeData } from './Interface' import { ICommonObject, INodeData } from './Interface'
import CallbackHandler from 'langfuse-langchain' import CallbackHandler from 'langfuse-langchain'
import { LangChainTracerFields } from '@langchain/core/tracers/tracer_langchain'
import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith' import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith'
import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse' import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse'
import monitor from 'llmonitor' import monitor from 'llmonitor'
@@ -235,11 +235,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
apiKey: langSmithApiKey apiKey: langSmithApiKey
}) })
const tracer = new LangChainTracer({ let langSmithField: LangChainTracerFields = {
projectName: langSmithProject ?? 'default', projectName: langSmithProject ?? 'default',
//@ts-ignore //@ts-ignore
client client
}) }
if (nodeData?.inputs?.analytics?.langSmith) {
langSmithField = { ...langSmithField, ...nodeData?.inputs?.analytics?.langSmith }
}
const tracer = new LangChainTracer(langSmithField)
callbacks.push(tracer) callbacks.push(tracer)
} else if (provider === 'langFuse') { } else if (provider === 'langFuse') {
const release = analytic[provider].release as string const release = analytic[provider].release as string
@@ -248,13 +254,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData) const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData)
const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData) const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData)
const langFuseOptions: any = { let langFuseOptions: any = {
secretKey: langFuseSecretKey, secretKey: langFuseSecretKey,
publicKey: langFusePublicKey, publicKey: langFusePublicKey,
baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com' baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com'
} }
if (release) langFuseOptions.release = release if (release) langFuseOptions.release = release
if (options.chatId) langFuseOptions.userId = options.chatId if (options.chatId) langFuseOptions.sessionId = options.chatId
if (nodeData?.inputs?.analytics?.langFuse) {
langFuseOptions = { ...langFuseOptions, ...nodeData?.inputs?.analytics?.langFuse }
}
const handler = new CallbackHandler(langFuseOptions) const handler = new CallbackHandler(langFuseOptions)
callbacks.push(handler) callbacks.push(handler)
@@ -262,11 +272,15 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, nodeData) const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, nodeData)
const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, nodeData) const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, nodeData)
const llmonitorFields: ICommonObject = { let llmonitorFields: LLMonitorHandlerFields = {
appId: llmonitorAppId, appId: llmonitorAppId,
apiUrl: llmonitorEndpoint ?? 'https://app.llmonitor.com' apiUrl: llmonitorEndpoint ?? 'https://app.llmonitor.com'
} }
if (nodeData?.inputs?.analytics?.llmonitor) {
llmonitorFields = { ...llmonitorFields, ...nodeData?.inputs?.analytics?.llmonitor }
}
const handler = new LLMonitorHandler(llmonitorFields) const handler = new LLMonitorHandler(llmonitorFields)
callbacks.push(handler) callbacks.push(handler)
} }
@@ -360,7 +374,8 @@ export class AnalyticHandler {
}, },
serialized: {}, serialized: {},
project_name: this.handlers['langSmith'].langSmithProject, project_name: this.handlers['langSmith'].langSmithProject,
client: this.handlers['langSmith'].client client: this.handlers['langSmith'].client,
...this.nodeData?.inputs?.analytics?.langSmith
} }
const parentRun = new RunTree(parentRunConfig) const parentRun = new RunTree(parentRunConfig)
await parentRun.postRun() await parentRun.postRun()
@@ -390,8 +405,9 @@ export class AnalyticHandler {
const langfuse: Langfuse = this.handlers['langFuse'].client const langfuse: Langfuse = this.handlers['langFuse'].client
langfuseTraceClient = langfuse.trace({ langfuseTraceClient = langfuse.trace({
name, name,
userId: this.options.chatId, sessionId: this.options.chatId,
metadata: { tags: ['openai-assistant'] } metadata: { tags: ['openai-assistant'] },
...this.nodeData?.inputs?.analytics?.langFuse
}) })
} else { } else {
langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']] langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']]
@@ -420,7 +436,8 @@ export class AnalyticHandler {
runId, runId,
name, name,
userId: this.options.chatId, userId: this.options.chatId,
input input,
...this.nodeData?.inputs?.analytics?.llmonitor
}) })
this.handlers['llmonitor'].chainEvent = { [runId]: runId } this.handlers['llmonitor'].chainEvent = { [runId]: runId }
returnIds['llmonitor'].chainEvent = runId returnIds['llmonitor'].chainEvent = runId
+86 -1
View File
@@ -5,7 +5,7 @@ import * as path from 'path'
import { JSDOM } from 'jsdom' import { JSDOM } from 'jsdom'
import { z } from 'zod' import { z } from 'zod'
import { DataSource } from 'typeorm' import { DataSource } from 'typeorm'
import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface' import { ICommonObject, IDatabaseEntity, IMessage, INodeData, IVariable } from './Interface'
import { AES, enc } from 'crypto-js' import { AES, enc } from 'crypto-js'
import { ChatMessageHistory } from 'langchain/memory' import { ChatMessageHistory } from 'langchain/memory'
import { AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' import { AIMessage, HumanMessage, BaseMessage } from 'langchain/schema'
@@ -70,6 +70,22 @@ export const availableDependencies = [
'weaviate-ts-client' 'weaviate-ts-client'
] ]
export const defaultAllowBuiltInDep = [
'assert',
'buffer',
'crypto',
'events',
'http',
'https',
'net',
'path',
'querystring',
'timers',
'tls',
'url',
'zlib'
]
/** /**
* Get base classes of components * Get base classes of components
* *
@@ -673,3 +689,72 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[
} }
return formatmessages return formatmessages
} }
/**
* Convert MultiOptions String to String Array
* @param {string} inputString
* @returns {string[]}
*/
export const convertMultiOptionsToStringArray = (inputString: string): string[] => {
let ArrayString: string[] = []
try {
ArrayString = JSON.parse(inputString)
} catch (e) {
ArrayString = []
}
return ArrayString
}
/**
* Get variables
* @param {DataSource} appDataSource
* @param {IDatabaseEntity} databaseEntities
* @param {INodeData} nodeData
*/
export const getVars = async (appDataSource: DataSource, databaseEntities: IDatabaseEntity, nodeData: INodeData) => {
const variables = ((await appDataSource.getRepository(databaseEntities['Variable']).find()) as IVariable[]) ?? []
// override variables defined in overrideConfig
// nodeData.inputs.variables is an Object, check each property and override the variable
if (nodeData?.inputs?.vars) {
for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) {
const foundVar = variables.find((v) => v.name === propertyName)
if (foundVar) {
// even if the variable was defined as runtime, we override it with static value
foundVar.type = 'static'
foundVar.value = nodeData.inputs.vars[propertyName]
} else {
// add it the variables, if not found locally in the db
variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] })
}
}
}
return variables
}
/**
* Prepare sandbox variables
* @param {IVariable[]} variables
*/
export const prepareSandboxVars = (variables: IVariable[]) => {
let vars = {}
if (variables) {
for (const item of variables) {
let value = item.value
// read from .env file
if (item.type === 'runtime') {
value = process.env[item.name] ?? ''
}
Object.defineProperty(vars, item.name, {
enumerable: true,
configurable: true,
writable: true,
value: value
})
}
}
return vars
}
+2
View File
@@ -26,3 +26,5 @@ PORT=3000
# LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
# LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_API_KEY=your_api_key
# LANGCHAIN_PROJECT=your_project # LANGCHAIN_PROJECT=your_project
# DISABLE_FLOWISE_TELEMETRY=true
@@ -936,7 +936,7 @@
"id": "conversationalAgent_0-input-tools-Tool" "id": "conversationalAgent_0-input-tools-Tool"
}, },
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseChatModel", "type": "BaseChatModel",
"id": "conversationalAgent_0-input-model-BaseChatModel" "id": "conversationalAgent_0-input-model-BaseChatModel"
@@ -511,7 +511,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -552,6 +552,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -576,7 +615,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "", "pineconeMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -166,7 +166,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -207,6 +207,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -231,7 +270,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "", "pineconeMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -13,7 +13,7 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
@@ -28,47 +28,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -89,9 +78,8 @@
"model": "{{chatOpenAI_0.data.instance}}", "model": "{{chatOpenAI_0.data.instance}}",
"vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}", "vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}",
"memory": "", "memory": "",
"returnSourceDocuments": "", "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"systemMessagePrompt": "", "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
"chainOption": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -625,9 +613,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -6,15 +6,15 @@
"height": 376, "height": 376,
"id": "bufferMemory_0", "id": "bufferMemory_0",
"position": { "position": {
"x": 451.4449437285705, "x": 240.5161028076149,
"y": 118.30026803362762 "y": 165.35849026339048
}, },
"type": "customNode", "type": "customNode",
"data": { "data": {
"id": "bufferMemory_0", "id": "bufferMemory_0",
"label": "Buffer Memory", "label": "Buffer Memory",
"name": "bufferMemory",
"version": 1, "version": 1,
"name": "bufferMemory",
"type": "BufferMemory", "type": "BufferMemory",
"baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"],
"category": "Memory", "category": "Memory",
@@ -53,8 +53,8 @@
}, },
"selected": false, "selected": false,
"positionAbsolute": { "positionAbsolute": {
"x": 451.4449437285705, "x": 240.5161028076149,
"y": 118.30026803362762 "y": 165.35849026339048
}, },
"dragging": false "dragging": false
}, },
@@ -63,17 +63,17 @@
"height": 383, "height": 383,
"id": "conversationChain_0", "id": "conversationChain_0",
"position": { "position": {
"x": 1176.1569322079652, "x": 958.9887390513221,
"y": 303.56879146735974 "y": 318.8734467468765
}, },
"type": "customNode", "type": "customNode",
"data": { "data": {
"id": "conversationChain_0", "id": "conversationChain_0",
"label": "Conversation Chain", "label": "Conversation Chain",
"version": 2,
"name": "conversationChain", "name": "conversationChain",
"version": 1,
"type": "ConversationChain", "type": "ConversationChain",
"baseClasses": ["ConversationChain", "LLMChain", "BaseChain"], "baseClasses": ["ConversationChain", "LLMChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Chat models specific conversational chain with memory", "description": "Chat models specific conversational chain with memory",
"inputParams": [ "inputParams": [
@@ -82,15 +82,17 @@
"name": "systemMessagePrompt", "name": "systemMessagePrompt",
"type": "string", "type": "string",
"rows": 4, "rows": 4,
"description": "If Chat Prompt Template is provided, this will be ignored",
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "You are a helpful assistant that write codes", "default": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.",
"placeholder": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.",
"id": "conversationChain_0-input-systemMessagePrompt-string" "id": "conversationChain_0-input-systemMessagePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseChatModel", "type": "BaseChatModel",
"id": "conversationChain_0-input-model-BaseChatModel" "id": "conversationChain_0-input-model-BaseChatModel"
@@ -102,27 +104,26 @@
"id": "conversationChain_0-input-memory-BaseMemory" "id": "conversationChain_0-input-memory-BaseMemory"
}, },
{ {
"label": "Document", "label": "Chat Prompt Template",
"name": "document", "name": "chatPromptTemplate",
"type": "Document", "type": "ChatPromptTemplate",
"description": "Include whole document into the context window", "description": "Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable",
"optional": true, "optional": true,
"list": true, "id": "conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate"
"id": "conversationChain_0-input-document-Document"
} }
], ],
"inputs": { "inputs": {
"model": "{{chatAnthropic_0.data.instance}}", "model": "{{chatAnthropic_0.data.instance}}",
"memory": "{{bufferMemory_0.data.instance}}", "memory": "{{bufferMemory_0.data.instance}}",
"document": ["{{pdfFile_0.data.instance}}"], "chatPromptTemplate": "{{chatPromptTemplate_0.data.instance}}",
"systemMessagePrompt": "" "systemMessagePrompt": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain", "id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain|Runnable",
"name": "conversationChain", "name": "conversationChain",
"label": "ConversationChain", "label": "ConversationChain",
"type": "ConversationChain | LLMChain | BaseChain" "type": "ConversationChain | LLMChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -130,27 +131,27 @@
}, },
"selected": false, "selected": false,
"positionAbsolute": { "positionAbsolute": {
"x": 1176.1569322079652, "x": 958.9887390513221,
"y": 303.56879146735974 "y": 318.8734467468765
}, },
"dragging": false "dragging": false
}, },
{ {
"width": 300, "width": 300,
"height": 523, "height": 574,
"id": "chatAnthropic_0", "id": "chatAnthropic_0",
"position": { "position": {
"x": 800.5525382783799, "x": 585.3308245972187,
"y": -130.7988221837009 "y": -116.32789506560908
}, },
"type": "customNode", "type": "customNode",
"data": { "data": {
"id": "chatAnthropic_0", "id": "chatAnthropic_0",
"label": "ChatAnthropic", "label": "ChatAnthropic",
"name": "chatAnthropic",
"version": 3, "version": 3,
"name": "chatAnthropic",
"type": "ChatAnthropic", "type": "ChatAnthropic",
"baseClasses": ["ChatAnthropic", "BaseChatModel", "BaseLanguageModel"], "baseClasses": ["ChatAnthropic", "BaseChatModel", "BaseLanguageModel", "Runnable"],
"category": "Chat Models", "category": "Chat Models",
"description": "Wrapper around ChatAnthropic large language models that use the Chat endpoint", "description": "Wrapper around ChatAnthropic large language models that use the Chat endpoint",
"inputParams": [ "inputParams": [
@@ -226,7 +227,7 @@
"name": "claude-instant-v1.1-100k" "name": "claude-instant-v1.1-100k"
} }
], ],
"default": "claude-v1", "default": "claude-2",
"optional": true, "optional": true,
"id": "chatAnthropic_0-input-modelName-options" "id": "chatAnthropic_0-input-modelName-options"
}, },
@@ -234,6 +235,7 @@
"label": "Temperature", "label": "Temperature",
"name": "temperature", "name": "temperature",
"type": "number", "type": "number",
"step": 0.1,
"default": 0.9, "default": 0.9,
"optional": true, "optional": true,
"id": "chatAnthropic_0-input-temperature-number" "id": "chatAnthropic_0-input-temperature-number"
@@ -242,6 +244,7 @@
"label": "Max Tokens", "label": "Max Tokens",
"name": "maxTokensToSample", "name": "maxTokensToSample",
"type": "number", "type": "number",
"step": 1,
"optional": true, "optional": true,
"additionalParams": true, "additionalParams": true,
"id": "chatAnthropic_0-input-maxTokensToSample-number" "id": "chatAnthropic_0-input-maxTokensToSample-number"
@@ -250,6 +253,7 @@
"label": "Top P", "label": "Top P",
"name": "topP", "name": "topP",
"type": "number", "type": "number",
"step": 0.1,
"optional": true, "optional": true,
"additionalParams": true, "additionalParams": true,
"id": "chatAnthropic_0-input-topP-number" "id": "chatAnthropic_0-input-topP-number"
@@ -258,6 +262,7 @@
"label": "Top K", "label": "Top K",
"name": "topK", "name": "topK",
"type": "number", "type": "number",
"step": 0.1,
"optional": true, "optional": true,
"additionalParams": true, "additionalParams": true,
"id": "chatAnthropic_0-input-topK-number" "id": "chatAnthropic_0-input-topK-number"
@@ -273,6 +278,7 @@
} }
], ],
"inputs": { "inputs": {
"cache": "",
"modelName": "claude-2.1", "modelName": "claude-2.1",
"temperature": 0.9, "temperature": 0.9,
"maxTokensToSample": "", "maxTokensToSample": "",
@@ -281,10 +287,10 @@
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel", "id": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel|Runnable",
"name": "chatAnthropic", "name": "chatAnthropic",
"label": "ChatAnthropic", "label": "ChatAnthropic",
"type": "ChatAnthropic | BaseChatModel | BaseLanguageModel" "type": "ChatAnthropic | BaseChatModel | BaseLanguageModel | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -292,61 +298,106 @@
}, },
"selected": false, "selected": false,
"positionAbsolute": { "positionAbsolute": {
"x": 800.5525382783799, "x": 585.3308245972187,
"y": -130.7988221837009 "y": -116.32789506560908
}, },
"dragging": false "dragging": false
}, },
{ {
"width": 300, "width": 300,
"height": 507, "height": 688,
"id": "pdfFile_0", "id": "chatPromptTemplate_0",
"position": { "position": {
"x": 94.16886576108482, "x": -106.44189698270114,
"y": 37.12056504707391 "y": 20.133956087516538
}, },
"type": "customNode", "type": "customNode",
"data": { "data": {
"id": "pdfFile_0", "id": "chatPromptTemplate_0",
"label": "Pdf File", "label": "Chat Prompt Template",
"name": "pdfFile",
"version": 1, "version": 1,
"name": "chatPromptTemplate",
"type": "ChatPromptTemplate",
"baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"],
"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": "json",
"optional": true,
"acceptVariable": true,
"list": true,
"id": "chatPromptTemplate_0-input-promptValues-json"
}
],
"inputAnchors": [],
"inputs": {
"systemMessagePrompt": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\nThe AI has the following context:\n{context}",
"humanMessagePrompt": "{input}",
"promptValues": "{\"context\":\"{{plainText_0.data.instance}}\",\"input\":\"{{question}}\"}"
},
"outputAnchors": [
{
"id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable",
"name": "chatPromptTemplate",
"label": "ChatPromptTemplate",
"type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate | Runnable"
}
],
"outputs": {},
"selected": false
},
"selected": false,
"positionAbsolute": {
"x": -106.44189698270114,
"y": 20.133956087516538
},
"dragging": false
},
{
"width": 300,
"height": 485,
"id": "plainText_0",
"position": {
"x": -487.7511991135089,
"y": 77.83838996645807
},
"type": "customNode",
"data": {
"id": "plainText_0",
"label": "Plain Text",
"version": 2,
"name": "plainText",
"type": "Document", "type": "Document",
"baseClasses": ["Document"], "baseClasses": ["Document"],
"category": "Document Loaders", "category": "Document Loaders",
"description": "Load data from PDF files", "description": "Load data from plain text",
"inputParams": [ "inputParams": [
{ {
"label": "Pdf File", "label": "Text",
"name": "pdfFile", "name": "text",
"type": "file", "type": "string",
"fileType": ".pdf", "rows": 4,
"id": "pdfFile_0-input-pdfFile-file" "placeholder": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...",
}, "id": "plainText_0-input-text-string"
{
"label": "Usage",
"name": "usage",
"type": "options",
"options": [
{
"label": "One document per page",
"name": "perPage"
},
{
"label": "One document per file",
"name": "perFile"
}
],
"default": "perPage",
"id": "pdfFile_0-input-usage-options"
},
{
"label": "Use Legacy Build",
"name": "legacyBuild",
"type": "boolean",
"optional": true,
"additionalParams": true,
"id": "pdfFile_0-input-legacyBuild-boolean"
}, },
{ {
"label": "Metadata", "label": "Metadata",
@@ -354,7 +405,7 @@
"type": "json", "type": "json",
"optional": true, "optional": true,
"additionalParams": true, "additionalParams": true,
"id": "pdfFile_0-input-metadata-json" "id": "plainText_0-input-metadata-json"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -363,30 +414,45 @@
"name": "textSplitter", "name": "textSplitter",
"type": "TextSplitter", "type": "TextSplitter",
"optional": true, "optional": true,
"id": "pdfFile_0-input-textSplitter-TextSplitter" "id": "plainText_0-input-textSplitter-TextSplitter"
} }
], ],
"inputs": { "inputs": {
"text": "Welcome to Skyworld Hotel, where your dreams take flight and your stay soars to new heights. Nestled amidst breathtaking cityscape views, our upscale establishment offers an unparalleled blend of luxury and comfort. Our rooms are elegantly appointed, featuring modern amenities and plush furnishings to ensure your relaxation.\n\nIndulge in culinary delights at our rooftop restaurant, offering a gastronomic journey with panoramic vistas. Skyworld Hotel boasts state-of-the-art conference facilities, perfect for business travelers, and an inviting spa for relaxation seekers. Our attentive staff is dedicated to ensuring your every need is met, making your stay memorable.\n\nCentrally located, we offer easy access to local attractions, making us an ideal choice for both leisure and business travelers. Experience the world of hospitality like never before at Skyworld Hotel.",
"textSplitter": "", "textSplitter": "",
"usage": "perPage",
"legacyBuild": "",
"metadata": "" "metadata": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "pdfFile_0-output-pdfFile-Document", "name": "output",
"name": "pdfFile", "label": "Output",
"label": "Document", "type": "options",
"type": "Document" "options": [
{
"id": "plainText_0-output-document-Document",
"name": "document",
"label": "Document",
"type": "Document"
},
{
"id": "plainText_0-output-text-string|json",
"name": "text",
"label": "Text",
"type": "string | json"
}
],
"default": "document"
} }
], ],
"outputs": {}, "outputs": {
"output": "text"
},
"selected": false "selected": false
}, },
"selected": false, "selected": false,
"positionAbsolute": { "positionAbsolute": {
"x": 94.16886576108482, "x": -487.7511991135089,
"y": 37.12056504707391 "y": 77.83838996645807
}, },
"dragging": false "dragging": false
} }
@@ -398,32 +464,31 @@
"target": "conversationChain_0", "target": "conversationChain_0",
"targetHandle": "conversationChain_0-input-memory-BaseMemory", "targetHandle": "conversationChain_0-input-memory-BaseMemory",
"type": "buttonedge", "type": "buttonedge",
"id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory", "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory"
"data": {
"label": ""
}
}, },
{ {
"source": "chatAnthropic_0", "source": "chatAnthropic_0",
"sourceHandle": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel", "sourceHandle": "chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationChain_0", "target": "conversationChain_0",
"targetHandle": "conversationChain_0-input-model-BaseChatModel", "targetHandle": "conversationChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatAnthropic_0-chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel-conversationChain_0-conversationChain_0-input-model-BaseChatModel", "id": "chatAnthropic_0-chatAnthropic_0-output-chatAnthropic-ChatAnthropic|BaseChatModel|BaseLanguageModel|Runnable-conversationChain_0-conversationChain_0-input-model-BaseChatModel"
"data": {
"label": ""
}
}, },
{ {
"source": "pdfFile_0", "source": "plainText_0",
"sourceHandle": "pdfFile_0-output-pdfFile-Document", "sourceHandle": "plainText_0-output-text-string|json",
"target": "conversationChain_0", "target": "chatPromptTemplate_0",
"targetHandle": "conversationChain_0-input-document-Document", "targetHandle": "chatPromptTemplate_0-input-promptValues-json",
"type": "buttonedge", "type": "buttonedge",
"id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-conversationChain_0-conversationChain_0-input-document-Document", "id": "plainText_0-plainText_0-output-text-string|json-chatPromptTemplate_0-chatPromptTemplate_0-input-promptValues-json"
"data": { },
"label": "" {
} "source": "chatPromptTemplate_0",
"sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable",
"target": "conversationChain_0",
"targetHandle": "conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate",
"type": "buttonedge",
"id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable-conversationChain_0-conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate"
} }
] ]
} }
@@ -354,7 +354,7 @@
"id": "conversationalAgent_0-input-tools-Tool" "id": "conversationalAgent_0-input-tools-Tool"
}, },
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseChatModel", "type": "BaseChatModel",
"id": "conversationalAgent_0-input-model-BaseChatModel" "id": "conversationalAgent_0-input-model-BaseChatModel"
@@ -301,7 +301,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -342,6 +342,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -366,7 +405,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "", "pineconeMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -249,10 +249,10 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Document QA - built on RetrievalQAChain to provide a chat history component", "description": "Document QA - built on RetrievalQAChain to provide a chat history component",
"inputParams": [ "inputParams": [
@@ -264,47 +264,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -325,16 +314,15 @@
"model": "{{chatOpenAI_0.data.instance}}", "model": "{{chatOpenAI_0.data.instance}}",
"vectorStoreRetriever": "{{pinecone_0.data.instance}}", "vectorStoreRetriever": "{{pinecone_0.data.instance}}",
"memory": "", "memory": "",
"returnSourceDocuments": "", "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"systemMessagePrompt": "", "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
"chainOption": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"label": "ConversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain | BaseChain" "type": "ConversationalRetrievalQAChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -553,7 +541,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -594,6 +582,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -618,7 +645,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "", "pineconeMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -704,9 +734,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -156,9 +156,9 @@
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"version": 1, "version": 2,
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Document QA - built on RetrievalQAChain to provide a chat history component", "description": "Document QA - built on RetrievalQAChain to provide a chat history component",
"inputParams": [ "inputParams": [
@@ -170,47 +170,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -232,15 +221,15 @@
"vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}", "vectorStoreRetriever": "{{memoryVectorStore_0.data.instance}}",
"memory": "", "memory": "",
"returnSourceDocuments": true, "returnSourceDocuments": true,
"systemMessagePrompt": "", "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"chainOption": "" "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"label": "ConversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain | BaseChain" "type": "ConversationalRetrievalQAChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -668,9 +657,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -83,10 +83,10 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Document QA - built on RetrievalQAChain to provide a chat history component", "description": "Document QA - built on RetrievalQAChain to provide a chat history component",
"inputParams": [ "inputParams": [
@@ -98,47 +98,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -158,14 +147,16 @@
"inputs": { "inputs": {
"model": "{{chatOllama_0.data.instance}}", "model": "{{chatOllama_0.data.instance}}",
"vectorStoreRetriever": "{{faiss_0.data.instance}}", "vectorStoreRetriever": "{{faiss_0.data.instance}}",
"memory": "" "memory": "",
"rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"label": "ConversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" "type": "ConversationalRetrievalQAChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -649,9 +640,9 @@
"source": "chatOllama_0", "source": "chatOllama_0",
"sourceHandle": "chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOllama_0-chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOllama_0-chatOllama_0-output-chatOllama-ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -13,10 +13,10 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Document QA - built on RetrievalQAChain to provide a chat history component", "description": "Document QA - built on RetrievalQAChain to provide a chat history component",
"inputParams": [ "inputParams": [
@@ -28,47 +28,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -89,14 +78,16 @@
"model": "{{chatOpenAI_0.data.instance}}", "model": "{{chatOpenAI_0.data.instance}}",
"vectorStoreRetriever": "{{qdrant_0.data.instance}}", "vectorStoreRetriever": "{{qdrant_0.data.instance}}",
"memory": "{{ZepMemory_0.data.instance}}", "memory": "{{ZepMemory_0.data.instance}}",
"returnSourceDocuments": true "returnSourceDocuments": true,
"rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"label": "ConversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" "type": "ConversationalRetrievalQAChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -232,7 +223,7 @@
"label": "Session Id", "label": "Session Id",
"name": "sessionId", "name": "sessionId",
"type": "string", "type": "string",
"description": "if empty, chatId will be used automatically", "description": "If not specified, a random id will be used. Learn <a target=\"_blank\" href=\"https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat\">more</a>",
"default": "", "default": "",
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
@@ -709,9 +700,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -249,10 +249,10 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Document QA - built on RetrievalQAChain to provide a chat history component", "description": "Document QA - built on RetrievalQAChain to provide a chat history component",
"inputParams": [ "inputParams": [
@@ -264,47 +264,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -323,14 +312,16 @@
], ],
"inputs": { "inputs": {
"model": "{{chatOpenAI_0.data.instance}}", "model": "{{chatOpenAI_0.data.instance}}",
"vectorStoreRetriever": "{{pinecone_0.data.instance}}" "vectorStoreRetriever": "{{pinecone_0.data.instance}}",
"rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|BaseLangChain", "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"label": "ConversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain | BaseChain | BaseLangChain" "type": "ConversationalRetrievalQAChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -634,7 +625,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -675,6 +666,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -699,7 +729,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "{\"id\":{\"$in\":[\"doc1\",\"doc2\"]}}", "pineconeMetadataFilter": "{\"id\":{\"$in\":[\"doc1\",\"doc2\"]}}",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -763,9 +796,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -560,7 +560,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -601,6 +601,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -625,7 +664,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "", "pineconeMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -840,6 +882,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "supabase_0-input-topK-number" "id": "supabase_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -865,7 +946,10 @@
"tableName": "", "tableName": "",
"queryName": "", "queryName": "",
"supabaseMetadataFilter": "", "supabaseMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -1567,7 +1567,7 @@
"id": "conversationalAgent_0-input-tools-Tool" "id": "conversationalAgent_0-input-tools-Tool"
}, },
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseChatModel", "type": "BaseChatModel",
"id": "conversationalAgent_0-input-model-BaseChatModel" "id": "conversationalAgent_0-input-model-BaseChatModel"
@@ -2,20 +2,210 @@
"description": "Basic example of Conversation Chain with built-in memory - works exactly like ChatGPT", "description": "Basic example of Conversation Chain with built-in memory - works exactly like ChatGPT",
"badge": "POPULAR", "badge": "POPULAR",
"nodes": [ "nodes": [
{
"width": 300,
"height": 574,
"id": "chatOpenAI_0",
"position": {
"x": 579.0877964395976,
"y": -138.68792413227874
},
"type": "customNode",
"data": {
"id": "chatOpenAI_0",
"label": "ChatOpenAI",
"version": 2,
"name": "chatOpenAI",
"type": "ChatOpenAI",
"baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"],
"category": "Chat Models",
"description": "Wrapper around OpenAI large language models that use the Chat endpoint",
"inputParams": [
{
"label": "Connect Credential",
"name": "credential",
"type": "credential",
"credentialNames": ["openAIApi"],
"id": "chatOpenAI_0-input-credential-credential"
},
{
"label": "Model Name",
"name": "modelName",
"type": "options",
"options": [
{
"label": "gpt-4",
"name": "gpt-4"
},
{
"label": "gpt-4-1106-preview",
"name": "gpt-4-1106-preview"
},
{
"label": "gpt-4-vision-preview",
"name": "gpt-4-vision-preview"
},
{
"label": "gpt-4-0613",
"name": "gpt-4-0613"
},
{
"label": "gpt-4-32k",
"name": "gpt-4-32k"
},
{
"label": "gpt-4-32k-0613",
"name": "gpt-4-32k-0613"
},
{
"label": "gpt-3.5-turbo",
"name": "gpt-3.5-turbo"
},
{
"label": "gpt-3.5-turbo-1106",
"name": "gpt-3.5-turbo-1106"
},
{
"label": "gpt-3.5-turbo-0613",
"name": "gpt-3.5-turbo-0613"
},
{
"label": "gpt-3.5-turbo-16k",
"name": "gpt-3.5-turbo-16k"
},
{
"label": "gpt-3.5-turbo-16k-0613",
"name": "gpt-3.5-turbo-16k-0613"
}
],
"default": "gpt-3.5-turbo",
"optional": true,
"id": "chatOpenAI_0-input-modelName-options"
},
{
"label": "Temperature",
"name": "temperature",
"type": "number",
"step": 0.1,
"default": 0.9,
"optional": true,
"id": "chatOpenAI_0-input-temperature-number"
},
{
"label": "Max Tokens",
"name": "maxTokens",
"type": "number",
"step": 1,
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-maxTokens-number"
},
{
"label": "Top Probability",
"name": "topP",
"type": "number",
"step": 0.1,
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-topP-number"
},
{
"label": "Frequency Penalty",
"name": "frequencyPenalty",
"type": "number",
"step": 0.1,
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-frequencyPenalty-number"
},
{
"label": "Presence Penalty",
"name": "presencePenalty",
"type": "number",
"step": 0.1,
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-presencePenalty-number"
},
{
"label": "Timeout",
"name": "timeout",
"type": "number",
"step": 1,
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-timeout-number"
},
{
"label": "BasePath",
"name": "basepath",
"type": "string",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-basepath-string"
},
{
"label": "BaseOptions",
"name": "baseOptions",
"type": "json",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-baseOptions-json"
}
],
"inputAnchors": [
{
"label": "Cache",
"name": "cache",
"type": "BaseCache",
"optional": true,
"id": "chatOpenAI_0-input-cache-BaseCache"
}
],
"inputs": {
"cache": "",
"modelName": "gpt-3.5-turbo-16k",
"temperature": 0.9,
"maxTokens": "",
"topP": "",
"frequencyPenalty": "",
"presencePenalty": "",
"timeout": "",
"basepath": "",
"baseOptions": ""
},
"outputAnchors": [
{
"id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"name": "chatOpenAI",
"label": "ChatOpenAI",
"type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable"
}
],
"outputs": {},
"selected": false
},
"selected": false,
"positionAbsolute": {
"x": 579.0877964395976,
"y": -138.68792413227874
},
"dragging": false
},
{ {
"width": 300, "width": 300,
"height": 376, "height": 376,
"id": "bufferMemory_0", "id": "bufferMemory_0",
"position": { "position": {
"x": 753.4300788823234, "x": 220.30240896145915,
"y": 479.5336426526603 "y": 351.61324070296877
}, },
"type": "customNode", "type": "customNode",
"data": { "data": {
"id": "bufferMemory_0", "id": "bufferMemory_0",
"label": "Buffer Memory", "label": "Buffer Memory",
"name": "bufferMemory",
"version": 1, "version": 1,
"name": "bufferMemory",
"type": "BufferMemory", "type": "BufferMemory",
"baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"],
"category": "Memory", "category": "Memory",
@@ -54,179 +244,8 @@
}, },
"selected": false, "selected": false,
"positionAbsolute": { "positionAbsolute": {
"x": 753.4300788823234, "x": 220.30240896145915,
"y": 479.5336426526603 "y": 351.61324070296877
},
"dragging": false
},
{
"width": 300,
"height": 523,
"id": "chatOpenAI_0",
"position": {
"x": 754.8942497823595,
"y": -140
},
"type": "customNode",
"data": {
"id": "chatOpenAI_0",
"label": "ChatOpenAI",
"name": "chatOpenAI",
"version": 2,
"type": "ChatOpenAI",
"baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"],
"category": "Chat Models",
"description": "Wrapper around OpenAI large language models that use the Chat endpoint",
"inputParams": [
{
"label": "Connect Credential",
"name": "credential",
"type": "credential",
"credentialNames": ["openAIApi"],
"id": "chatOpenAI_0-input-credential-credential"
},
{
"label": "Model Name",
"name": "modelName",
"type": "options",
"options": [
{
"label": "gpt-4",
"name": "gpt-4"
},
{
"label": "gpt-4-0613",
"name": "gpt-4-0613"
},
{
"label": "gpt-4-32k",
"name": "gpt-4-32k"
},
{
"label": "gpt-4-32k-0613",
"name": "gpt-4-32k-0613"
},
{
"label": "gpt-3.5-turbo",
"name": "gpt-3.5-turbo"
},
{
"label": "gpt-3.5-turbo-0613",
"name": "gpt-3.5-turbo-0613"
},
{
"label": "gpt-3.5-turbo-16k",
"name": "gpt-3.5-turbo-16k"
},
{
"label": "gpt-3.5-turbo-16k-0613",
"name": "gpt-3.5-turbo-16k-0613"
}
],
"default": "gpt-3.5-turbo",
"optional": true,
"id": "chatOpenAI_0-input-modelName-options"
},
{
"label": "Temperature",
"name": "temperature",
"type": "number",
"default": 0.9,
"optional": true,
"id": "chatOpenAI_0-input-temperature-number"
},
{
"label": "Max Tokens",
"name": "maxTokens",
"type": "number",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-maxTokens-number"
},
{
"label": "Top Probability",
"name": "topP",
"type": "number",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-topP-number"
},
{
"label": "Frequency Penalty",
"name": "frequencyPenalty",
"type": "number",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-frequencyPenalty-number"
},
{
"label": "Presence Penalty",
"name": "presencePenalty",
"type": "number",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-presencePenalty-number"
},
{
"label": "Timeout",
"name": "timeout",
"type": "number",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-timeout-number"
},
{
"label": "BasePath",
"name": "basepath",
"type": "string",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-basepath-string"
},
{
"label": "BaseOptions",
"name": "baseOptions",
"type": "json",
"optional": true,
"additionalParams": true,
"id": "chatOpenAI_0-input-baseOptions-json"
}
],
"inputAnchors": [
{
"label": "Cache",
"name": "cache",
"type": "BaseCache",
"optional": true,
"id": "chatOpenAI_0-input-cache-BaseCache"
}
],
"inputs": {
"modelName": "gpt-3.5-turbo",
"temperature": 0.9,
"maxTokens": "",
"topP": "",
"frequencyPenalty": "",
"presencePenalty": "",
"timeout": "",
"basepath": "",
"baseOptions": ""
},
"outputAnchors": [
{
"id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel",
"name": "chatOpenAI",
"label": "ChatOpenAI",
"type": "ChatOpenAI | BaseChatModel | BaseLanguageModel"
}
],
"outputs": {},
"selected": false
},
"selected": false,
"positionAbsolute": {
"x": 754.8942497823595,
"y": -140
}, },
"dragging": false "dragging": false
}, },
@@ -235,17 +254,17 @@
"height": 383, "height": 383,
"id": "conversationChain_0", "id": "conversationChain_0",
"position": { "position": {
"x": 1174.6496397666272, "x": 958.9887390513221,
"y": 311.1052536740497 "y": 318.8734467468765
}, },
"type": "customNode", "type": "customNode",
"data": { "data": {
"id": "conversationChain_0", "id": "conversationChain_0",
"label": "Conversation Chain", "label": "Conversation Chain",
"version": 2,
"name": "conversationChain", "name": "conversationChain",
"version": 1,
"type": "ConversationChain", "type": "ConversationChain",
"baseClasses": ["ConversationChain", "LLMChain", "BaseChain"], "baseClasses": ["ConversationChain", "LLMChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Chat models specific conversational chain with memory", "description": "Chat models specific conversational chain with memory",
"inputParams": [ "inputParams": [
@@ -254,15 +273,17 @@
"name": "systemMessagePrompt", "name": "systemMessagePrompt",
"type": "string", "type": "string",
"rows": 4, "rows": 4,
"description": "If Chat Prompt Template is provided, this will be ignored",
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "You are a helpful assistant that write codes", "default": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.",
"placeholder": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.",
"id": "conversationChain_0-input-systemMessagePrompt-string" "id": "conversationChain_0-input-systemMessagePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseChatModel", "type": "BaseChatModel",
"id": "conversationChain_0-input-model-BaseChatModel" "id": "conversationChain_0-input-model-BaseChatModel"
@@ -274,27 +295,26 @@
"id": "conversationChain_0-input-memory-BaseMemory" "id": "conversationChain_0-input-memory-BaseMemory"
}, },
{ {
"label": "Document", "label": "Chat Prompt Template",
"name": "document", "name": "chatPromptTemplate",
"type": "Document", "type": "ChatPromptTemplate",
"description": "Include whole document into the context window", "description": "Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable",
"optional": true, "optional": true,
"list": true, "id": "conversationChain_0-input-chatPromptTemplate-ChatPromptTemplate"
"id": "conversationChain_0-input-document-Document"
} }
], ],
"inputs": { "inputs": {
"model": "{{chatOpenAI_0.data.instance}}", "model": "{{chatOpenAI_0.data.instance}}",
"memory": "{{bufferMemory_0.data.instance}}", "memory": "{{bufferMemory_0.data.instance}}",
"document": "", "chatPromptTemplate": "",
"systemMessagePrompt": "" "systemMessagePrompt": "The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain", "id": "conversationChain_0-output-conversationChain-ConversationChain|LLMChain|BaseChain|Runnable",
"name": "conversationChain", "name": "conversationChain",
"label": "ConversationChain", "label": "ConversationChain",
"type": "ConversationChain | LLMChain | BaseChain" "type": "ConversationChain | LLMChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -302,8 +322,8 @@
}, },
"selected": false, "selected": false,
"positionAbsolute": { "positionAbsolute": {
"x": 1174.6496397666272, "x": 958.9887390513221,
"y": 311.1052536740497 "y": 318.8734467468765
}, },
"dragging": false "dragging": false
} }
@@ -311,14 +331,11 @@
"edges": [ "edges": [
{ {
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationChain_0", "target": "conversationChain_0",
"targetHandle": "conversationChain_0-input-model-BaseChatModel", "targetHandle": "conversationChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationChain_0-conversationChain_0-input-model-BaseChatModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationChain_0-conversationChain_0-input-model-BaseChatModel"
"data": {
"label": ""
}
}, },
{ {
"source": "bufferMemory_0", "source": "bufferMemory_0",
@@ -326,10 +343,7 @@
"target": "conversationChain_0", "target": "conversationChain_0",
"targetHandle": "conversationChain_0-input-memory-BaseMemory", "targetHandle": "conversationChain_0-input-memory-BaseMemory",
"type": "buttonedge", "type": "buttonedge",
"id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory", "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationChain_0-conversationChain_0-input-memory-BaseMemory"
"data": {
"label": ""
}
} }
] ]
} }
@@ -190,7 +190,7 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
@@ -205,47 +205,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -267,8 +256,8 @@
"vectorStoreRetriever": "{{vectara_0.data.instance}}", "vectorStoreRetriever": "{{vectara_0.data.instance}}",
"memory": "", "memory": "",
"returnSourceDocuments": true, "returnSourceDocuments": true,
"systemMessagePrompt": "", "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"chainOption": "" "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -361,12 +350,33 @@
{ {
"label": "Top K", "label": "Top K",
"name": "topK", "name": "topK",
"description": "Number of top results to fetch. Defaults to 4", "description": "Number of top results to fetch. Defaults to 5",
"placeholder": "4", "placeholder": "5",
"type": "number", "type": "number",
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "vectara_0-input-topK-number" "id": "vectara_0-input-topK-number"
},
{
"label": "MMR K",
"name": "mmrK",
"description": "The number of results to rerank if MMR is enabled.",
"placeholder": "50",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "vectara_0-input-mmrK-number"
},
{
"label": "MMR Diversity Bias",
"name": "mmrDiversityBias",
"step": 0.1,
"description": "Diversity Bias parameter for MMR, if enabled. 0.0 means no diversiry bias, 1.0 means maximum diversity bias. Defaults to 0.0 (MMR disabled).",
"placeholder": "0.0",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "vectara_0-input-mmrDiversityBias-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -385,7 +395,9 @@
"sentencesBefore": "", "sentencesBefore": "",
"sentencesAfter": "", "sentencesAfter": "",
"lambda": "", "lambda": "",
"topK": "" "topK": "",
"mmrK": "",
"mmrDiversityBias": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -427,9 +439,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
@@ -578,7 +578,7 @@
"id": "conversationalAgent_0-input-tools-Tool" "id": "conversationalAgent_0-input-tools-Tool"
}, },
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseChatModel", "type": "BaseChatModel",
"id": "conversationalAgent_0-input-model-BaseChatModel" "id": "conversationalAgent_0-input-model-BaseChatModel"
@@ -162,10 +162,10 @@
"data": { "data": {
"id": "conversationalRetrievalQAChain_0", "id": "conversationalRetrievalQAChain_0",
"label": "Conversational Retrieval QA Chain", "label": "Conversational Retrieval QA Chain",
"version": 1, "version": 2,
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain",
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"],
"category": "Chains", "category": "Chains",
"description": "Document QA - built on RetrievalQAChain to provide a chat history component", "description": "Document QA - built on RetrievalQAChain to provide a chat history component",
"inputParams": [ "inputParams": [
@@ -177,47 +177,36 @@
"id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean"
}, },
{ {
"label": "System Message", "label": "Rephrase Prompt",
"name": "systemMessagePrompt", "name": "rephrasePrompt",
"type": "string", "type": "string",
"description": "Using previous chat history, rephrase question into a standalone question",
"warning": "Prompt must include input variables: {chat_history} and {question}",
"rows": 4, "rows": 4,
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "default": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" "id": "conversationalRetrievalQAChain_0-input-rephrasePrompt-string"
}, },
{ {
"label": "Chain Option", "label": "Response Prompt",
"name": "chainOption", "name": "responsePrompt",
"type": "options", "type": "string",
"options": [ "description": "Taking the rephrased question, search for answer from the provided context",
{ "warning": "Prompt must include input variable: {context}",
"label": "MapReduceDocumentsChain", "rows": 4,
"name": "map_reduce",
"description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time"
},
{
"label": "RefineDocumentsChain",
"name": "refine",
"description": "Suitable for QA tasks over a large number of documents."
},
{
"label": "StuffDocumentsChain",
"name": "stuff",
"description": "Suitable for QA tasks over a small number of documents."
}
],
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "conversationalRetrievalQAChain_0-input-chainOption-options" "default": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.",
"id": "conversationalRetrievalQAChain_0-input-responsePrompt-string"
} }
], ],
"inputAnchors": [ "inputAnchors": [
{ {
"label": "Language Model", "label": "Chat Model",
"name": "model", "name": "model",
"type": "BaseLanguageModel", "type": "BaseChatModel",
"id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" "id": "conversationalRetrievalQAChain_0-input-model-BaseChatModel"
}, },
{ {
"label": "Vector Store Retriever", "label": "Vector Store Retriever",
@@ -239,15 +228,15 @@
"vectorStoreRetriever": "{{pinecone_0.data.instance}}", "vectorStoreRetriever": "{{pinecone_0.data.instance}}",
"memory": "{{RedisBackedChatMemory_0.data.instance}}", "memory": "{{RedisBackedChatMemory_0.data.instance}}",
"returnSourceDocuments": true, "returnSourceDocuments": true,
"systemMessagePrompt": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given context. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Do not make up any information that is not in the context. Refuse to answer any question not about the info. Never break character.", "rephrasePrompt": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone Question:",
"chainOption": "" "responsePrompt": "You are a helpful assistant. Using the provided context, answer the user's question to the best of your ability using the resources provided.\nIf there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n------------\n{context}\n------------\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer."
}, },
"outputAnchors": [ "outputAnchors": [
{ {
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable",
"name": "conversationalRetrievalQAChain", "name": "conversationalRetrievalQAChain",
"label": "ConversationalRetrievalQAChain", "label": "ConversationalRetrievalQAChain",
"type": "ConversationalRetrievalQAChain | BaseChain" "type": "ConversationalRetrievalQAChain | BaseChain | Runnable"
} }
], ],
"outputs": {}, "outputs": {},
@@ -589,7 +578,7 @@
"label": "Session Id", "label": "Session Id",
"name": "sessionId", "name": "sessionId",
"type": "string", "type": "string",
"description": "If not specified, the first CHAT_MESSAGE_ID will be used as sessionId", "description": "If not specified, a random id will be used. Learn <a target=\"_blank\" href=\"https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat\">more</a>",
"default": "", "default": "",
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
@@ -654,7 +643,7 @@
"type": "Pinecone", "type": "Pinecone",
"baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"],
"category": "Vector Stores", "category": "Vector Stores",
"description": "Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database", "description": "Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database",
"inputParams": [ "inputParams": [
{ {
"label": "Connect Credential", "label": "Connect Credential",
@@ -695,6 +684,45 @@
"additionalParams": true, "additionalParams": true,
"optional": true, "optional": true,
"id": "pinecone_0-input-topK-number" "id": "pinecone_0-input-topK-number"
},
{
"label": "Search Type",
"name": "searchType",
"type": "options",
"default": "similarity",
"options": [
{
"label": "Similarity",
"name": "similarity"
},
{
"label": "Max Marginal Relevance",
"name": "mmr"
}
],
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-searchType-options"
},
{
"label": "Fetch K (for MMR Search)",
"name": "fetchK",
"description": "Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR",
"placeholder": "20",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-fetchK-number"
},
{
"label": "Lambda (for MMR Search)",
"name": "lambda",
"description": "Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR",
"placeholder": "0.5",
"type": "number",
"additionalParams": true,
"optional": true,
"id": "pinecone_0-input-lambda-number"
} }
], ],
"inputAnchors": [ "inputAnchors": [
@@ -719,7 +747,10 @@
"pineconeIndex": "", "pineconeIndex": "",
"pineconeNamespace": "", "pineconeNamespace": "",
"pineconeMetadataFilter": "", "pineconeMetadataFilter": "",
"topK": "" "topK": "",
"searchType": "similarity",
"fetchK": "",
"lambda": ""
}, },
"outputAnchors": [ "outputAnchors": [
{ {
@@ -772,9 +803,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalQAChain_0", "target": "conversationalRetrievalQAChain_0",
"targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"type": "buttonedge", "type": "buttonedge",
"id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseChatModel",
"data": { "data": {
"label": "" "label": ""
} }
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "flowise", "name": "flowise",
"version": "1.4.9", "version": "1.4.10",
"description": "Flowiseai Server", "description": "Flowiseai Server",
"main": "dist/index", "main": "dist/index",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
@@ -60,6 +60,7 @@
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"pg": "^8.11.1", "pg": "^8.11.1",
"posthog-node": "^3.5.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"sanitize-html": "^2.11.0", "sanitize-html": "^2.11.0",
"socket.io": "^4.6.1", "socket.io": "^4.6.1",
+5 -1
View File
@@ -39,7 +39,8 @@ export default class Start extends Command {
LANGCHAIN_TRACING_V2: Flags.string(), LANGCHAIN_TRACING_V2: Flags.string(),
LANGCHAIN_ENDPOINT: Flags.string(), LANGCHAIN_ENDPOINT: Flags.string(),
LANGCHAIN_API_KEY: Flags.string(), LANGCHAIN_API_KEY: Flags.string(),
LANGCHAIN_PROJECT: Flags.string() LANGCHAIN_PROJECT: Flags.string(),
DISABLE_FLOWISE_TELEMETRY: Flags.string()
} }
async stopProcess() { async stopProcess() {
@@ -113,6 +114,9 @@ export default class Start extends Command {
if (flags.LANGCHAIN_API_KEY) process.env.LANGCHAIN_API_KEY = flags.LANGCHAIN_API_KEY if (flags.LANGCHAIN_API_KEY) process.env.LANGCHAIN_API_KEY = flags.LANGCHAIN_API_KEY
if (flags.LANGCHAIN_PROJECT) process.env.LANGCHAIN_PROJECT = flags.LANGCHAIN_PROJECT if (flags.LANGCHAIN_PROJECT) process.env.LANGCHAIN_PROJECT = flags.LANGCHAIN_PROJECT
// Telemetry
if (flags.DISABLE_FLOWISE_TELEMETRY) process.env.DISABLE_FLOWISE_TELEMETRY = flags.DISABLE_FLOWISE_TELEMETRY
await (async () => { await (async () => {
try { try {
logger.info('Starting Flowise...') logger.info('Starting Flowise...')
@@ -1,9 +1,9 @@
/* eslint-disable */ /* eslint-disable */
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { IVariable } from "../../Interface"; import { IVariable } from '../../Interface'
@Entity() @Entity()
export class Variable implements IVariable{ export class Variable implements IVariable {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id: string id: string
@@ -13,10 +13,9 @@ export class Variable implements IVariable{
@Column({ nullable: true, type: 'text' }) @Column({ nullable: true, type: 'text' })
value: string value: string
@Column({default: 'string', type: 'text'}) @Column({ default: 'string', type: 'text' })
type: string type: string
@CreateDateColumn() @CreateDateColumn()
createdDate: Date createdDate: Date
+101 -83
View File
@@ -20,7 +20,6 @@ import {
ICredentialReturnResponse, ICredentialReturnResponse,
chatType, chatType,
IChatMessage, IChatMessage,
IReactFlowEdge,
IDepthQueue, IDepthQueue,
INodeDirectedGraph INodeDirectedGraph
} from './Interface' } from './Interface'
@@ -39,14 +38,16 @@ import {
databaseEntities, databaseEntities,
transformToCredentialEntity, transformToCredentialEntity,
decryptCredentialData, decryptCredentialData,
clearAllSessionMemory,
replaceInputsWithConfig, replaceInputsWithConfig,
getEncryptionKey, getEncryptionKey,
checkMemorySessionId, getMemorySessionId,
clearSessionMemoryFromViewMessageDialog,
getUserHome, getUserHome,
replaceChatHistory, getSessionChatHistory,
getAllConnectedNodes getAllConnectedNodes,
clearSessionMemory,
findMemoryNode,
getTelemetryFlowObj,
getAppVersion
} from './utils' } from './utils'
import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash'
import { getDataSource } from './DataSource' import { getDataSource } from './DataSource'
@@ -65,6 +66,7 @@ import { sanitizeMiddleware } from './utils/XSS'
import axios from 'axios' import axios from 'axios'
import { Client } from 'langchainhub' import { Client } from 'langchainhub'
import { parsePrompt } from './utils/hub' import { parsePrompt } from './utils/hub'
import { Telemetry } from './utils/telemetry'
import { Variable } from './database/entities/Variable' import { Variable } from './database/entities/Variable'
export class App { export class App {
@@ -72,6 +74,7 @@ export class App {
nodesPool: NodesPool nodesPool: NodesPool
chatflowPool: ChatflowPool chatflowPool: ChatflowPool
cachePool: CachePool cachePool: CachePool
telemetry: Telemetry
AppDataSource = getDataSource() AppDataSource = getDataSource()
constructor() { constructor() {
@@ -106,6 +109,9 @@ export class App {
// Initialize cache pool // Initialize cache pool
this.cachePool = new CachePool() this.cachePool = new CachePool()
// Initialize telemetry
this.telemetry = new Telemetry()
}) })
.catch((err) => { .catch((err) => {
logger.error('❌ [server]: Error during Data Source initialization:', err) logger.error('❌ [server]: Error during Data Source initialization:', err)
@@ -295,7 +301,13 @@ export class App {
const nodeModule = await import(nodeInstanceFilePath) const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass() const newNodeInstance = new nodeModule.nodeClass()
const returnData = await newNodeInstance.init(nodeData) const options: ICommonObject = {
appDataSource: this.AppDataSource,
databaseEntities,
logger
}
const returnData = await newNodeInstance.init(nodeData, '', options)
const result = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData const result = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData
return res.json(result) return res.json(result)
@@ -383,6 +395,12 @@ export class App {
const chatflow = this.AppDataSource.getRepository(ChatFlow).create(newChatFlow) const chatflow = this.AppDataSource.getRepository(ChatFlow).create(newChatFlow)
const results = await this.AppDataSource.getRepository(ChatFlow).save(chatflow) const results = await this.AppDataSource.getRepository(ChatFlow).save(chatflow)
await this.telemetry.sendTelemetry('chatflow_created', {
version: await getAppVersion(),
chatlowId: results.id,
flowGraph: getTelemetryFlowObj(JSON.parse(results.flowData)?.nodes, JSON.parse(results.flowData)?.edges)
})
return res.json(results) return res.json(results)
}) })
@@ -523,7 +541,7 @@ export class App {
res.status(404).send(`Chatflow ${chatflowid} not found`) res.status(404).send(`Chatflow ${chatflowid} not found`)
return return
} }
const chatId = (req.query?.chatId as string) ?? (await getChatId(chatflowid)) const chatId = req.query?.chatId as string
const memoryType = req.query?.memoryType as string | undefined const memoryType = req.query?.memoryType as string | undefined
const sessionId = req.query?.sessionId as string | undefined const sessionId = req.query?.sessionId as string | undefined
const chatType = req.query?.chatType as string | undefined const chatType = req.query?.chatType as string | undefined
@@ -533,20 +551,22 @@ export class App {
const parsedFlowData: IReactFlowObject = JSON.parse(flowData) const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes const nodes = parsedFlowData.nodes
if (isClearFromViewMessageDialog) { try {
await clearSessionMemoryFromViewMessageDialog( await clearSessionMemory(
nodes, nodes,
this.nodesPool.componentNodes, this.nodesPool.componentNodes,
chatId, chatId,
this.AppDataSource, this.AppDataSource,
sessionId, sessionId,
memoryType memoryType,
isClearFromViewMessageDialog
) )
} else { } catch (e) {
await clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId) return res.status(500).send('Error clearing chat messages')
} }
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid, chatId } const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid }
if (chatId) deleteOptions.chatId = chatId
if (memoryType) deleteOptions.memoryType = memoryType if (memoryType) deleteOptions.memoryType = memoryType
if (sessionId) deleteOptions.sessionId = sessionId if (sessionId) deleteOptions.sessionId = sessionId
if (chatType) deleteOptions.chatType = chatType if (chatType) deleteOptions.chatType = chatType
@@ -634,7 +654,7 @@ export class App {
return res.json(result) return res.json(result)
}) })
// Delete all chatmessages from chatflowid // Delete all credentials from chatflowid
this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => {
const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id }) const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id })
return res.json(results) return res.json(results)
@@ -667,6 +687,12 @@ export class App {
const tool = this.AppDataSource.getRepository(Tool).create(newTool) const tool = this.AppDataSource.getRepository(Tool).create(newTool)
const results = await this.AppDataSource.getRepository(Tool).save(tool) const results = await this.AppDataSource.getRepository(Tool).save(tool)
await this.telemetry.sendTelemetry('tool_created', {
version: await getAppVersion(),
toolId: results.id,
toolName: results.name
})
return res.json(results) return res.json(results)
}) })
@@ -873,6 +899,11 @@ export class App {
const assistant = this.AppDataSource.getRepository(Assistant).create(newAssistant) const assistant = this.AppDataSource.getRepository(Assistant).create(newAssistant)
const results = await this.AppDataSource.getRepository(Assistant).save(assistant) const results = await this.AppDataSource.getRepository(Assistant).save(assistant)
await this.telemetry.sendTelemetry('assistant_created', {
version: await getAppVersion(),
assistantId: results.id
})
return res.json(results) return res.json(results)
}) })
@@ -1398,26 +1429,6 @@ export class App {
return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage)
} }
/**
* Method that find memory label that is connected within chatflow
* In a chatflow, there should only be 1 memory node
* @param {IReactFlowNode[]} nodes
* @param {IReactFlowEdge[]} edges
* @returns {string | undefined}
*/
findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined {
const memoryNodes = nodes.filter((node) => node.data.category === 'Memory')
const memoryNodeIds = memoryNodes.map((mem) => mem.data.id)
for (const edge of edges) {
if (memoryNodeIds.includes(edge.source)) {
const memoryNode = nodes.find((node) => node.data.id === edge.source)
return memoryNode
}
}
return undefined
}
async upsertVector(req: Request, res: Response, isInternal: boolean = false) { async upsertVector(req: Request, res: Response, isInternal: boolean = false) {
try { try {
const chatflowid = req.params.id const chatflowid = req.params.id
@@ -1467,6 +1478,11 @@ export class App {
let chatId = incomingInput.chatId ?? '' let chatId = incomingInput.chatId ?? ''
let isUpsert = true let isUpsert = true
// Get session ID
const memoryNode = findMemoryNode(nodes, edges)
let sessionId = undefined
if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal)
const vsNodes = nodes.filter( const vsNodes = nodes.filter(
(node) => (node) =>
node.data.category === 'Vector Stores' && node.data.category === 'Vector Stores' &&
@@ -1504,6 +1520,7 @@ export class App {
incomingInput.question, incomingInput.question,
chatHistory, chatHistory,
chatId, chatId,
sessionId ?? '',
chatflowid, chatflowid,
this.AppDataSource, this.AppDataSource,
incomingInput?.overrideConfig, incomingInput?.overrideConfig,
@@ -1515,6 +1532,15 @@ export class App {
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.data.id)) const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.data.id))
this.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig) this.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig)
await this.telemetry.sendTelemetry('vector_upserted', {
version: await getAppVersion(),
chatlowId: chatflowid,
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
flowGraph: getTelemetryFlowObj(nodes, edges),
stopNodeId
})
return res.status(201).send('Successfully Upserted') return res.status(201).send('Successfully Upserted')
} catch (e: any) { } catch (e: any) {
logger.error('[server]: Error:', e) logger.error('[server]: Error:', e)
@@ -1581,12 +1607,17 @@ export class App {
const nodes = parsedFlowData.nodes const nodes = parsedFlowData.nodes
const edges = parsedFlowData.edges const edges = parsedFlowData.edges
// Get session ID
const memoryNode = findMemoryNode(nodes, edges)
const memoryType = memoryNode?.data.label
let sessionId = undefined
if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal)
/* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met: /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met:
* - Node Data already exists in pool * - Node Data already exists in pool
* - Still in sync (i.e the flow has not been modified since) * - Still in sync (i.e the flow has not been modified since)
* - Existing overrideConfig and new overrideConfig are the same * - Existing overrideConfig and new overrideConfig are the same
* - Flow doesn't start with/contain nodes that depend on incomingInput.question * - Flow doesn't start with/contain nodes that depend on incomingInput.question
* - Its not an Upsert request
* TODO: convert overrideConfig to hash when we no longer store base64 string but filepath * TODO: convert overrideConfig to hash when we no longer store base64 string but filepath
***/ ***/
const isFlowReusable = () => { const isFlowReusable = () => {
@@ -1640,22 +1671,28 @@ export class App {
isStreamValid = isFlowValidForStream(nodes, endingNodeData) isStreamValid = isFlowValidForStream(nodes, endingNodeData)
} }
let chatHistory: IMessage[] | string = incomingInput.history let chatHistory: IMessage[] = incomingInput.history ?? []
// When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory // When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory node
for (const endingNode of endingNodes) { for (const endingNode of endingNodes) {
const endingNodeData = endingNode.data const endingNodeData = endingNode.data
if (!endingNodeData.inputs?.memory) continue if (!endingNodeData.inputs?.memory) continue
if (
endingNodeData.inputs?.memory && const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '')
!incomingInput.history && const memoryNode = nodes.find((node) => node.data.id === memoryNodeId)
(incomingInput.chatId || incomingInput.overrideConfig?.sessionId)
) { if (!memoryNode) continue
const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '')
const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) if (!chatHistory.length && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) {
if (memoryNode) { chatHistory = await getSessionChatHistory(
chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) memoryNode,
} this.nodesPool.componentNodes,
incomingInput,
this.AppDataSource,
databaseEntities,
logger
)
} }
} }
@@ -1685,6 +1722,7 @@ export class App {
incomingInput.question, incomingInput.question,
chatHistory, chatHistory,
chatId, chatId,
sessionId ?? '',
chatflowid, chatflowid,
this.AppDataSource, this.AppDataSource,
incomingInput?.overrideConfig, incomingInput?.overrideConfig,
@@ -1714,41 +1752,30 @@ export class App {
logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
let sessionId = undefined
if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId)
const memoryNode = this.findMemoryLabel(nodes, edges)
const memoryType = memoryNode?.data.label
let chatHistory: IMessage[] | string = incomingInput.history
if (memoryNode && !incomingInput.history && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) {
chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger)
}
const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath) const nodeModule = await import(nodeInstanceFilePath)
const nodeInstance = new nodeModule.nodeClass({ sessionId }) const nodeInstance = new nodeModule.nodeClass({ sessionId })
let result = isStreamValid let result = isStreamValid
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatId,
chatflowid, chatflowid,
chatHistory, chatHistory: incomingInput.history,
socketIO,
socketIOClientId: incomingInput.socketIOClientId,
logger, logger,
appDataSource: this.AppDataSource, appDataSource: this.AppDataSource,
databaseEntities, databaseEntities,
analytic: chatflow.analytic, analytic: chatflow.analytic,
chatId socketIO,
socketIOClientId: incomingInput.socketIOClientId
}) })
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, { : await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatId,
chatflowid, chatflowid,
chatHistory, chatHistory: incomingInput.history,
logger, logger,
appDataSource: this.AppDataSource, appDataSource: this.AppDataSource,
databaseEntities, databaseEntities,
analytic: chatflow.analytic, analytic: chatflow.analytic
chatId
}) })
result = typeof result === 'string' ? { text: result } : result result = typeof result === 'string' ? { text: result } : result
@@ -1790,6 +1817,13 @@ export class App {
await this.addChatMessage(apiMessage) await this.addChatMessage(apiMessage)
logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
await this.telemetry.sendTelemetry('prediction_sent', {
version: await getAppVersion(),
chatlowId: chatflowid,
chatId,
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
flowGraph: getTelemetryFlowObj(nodes, edges)
})
// Only return ChatId when its Internal OR incoming input has ChatId, to avoid confusion when calling API // Only return ChatId when its Internal OR incoming input has ChatId, to avoid confusion when calling API
if (incomingInput.chatId || isInternal) result.chatId = chatId if (incomingInput.chatId || isInternal) result.chatId = chatId
@@ -1804,6 +1838,7 @@ export class App {
async stopApp() { async stopApp() {
try { try {
const removePromises: any[] = [] const removePromises: any[] = []
removePromises.push(this.telemetry.flush())
await Promise.all(removePromises) await Promise.all(removePromises)
} catch (e) { } catch (e) {
logger.error(`❌[server]: Flowise Server shut down error: ${e}`) logger.error(`❌[server]: Flowise Server shut down error: ${e}`)
@@ -1811,23 +1846,6 @@ export class App {
} }
} }
/**
* Get first chat message id
* @param {string} chatflowid
* @returns {string}
*/
export async function getChatId(chatflowid: string): Promise<string> {
// first chatmessage id as the unique chat id
const firstChatMessage = await getDataSource()
.getRepository(ChatMessage)
.createQueryBuilder('cm')
.select('cm.id')
.where('chatflowid = :chatflowid', { chatflowid })
.orderBy('cm.createdDate', 'ASC')
.getOne()
return firstChatMessage ? firstChatMessage.id : ''
}
let serverApp: App | undefined let serverApp: App | undefined
export async function getAllChatFlow(): Promise<IChatFlow[]> { export async function getAllChatFlow(): Promise<IChatFlow[]> {
+161 -73
View File
@@ -26,7 +26,8 @@ import {
getEncryptionKeyPath, getEncryptionKeyPath,
ICommonObject, ICommonObject,
IDatabaseEntity, IDatabaseEntity,
IMessage IMessage,
FlowiseMemory
} from 'flowise-components' } from 'flowise-components'
import { randomBytes } from 'crypto' import { randomBytes } from 'crypto'
import { AES, enc } from 'crypto-js' import { AES, enc } from 'crypto-js'
@@ -270,8 +271,9 @@ export const buildLangchain = async (
depthQueue: IDepthQueue, depthQueue: IDepthQueue,
componentNodes: IComponentNodes, componentNodes: IComponentNodes,
question: string, question: string,
chatHistory: IMessage[] | string, chatHistory: IMessage[],
chatId: string, chatId: string,
sessionId: string,
chatflowid: string, chatflowid: string,
appDataSource: DataSource, appDataSource: DataSource,
overrideConfig?: ICommonObject, overrideConfig?: ICommonObject,
@@ -316,10 +318,12 @@ export const buildLangchain = async (
logger.debug(`[server]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) logger.debug(`[server]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
await newNodeInstance.vectorStoreMethods!['upsert']!.call(newNodeInstance, reactFlowNodeData, { await newNodeInstance.vectorStoreMethods!['upsert']!.call(newNodeInstance, reactFlowNodeData, {
chatId, chatId,
sessionId,
chatflowid, chatflowid,
chatHistory,
logger,
appDataSource, appDataSource,
databaseEntities, databaseEntities,
logger,
cachePool, cachePool,
dynamicVariables dynamicVariables
}) })
@@ -329,10 +333,12 @@ export const buildLangchain = async (
logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
let outputResult = await newNodeInstance.init(reactFlowNodeData, question, { let outputResult = await newNodeInstance.init(reactFlowNodeData, question, {
chatId, chatId,
sessionId,
chatflowid, chatflowid,
chatHistory,
logger,
appDataSource, appDataSource,
databaseEntities, databaseEntities,
logger,
cachePool, cachePool,
dynamicVariables dynamicVariables
}) })
@@ -424,66 +430,52 @@ export const buildLangchain = async (
} }
/** /**
* Clear all session memories on the canvas * Clear session memories
* @param {IReactFlowNode[]} reactFlowNodes
* @param {IComponentNodes} componentNodes
* @param {string} chatId
* @param {DataSource} appDataSource
* @param {string} sessionId
*/
export const clearAllSessionMemory = async (
reactFlowNodes: IReactFlowNode[],
componentNodes: IComponentNodes,
chatId: string,
appDataSource: DataSource,
sessionId?: string
) => {
for (const node of reactFlowNodes) {
if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass()
if (sessionId && node.data.inputs) {
node.data.inputs.sessionId = sessionId
}
if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) {
await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
}
}
}
/**
* Clear specific session memory from View Message Dialog UI
* @param {IReactFlowNode[]} reactFlowNodes * @param {IReactFlowNode[]} reactFlowNodes
* @param {IComponentNodes} componentNodes * @param {IComponentNodes} componentNodes
* @param {string} chatId * @param {string} chatId
* @param {DataSource} appDataSource * @param {DataSource} appDataSource
* @param {string} sessionId * @param {string} sessionId
* @param {string} memoryType * @param {string} memoryType
* @param {string} isClearFromViewMessageDialog
*/ */
export const clearSessionMemoryFromViewMessageDialog = async ( export const clearSessionMemory = async (
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
componentNodes: IComponentNodes, componentNodes: IComponentNodes,
chatId: string, chatId: string,
appDataSource: DataSource, appDataSource: DataSource,
sessionId?: string, sessionId?: string,
memoryType?: string memoryType?: string,
isClearFromViewMessageDialog?: string
) => { ) => {
if (!sessionId) return
for (const node of reactFlowNodes) { for (const node of reactFlowNodes) {
if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue
if (memoryType && node.data.label !== memoryType) continue
// Only clear specific session memory from View Message Dialog UI
if (isClearFromViewMessageDialog && memoryType && node.data.label !== memoryType) continue
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath) const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass() const newNodeInstance = new nodeModule.nodeClass()
const options: ICommonObject = { chatId, appDataSource, databaseEntities, logger }
if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId // SessionId always take priority first because it is the sessionId used for 3rd party memory node
if (sessionId && node.data.inputs) {
if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { if (node.data.type === 'OpenAIAssistant') {
await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) await newNodeInstance.clearChatMessages(node.data, options, { type: 'threadId', id: sessionId })
return } else {
node.data.inputs.sessionId = sessionId
const initializedInstance: FlowiseMemory = await newNodeInstance.init(node.data, '', options)
await initializedInstance.clearChatMessages(sessionId)
}
} else if (chatId && node.data.inputs) {
if (node.data.type === 'OpenAIAssistant') {
await newNodeInstance.clearChatMessages(node.data, options, { type: 'chatId', id: chatId })
} else {
node.data.inputs.sessionId = chatId
const initializedInstance: FlowiseMemory = await newNodeInstance.init(node.data, '', options)
await initializedInstance.clearChatMessages(chatId)
}
} }
} }
} }
@@ -500,7 +492,7 @@ export const getVariableValue = (
paramValue: string, paramValue: string,
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
question: string, question: string,
chatHistory: IMessage[] | string, chatHistory: IMessage[],
isAcceptVariable = false isAcceptVariable = false
) => { ) => {
let returnVal = paramValue let returnVal = paramValue
@@ -533,10 +525,7 @@ export const getVariableValue = (
} }
if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) {
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters( variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false)
typeof chatHistory === 'string' ? chatHistory : convertChatHistoryToText(chatHistory),
false
)
} }
// Split by first occurrence of '.' to get just nodeId // Split by first occurrence of '.' to get just nodeId
@@ -583,7 +572,7 @@ export const resolveVariables = (
reactFlowNodeData: INodeData, reactFlowNodeData: INodeData,
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
question: string, question: string,
chatHistory: IMessage[] | string chatHistory: IMessage[]
): INodeData => { ): INodeData => {
let flowNodeData = cloneDeep(reactFlowNodeData) let flowNodeData = cloneDeep(reactFlowNodeData)
const types = 'inputs' const types = 'inputs'
@@ -970,21 +959,43 @@ export const redactCredentialWithPasswordType = (
} }
/** /**
* Replace sessionId with new chatId * Get sessionId
* Ex: after clear chat history, use the new chatId as sessionId * Hierarchy of sessionId (top down)
* API/Embed:
* (1) Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' }
* (2) Provided in API body - incomingInput.chatId
*
* API/Embed + UI:
* (3) Hard-coded sessionId in UI
* (4) Not specified on UI nor API, default to chatId
* @param {any} instance * @param {any} instance
* @param {IncomingInput} incomingInput
* @param {string} chatId * @param {string} chatId
*/ */
export const checkMemorySessionId = (instance: any, chatId: string): string | undefined => { export const getMemorySessionId = (
if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) { memoryNode: IReactFlowNode,
instance.memory.sessionId = chatId incomingInput: IncomingInput,
instance.memory.chatHistory.sessionId = chatId chatId: string,
isInternal: boolean
): string | undefined => {
if (!isInternal) {
// Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' }
if (incomingInput.overrideConfig?.sessionId) {
return incomingInput.overrideConfig?.sessionId
}
// Provided in API body - incomingInput.chatId
if (incomingInput.chatId) {
return incomingInput.chatId
}
} }
if (instance.memory && instance.memory.sessionId) return instance.memory.sessionId // Hard-coded sessionId in UI
else if (instance.memory && instance.memory.chatHistory && instance.memory.chatHistory.sessionId) if (memoryNode.data.inputs?.sessionId) {
return instance.memory.chatHistory.sessionId return memoryNode.data.inputs.sessionId
return undefined }
// Default chatId
return chatId
} }
/** /**
@@ -996,31 +1007,52 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un
* @param {any} logger * @param {any} logger
* @returns {string} * @returns {string}
*/ */
export const replaceChatHistory = async ( export const getSessionChatHistory = async (
memoryNode: IReactFlowNode, memoryNode: IReactFlowNode,
componentNodes: IComponentNodes,
incomingInput: IncomingInput, incomingInput: IncomingInput,
appDataSource: DataSource, appDataSource: DataSource,
databaseEntities: IDatabaseEntity, databaseEntities: IDatabaseEntity,
logger: any logger: any
): Promise<string> => { ): Promise<IMessage[]> => {
const nodeInstanceFilePath = memoryNode.data.filePath as string const nodeInstanceFilePath = componentNodes[memoryNode.data.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath) const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass() const newNodeInstance = new nodeModule.nodeClass()
// Replace memory's sessionId/chatId
if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) {
memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId
} else if (incomingInput.chatId && memoryNode.data.inputs) {
memoryNode.data.inputs.sessionId = incomingInput.chatId
} }
if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.getChatMessages) { const initializedInstance: FlowiseMemory = await newNodeInstance.init(memoryNode.data, '', {
return await newNodeInstance.memoryMethods.getChatMessages(memoryNode.data, { appDataSource,
chatId: incomingInput.chatId, databaseEntities,
appDataSource, logger
databaseEntities, })
logger
})
}
return '' return (await initializedInstance.getChatMessages()) as IMessage[]
}
/**
* Method that find memory that is connected within chatflow
* In a chatflow, there should only be 1 memory node
* @param {IReactFlowNode[]} nodes
* @param {IReactFlowEdge[]} edges
* @returns {string | undefined}
*/
export const findMemoryNode = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined => {
const memoryNodes = nodes.filter((node) => node.data.category === 'Memory')
const memoryNodeIds = memoryNodes.map((mem) => mem.data.id)
for (const edge of edges) {
if (memoryNodeIds.includes(edge.source)) {
const memoryNode = nodes.find((node) => node.data.id === edge.source)
return memoryNode
}
}
return undefined
} }
/** /**
@@ -1050,3 +1082,59 @@ export const getAllValuesFromJson = (obj: any): any[] => {
extractValues(obj) extractValues(obj)
return values return values
} }
/**
* Get only essential flow data items for telemetry
* @param {IReactFlowNode[]} nodes
* @param {IReactFlowEdge[]} edges
*/
export const getTelemetryFlowObj = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]) => {
const nodeData = nodes.map((node) => node.id)
const edgeData = edges.map((edge) => ({ source: edge.source, target: edge.target }))
return { nodes: nodeData, edges: edgeData }
}
/**
* Get user settings file
* TODO: move env variables to settings json file, easier configuration
*/
export const getUserSettingsFilePath = () => {
const checkPaths = [path.join(getUserHome(), '.flowise', 'settings.json')]
for (const checkPath of checkPaths) {
if (fs.existsSync(checkPath)) {
return checkPath
}
}
return ''
}
/**
* Get app current version
*/
export const getAppVersion = async () => {
const getPackageJsonPath = (): string => {
const checkPaths = [
path.join(__dirname, '..', 'package.json'),
path.join(__dirname, '..', '..', 'package.json'),
path.join(__dirname, '..', '..', '..', 'package.json'),
path.join(__dirname, '..', '..', '..', '..', 'package.json'),
path.join(__dirname, '..', '..', '..', '..', '..', 'package.json')
]
for (const checkPath of checkPaths) {
if (fs.existsSync(checkPath)) {
return checkPath
}
}
return ''
}
const packagejsonPath = getPackageJsonPath()
if (!packagejsonPath) return ''
try {
const content = await fs.promises.readFile(packagejsonPath, 'utf8')
const parsedContent = JSON.parse(content)
return parsedContent.version
} catch (error) {
return ''
}
}
+50
View File
@@ -0,0 +1,50 @@
import { v4 as uuidv4 } from 'uuid'
import { PostHog } from 'posthog-node'
import path from 'path'
import fs from 'fs'
import { getUserHome, getUserSettingsFilePath } from '.'
export class Telemetry {
postHog?: PostHog
constructor() {
if (process.env.DISABLE_FLOWISE_TELEMETRY !== 'true') {
this.postHog = new PostHog('phc_jEDuFYnOnuXsws986TLWzuisbRjwFqTl9JL8tDMgqme')
} else {
this.postHog = undefined
}
}
async id(): Promise<string> {
try {
const settingsContent = await fs.promises.readFile(getUserSettingsFilePath(), 'utf8')
const settings = JSON.parse(settingsContent)
return settings.instanceId
} catch (error) {
const instanceId = uuidv4()
const settings = {
instanceId
}
const defaultLocation = path.join(getUserHome(), '.flowise', 'settings.json')
await fs.promises.writeFile(defaultLocation, JSON.stringify(settings, null, 2))
return instanceId
}
}
async sendTelemetry(event: string, properties = {}): Promise<void> {
if (this.postHog) {
const distinctId = await this.id()
this.postHog.capture({
event,
distinctId,
properties
})
}
}
async flush(): Promise<void> {
if (this.postHog) {
await this.postHog.shutdownAsync()
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "flowise-ui", "name": "flowise-ui",
"version": "1.4.6", "version": "1.4.7",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://flowiseai.com", "homepage": "https://flowiseai.com",
"author": { "author": {
@@ -0,0 +1,21 @@
// material-ui
import { styled } from '@mui/material/styles'
// project imports
import MainCard from './MainCard'
const NodeCardWrapper = styled(MainCard)(({ theme }) => ({
background: theme.palette.card.main,
color: theme.darkTextPrimary,
border: 'solid 1px',
borderColor: theme.palette.primary[200] + 75,
width: '300px',
height: 'auto',
padding: '10px',
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
'&:hover': {
borderColor: theme.palette.primary.main
}
}))
export default NodeCardWrapper
+62 -24
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { FormControl, OutlinedInput, Popover } from '@mui/material' import { FormControl, OutlinedInput, InputBase, Popover } from '@mui/material'
import SelectVariable from 'ui-component/json/SelectVariable' import SelectVariable from 'ui-component/json/SelectVariable'
import { getAvailableNodesForVariable } from 'utils/genericHelper' import { getAvailableNodesForVariable } from 'utils/genericHelper'
@@ -50,29 +50,67 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disab
return ( return (
<> <>
<FormControl sx={{ mt: 1, width: '100%' }} size='small'> {inputParam.name === 'note' ? (
<OutlinedInput <FormControl sx={{ width: '100%', height: 'auto' }} size='small'>
id={inputParam.name} <InputBase
size='small' id={nodeId}
disabled={disabled} size='small'
type={getInputType(inputParam.type)} disabled={disabled}
placeholder={inputParam.placeholder} type={getInputType(inputParam.type)}
multiline={!!inputParam.rows} placeholder={inputParam.placeholder}
rows={inputParam.rows ?? 1} multiline={!!inputParam.rows}
value={myValue} minRows={inputParam.rows ?? 1}
name={inputParam.name} value={myValue}
onChange={(e) => { name={inputParam.name}
setMyValue(e.target.value) onChange={(e) => {
onChange(e.target.value) setMyValue(e.target.value)
}} onChange(e.target.value)
inputProps={{ }}
step: inputParam.step ?? 1, inputProps={{
style: { step: inputParam.step ?? 1,
height: inputParam.rows ? '90px' : 'inherit' style: {
} border: 'none',
}} background: 'none',
/> color: '#212121'
</FormControl> }
}}
sx={{
border: 'none',
background: 'none',
padding: '10px 14px',
textarea: {
'&::placeholder': {
color: '#616161'
}
}
}}
/>
</FormControl>
) : (
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
<OutlinedInput
id={inputParam.name}
size='small'
disabled={disabled}
type={getInputType(inputParam.type)}
placeholder={inputParam.placeholder}
multiline={!!inputParam.rows}
rows={inputParam.rows ?? 1}
value={myValue}
name={inputParam.name}
onChange={(e) => {
setMyValue(e.target.value)
onChange(e.target.value)
}}
inputProps={{
step: inputParam.step ?? 1,
style: {
height: inputParam.rows ? '90px' : 'inherit'
}
}}
/>
</FormControl>
)}
<div ref={ref}></div> <div ref={ref}></div>
{inputParam?.acceptVariable && ( {inputParam?.acceptVariable && (
<Popover <Popover
@@ -0,0 +1,12 @@
import { styled } from '@mui/material/styles'
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'
const NodeTooltip = styled(({ className, ...props }) => <Tooltip {...props} classes={{ popper: className }} />)(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.nodeToolTip.background,
color: theme.palette.nodeToolTip.color,
boxShadow: theme.shadows[1]
}
}))
export default NodeTooltip
+8 -30
View File
@@ -3,12 +3,13 @@ import { useContext, useState, useEffect } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
// material-ui // material-ui
import { styled, useTheme } from '@mui/material/styles' import { useTheme } from '@mui/material/styles'
import { IconButton, Box, Typography, Divider, Button } from '@mui/material' import { IconButton, Box, Typography, Divider, Button } from '@mui/material'
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
// project imports // project imports
import MainCard from 'ui-component/cards/MainCard' import NodeCardWrapper from '../../ui-component/cards/NodeCardWrapper'
import NodeTooltip from '../../ui-component/tooltip/NodeTooltip'
import NodeInputHandler from './NodeInputHandler' import NodeInputHandler from './NodeInputHandler'
import NodeOutputHandler from './NodeOutputHandler' import NodeOutputHandler from './NodeOutputHandler'
import AdditionalParamsDialog from 'ui-component/dialog/AdditionalParamsDialog' import AdditionalParamsDialog from 'ui-component/dialog/AdditionalParamsDialog'
@@ -19,28 +20,6 @@ import { baseURL } from 'store/constant'
import { IconTrash, IconCopy, IconInfoCircle, IconAlertTriangle } from '@tabler/icons' import { IconTrash, IconCopy, IconInfoCircle, IconAlertTriangle } from '@tabler/icons'
import { flowContext } from 'store/context/ReactFlowContext' import { flowContext } from 'store/context/ReactFlowContext'
const CardWrapper = styled(MainCard)(({ theme }) => ({
background: theme.palette.card.main,
color: theme.darkTextPrimary,
border: 'solid 1px',
borderColor: theme.palette.primary[200] + 75,
width: '300px',
height: 'auto',
padding: '10px',
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
'&:hover': {
borderColor: theme.palette.primary.main
}
}))
const LightTooltip = styled(({ className, ...props }) => <Tooltip {...props} classes={{ popper: className }} />)(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.nodeToolTip.background,
color: theme.palette.nodeToolTip.color,
boxShadow: theme.shadows[1]
}
}))
// ===========================|| CANVAS NODE ||=========================== // // ===========================|| CANVAS NODE ||=========================== //
const CanvasNode = ({ data }) => { const CanvasNode = ({ data }) => {
@@ -93,7 +72,7 @@ const CanvasNode = ({ data }) => {
return ( return (
<> <>
<CardWrapper <NodeCardWrapper
content={false} content={false}
sx={{ sx={{
padding: 0, padding: 0,
@@ -101,7 +80,7 @@ const CanvasNode = ({ data }) => {
}} }}
border={false} border={false}
> >
<LightTooltip <NodeTooltip
open={!canvas.canvasDialogShow && open} open={!canvas.canvasDialogShow && open}
onClose={handleClose} onClose={handleClose}
onOpen={handleOpen} onOpen={handleOpen}
@@ -242,13 +221,12 @@ const CanvasNode = ({ data }) => {
</Typography> </Typography>
</Box> </Box>
<Divider /> <Divider />
{data.outputAnchors.map((outputAnchor, index) => ( {data.outputAnchors.map((outputAnchor, index) => (
<NodeOutputHandler key={index} outputAnchor={outputAnchor} data={data} /> <NodeOutputHandler key={index} outputAnchor={outputAnchor} data={data} />
))} ))}
</Box> </Box>
</LightTooltip> </NodeTooltip>
</CardWrapper> </NodeCardWrapper>
<AdditionalParamsDialog <AdditionalParamsDialog
show={showDialog} show={showDialog}
dialogProps={dialogProps} dialogProps={dialogProps}

Some files were not shown because too many files have changed in this diff Show More