From 288e451161fa206265d5830cb583f0b18f13307c Mon Sep 17 00:00:00 2001 From: Kenny Vaneetvelde Date: Sat, 3 Feb 2024 19:43:44 +0100 Subject: [PATCH] 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 fc28fd1c2..849d825d6 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 bcb746b04..2efb2143c 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 923364432..93bb96bb9 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