Skip to content

fix(dart): parse Picoschema parenthetical types per spec - #627

Open
pavelgj wants to merge 4 commits into
mainfrom
pj/dart-picoschema-fix-k7q2m9xv4t
Open

pavelgj wants to merge 4 commits into
mainfrom
pj/dart-picoschema-fix-k7q2m9xv4t

Conversation

@pavelgj

@pavelgj pavelgj commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes genkit-ai/genkit-dart#562

The Dart Picoschema converter treated parenthesized qualifiers as descriptions, so tags(array): string produced {type: string, description: "array"}. Some schemas also skipped conversion entirely (raw Picoschema was sent to the model) because isPicoschema only recognized bare primitive values. The spec runner never compared output, so spec/picoschema.yaml reported "all pass" while 9 of its 18 cases were actually wrong.

The converter now follows the JS reference implementation.

output:
  schema:
    tags(array, the tags): string, a tag   # {type: array, description: the tags, items: {type: string, description: a tag}}
    obj?(object, nested):                 # {type: [object, null], description: nested, properties: ..., additionalProperties: false}
      x: integer
    status(enum): [A, B]                  # {enum: [A, B]}
    (*): number                           # additionalProperties: {type: number}

Changes

  • picoschema.dart: rewritten to mirror js/src/picoschema.ts:
    • (array|object|enum[, description]) qualifiers and (*) wildcards per spec; optional fields are nullable.
    • Top-level JSON Schema (type: ..., bare properties) is passed through.
    • New Picoschema.parse(schema, {schemas, schemaResolver}) resolves named schemas from schemas first, then the async resolver. toJsonSchema stays sync.
  • Dotprompt._resolveMetadata always converts via Picoschema.parse, so DotpromptOptions.schemaResolver is now used for named schemas (it was ignored before).
  • spec_test.dart checks output and passes the spec's schemas:.
  • picoschema_test.dart: ports the JS cases and adds a regression group for genkit-dart#562.
  • Version 1.1.0, CHANGELOG, PARITY, API docs. Also fixed the README (age: integer? -> age?: integer) and replaced gemini-pro in examples.

Behavior changes (strict, like other runtimes)

These now throw PicoschemaException:

Picoschema.toJsonSchema({'email(the email)': 'string'}); // use `email: string, the email`
Picoschema.toJsonSchema({'wild(*)': 'string'});          // use `(*): string`
Picoschema.toJsonSchema({'tags': 'string[]'});           // use `tags(array): string`
Picoschema.toJsonSchema({'s': 'a | b'});                 // use `s(enum): [a, b]`
Picoschema.toJsonSchema({'n': 'int'});                   // use `integer`
Picoschema.toJsonSchema({'a': 'Unknown'});               // was a silent {"$ref": "Unknown"}

Intentional deviations from JS (commented in code): a top-level any yields {} (JS yields the invalid {type: "any"}), and toJsonSchema(null) still returns {type: object}.

Testing

  • dart analyze: clean; dart test: 237 passing.
  • genkit-dart packages/genkit/test/ai/prompt_test.dart passes (141) against this branch via a local path override.

Follow-up in genkit-dart after publishing: bump to dotprompt: ^1.1.0, optionally use Picoschema.parse with the registry resolver in prompt_loader_io.dart, and add an (array) output to the testapps/prompts sample.

The Dart Picoschema converter treated `field(array)`, `field(object)`
and `field(enum)` qualifiers as descriptions, so `tags(array): string`
became a string schema. Some schemas also skipped conversion entirely
because `isPicoschema` only recognized bare primitive values.

The converter now follows the JS reference implementation:

- `(array|object|enum[, description])` qualifiers, `(*)` wildcards and
  optional-is-nullable semantics match the spec.
- Top-level JSON Schema (`type`, bare `properties`) is passed through.
- Named schemas resolve via `schemas` and then
  `DotpromptOptions.schemaResolver` (new `Picoschema.parse`).
- Invalid input throws `PicoschemaException` (free-text parentheses,
  `name(*)`, `string[]`, `a | b`, type aliases, unknown schema names).

The spec runner now checks `output` and named `schemas`, so
spec/picoschema.yaml is enforced (9 of 18 cases failed before).

ISSUE: genkit-ai/genkit-dart#562

CHANGELOG:
- [x] Fix Picoschema `(array)`, `(object)`, `(enum)` and `(*)` handling
- [x] Add `Picoschema.parse` with async `schemaResolver` support
- [x] Make Picoschema strict, matching other runtimes
@github-actions github-actions Bot added documentation Improvements or additions to documentation config fix dart dotprompt-dart labels Oct 3, 2026
@pavelgj
pavelgj requested a review from huangjeff5 October 3, 2026 14:56
Comment thread dart/dotprompt/lib/src/picoschema.dart
@huangjeff5

Copy link
Copy Markdown
Collaborator

This package is 1.1.0. The install snippet still says dotprompt: ^0.0.1.

^0.0.1 only allows 0.0.1. Someone following that page never gets this parser, so tags(array): string stays a string whose description is array.

I suggest dotprompt: ^1.1.0.

@pavelgj

pavelgj commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator Author

This package is 1.1.0. The install snippet still says dotprompt: ^0.0.1.

Good catch, updated both the API docs and the README to ^1.1.0. (a608d56)

Treat top-level anyOf/oneOf/allOf/enum lists, list-valued type, items and $defs as JSON Schema so they are not misparsed as Picoschema (e.g. via additionalMetadata). Keywords are matched by value shape so Picoschema fields named items/enum still parse.
Tighten JSON Schema detection so Picoschema fields that coincide with JSON
Schema keywords are converted instead of passed through raw:

- `items`/`$defs` with a map value no longer imply JSON Schema.
- A scalar `type` only implies JSON Schema when all siblings are JSON Schema
  keywords whose values are not Picoschema type strings, so
  `{type: string, payload: string}` is a Picoschema object.
- `properties` only implies JSON Schema when its values are JSON Schemas.
- Picoschema key syntax (`a?`, `a(array)`, `(*)`) always means Picoschema.

Also:
- `input: Name` / `output: Name` resolves the named schema instead of
  producing a raw `{$ref: Name}`.
- Duplicate property names (`a` and `a?`) throw.
- `x?: null` yields `{type: null}` instead of `[null, null]`.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config dart documentation Improvements or additions to documentation dotprompt-dart fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Picoschema (array)/(object)/(enum)/(*) qualifiers are parsed as descriptions

2 participants