mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix: Simplify Structured Output Parser wrapping and fix auto-fixing output parser (#8778)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
parent
08e2b068fb
commit
7d82dc1ea8
|
@ -41,6 +41,7 @@ class N8nStructuredOutputParser<T extends z.ZodTypeAny> extends StructuredOutput
|
||||||
|
|
||||||
static fromZedJsonSchema(
|
static fromZedJsonSchema(
|
||||||
schema: JSONSchema7,
|
schema: JSONSchema7,
|
||||||
|
nodeVersion: number,
|
||||||
): StructuredOutputParser<z.ZodType<object, z.ZodTypeDef, object>> {
|
): StructuredOutputParser<z.ZodType<object, z.ZodTypeDef, object>> {
|
||||||
// Make sure to remove the description from root schema
|
// Make sure to remove the description from root schema
|
||||||
const { description, ...restOfSchema } = schema;
|
const { description, ...restOfSchema } = schema;
|
||||||
|
@ -51,30 +52,37 @@ class N8nStructuredOutputParser<T extends z.ZodTypeAny> extends StructuredOutput
|
||||||
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
||||||
const itemSchema = new Function('z', `return (${zodSchemaString})`)(z) as z.ZodSchema<object>;
|
const itemSchema = new Function('z', `return (${zodSchemaString})`)(z) as z.ZodSchema<object>;
|
||||||
|
|
||||||
const returnSchema = z.object({
|
let returnSchema: z.ZodSchema<object>;
|
||||||
[STRUCTURED_OUTPUT_KEY]: z
|
if (nodeVersion === 1) {
|
||||||
.object({
|
returnSchema = z.object({
|
||||||
[STRUCTURED_OUTPUT_OBJECT_KEY]: itemSchema.optional(),
|
[STRUCTURED_OUTPUT_KEY]: z
|
||||||
[STRUCTURED_OUTPUT_ARRAY_KEY]: z.array(itemSchema).optional(),
|
.object({
|
||||||
})
|
[STRUCTURED_OUTPUT_OBJECT_KEY]: itemSchema.optional(),
|
||||||
.describe(
|
[STRUCTURED_OUTPUT_ARRAY_KEY]: z.array(itemSchema).optional(),
|
||||||
`Wrapper around the output data. It can only contain ${STRUCTURED_OUTPUT_OBJECT_KEY} or ${STRUCTURED_OUTPUT_ARRAY_KEY} but never both.`,
|
})
|
||||||
)
|
.describe(
|
||||||
.refine(
|
`Wrapper around the output data. It can only contain ${STRUCTURED_OUTPUT_OBJECT_KEY} or ${STRUCTURED_OUTPUT_ARRAY_KEY} but never both.`,
|
||||||
(data) => {
|
)
|
||||||
// Validate that one and only one of the properties exists
|
.refine(
|
||||||
return (
|
(data) => {
|
||||||
Boolean(data[STRUCTURED_OUTPUT_OBJECT_KEY]) !==
|
// Validate that one and only one of the properties exists
|
||||||
Boolean(data[STRUCTURED_OUTPUT_ARRAY_KEY])
|
return (
|
||||||
);
|
Boolean(data[STRUCTURED_OUTPUT_OBJECT_KEY]) !==
|
||||||
},
|
Boolean(data[STRUCTURED_OUTPUT_ARRAY_KEY])
|
||||||
{
|
);
|
||||||
message:
|
},
|
||||||
'One and only one of __structured__output__object and __structured__output__array should be present.',
|
{
|
||||||
path: [STRUCTURED_OUTPUT_KEY],
|
message:
|
||||||
},
|
'One and only one of __structured__output__object and __structured__output__array should be present.',
|
||||||
),
|
path: [STRUCTURED_OUTPUT_KEY],
|
||||||
});
|
},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
returnSchema = z.object({
|
||||||
|
output: itemSchema.optional(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return N8nStructuredOutputParser.fromZodSchema(returnSchema);
|
return N8nStructuredOutputParser.fromZodSchema(returnSchema);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +93,8 @@ export class OutputParserStructured implements INodeType {
|
||||||
name: 'outputParserStructured',
|
name: 'outputParserStructured',
|
||||||
icon: 'fa:code',
|
icon: 'fa:code',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: 1,
|
version: [1, 1.1],
|
||||||
|
defaultVersion: 1.1,
|
||||||
description: 'Return data in a defined JSON format',
|
description: 'Return data in a defined JSON format',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Structured Output Parser',
|
name: 'Structured Output Parser',
|
||||||
|
@ -152,11 +161,20 @@ export class OutputParserStructured implements INodeType {
|
||||||
let itemSchema: JSONSchema7;
|
let itemSchema: JSONSchema7;
|
||||||
try {
|
try {
|
||||||
itemSchema = jsonParse<JSONSchema7>(schema);
|
itemSchema = jsonParse<JSONSchema7>(schema);
|
||||||
|
|
||||||
|
// If the type is not defined, we assume it's an object
|
||||||
|
if (itemSchema.type === undefined) {
|
||||||
|
itemSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: itemSchema.properties || (itemSchema as { [key: string]: JSONSchema7 }),
|
||||||
|
};
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NodeOperationError(this.getNode(), 'Error during parsing of JSON Schema.');
|
throw new NodeOperationError(this.getNode(), 'Error during parsing of JSON Schema.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = N8nStructuredOutputParser.fromZedJsonSchema(itemSchema);
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
const parser = N8nStructuredOutputParser.fromZedJsonSchema(itemSchema, nodeVersion);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response: logWrapper(parser, this),
|
response: logWrapper(parser, this),
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { BaseChatMemory } from 'langchain/memory';
|
||||||
import type { MemoryVariables } from 'langchain/dist/memory/base';
|
import type { MemoryVariables } from 'langchain/dist/memory/base';
|
||||||
import { BaseRetriever } from 'langchain/schema/retriever';
|
import { BaseRetriever } from 'langchain/schema/retriever';
|
||||||
import type { FormatInstructionsOptions } from 'langchain/schema/output_parser';
|
import type { FormatInstructionsOptions } from 'langchain/schema/output_parser';
|
||||||
import { BaseOutputParser } from 'langchain/schema/output_parser';
|
import { BaseOutputParser, OutputParserException } from 'langchain/schema/output_parser';
|
||||||
import { isObject } from 'lodash';
|
import { isObject } from 'lodash';
|
||||||
import { N8nJsonLoader } from './N8nJsonLoader';
|
import { N8nJsonLoader } from './N8nJsonLoader';
|
||||||
import { N8nBinaryLoader } from './N8nBinaryLoader';
|
import { N8nBinaryLoader } from './N8nBinaryLoader';
|
||||||
|
@ -44,6 +44,10 @@ export async function callMethodAsync<T>(
|
||||||
try {
|
try {
|
||||||
return await parameters.method.call(this, ...parameters.arguments);
|
return await parameters.method.call(this, ...parameters.arguments);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// Langchain checks for OutputParserException to run retry chain
|
||||||
|
// for auto-fixing the output so skip wrapping in this case
|
||||||
|
if (e instanceof OutputParserException) throw e;
|
||||||
|
|
||||||
// Propagate errors from sub-nodes
|
// Propagate errors from sub-nodes
|
||||||
if (e.functionality === 'configuration-node') throw e;
|
if (e.functionality === 'configuration-node') throw e;
|
||||||
const connectedNode = parameters.executeFunctions.getNode();
|
const connectedNode = parameters.executeFunctions.getNode();
|
||||||
|
|
Loading…
Reference in a new issue