Skip to content

json-schema-to-grammar : convert empty anyOf / oneOf / type unions instead of emitting an empty rule - #333

Open
professorpalmer wants to merge 1 commit into
PrismML-Eng:prismfrom
professorpalmer:json-schema-empty-unions
Open

professorpalmer wants to merge 1 commit into
PrismML-Eng:prismfrom
professorpalmer:json-schema-empty-unions

Conversation

@professorpalmer

Copy link
Copy Markdown

Overview

A JSON schema with an empty union ("anyOf": [], "oneOf": [] or "type": []) makes json-schema-to-grammar emit a
rule with no alternatives. For a root schema that is root ::= followed by the space rule; the grammar parser rejects
it and the request fails with Failed to initialize samplers: failed to parse grammar. Reproduces on prism with any
model:

curl -s localhost:8080/v1/completions -d '{"prompt":"x","max_tokens":4,"json_schema":{"anyOf":[]}}'
# {"error":{"code":400,"message":"Failed to initialize samplers: failed to parse grammar",...}}

Agent frameworks emit these unions from generated schemas (reported against a Bonsai 2 27B serve by Milor123 running
Hermes Agent: professorpalmer/bonsai-ada-surgery#3), and the failure ends the agent's turn. The same happens when the
empty union sits inside a tool parameter's schema.

This PR drops an empty union keyword and converts the rest of the schema, in the C++ converter and in
examples/json_schema_to_grammar.py (kept in sync, as test-json-schema-to-grammar runs both). An empty anyOf on
its own therefore converts like {}; {"type": [], "properties": {...}} converts like the object without type.

Additional information

  • Upstream ggml-org/llama.cpp handles this since its schema rewrite (common : implement common_schema internal representation for JSON schemas ggml-org/llama.cpp#28736, merged 2026-09-12),
    which prism has not synced yet: there the empty union is rejected up front with a clear "must not be empty" schema
    error. So the choice for this fork is between this conversion (the request succeeds) and backporting upstream's
    rejection (the request still fails, with a readable message). I went with conversion because the reported failure is
    an agent loop that cannot recover from a 400, and the keyword constrains nothing; happy to switch to a minimal
    "reject with a clear error" version instead if you prefer to match upstream until the next sync.
  • Tests: four cases added to tests/test-json-schema-to-grammar.cpp (empty anyOf, empty oneOf, empty type with
    properties, empty unions inside properties). The full test passes for C++, Python and the expectation-validity pass;
    without the C++ change the new empty anyOf case fails. Run on our fork, whose three touched files are
    byte-identical to prism at 6bfcd79.
  • Served check on an RTX 4070 (Bonsai 2 27B PTQ1_0): every empty-union shape above returns 200, and greedy output is
    unchanged on unrelated prompts.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES. An AI coding assistant reproduced the bug, located the empty rule in the converter, drafted
    the change and the test cases and ran the tests; I reviewed the change and the test output and can explain both.

…stead of emitting an empty rule

A schema with an empty union ("anyOf": [], "oneOf": [] or "type": []) produced a rule with no
alternatives ("root ::=" for a root schema). The grammar parser rejects it, so the request fails
with "failed to parse grammar". Schema generators used by agent frameworks emit such unions, and
the failure ends the agent's turn. The keyword constrains nothing, so drop it and convert the rest
of the schema, in the C++ and the Python converter. Adds test cases for both converters.

Reported-by: Milor123 (professorpalmer/bonsai-ada-surgery#3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant