From 288e451161fa206265d5830cb583f0b18f13307c Mon Sep 17 00:00:00 2001 From: Kenny Vaneetvelde Date: Sat, 3 Feb 2024 19:43:44 +0100 Subject: [PATCH 1/5] Improve flexibility of the structured output parser by allowing the user to input an example JSON and automatically converting it to a zod scheme --- .../StructuredOutputParser.ts | 48 +++----- packages/components/package.json | 1 + .../chatflows/Structured Output Parser.json | 114 +++++++----------- 3 files changed, 64 insertions(+), 99 deletions(-) diff --git a/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts b/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts index fc28fd1c..849d825d 100644 --- a/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts +++ b/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts @@ -1,8 +1,9 @@ -import { convertSchemaToZod, getBaseClasses, INode, INodeData, INodeParams } from '../../../src' +import { getBaseClasses, INode, INodeData, INodeParams } from '../../../src' import { BaseOutputParser } from 'langchain/schema/output_parser' import { StructuredOutputParser as LangchainStructuredOutputParser } from 'langchain/output_parsers' import { CATEGORY } from '../OutputParserHelpers' import { z } from 'zod' +import { jsonToZod } from 'json-to-zod' class StructuredOutputParser implements INode { label: string @@ -34,44 +35,31 @@ class StructuredOutputParser implements INode { description: 'In the event that the first call fails, will make another call to the model to fix any errors.' }, { - label: 'JSON Structure', - name: 'jsonStructure', - type: 'datagrid', - description: 'JSON structure for LLM to return', - datagrid: [ - { field: 'property', headerName: 'Property', editable: true }, - { - field: 'type', - headerName: 'Type', - type: 'singleSelect', - valueOptions: ['string', 'number', 'boolean'], - editable: true - }, - { field: 'description', headerName: 'Description', editable: true, flex: 1 } - ], - default: [ - { - property: 'answer', - type: 'string', - description: `answer to the user's question` - }, - { - property: 'source', - type: 'string', - description: `sources used to answer the question, should be websites` - } - ], + label: 'Example JSON', + name: 'exampleJson', + type: 'string', + description: 'Example JSON structure for LLM to return', + rows: 10, + default: '{"answer": "the answer", "followupQuestions": ["question1", "question2"]}', additionalParams: true } ] } async init(nodeData: INodeData): Promise { - const jsonStructure = nodeData.inputs?.jsonStructure as string + const exampleJson = nodeData.inputs?.exampleJson as string const autoFix = nodeData.inputs?.autofixParser as boolean + const jsonToZodString = jsonToZod(JSON.parse(exampleJson)) + const splitString = jsonToZodString.split('const schema = ') + const schemaString = splitString[1].trim() + + const fnString = `function proxyFn(z){ return ${schemaString} }` + const zodSchemaFunction = new Function('z', `return ${schemaString}`) + const zodSchema = zodSchemaFunction(z) + try { - const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(z.object(convertSchemaToZod(jsonStructure))) + const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(zodSchema) // NOTE: When we change Flowise to return a json response, the following has to be changed to: JsonStructuredOutputParser Object.defineProperty(structuredOutputParser, 'autoFix', { diff --git a/packages/components/package.json b/packages/components/package.json index bcb746b0..2efb2143 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -56,6 +56,7 @@ "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", + "json-to-zod": "1.1.2", "langchain": "^0.0.214", "langfuse": "2.0.2", "langfuse-langchain": "2.3.3", diff --git a/packages/server/marketplaces/chatflows/Structured Output Parser.json b/packages/server/marketplaces/chatflows/Structured Output Parser.json index 92336443..93bb96bb 100644 --- a/packages/server/marketplaces/chatflows/Structured Output Parser.json +++ b/packages/server/marketplaces/chatflows/Structured Output Parser.json @@ -4,7 +4,7 @@ "nodes": [ { "width": 300, - "height": 574, + "height": 576, "id": "chatOpenAI_0", "position": { "x": 845.3961479115309, @@ -17,7 +17,12 @@ "version": 3, "name": "chatOpenAI", "type": "ChatOpenAI", - "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "baseClasses": [ + "ChatOpenAI", + "BaseChatModel", + "BaseLanguageModel", + "Runnable" + ], "category": "Chat Models", "description": "Wrapper around OpenAI large language models that use the Chat endpoint", "inputParams": [ @@ -25,7 +30,9 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": ["openAIApi"], + "credentialNames": [ + "openAIApi" + ], "id": "chatOpenAI_0-input-credential-credential" }, { @@ -202,7 +209,7 @@ }, { "width": 300, - "height": 456, + "height": 508, "id": "llmChain_0", "position": { "x": 1229.1699649849293, @@ -215,7 +222,11 @@ "version": 3, "name": "llmChain", "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "baseClasses": [ + "LLMChain", + "BaseChain", + "Runnable" + ], "category": "Chains", "description": "Chain to run queries against LLMs", "inputParams": [ @@ -300,7 +311,7 @@ }, { "width": 300, - "height": 652, + "height": 690, "id": "chatPromptTemplate_0", "position": { "x": 501.1597501123828, @@ -313,7 +324,12 @@ "version": 1, "name": "chatPromptTemplate", "type": "ChatPromptTemplate", - "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"], + "baseClasses": [ + "ChatPromptTemplate", + "BaseChatPromptTemplate", + "BasePromptTemplate", + "Runnable" + ], "category": "Prompts", "description": "Schema to represent a chat prompt", "inputParams": [ @@ -369,11 +385,11 @@ }, { "width": 300, - "height": 328, + "height": 329, "id": "structuredOutputParser_0", "position": { - "x": 170.3869571939727, - "y": 343.9298288967859 + "x": 498.2326128526694, + "y": 566.5473204649535 }, "type": "customNode", "data": { @@ -382,7 +398,11 @@ "version": 1, "name": "structuredOutputParser", "type": "StructuredOutputParser", - "baseClasses": ["StructuredOutputParser", "BaseLLMOutputParser", "Runnable"], + "baseClasses": [ + "StructuredOutputParser", + "BaseLLMOutputParser", + "Runnable" + ], "category": "Output Parsers", "description": "Parse the output of an LLM call into a given (JSON) structure.", "inputParams": [ @@ -395,61 +415,20 @@ "id": "structuredOutputParser_0-input-autofixParser-boolean" }, { - "label": "JSON Structure", - "name": "jsonStructure", - "type": "datagrid", - "description": "JSON structure for LLM to return", - "datagrid": [ - { - "field": "property", - "headerName": "Property", - "editable": true - }, - { - "field": "type", - "headerName": "Type", - "type": "singleSelect", - "valueOptions": ["string", "number", "boolean"], - "editable": true - }, - { - "field": "description", - "headerName": "Description", - "editable": true, - "flex": 1 - } - ], - "default": [ - { - "property": "answer", - "type": "string", - "description": "answer to the user's question" - }, - { - "property": "source", - "type": "string", - "description": "sources used to answer the question, should be websites" - } - ], + "label": "Example JSON", + "name": "exampleJson", + "type": "string", + "description": "Example JSON structure for LLM to return", + "rows": 10, + "default": "{\"answer\": \"the answer\", \"followupQuestions\": [\"question1\", \"question2\"]}", "additionalParams": true, - "id": "structuredOutputParser_0-input-jsonStructure-datagrid" + "id": "structuredOutputParser_0-input-exampleJson-string" } ], "inputAnchors": [], "inputs": { "autofixParser": true, - "jsonStructure": [ - { - "property": "answer", - "type": "string", - "description": "answer to the user's question" - }, - { - "property": "source", - "type": "string", - "description": "sources used to answer the question, should be websites" - } - ] + "exampleJson": "{\"answer\": \"the answer\", \"followupQuestions\": [\"question1\", \"question2\"]}" }, "outputAnchors": [ { @@ -463,11 +442,11 @@ "selected": false }, "selected": false, + "dragging": false, "positionAbsolute": { - "x": 170.3869571939727, - "y": 343.9298288967859 - }, - "dragging": false + "x": 498.2326128526694, + "y": 566.5473204649535 + } } ], "edges": [ @@ -499,10 +478,7 @@ "target": "llmChain_0", "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", "type": "buttonedge", - "id": "structuredOutputParser_0-structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser", - "data": { - "label": "" - } + "id": "structuredOutputParser_0-structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser" } ] -} +} \ No newline at end of file From 95b251f02acfdb6e55f2d9f511732b262e0c0370 Mon Sep 17 00:00:00 2001 From: Kenny Vaneetvelde Date: Sat, 3 Feb 2024 21:22:49 +0100 Subject: [PATCH 2/5] Make it a separate node instead --- .../StructuredOutputParserAdvanced.ts | 80 +++ .../structure.svg | 8 + packages/components/package.json | 1 - .../Advanced Structured Output Parser.json | 463 ++++++++++++++++++ 4 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts create mode 100644 packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/structure.svg create mode 100644 packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json diff --git a/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts b/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts new file mode 100644 index 00000000..b0fad136 --- /dev/null +++ b/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts @@ -0,0 +1,80 @@ +import { getBaseClasses, INode, INodeData, INodeParams } from '../../../src' +import { BaseOutputParser } from 'langchain/schema/output_parser' +import { StructuredOutputParser as LangchainStructuredOutputParser } from 'langchain/output_parsers' +import { CATEGORY } from '../OutputParserHelpers' +import { z } from 'zod' + +class AdvancedStructuredOutputParser implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Advanced Structured Output Parser' + this.name = 'advancedStructuredOutputParser' + this.version = 1.0 + this.type = 'AdvancedStructuredOutputParser' + this.description = 'Parse the output of an LLM call into a given structure by providing a Zod schema.' + this.icon = 'structure.svg' + this.category = CATEGORY + this.baseClasses = [this.type, ...getBaseClasses(BaseOutputParser)] + this.inputs = [ + { + label: 'Autofix', + name: 'autofixParser', + type: 'boolean', + optional: true, + description: 'In the event that the first call fails, will make another call to the model to fix any errors.' + }, + { + label: 'Example JSON', + name: 'exampleJson', + type: 'string', + description: 'Zod schema for the output of the model', + rows: 10, + default: `z.object({ + title: z.string(), // Title of the movie as a string + yearOfRelease: z.number().int(), // Release year as an integer number, + genres: z.enum([ + "Action", "Comedy", "Drama", "Fantasy", "Horror", + "Mystery", "Romance", "Science Fiction", "Thriller", "Documentary" + ]).array().max(2), // Array of genres, max of 2 from the defined enum + shortDescription: z.string().max(500) // Short description, max 150 characters +})`, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const schemaString = nodeData.inputs?.exampleJson as string + const autoFix = nodeData.inputs?.autofixParser as boolean + + const zodSchemaFunction = new Function('z', `return ${schemaString}`) + const zodSchema = zodSchemaFunction(z) + + try { + const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(zodSchema) + + // NOTE: When we change Flowise to return a json response, the following has to be changed to: JsonStructuredOutputParser + Object.defineProperty(structuredOutputParser, 'autoFix', { + enumerable: true, + configurable: true, + writable: true, + value: autoFix + }) + return structuredOutputParser + } catch (exception) { + throw new Error('Error parsing Zod Schema: ' + exception) + } + } +} + +module.exports = { nodeClass: AdvancedStructuredOutputParser } diff --git a/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/structure.svg b/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/structure.svg new file mode 100644 index 00000000..3875982d --- /dev/null +++ b/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/structure.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/components/package.json b/packages/components/package.json index 2efb2143..bcb746b0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -56,7 +56,6 @@ "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", - "json-to-zod": "1.1.2", "langchain": "^0.0.214", "langfuse": "2.0.2", "langfuse-langchain": "2.3.3", diff --git a/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json b/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json new file mode 100644 index 00000000..41cbb08f --- /dev/null +++ b/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json @@ -0,0 +1,463 @@ +{ + "nodes": [ + { + "width": 300, + "height": 508, + "id": "llmChain_0", + "position": { + "x": 1229.1699649849293, + "y": 245.55173505632646 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_0-input-prompt-BasePromptTemplate" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "llmChain_0-input-inputModeration-Moderation" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", + "outputParser": "{{advancedStructuredOutputParser_0.data.instance}}", + "chainName": "", + "inputModeration": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_0-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "llmChain" + }, + "selected": false + }, + "positionAbsolute": { + "x": 1229.1699649849293, + "y": 245.55173505632646 + }, + "selected": false + }, + { + "width": 300, + "height": 690, + "id": "chatPromptTemplate_0", + "position": { + "x": 493.26582927222483, + "y": -156.20470841335592 + }, + "type": "customNode", + "data": { + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", + "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": "This AI is designed to only output information in JSON format without exception. This AI can only output JSON and will never output any other text.\n\nWhen asked to correct itself, this AI will only output the corrected JSON and never any other text.", + "humanMessagePrompt": "{text}", + "promptValues": "" + }, + "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": 493.26582927222483, + "y": -156.20470841335592 + }, + "dragging": false + }, + { + "width": 300, + "height": 329, + "id": "advancedStructuredOutputParser_0", + "position": { + "x": 494.20163170226266, + "y": 568.3420937517054 + }, + "type": "customNode", + "data": { + "id": "advancedStructuredOutputParser_0", + "label": "Advanced Structured Output Parser", + "version": 1, + "name": "advancedStructuredOutputParser", + "type": "AdvancedStructuredOutputParser", + "baseClasses": ["AdvancedStructuredOutputParser", "BaseLLMOutputParser", "Runnable"], + "category": "Output Parsers", + "description": "Parse the output of an LLM call into a given structure by providing a Zod schema.", + "inputParams": [ + { + "label": "Autofix", + "name": "autofixParser", + "type": "boolean", + "optional": true, + "description": "In the event that the first call fails, will make another call to the model to fix any errors.", + "id": "advancedStructuredOutputParser_0-input-autofixParser-boolean" + }, + { + "label": "Example JSON", + "name": "exampleJson", + "type": "string", + "description": "Zod schema for the output of the model", + "rows": 10, + "default": "z.object({\n title: z.string(), // Title of the movie as a string\n yearOfRelease: z.number().int(), // Release year as an integer number,\n genres: z.enum([\n \"Action\", \"Comedy\", \"Drama\", \"Fantasy\", \"Horror\",\n \"Mystery\", \"Romance\", \"Science Fiction\", \"Thriller\", \"Documentary\"\n ]).array().max(2), // Array of genres, max of 2 from the defined enum\n shortDescription: z.string().max(500) // Short description, max 150 characters\n})", + "additionalParams": true, + "id": "advancedStructuredOutputParser_0-input-exampleJson-string" + } + ], + "inputAnchors": [], + "inputs": { + "autofixParser": true, + "exampleJson": "z.object({\n title: z.string(), // Title of the movie as a string\n yearOfRelease: z.number().int(), // Release year as an integer number,\n genres: z.enum([\n \"Action\", \"Comedy\", \"Drama\", \"Fantasy\", \"Horror\",\n \"Mystery\", \"Romance\", \"Science Fiction\", \"Thriller\", \"Documentary\"\n ]).array().max(2), // Array of genres, max of 2 from the defined enum\n shortDescription: z.string().max(500) // Short description, max 150 characters\n})" + }, + "outputAnchors": [ + { + "id": "advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable", + "name": "advancedStructuredOutputParser", + "label": "AdvancedStructuredOutputParser", + "type": "AdvancedStructuredOutputParser | BaseLLMOutputParser | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 494.20163170226266, + "y": 568.3420937517054 + }, + "dragging": false + }, + { + "width": 300, + "height": 576, + "id": "chatOpenAI_0", + "position": { + "x": 860.555928011636, + "y": -355.71028569475095 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 3, + "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-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" + }, + { + "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-0125", + "name": "gpt-3.5-turbo-0125" + }, + { + "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": "", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "http://localhost:8901/v1", + "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": 860.555928011636, + "y": -355.71028569475095 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "advancedStructuredOutputParser_0", + "sourceHandle": "advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", + "type": "buttonedge", + "id": "advancedStructuredOutputParser_0-advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser" + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel" + } + ] +} From 62f70ab6efe46f6be2d2cddefc2cc12d8d008672 Mon Sep 17 00:00:00 2001 From: Kenny Vaneetvelde Date: Sat, 3 Feb 2024 21:25:26 +0100 Subject: [PATCH 3/5] Revert the changes to the original parser & template --- .../StructuredOutputParser.ts | 48 +++++--- .../chatflows/Structured Output Parser.json | 114 +++++++++++------- 2 files changed, 99 insertions(+), 63 deletions(-) diff --git a/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts b/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts index 849d825d..fc28fd1c 100644 --- a/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts +++ b/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts @@ -1,9 +1,8 @@ -import { getBaseClasses, INode, INodeData, INodeParams } from '../../../src' +import { convertSchemaToZod, getBaseClasses, INode, INodeData, INodeParams } from '../../../src' import { BaseOutputParser } from 'langchain/schema/output_parser' import { StructuredOutputParser as LangchainStructuredOutputParser } from 'langchain/output_parsers' import { CATEGORY } from '../OutputParserHelpers' import { z } from 'zod' -import { jsonToZod } from 'json-to-zod' class StructuredOutputParser implements INode { label: string @@ -35,31 +34,44 @@ class StructuredOutputParser implements INode { description: 'In the event that the first call fails, will make another call to the model to fix any errors.' }, { - label: 'Example JSON', - name: 'exampleJson', - type: 'string', - description: 'Example JSON structure for LLM to return', - rows: 10, - default: '{"answer": "the answer", "followupQuestions": ["question1", "question2"]}', + label: 'JSON Structure', + name: 'jsonStructure', + type: 'datagrid', + description: 'JSON structure for LLM to return', + datagrid: [ + { field: 'property', headerName: 'Property', editable: true }, + { + field: 'type', + headerName: 'Type', + type: 'singleSelect', + valueOptions: ['string', 'number', 'boolean'], + editable: true + }, + { field: 'description', headerName: 'Description', editable: true, flex: 1 } + ], + default: [ + { + property: 'answer', + type: 'string', + description: `answer to the user's question` + }, + { + property: 'source', + type: 'string', + description: `sources used to answer the question, should be websites` + } + ], additionalParams: true } ] } async init(nodeData: INodeData): Promise { - const exampleJson = nodeData.inputs?.exampleJson as string + const jsonStructure = nodeData.inputs?.jsonStructure as string const autoFix = nodeData.inputs?.autofixParser as boolean - const jsonToZodString = jsonToZod(JSON.parse(exampleJson)) - const splitString = jsonToZodString.split('const schema = ') - const schemaString = splitString[1].trim() - - const fnString = `function proxyFn(z){ return ${schemaString} }` - const zodSchemaFunction = new Function('z', `return ${schemaString}`) - const zodSchema = zodSchemaFunction(z) - try { - const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(zodSchema) + const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(z.object(convertSchemaToZod(jsonStructure))) // NOTE: When we change Flowise to return a json response, the following has to be changed to: JsonStructuredOutputParser Object.defineProperty(structuredOutputParser, 'autoFix', { diff --git a/packages/server/marketplaces/chatflows/Structured Output Parser.json b/packages/server/marketplaces/chatflows/Structured Output Parser.json index 93bb96bb..92336443 100644 --- a/packages/server/marketplaces/chatflows/Structured Output Parser.json +++ b/packages/server/marketplaces/chatflows/Structured Output Parser.json @@ -4,7 +4,7 @@ "nodes": [ { "width": 300, - "height": 576, + "height": 574, "id": "chatOpenAI_0", "position": { "x": 845.3961479115309, @@ -17,12 +17,7 @@ "version": 3, "name": "chatOpenAI", "type": "ChatOpenAI", - "baseClasses": [ - "ChatOpenAI", - "BaseChatModel", - "BaseLanguageModel", - "Runnable" - ], + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], "category": "Chat Models", "description": "Wrapper around OpenAI large language models that use the Chat endpoint", "inputParams": [ @@ -30,9 +25,7 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": [ - "openAIApi" - ], + "credentialNames": ["openAIApi"], "id": "chatOpenAI_0-input-credential-credential" }, { @@ -209,7 +202,7 @@ }, { "width": 300, - "height": 508, + "height": 456, "id": "llmChain_0", "position": { "x": 1229.1699649849293, @@ -222,11 +215,7 @@ "version": 3, "name": "llmChain", "type": "LLMChain", - "baseClasses": [ - "LLMChain", - "BaseChain", - "Runnable" - ], + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Chain to run queries against LLMs", "inputParams": [ @@ -311,7 +300,7 @@ }, { "width": 300, - "height": 690, + "height": 652, "id": "chatPromptTemplate_0", "position": { "x": 501.1597501123828, @@ -324,12 +313,7 @@ "version": 1, "name": "chatPromptTemplate", "type": "ChatPromptTemplate", - "baseClasses": [ - "ChatPromptTemplate", - "BaseChatPromptTemplate", - "BasePromptTemplate", - "Runnable" - ], + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"], "category": "Prompts", "description": "Schema to represent a chat prompt", "inputParams": [ @@ -385,11 +369,11 @@ }, { "width": 300, - "height": 329, + "height": 328, "id": "structuredOutputParser_0", "position": { - "x": 498.2326128526694, - "y": 566.5473204649535 + "x": 170.3869571939727, + "y": 343.9298288967859 }, "type": "customNode", "data": { @@ -398,11 +382,7 @@ "version": 1, "name": "structuredOutputParser", "type": "StructuredOutputParser", - "baseClasses": [ - "StructuredOutputParser", - "BaseLLMOutputParser", - "Runnable" - ], + "baseClasses": ["StructuredOutputParser", "BaseLLMOutputParser", "Runnable"], "category": "Output Parsers", "description": "Parse the output of an LLM call into a given (JSON) structure.", "inputParams": [ @@ -415,20 +395,61 @@ "id": "structuredOutputParser_0-input-autofixParser-boolean" }, { - "label": "Example JSON", - "name": "exampleJson", - "type": "string", - "description": "Example JSON structure for LLM to return", - "rows": 10, - "default": "{\"answer\": \"the answer\", \"followupQuestions\": [\"question1\", \"question2\"]}", + "label": "JSON Structure", + "name": "jsonStructure", + "type": "datagrid", + "description": "JSON structure for LLM to return", + "datagrid": [ + { + "field": "property", + "headerName": "Property", + "editable": true + }, + { + "field": "type", + "headerName": "Type", + "type": "singleSelect", + "valueOptions": ["string", "number", "boolean"], + "editable": true + }, + { + "field": "description", + "headerName": "Description", + "editable": true, + "flex": 1 + } + ], + "default": [ + { + "property": "answer", + "type": "string", + "description": "answer to the user's question" + }, + { + "property": "source", + "type": "string", + "description": "sources used to answer the question, should be websites" + } + ], "additionalParams": true, - "id": "structuredOutputParser_0-input-exampleJson-string" + "id": "structuredOutputParser_0-input-jsonStructure-datagrid" } ], "inputAnchors": [], "inputs": { "autofixParser": true, - "exampleJson": "{\"answer\": \"the answer\", \"followupQuestions\": [\"question1\", \"question2\"]}" + "jsonStructure": [ + { + "property": "answer", + "type": "string", + "description": "answer to the user's question" + }, + { + "property": "source", + "type": "string", + "description": "sources used to answer the question, should be websites" + } + ] }, "outputAnchors": [ { @@ -442,11 +463,11 @@ "selected": false }, "selected": false, - "dragging": false, "positionAbsolute": { - "x": 498.2326128526694, - "y": 566.5473204649535 - } + "x": 170.3869571939727, + "y": 343.9298288967859 + }, + "dragging": false } ], "edges": [ @@ -478,7 +499,10 @@ "target": "llmChain_0", "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", "type": "buttonedge", - "id": "structuredOutputParser_0-structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser" + "id": "structuredOutputParser_0-structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser", + "data": { + "label": "" + } } ] -} \ No newline at end of file +} From 113415e2c91a24b9d8a46d66f4106430c8fc8338 Mon Sep 17 00:00:00 2001 From: Kenny Vaneetvelde Date: Sat, 3 Feb 2024 21:32:00 +0100 Subject: [PATCH 4/5] remove basepath value from template --- .../chatflows/Advanced Structured Output Parser.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json b/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json index 41cbb08f..a48f5b2a 100644 --- a/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json +++ b/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json @@ -1,4 +1,6 @@ { + "description": "Return response as a JSON structure as specified by a Zod schema", + "badge": "NEW", "nodes": [ { "width": 300, @@ -409,7 +411,7 @@ "frequencyPenalty": "", "presencePenalty": "", "timeout": "", - "basepath": "http://localhost:8901/v1", + "basepath": "", "baseOptions": "" }, "outputAnchors": [ From 90e6a804e462dc9ea1db6fd598840830b142364b Mon Sep 17 00:00:00 2001 From: Kenny Vaneetvelde Date: Tue, 6 Feb 2024 13:02:30 +0100 Subject: [PATCH 5/5] Make the zod schema a main parameter instead of an additional param --- .../StructuredOutputParserAdvanced.ts | 5 +- .../Advanced Structured Output Parser.json | 137 +++++++++--------- 2 files changed, 70 insertions(+), 72 deletions(-) diff --git a/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts b/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts index b0fad136..e7fe8ea7 100644 --- a/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts +++ b/packages/components/nodes/outputparsers/StructuredOutputParserAdvanced/StructuredOutputParserAdvanced.ts @@ -46,9 +46,8 @@ class AdvancedStructuredOutputParser implements INode { "Action", "Comedy", "Drama", "Fantasy", "Horror", "Mystery", "Romance", "Science Fiction", "Thriller", "Documentary" ]).array().max(2), // Array of genres, max of 2 from the defined enum - shortDescription: z.string().max(500) // Short description, max 150 characters -})`, - additionalParams: true + shortDescription: z.string().max(500) // Short description, max 500 characters +})` } ] } diff --git a/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json b/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json index a48f5b2a..3fd71988 100644 --- a/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json +++ b/packages/server/marketplaces/chatflows/Advanced Structured Output Parser.json @@ -169,67 +169,6 @@ }, "dragging": false }, - { - "width": 300, - "height": 329, - "id": "advancedStructuredOutputParser_0", - "position": { - "x": 494.20163170226266, - "y": 568.3420937517054 - }, - "type": "customNode", - "data": { - "id": "advancedStructuredOutputParser_0", - "label": "Advanced Structured Output Parser", - "version": 1, - "name": "advancedStructuredOutputParser", - "type": "AdvancedStructuredOutputParser", - "baseClasses": ["AdvancedStructuredOutputParser", "BaseLLMOutputParser", "Runnable"], - "category": "Output Parsers", - "description": "Parse the output of an LLM call into a given structure by providing a Zod schema.", - "inputParams": [ - { - "label": "Autofix", - "name": "autofixParser", - "type": "boolean", - "optional": true, - "description": "In the event that the first call fails, will make another call to the model to fix any errors.", - "id": "advancedStructuredOutputParser_0-input-autofixParser-boolean" - }, - { - "label": "Example JSON", - "name": "exampleJson", - "type": "string", - "description": "Zod schema for the output of the model", - "rows": 10, - "default": "z.object({\n title: z.string(), // Title of the movie as a string\n yearOfRelease: z.number().int(), // Release year as an integer number,\n genres: z.enum([\n \"Action\", \"Comedy\", \"Drama\", \"Fantasy\", \"Horror\",\n \"Mystery\", \"Romance\", \"Science Fiction\", \"Thriller\", \"Documentary\"\n ]).array().max(2), // Array of genres, max of 2 from the defined enum\n shortDescription: z.string().max(500) // Short description, max 150 characters\n})", - "additionalParams": true, - "id": "advancedStructuredOutputParser_0-input-exampleJson-string" - } - ], - "inputAnchors": [], - "inputs": { - "autofixParser": true, - "exampleJson": "z.object({\n title: z.string(), // Title of the movie as a string\n yearOfRelease: z.number().int(), // Release year as an integer number,\n genres: z.enum([\n \"Action\", \"Comedy\", \"Drama\", \"Fantasy\", \"Horror\",\n \"Mystery\", \"Romance\", \"Science Fiction\", \"Thriller\", \"Documentary\"\n ]).array().max(2), // Array of genres, max of 2 from the defined enum\n shortDescription: z.string().max(500) // Short description, max 150 characters\n})" - }, - "outputAnchors": [ - { - "id": "advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable", - "name": "advancedStructuredOutputParser", - "label": "AdvancedStructuredOutputParser", - "type": "AdvancedStructuredOutputParser | BaseLLMOutputParser | Runnable" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 494.20163170226266, - "y": 568.3420937517054 - }, - "dragging": false - }, { "width": 300, "height": 576, @@ -431,6 +370,66 @@ "y": -355.71028569475095 }, "dragging": false + }, + { + "width": 300, + "height": 454, + "id": "advancedStructuredOutputParser_0", + "position": { + "x": 489.3637511211284, + "y": 580.0628053662244 + }, + "type": "customNode", + "data": { + "id": "advancedStructuredOutputParser_0", + "label": "Advanced Structured Output Parser", + "version": 1, + "name": "advancedStructuredOutputParser", + "type": "AdvancedStructuredOutputParser", + "baseClasses": ["AdvancedStructuredOutputParser", "BaseLLMOutputParser", "Runnable"], + "category": "Output Parsers", + "description": "Parse the output of an LLM call into a given structure by providing a Zod schema.", + "inputParams": [ + { + "label": "Autofix", + "name": "autofixParser", + "type": "boolean", + "optional": true, + "description": "In the event that the first call fails, will make another call to the model to fix any errors.", + "id": "advancedStructuredOutputParser_0-input-autofixParser-boolean" + }, + { + "label": "Example JSON", + "name": "exampleJson", + "type": "string", + "description": "Zod schema for the output of the model", + "rows": 10, + "default": "z.object({\n title: z.string(), // Title of the movie as a string\n yearOfRelease: z.number().int(), // Release year as an integer number,\n genres: z.enum([\n \"Action\", \"Comedy\", \"Drama\", \"Fantasy\", \"Horror\",\n \"Mystery\", \"Romance\", \"Science Fiction\", \"Thriller\", \"Documentary\"\n ]).array().max(2), // Array of genres, max of 2 from the defined enum\n shortDescription: z.string().max(500) // Short description, max 500 characters\n})", + "id": "advancedStructuredOutputParser_0-input-exampleJson-string" + } + ], + "inputAnchors": [], + "inputs": { + "autofixParser": "", + "exampleJson": "z.object({\n title: z.string(), // Title of the movie as a string\n yearOfRelease: z.number().int(), // Release year as an integer number,\n genres: z.enum([\n \"Action\", \"Comedy\", \"Drama\", \"Fantasy\", \"Horror\",\n \"Mystery\", \"Romance\", \"Science Fiction\", \"Thriller\", \"Documentary\"\n ]).array().max(2), // Array of genres, max of 2 from the defined enum\n shortDescription: z.string().max(500) // Short description, max 500 characters\n})" + }, + "outputAnchors": [ + { + "id": "advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable", + "name": "advancedStructuredOutputParser", + "label": "AdvancedStructuredOutputParser", + "type": "AdvancedStructuredOutputParser | BaseLLMOutputParser | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 489.3637511211284, + "y": 580.0628053662244 + } } ], "edges": [ @@ -445,14 +444,6 @@ "label": "" } }, - { - "source": "advancedStructuredOutputParser_0", - "sourceHandle": "advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable", - "target": "llmChain_0", - "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", - "type": "buttonedge", - "id": "advancedStructuredOutputParser_0-advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser" - }, { "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", @@ -460,6 +451,14 @@ "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel" + }, + { + "source": "advancedStructuredOutputParser_0", + "sourceHandle": "advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", + "type": "buttonedge", + "id": "advancedStructuredOutputParser_0-advancedStructuredOutputParser_0-output-advancedStructuredOutputParser-AdvancedStructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser" } ] }