diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI_LlamaIndex.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI_LlamaIndex.ts index 58b40823e..8b3567a6c 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI_LlamaIndex.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI_LlamaIndex.ts @@ -41,6 +41,14 @@ class ChatOpenAI_LlamaIndex_LLMs implements INode { label: 'gpt-4', name: 'gpt-4' }, + { + label: 'gpt-4-turbo-preview', + name: 'gpt-4-turbo-preview' + }, + { + label: 'gpt-4-0125-preview', + name: 'gpt-4-0125-preview' + }, { label: 'gpt-4-1106-preview', name: 'gpt-4-1106-preview' diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding_LlamaIndex.ts b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding_LlamaIndex.ts index dfd6bbf52..960197fe2 100644 --- a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding_LlamaIndex.ts +++ b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding_LlamaIndex.ts @@ -32,6 +32,27 @@ class OpenAIEmbedding_LlamaIndex_Embeddings implements INode { credentialNames: ['openAIApi'] } this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'text-embedding-3-large', + name: 'text-embedding-3-large' + }, + { + label: 'text-embedding-3-small', + name: 'text-embedding-3-small' + }, + { + label: 'text-embedding-ada-002', + name: 'text-embedding-ada-002' + } + ], + default: 'text-embedding-ada-002', + optional: true + }, { label: 'Timeout', name: 'timeout', @@ -51,12 +72,14 @@ class OpenAIEmbedding_LlamaIndex_Embeddings implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const timeout = nodeData.inputs?.timeout as string + const modelName = nodeData.inputs?.modelName as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) const obj: Partial = { - apiKey: openAIApiKey + apiKey: openAIApiKey, + model: modelName } if (timeout) obj.timeout = parseInt(timeout, 10) diff --git a/packages/components/nodes/engine/SubQuestionQueryEngine/SubQuestionQueryEngine.ts b/packages/components/nodes/engine/SubQuestionQueryEngine/SubQuestionQueryEngine.ts new file mode 100644 index 000000000..a872c0a23 --- /dev/null +++ b/packages/components/nodes/engine/SubQuestionQueryEngine/SubQuestionQueryEngine.ts @@ -0,0 +1,193 @@ +import { flatten } from 'lodash' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { + TreeSummarize, + SimpleResponseBuilder, + Refine, + BaseEmbedding, + ResponseSynthesizer, + CompactAndRefine, + QueryEngineTool, + LLMQuestionGenerator, + SubQuestionQueryEngine, + BaseNode, + Metadata, + serviceContextFromDefaults +} from 'llamaindex' +import { reformatSourceDocuments } from '../EngineUtils' + +class SubQuestionQueryEngine_LlamaIndex implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + tags: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + sessionId?: string + + constructor(fields?: { sessionId?: string }) { + this.label = 'Sub Question Query Engine' + this.name = 'subQuestionQueryEngine' + this.version = 1.0 + this.type = 'SubQuestionQueryEngine' + this.icon = 'subQueryEngine.svg' + this.category = 'Engine' + this.description = + 'Breaks complex query into sub questions for each relevant data source, then gather all the intermediate reponses and synthesizes a final response' + this.baseClasses = [this.type] + this.tags = ['LlamaIndex'] + this.inputs = [ + { + label: 'QueryEngine Tools', + name: 'queryEngineTools', + type: 'QueryEngineTool', + list: true + }, + { + label: 'Chat Model', + name: 'model', + type: 'BaseChatModel_LlamaIndex' + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'BaseEmbedding_LlamaIndex' + }, + { + label: 'Response Synthesizer', + name: 'responseSynthesizer', + type: 'ResponseSynthesizer', + description: + 'ResponseSynthesizer is responsible for sending the query, nodes, and prompt templates to the LLM to generate a response. See more', + optional: true + }, + { + label: 'Return Source Documents', + name: 'returnSourceDocuments', + type: 'boolean', + optional: true + } + ] + this.sessionId = fields?.sessionId + } + + async init(): Promise { + return null + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const embeddings = nodeData.inputs?.embeddings as BaseEmbedding + const model = nodeData.inputs?.model + + const serviceContext = serviceContextFromDefaults({ + llm: model, + embedModel: embeddings + }) + + let queryEngineTools = nodeData.inputs?.queryEngineTools as QueryEngineTool[] + queryEngineTools = flatten(queryEngineTools) + + let queryEngine = SubQuestionQueryEngine.fromDefaults({ + serviceContext, + queryEngineTools, + questionGen: new LLMQuestionGenerator({ llm: model }) + }) + + const responseSynthesizerObj = nodeData.inputs?.responseSynthesizer + if (responseSynthesizerObj) { + if (responseSynthesizerObj.type === 'TreeSummarize') { + const responseSynthesizer = new ResponseSynthesizer({ + responseBuilder: new TreeSummarize(serviceContext, responseSynthesizerObj.textQAPromptTemplate), + serviceContext + }) + queryEngine = SubQuestionQueryEngine.fromDefaults({ + responseSynthesizer, + serviceContext, + queryEngineTools, + questionGen: new LLMQuestionGenerator({ llm: model }) + }) + } else if (responseSynthesizerObj.type === 'CompactAndRefine') { + const responseSynthesizer = new ResponseSynthesizer({ + responseBuilder: new CompactAndRefine( + serviceContext, + responseSynthesizerObj.textQAPromptTemplate, + responseSynthesizerObj.refinePromptTemplate + ), + serviceContext + }) + queryEngine = SubQuestionQueryEngine.fromDefaults({ + responseSynthesizer, + serviceContext, + queryEngineTools, + questionGen: new LLMQuestionGenerator({ llm: model }) + }) + } else if (responseSynthesizerObj.type === 'Refine') { + const responseSynthesizer = new ResponseSynthesizer({ + responseBuilder: new Refine( + serviceContext, + responseSynthesizerObj.textQAPromptTemplate, + responseSynthesizerObj.refinePromptTemplate + ), + serviceContext + }) + queryEngine = SubQuestionQueryEngine.fromDefaults({ + responseSynthesizer, + serviceContext, + queryEngineTools, + questionGen: new LLMQuestionGenerator({ llm: model }) + }) + } else if (responseSynthesizerObj.type === 'SimpleResponseBuilder') { + const responseSynthesizer = new ResponseSynthesizer({ + responseBuilder: new SimpleResponseBuilder(serviceContext), + serviceContext + }) + queryEngine = SubQuestionQueryEngine.fromDefaults({ + responseSynthesizer, + serviceContext, + queryEngineTools, + questionGen: new LLMQuestionGenerator({ llm: model }) + }) + } + } + + let text = '' + let sourceDocuments: ICommonObject[] = [] + let sourceNodes: BaseNode[] = [] + let isStreamingStarted = false + const isStreamingEnabled = options.socketIO && options.socketIOClientId + + if (isStreamingEnabled) { + const stream = await queryEngine.query({ query: input, stream: true }) + for await (const chunk of stream) { + text += chunk.response + if (chunk.sourceNodes) sourceNodes = chunk.sourceNodes + if (!isStreamingStarted) { + isStreamingStarted = true + options.socketIO.to(options.socketIOClientId).emit('start', chunk.response) + } + + options.socketIO.to(options.socketIOClientId).emit('token', chunk.response) + } + + if (returnSourceDocuments) { + sourceDocuments = reformatSourceDocuments(sourceNodes) + options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', sourceDocuments) + } + } else { + const response = await queryEngine.query({ query: input }) + text = response?.response + sourceDocuments = reformatSourceDocuments(response?.sourceNodes ?? []) + } + + if (returnSourceDocuments) return { text, sourceDocuments } + else return { text } + } +} + +module.exports = { nodeClass: SubQuestionQueryEngine_LlamaIndex } diff --git a/packages/components/nodes/engine/SubQuestionQueryEngine/subQueryEngine.svg b/packages/components/nodes/engine/SubQuestionQueryEngine/subQueryEngine.svg new file mode 100644 index 000000000..b94c20b5e --- /dev/null +++ b/packages/components/nodes/engine/SubQuestionQueryEngine/subQueryEngine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/tools/QueryEngineTool/QueryEngineTool.ts b/packages/components/nodes/tools/QueryEngineTool/QueryEngineTool.ts new file mode 100644 index 000000000..163eff766 --- /dev/null +++ b/packages/components/nodes/tools/QueryEngineTool/QueryEngineTool.ts @@ -0,0 +1,68 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { VectorStoreIndex } from 'llamaindex' + +class QueryEngine_Tools implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + tags: string[] + baseClasses: string[] + inputs?: INodeParams[] + + constructor() { + this.label = 'QueryEngine Tool' + this.name = 'queryEngineToolLlamaIndex' + this.version = 1.0 + this.type = 'QueryEngineTool' + this.icon = 'queryEngineTool.svg' + this.category = 'Tools' + this.tags = ['LlamaIndex'] + this.description = 'Tool used to invoke query engine' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Vector Store Index', + name: 'vectorStoreIndex', + type: 'VectorStoreIndex' + }, + { + label: 'Tool Name', + name: 'toolName', + type: 'string', + description: 'Tool name must be small capital letter with underscore. Ex: my_tool' + }, + { + label: 'Tool Description', + name: 'toolDesc', + type: 'string', + rows: 4 + } + ] + } + + async init(nodeData: INodeData): Promise { + const vectorStoreIndex = nodeData.inputs?.vectorStoreIndex as VectorStoreIndex + const toolName = nodeData.inputs?.toolName as string + const toolDesc = nodeData.inputs?.toolDesc as string + const queryEngineTool = { + queryEngine: vectorStoreIndex.asQueryEngine({ + preFilters: { + ...(vectorStoreIndex as any).metadatafilter + } + }), + metadata: { + name: toolName, + description: toolDesc + }, + vectorStoreIndex + } + + return queryEngineTool + } +} + +module.exports = { nodeClass: QueryEngine_Tools } diff --git a/packages/components/nodes/tools/QueryEngineTool/queryEngineTool.svg b/packages/components/nodes/tools/QueryEngineTool/queryEngineTool.svg new file mode 100644 index 000000000..d49d8375c --- /dev/null +++ b/packages/components/nodes/tools/QueryEngineTool/queryEngineTool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone_LlamaIndex.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone_LlamaIndex.ts index a584fedeb..c0b2e5c11 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone_LlamaIndex.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone_LlamaIndex.ts @@ -13,7 +13,7 @@ import { import { FetchResponse, Index, Pinecone, ScoredPineconeRecord } from '@pinecone-database/pinecone' import { flatten } from 'lodash' import { Document as LCDocument } from 'langchain/document' -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { flattenObject, getCredentialData, getCredentialParam } from '../../../src/utils' class PineconeLlamaIndex_VectorStores implements INode { @@ -28,6 +28,7 @@ class PineconeLlamaIndex_VectorStores implements INode { baseClasses: string[] inputs: INodeParams[] credential: INodeParams + outputs: INodeOutputsValue[] constructor() { this.label = 'Pinecone' @@ -93,6 +94,18 @@ class PineconeLlamaIndex_VectorStores implements INode { optional: true } ] + this.outputs = [ + { + label: 'Pinecone Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Pinecone Vector Store Index', + name: 'vectorStore', + baseClasses: [this.type, 'VectorStoreIndex'] + } + ] } //@ts-ignore @@ -155,8 +168,10 @@ class PineconeLlamaIndex_VectorStores implements INode { } if (pineconeNamespace) obj.namespace = pineconeNamespace + + let metadatafilter = {} if (pineconeMetadataFilter) { - const metadatafilter = typeof pineconeMetadataFilter === 'object' ? pineconeMetadataFilter : JSON.parse(pineconeMetadataFilter) + metadatafilter = typeof pineconeMetadataFilter === 'object' ? pineconeMetadataFilter : JSON.parse(pineconeMetadataFilter) obj.queryFilter = metadatafilter } @@ -171,11 +186,21 @@ class PineconeLlamaIndex_VectorStores implements INode { serviceContext }) - const retriever = index.asRetriever() - retriever.similarityTopK = k - ;(retriever as any).serviceContext = serviceContext + const output = nodeData.outputs?.output as string - return retriever + if (output === 'retriever') { + const retriever = index.asRetriever() + retriever.similarityTopK = k + ;(retriever as any).serviceContext = serviceContext + return retriever + } else if (output === 'vectorStore') { + ;(index as any).k = k + if (metadatafilter) { + ;(index as any).metadatafilter = metadatafilter + } + return index + } + return index } } diff --git a/packages/components/nodes/vectorstores/SimpleStore/SimpleStore.ts b/packages/components/nodes/vectorstores/SimpleStore/SimpleStore.ts index eeef6f693..36c383e98 100644 --- a/packages/components/nodes/vectorstores/SimpleStore/SimpleStore.ts +++ b/packages/components/nodes/vectorstores/SimpleStore/SimpleStore.ts @@ -63,6 +63,18 @@ class SimpleStoreUpsert_LlamaIndex_VectorStores implements INode { optional: true } ] + this.outputs = [ + { + label: 'SimpleStore Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'SimpleStore Vector Store Index', + name: 'vectorStore', + baseClasses: [this.type, 'VectorStoreIndex'] + } + ] } //@ts-ignore @@ -114,10 +126,19 @@ class SimpleStoreUpsert_LlamaIndex_VectorStores implements INode { const storageContext = await storageContextFromDefaults({ persistDir: filePath }) const index = await VectorStoreIndex.init({ storageContext, serviceContext }) - const retriever = index.asRetriever() - retriever.similarityTopK = k - return retriever + const output = nodeData.outputs?.output as string + + if (output === 'retriever') { + const retriever = index.asRetriever() + retriever.similarityTopK = k + ;(retriever as any).serviceContext = serviceContext + return retriever + } else if (output === 'vectorStore') { + ;(index as any).k = k + return index + } + return index } } diff --git a/packages/server/marketplaces/chatflows/Context Chat Engine.json b/packages/server/marketplaces/chatflows/Context Chat Engine.json index 7608a550c..475c6b3a5 100644 --- a/packages/server/marketplaces/chatflows/Context Chat Engine.json +++ b/packages/server/marketplaces/chatflows/Context Chat Engine.json @@ -181,6 +181,28 @@ "credentialNames": ["openAIApi"], "id": "openAIEmbedding_LlamaIndex_0-input-credential-credential" }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-embedding-3-large", + "name": "text-embedding-3-large" + }, + { + "label": "text-embedding-3-small", + "name": "text-embedding-3-small" + }, + { + "label": "text-embedding-ada-002", + "name": "text-embedding-ada-002" + } + ], + "default": "text-embedding-ada-002", + "optional": true, + "id": "openAIEmbedding_LlamaIndex_0-input-modelName-options" + }, { "label": "Timeout", "name": "timeout", @@ -315,13 +337,29 @@ }, "outputAnchors": [ { - "id": "pineconeLlamaIndex_0-output-pineconeLlamaIndex-Pinecone|VectorIndexRetriever", - "name": "pineconeLlamaIndex", - "label": "Pinecone", - "type": "Pinecone | VectorIndexRetriever" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeLlamaIndex_0-output-retriever-Pinecone|VectorIndexRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | VectorIndexRetriever" + }, + { + "id": "pineconeLlamaIndex_0-output-retriever-Pinecone|VectorStoreIndex", + "name": "vectorStore", + "label": "Pinecone Vector Store Index", + "type": "Pinecone | VectorStoreIndex" + } + ], + "default": "retriever" } ], - "outputs": {}, + "outputs": { + "output": "retriever" + }, "selected": false }, "selected": false, @@ -367,6 +405,14 @@ "label": "gpt-4", "name": "gpt-4" }, + { + "label": "gpt-4-turbo-preview", + "name": "gpt-4-turbo-preview" + }, + { + "label": "gpt-4-0125-preview", + "name": "gpt-4-0125-preview" + }, { "label": "gpt-4-1106-preview", "name": "gpt-4-1106-preview" @@ -672,6 +718,14 @@ "label": "gpt-4", "name": "gpt-4" }, + { + "label": "gpt-4-turbo-preview", + "name": "gpt-4-turbo-preview" + }, + { + "label": "gpt-4-0125-preview", + "name": "gpt-4-0125-preview" + }, { "label": "gpt-4-1106-preview", "name": "gpt-4-1106-preview" diff --git a/packages/server/marketplaces/chatflows/Query Engine.json b/packages/server/marketplaces/chatflows/Query Engine.json index 625097cc4..825533339 100644 --- a/packages/server/marketplaces/chatflows/Query Engine.json +++ b/packages/server/marketplaces/chatflows/Query Engine.json @@ -163,13 +163,29 @@ }, "outputAnchors": [ { - "id": "pineconeLlamaIndex_0-output-pineconeLlamaIndex-Pinecone|VectorIndexRetriever", - "name": "pineconeLlamaIndex", - "label": "Pinecone", - "type": "Pinecone | VectorIndexRetriever" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeLlamaIndex_0-output-retriever-Pinecone|VectorIndexRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | VectorIndexRetriever" + }, + { + "id": "pineconeLlamaIndex_0-output-retriever-Pinecone|VectorStoreIndex", + "name": "vectorStore", + "label": "Pinecone Vector Store Index", + "type": "Pinecone | VectorStoreIndex" + } + ], + "default": "retriever" } ], - "outputs": {}, + "outputs": { + "output": "retriever" + }, "selected": false }, "selected": false, @@ -206,6 +222,28 @@ "credentialNames": ["openAIApi"], "id": "openAIEmbedding_LlamaIndex_0-input-credential-credential" }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "text-embedding-3-large", + "name": "text-embedding-3-large" + }, + { + "label": "text-embedding-3-small", + "name": "text-embedding-3-small" + }, + { + "label": "text-embedding-ada-002", + "name": "text-embedding-ada-002" + } + ], + "default": "text-embedding-ada-002", + "optional": true, + "id": "openAIEmbedding_LlamaIndex_0-input-modelName-options" + }, { "label": "Timeout", "name": "timeout", diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index d00633432..9a14429da 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -844,7 +844,7 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod const whitelistAgents = ['openAIFunctionAgent', 'csvAgent', 'airtableAgent', 'conversationalRetrievalAgent'] isValidChainOrAgent = whitelistAgents.includes(endingNodeData.name) } else if (endingNodeData.category === 'Engine') { - const whitelistEngine = ['contextChatEngine', 'simpleChatEngine', 'queryEngine'] + const whitelistEngine = ['contextChatEngine', 'simpleChatEngine', 'queryEngine', 'subQuestionQueryEngine'] isValidChainOrAgent = whitelistEngine.includes(endingNodeData.name) }