Pārlūkot izejas kodu

feat: Add customized json schema validation (#25408)

tags/1.9.0
Wu Tianwei pirms 1 mēnesi
vecāks
revīzija
37975319f2
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam

+ 1
- 1
web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx Parādīt failu

@@ -17,7 +17,7 @@ const ErrorMessage: FC<ErrorMessageProps> = ({
className,
)}>
<RiErrorWarningFill className='h-4 w-4 shrink-0 text-text-destructive' />
<div className='system-xs-medium max-h-12 grow overflow-y-auto break-words text-text-primary'>
<div className='system-xs-medium max-h-12 grow overflow-y-auto whitespace-pre-line break-words text-text-primary'>
{message}
</div>
</div>

+ 15
- 185
web/app/components/workflow/nodes/llm/utils.ts Parādīt failu

@@ -1,9 +1,8 @@
import { z } from 'zod'
import { ArrayType, Type } from './types'
import type { ArrayItems, Field, LLMNodeType } from './types'
import type { Schema, ValidationError } from 'jsonschema'
import { Validator } from 'jsonschema'
import produce from 'immer'
import { z } from 'zod'
import { draft07Validator, forbidBooleanProperties } from '@/utils/validators'
import type { ValidationError } from 'jsonschema'

export const checkNodeValid = (_payload: LLMNodeType) => {
return true
@@ -14,7 +13,7 @@ export const getFieldType = (field: Field) => {
if (type !== Type.array || !items)
return type

return ArrayType[items.type]
return ArrayType[items.type as keyof typeof ArrayType]
}

export const getHasChildren = (schema: Field) => {
@@ -115,191 +114,22 @@ export const findPropertyWithPath = (target: any, path: string[]) => {
return current
}

const draft07MetaSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'http://json-schema.org/draft-07/schema#',
title: 'Core schema meta-schema',
definitions: {
schemaArray: {
type: 'array',
minItems: 1,
items: { $ref: '#' },
},
nonNegativeInteger: {
type: 'integer',
minimum: 0,
},
nonNegativeIntegerDefault0: {
allOf: [
{ $ref: '#/definitions/nonNegativeInteger' },
{ default: 0 },
],
},
simpleTypes: {
enum: [
'array',
'boolean',
'integer',
'null',
'number',
'object',
'string',
],
},
stringArray: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
default: [],
},
},
type: ['object', 'boolean'],
properties: {
$id: {
type: 'string',
format: 'uri-reference',
},
$schema: {
type: 'string',
format: 'uri',
},
$ref: {
type: 'string',
format: 'uri-reference',
},
title: {
type: 'string',
},
description: {
type: 'string',
},
default: true,
readOnly: {
type: 'boolean',
default: false,
},
examples: {
type: 'array',
items: true,
},
multipleOf: {
type: 'number',
exclusiveMinimum: 0,
},
maximum: {
type: 'number',
},
exclusiveMaximum: {
type: 'number',
},
minimum: {
type: 'number',
},
exclusiveMinimum: {
type: 'number',
},
maxLength: { $ref: '#/definitions/nonNegativeInteger' },
minLength: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
pattern: {
type: 'string',
format: 'regex',
},
additionalItems: { $ref: '#' },
items: {
anyOf: [
{ $ref: '#' },
{ $ref: '#/definitions/schemaArray' },
],
default: true,
},
maxItems: { $ref: '#/definitions/nonNegativeInteger' },
minItems: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
uniqueItems: {
type: 'boolean',
default: false,
},
contains: { $ref: '#' },
maxProperties: { $ref: '#/definitions/nonNegativeInteger' },
minProperties: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
required: { $ref: '#/definitions/stringArray' },
additionalProperties: { $ref: '#' },
definitions: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
properties: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
patternProperties: {
type: 'object',
additionalProperties: { $ref: '#' },
propertyNames: { format: 'regex' },
default: {},
},
dependencies: {
type: 'object',
additionalProperties: {
anyOf: [
{ $ref: '#' },
{ $ref: '#/definitions/stringArray' },
],
},
},
propertyNames: { $ref: '#' },
const: true,
enum: {
type: 'array',
items: true,
minItems: 1,
uniqueItems: true,
},
type: {
anyOf: [
{ $ref: '#/definitions/simpleTypes' },
{
type: 'array',
items: { $ref: '#/definitions/simpleTypes' },
minItems: 1,
uniqueItems: true,
},
],
},
format: { type: 'string' },
allOf: { $ref: '#/definitions/schemaArray' },
anyOf: { $ref: '#/definitions/schemaArray' },
oneOf: { $ref: '#/definitions/schemaArray' },
not: { $ref: '#' },
},
default: true,
} as unknown as Schema

const validator = new Validator()

export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
const schema = produce(schemaToValidate, (draft: any) => {
// Make sure the schema has the $schema property for draft-07
if (!draft.$schema)
draft.$schema = 'http://json-schema.org/draft-07/schema#'
})

const result = validator.validate(schema, draft07MetaSchema, {
nestedErrors: true,
throwError: false,
})

// Access errors from the validation result
const errors = result.valid ? [] : result.errors || []
// First check against Draft-07
const result = draft07Validator(schemaToValidate)
// Then apply custom rule
const customErrors = forbidBooleanProperties(schemaToValidate)

return errors
return [...result.errors, ...customErrors]
}

export const getValidationErrorMessage = (errors: ValidationError[]) => {
export const getValidationErrorMessage = (errors: Array<ValidationError | string>) => {
const message = errors.map((error) => {
return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
}).join('; ')
if (typeof error === 'string')
return error
else
return `Error: ${error.stack}\n`
}).join('')
return message
}


+ 1
- 1
web/pnpm-lock.yaml Parādīt failu

@@ -12603,7 +12603,7 @@ snapshots:

'@vue/compiler-sfc@3.5.17':
dependencies:
'@babel/parser': 7.28.0
'@babel/parser': 7.28.3
'@vue/compiler-core': 3.5.17
'@vue/compiler-dom': 3.5.17
'@vue/compiler-ssr': 3.5.17

+ 245
- 0
web/utils/draft-07.json Parādīt failu

@@ -0,0 +1,245 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#"
}
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{
"$ref": "#/definitions/nonNegativeInteger"
},
{
"default": 0
}
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true,
"default": []
}
},
"type": [
"object",
"boolean"
],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"readOnly": {
"type": "boolean",
"default": false
},
"writeOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": {
"$ref": "#/definitions/nonNegativeInteger"
},
"minLength": {
"$ref": "#/definitions/nonNegativeIntegerDefault0"
},
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"$ref": "#"
},
"items": {
"anyOf": [
{
"$ref": "#"
},
{
"$ref": "#/definitions/schemaArray"
}
],
"default": true
},
"maxItems": {
"$ref": "#/definitions/nonNegativeInteger"
},
"minItems": {
"$ref": "#/definitions/nonNegativeIntegerDefault0"
},
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": {
"$ref": "#"
},
"maxProperties": {
"$ref": "#/definitions/nonNegativeInteger"
},
"minProperties": {
"$ref": "#/definitions/nonNegativeIntegerDefault0"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"additionalProperties": {
"$ref": "#"
},
"definitions": {
"type": "object",
"additionalProperties": {
"$ref": "#"
},
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": {
"$ref": "#"
},
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": {
"$ref": "#"
},
"propertyNames": {
"format": "regex"
},
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"$ref": "#"
},
{
"$ref": "#/definitions/stringArray"
}
]
}
},
"propertyNames": {
"$ref": "#"
},
"const": true,
"enum": {
"type": "array",
"items": true,
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{
"$ref": "#/definitions/simpleTypes"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/simpleTypes"
},
"minItems": 1,
"uniqueItems": true
}
]
},
"format": {
"type": "string"
},
"contentMediaType": {
"type": "string"
},
"contentEncoding": {
"type": "string"
},
"if": {
"$ref": "#"
},
"then": {
"$ref": "#"
},
"else": {
"$ref": "#"
},
"allOf": {
"$ref": "#/definitions/schemaArray"
},
"anyOf": {
"$ref": "#/definitions/schemaArray"
},
"oneOf": {
"$ref": "#/definitions/schemaArray"
},
"not": {
"$ref": "#"
}
},
"default": true
}

+ 27
- 0
web/utils/validators.ts Parādīt failu

@@ -0,0 +1,27 @@
import type { Schema } from 'jsonschema'
import { Validator } from 'jsonschema'
import draft07Schema from './draft-07.json'

const validator = new Validator()

export const draft07Validator = (schema: any) => {
return validator.validate(schema, draft07Schema as unknown as Schema)
}

export const forbidBooleanProperties = (schema: any, path: string[] = []): string[] => {
let errors: string[] = []

if (schema && typeof schema === 'object' && schema.properties) {
for (const [key, val] of Object.entries(schema.properties)) {
if (typeof val === 'boolean') {
errors.push(
`Error: Property '${[...path, key].join('.')}' must not be a boolean schema`,
)
}
else if (typeof val === 'object') {
errors = errors.concat(forbidBooleanProperties(val, [...path, key]))
}
}
}
return errors
}

Notiek ielāde…
Atcelt
Saglabāt