mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
fix(core): coerce stringified JSON values for anyOf/oneOf schemas (#2839)
Some LLMs serialize array/object values as JSON strings when the schema uses anyOf/oneOf with mixed types (e.g., Python's `list[str] | None`). The model returns '["url"]' (a string) instead of ["url"] (an array), causing Ajv to reject otherwise valid input. Add fixStringifiedJsonValues() coercion — same pattern as the existing fixBooleanValues() — that parses stringified arrays/objects back to their intended type when the schema expects a non-string type.
This commit is contained in:
parent
61bc80fe19
commit
0eaa5e4561
2 changed files with 238 additions and 0 deletions
|
|
@ -210,6 +210,132 @@ describe('SchemaValidator', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('stringified JSON value coercion', () => {
|
||||
it('should coerce stringified array for anyOf [array, null]', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urls: {
|
||||
anyOf: [
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
{ type: 'null' },
|
||||
],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
const params = { urls: '["https://example.com"]' };
|
||||
expect(SchemaValidator.validate(schema, params)).toBeNull();
|
||||
expect(params.urls).toEqual(['https://example.com']);
|
||||
});
|
||||
|
||||
it('should coerce stringified object for anyOf [object, null]', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
config: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: { key: { type: 'string' } },
|
||||
},
|
||||
{ type: 'null' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const params = { config: '{"key":"value"}' };
|
||||
expect(SchemaValidator.validate(schema, params)).toBeNull();
|
||||
expect(params.config).toEqual({ key: 'value' });
|
||||
});
|
||||
|
||||
it('should coerce stringified array for oneOf [array, null]', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
items: {
|
||||
oneOf: [
|
||||
{ type: 'array', items: { type: 'integer' } },
|
||||
{ type: 'null' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const params = { items: '[1, 2, 3]' };
|
||||
expect(SchemaValidator.validate(schema, params)).toBeNull();
|
||||
expect(params.items).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should not coerce when schema accepts string type', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
anyOf: [
|
||||
{ type: 'string' },
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const params = { data: '["hello"]' };
|
||||
expect(SchemaValidator.validate(schema, params)).toBeNull();
|
||||
// Value should remain a string since string is accepted
|
||||
expect(params.data).toBe('["hello"]');
|
||||
});
|
||||
|
||||
it('should not coerce invalid JSON strings', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urls: {
|
||||
anyOf: [
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
{ type: 'null' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const params = { urls: '[not valid json' };
|
||||
expect(SchemaValidator.validate(schema, params)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not coerce strings that do not look like JSON', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urls: {
|
||||
anyOf: [
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
{ type: 'null' },
|
||||
],
|
||||
},
|
||||
},
|
||||
required: ['urls'],
|
||||
};
|
||||
const params = { urls: 'hello world' };
|
||||
expect(SchemaValidator.validate(schema, params)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should handle stringified array with plain type (no anyOf)', () => {
|
||||
// Should NOT coerce when there is no anyOf/oneOf — the schema just
|
||||
// says type: array, and a string value is simply invalid.
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urls: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['urls'],
|
||||
};
|
||||
const params = { urls: '["https://example.com"]' };
|
||||
// No anyOf/oneOf, so fixStringifiedJsonValues won't have types to check
|
||||
// against — but getAcceptedTypes reads plain 'type' too, so it should
|
||||
// still coerce since 'string' is not in the accepted types.
|
||||
expect(SchemaValidator.validate(schema, params)).toBeNull();
|
||||
expect(params.urls).toEqual(['https://example.com']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSON Schema version support', () => {
|
||||
it('should support JSON Schema draft-2020-12', () => {
|
||||
const schema = {
|
||||
|
|
@ -280,6 +406,29 @@ describe('SchemaValidator', () => {
|
|||
expect(SchemaValidator.validate(schema, params)).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle anyOf union types with draft-2020-12', () => {
|
||||
const schema = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
urls: {
|
||||
anyOf: [
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
{ type: 'null' },
|
||||
],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(
|
||||
SchemaValidator.validate(schema, {
|
||||
urls: ['https://example.com'],
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(SchemaValidator.validate(schema, { urls: null })).toBeNull();
|
||||
expect(SchemaValidator.validate(schema, {})).toBeNull();
|
||||
});
|
||||
|
||||
it('should gracefully handle unsupported schema versions', () => {
|
||||
// draft-2019-09 is not supported by Ajv by default
|
||||
const schema = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue