[Feature]: Attribute-scoped AI prompts with declarative constraints, and structured-output schema generation
Is your feature request related to a problem?
magic_ai_prompts has no link to an attribute. The table carries title,
prompt, type (product|category), purpose and tone — nothing that says
which field this prompt fills. A prompt is a free-floating library entry.
To drive per-field generation on a 1589-product catalogue with 74 attributes, we
ended up binding prompts to attributes by naming convention, writing the
title as family:attribute_code (affiche:point_1, affiche:meta_title, …).
34 prompts today. Nothing validates that convention: rename an attribute and the
link breaks silently; there is no way to ask "which attributes have a prompt?"
other than parsing titles.
Two consequences that hurt more than the naming itself:
1. Constraints can only be expressed in prose, and prose is not enforced.
Our bullet fields must stay under 80 characters. Written as a sentence in the
prompt, that constraint is respected sometimes. Written as an enum in a JSON
Schema, a constraint is respected always — that difference is very visible in
practice. We had to build a post-write verifier whose only job is to re-read
what was generated and flag bullets that came back at 95 characters. That check
should never have needed to exist: the model should have been told in the schema,
not asked politely in the prompt.
2. Structured output exists in 3.0, but only for translation.
Webkul\MagicAI\Agents\TranslationAgent already implements
schema(JsonSchema $schema) and returns a real structured-output schema — but a
hardcoded, single-field one (translated_html). The machinery is in place;
generation simply cannot reach it. So we reimplemented outside UnoPim a
generator that walks a family, reads each attribute's type, and reproduces
UnoPim's own type→JSON-Schema mapping (boolean → string enum ["true","false"],
multiselect → array of enum, datetime → string with a YYYY-MM-DD HH:MM:SS
pattern and no T). That mapping is UnoPim's business, not ours — we are
guessing at rules the product already knows.
Describe the solution you'd like
A. An attribute-scoped prompt kind.
Extend magic_ai_prompts.type beyond product|category with an attribute
kind, and add a real foreign key to attributes (plus, ideally, an optional
attribute-family scope, so the same attribute can be prompted differently in two
families). The naming convention then disappears, and the admin can show the
prompt next to the attribute it fills.
B. Declarative constraints on the attribute itself.
attributes already carries validation, regex_pattern, is_required,
is_unique, max_file_size, allowed_extensions — but nothing bounding a text
length or a collection size. We would like:
| property | applies to | feeds |
|---|---|---|
min_length / max_length |
text, textarea | schema + form validation |
min_items / max_items |
multiselect, checkbox | schema + form validation |
min_value / max_value |
integer, decimal, price | schema + form validation |
The important part is that these live on the attribute, not in the prompt text. They are already validation concerns; a storefront and an AI generation should not disagree about how long a field may be, and nobody should have to restate the same bound in two places and keep them in sync by hand.
C. Schema generation for a family, reusing the existing Agent pattern.
Given a family (and a set of attributes that have an attribute-prompt), UnoPim already has everything needed to emit the JSON Schema:
- the type mapping it already applies elsewhere;
- the option list of every
select/multiselect, which becomes anenum— this is the single highest-value piece. Our family has 188 options across its select attributes. Restating them in prose is unmaintainable and goes stale the day someone adds an option; read from the attribute, they cannot; - the bounds from (B), as
maxLength/minItems/maximum; is_required→required;value_per_locale/value_per_channel, so a translation run can emit a schema limited to localisable attributes while a generation run emits the full one.
Concretely: generalise TranslationAgent into an agent whose schema() is built
from the family rather than hardcoded.
D. Let the schema be read from outside.
Please expose the generated schema — via the REST API, and/or an MCP tool, and/or a download in the admin — not only as an internal input to UnoPim's own LLM call.
Not every team can route generation through MagicAI. We can't: we have no LLM API budget at all, and work through an assistant subscription instead. The schema is useful to us even when the inference happens elsewhere; it is the part that encodes UnoPim's knowledge of the catalogue. Keeping it internal-only means anyone in that position has to reimplement the mapping, which is exactly what we did and exactly what goes stale.
Describe alternatives you've considered
Naming convention on title — what we do today. Works, but the link is
unverifiable and breaks on rename.
Constraints in the prompt prose — what we do today for lengths. Measurably unreliable: enums declared in a schema are respected, numeric bounds asked for in prose are not. It also duplicates a rule that belongs to the attribute.
Regenerating our schema from attributes on every run — what we do today,
and it works precisely because it derives everything from the family rather
than from a frozen list. But it means we maintain a second implementation of
UnoPim's own type mapping, and we discover changes to it by having output break.
A separate attribute_prompts table instead of extending
magic_ai_prompts — reasonable too; we have no preference, as long as the link
is a real one.
Additional Context
Running UnoPim 3.0.0 (Laravel 13.23, PHP 8.4) on a 1589-product catalogue, 74 attributes, 188 select options, 34 prompts.
One observation from building this by hand, in case it is useful: what made the generated schema trustworthy was deriving the field list from the family rather than declaring it. Our first version carried a hardcoded list of attributes; it was already stale four hours later, missing four attributes added that same afternoon, and nothing signalled it. Deriving removed the problem instead of managing it. If this feature ships, the same principle would be worth keeping: an attribute added today should appear in tomorrow's schema without anyone editing anything.
Happy to test against a real catalogue, and to share the standalone generator we wrote if it would save time.
Source: unopim/unopim