feat(core,prompt): Simplified property value transformers for AI Prompt generation#250
Open
mattbrailsford wants to merge 3 commits into
Open
feat(core,prompt): Simplified property value transformers for AI Prompt generation#250mattbrailsford wants to merge 3 commits into
mattbrailsford wants to merge 3 commits into
Conversation
…structured output
Executing a prompt against a Block List or Block Grid property failed with an
opaque HTTP 400 (invalid_json_schema) from strict providers like OpenAI. The
resolved CMS property value schema embeds an intentionally unconstrained `{}`
node (BlockJsonSchemaHelper's "any type - depends on property editor"), which
has no faithful equivalent in the strict structured-output subset.
Add AIPromptSchemaCompatibility to detect schemas that can't be represented in
the strict subset (nodes with no resolvable type, arrays without items) and
fail fast in AIPromptService with a clear, user-facing message instead of
letting the provider reject the request. Editors that expose no schema still
fall back to the string-based response models; well-typed editor schemas (e.g.
ColorPicker) remain schema-constrained.
Fixes #249
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pt generation
Executing an AI Prompt against a rich-text (Umbraco.RichText / TipTap) property
returned an opaque HTTP 400 (invalid_json_schema) from strict providers: the
schema-driven wand constrained structured output to the editor's write schema,
whose nested block value is an unconstrained `{}` node OpenAI strict mode rejects.
Introduce IAISimplifiedPropertyValueTransformer (Umbraco.AI.Core): a per-editor
simplified, strict-representable LLM schema plus a total/idempotent transform back
to the editor's write value. A RichText transformer offers the LLM a plain markup
string and wraps it into { markup, blocks }, preserving existing inline blocks.
AIPromptService now:
- uses the simplified schema when a transformer is registered for the target editor
(else the write schema, else the string model);
- runs the transform server-side so ValueChange.Value is the editor's write shape,
while DisplayValue stays the simplified (markup) value for the preview;
- reads the current write-shape value from the serialized entity/element already in
the runtime context (block preservation on regeneration) — no new request field;
- runs the strict-representability guard on whichever schema is used (incl. a
simplified one) so a mis-behaving transformer fails fast cleanly, and blocks
(no transformer) still fail fast rather than 400;
- runs in plain-text output mode for TipTapTool prompts (decided server-side from
prompt.DisplayMode), so the in-editor toolbar keeps receiving a string;
- surfaces transform failures as a clean 400 and isolates per-option failures.
The resolver gains ResolvePropertyValueSchemaAsync (the old ResolveValueSchemaAsync
is kept and marked [Obsolete], delegating to it). Client applyValueChange now
resolves the editor alias from the data type when a field is empty, so value
preparers still run into empty fields.
Fixes #249
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…quest/response Pins the exact live execute payload from the manual #249 validation: given the LLM's markup string plus the property's current value (with an empty blocks envelope, layout: {}), the RichText transformer reproduces the server response value byte-for-byte and preserves the existing empty blocks rather than substituting the Empty("Umbraco.RichText") default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #249 — executing an AI Prompt against a rich-text (
Umbraco.RichText/ TipTap) property failed with an opaque HTTP 400 (invalid_json_schema) from strict providers like OpenAI (Responses API). (The issue was reported as "Block List/Grid", but the reporter's real case was RichText — whose write schema embeds the same block structure.)The schema-driven wand constrains the LLM's strict structured output to the target property's CMS write schema. RichText's schema is
{ markup, blocks: {…} }, andblocksembeds an intentionally unconstrainedvalues[].valuenode ({}— "Any type - depends on property editor", from CMSBlockJsonSchemaHelper). That{}has no faithful equivalent in OpenAI's strict subset, so the request is rejected before generation.Approach — simplified value transformers
New general server-side abstraction
IAISimplifiedPropertyValueTransformer(Umbraco.AI.Core,PropertyValueOperations/, discovered likeIAIPropertyValueHandler): a per-editor simplified, strict-representable LLM schema plus a total/idempotent transform back to the editor's real write value.RichTextSimplifiedPropertyValueTransformeroffers the LLM a plain markup string ({ "type": "string" }) and wraps the result into{ markup, blocks }, preserving any existing inline blocks from the property's current value.Consumer chooses the representation by capability (no regression):
add_itemhandlers)What
AIPromptServicenow doesValueChange.Valueis the editor's write shape, whileDisplayValuestays the simplified (markup) value for the preview.currentValuetemplate variable) to preserve existing blocks — no new request field / client plumbing.IsStrictRepresentableguard on whichever schema is used (incl. a simplified one), so a mis-behaving transformer fails fast cleanly and block editors (no transformer) still fail fast rather than 400.DisplayMode == TipTapToolprompts (decided server-side from the persisted entity), so the in-editor toolbar keeps receiving a string.ResolvePropertyValueSchemaAsync; oldResolveValueSchemaAsynckept and marked[Obsolete("… Will be removed in v20")].Frontend
applyValueChange(document/block/media adapters) resolves the editor alias from the data-type item repository when a field is empty, so value preparers still run into empty fields.Validation
{ markup, blocks }write shape with the property's existing blocks preserved.Not in scope (follow-ups)
SetValueleaf (additive, non-breaking).contentTypeKey+alias; strict mode bansif/then/allOf).🤖 Generated with Claude Code