4.6 KiB
Category formulas
A category formula is what makes enhance generate copy instead of reshuffling the feed. The feed supplies facts; the formula supplies the shape.
Legacy Descrybe (descrybe-legacy/generator.php) did this by making the category's
Prompt column the entire user message — a "GPT predloga" with <name>{…}</name>,
<metaDescription>{…}</metaDescription> and an HTML body of <H2>/<p> pairs
closing with a technical-specification list. v2 keeps that behaviour and adds
structured title_template / description_template columns on top of it.
Two formula sets
| A1 Slovenija (legacy plan) | Everyone else | |
|---|---|---|
| Source | scripts/seed/a1-category-prompts.json (+ wp_product_categories.sql) |
apps/api/internal/aiprompts/defaults/default-category-prompts.json |
| Applied by | cmd/seed-a1, cmd/repair-category-prompts, Admin → Sync A1 |
category create + enhance-time fallback |
| Language of the formula text | Slovenian (the tenant's own copy) | English |
| Prompt scaffolding | Slovenian (GPT predloga:, Staro_ime_izdelka:) |
English (Product template:, Old_product_name:) |
Both drive the same code path. The scaffolding language follows the formula's own
language (reSlovenianFormulaCue in processing/a1_enhance_prompt.go) so A1 keeps
the exact frame generator.php used. Output language is always {{language}} from
the company's content language — an English formula still produces Slovenian copy
for a Slovenian tenant.
The English defaults
default-category-prompts.json is the A1 seed translated: 116 categories, same
naming formulas and body structure, generated by
make seed-defaults # regenerate
make check-defaults # fail if stale (CI guard)
Translation is phrase-level over a closed vocabulary (55 title slots, 39 body blocks, 1 meta rule) and the generator fails if any Slovenian text survives, so a new legacy phrase can never ship untranslated.
Where they apply:
- Category create —
catalog.ApplyDefaultCategoryFormulasfillsprompt,title_templateanddescription_templateon new categories (API create and CSV taxonomy import). Only empty columns are written, so nothing existing is overwritten. - Enhance-time fallback —
processing.withDefaultCategoryFormulasupplies the default for a categorised product whose category still has no formula. It is skipped when the product has no category, when the category has its own prompt/formula, or when the tenant authored their own enhance template.
A category outside the seeded taxonomy gets GenericDefaultCategoryPrompt — same
structure, no category-specific naming slots.
Proving it locally
cmd/formula-e2e builds the pipeline the way cmd/worker does, runs a real job and
checks the output is not a feed copy:
make up # Postgres :5433
cd apps/api && go run ./cmd/mock-llm & # formula-aware local LLM
make formula-e2e
cmd/mock-llm reads the template out of the prompt and answers in its shape using
only the supplied name/description/category/attrs. That proves two things a static
stub cannot: the formula reached the model, and a compliant reply survives the
pipeline's formula gate.
Useful flags:
go run ./cmd/formula-e2e -company "A1 Slovenija" -gtin 8022068075495 -llm-base http://127.0.0.1:18767/v1
go run ./cmd/formula-e2e -new-tenant -llm-base http://127.0.0.1:18767/v1
-llm-base pins the provider for the run; without it the harness uses whatever
platformsettings.ResolveOpenAI returns (admin DB setting first, then env).
Why enhance can still return feed copy
In order of how often it bites:
- The product has no category. No category → no formula. Roughly 83% of the A1
catalog has no
mapped_data.category(seescripts/seed/README.txt); those rely on thecategorizestep, which needs a working LLM or Pinecone. - The AI provider is unreachable. Enhance falls back to supplier copy and marks
field_sources.name = ai_enhance_failed. Check the admin OpenAI setting — it overrides theOPENAI_*env values. - The plan/credits gate.
ai_enhance skip reason=entitlement_can_use_aiorinsufficient credits. - The model ignored the formula. One retry is issued
(
descriptionFormulaRetrySuffix/titleRewriteRetrySuffix); if it still fails, the result is markedsynthesized/refusedand noenhance_input_hashis stored, so a reprocess tries again rather than caching the bad copy.
processing: ai_enhance outcome=… category_uid=… title_formula=… desc_formula=… formula_override=… in the worker log tells you which of these happened.